Confusing nullability warning is reported for a deconstruction
Open
Nobody has claimed this yet.
Area-Compilers
Feature - Nullable Reference Types
- Dominant language
- C#
- Stars
- 20.7k
- Forks
- 4.3k
- PR merge metrics
- PR metrics pending
Description
[Fact]
[WorkItem("https://github.com/dotnet/roslyn/issues/72750")]
[WorkItem("https://github.com/dotnet/roslyn/issues/33011")]
public void SingleCandidate_ResultIsDynamic_Property_Assignment_Deconstruction_Method_01()
{
string source = @"
#nullable enable
public class C
{
public static void Main()
{
dynamic d = 1;
var c = new C();
var a = (c[d], _) = new C2();
System.Console.Write(a);
Print(a.Item1);
System.Console.Write("" "");
System.Console.Write(c._test1);
}
int _test1 = 0;
int this[int x]
{
get => _test1;
set => _test1 = value;
}
static void Print(dynamic b)
{
}
}
class C2
{
public void Deconstruct(out int x, out int y)
{
(x, y) = (2, 123);
}
}
";
var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp);
var tree = comp.SyntaxTrees.Single();
var node = tree.GetRoot().DescendantNodes().OfType<IdentifierNameSyntax>().Where(id => id.Identifier.ValueText == "a").First();
var model = comp.GetSemanticModel(tree);
var symbolInfo = model.GetSymbolInfo(node);
Assert.Equal("(dynamic, System.Int32) a", symbolInfo.Symbol.ToTestDisplayString());
node = tree.GetRoot().DescendantNodes().OfType<IdentifierNameSyntax>().Where(id => id.Identifier.ValueText == "Item1").Single();
symbolInfo = model.GetSymbolInfo(node);
Assert.Equal("dynamic (dynamic, System.Int32).Item1", symbolInfo.Symbol.ToTestDisplayString());
var typeInfo = model.GetTypeInfo(node);
AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString());
Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState);
var elementAccess = tree.GetRoot().DescendantNodes().OfType<ElementAccessExpressionSyntax>().Single();
symbolInfo = model.GetSymbolInfo(elementAccess);
AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString());
typeInfo = model.GetTypeInfo(elementAccess);
AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString());
AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString());
AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString());
AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString());
var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess);
AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString());
var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess);
Assert.Equal(typeInfo.Type, propertyRef.Type);
var left = (TupleExpressionSyntax)elementAccess.Parent.Parent;
var tupleTypeInfo = model.GetTypeInfo(left);
AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.Type.ToTestDisplayString());
AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.ConvertedType.ToTestDisplayString());
AssertEx.Equal("(dynamic, System.Int32)", tupleTypeInfo.Type.ToTestDisplayString());
AssertEx.Equal("(dynamic, System.Int32)", tupleTypeInfo.ConvertedType.ToTestDisplayString());
var assignment = (AssignmentExpressionSyntax)left.Parent;
typeInfo = model.GetTypeInfo(assignment);
AssertEx.Equal("(dynamic, System.Int32)", typeInfo.Type.ToTestDisplayString());
AssertEx.Equal("(dynamic, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString());
Assert.True(model.GetDeconstructionInfo(assignment) is { Method: not null, Conversion: null, Nested: [{ Method: null, Conversion: { IsIdentity: true }, Nested: [] }, _] });
Assert.True(model.GetDeconstructionInfo(assignment) is { Method: not null, Conversion: null, Nested: [{ Method: null, Conversion: { IsBoxing: true }, Nested: [] }, _] });
var operation = (IDeconstructionAssignmentOperation)model.GetOperation(assignment);
Assert.Equal(tupleTypeInfo.Type, operation.Target.Type);
Assert.Equal("C2", operation.Value.Type.ToTestDisplayString());
Assert.Equal(typeInfo.Type, operation.Type);
var right = assignment.Right;
typeInfo = model.GetTypeInfo(right);
AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString());
AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString());
CompileAndVerify(comp, expectedOutput: "(2, 123) 2").VerifyDiagnostics();
CompileAndVerify(comp, expectedOutput: "(2, 123) 2").VerifyDiagnostics(
// (10,18): warning CS8624: Argument of type 'dynamic' cannot be used as an output of type 'int' for parameter 'x' in 'void C2.Deconstruct(out int x, out int y)' due to differences in the nullability of reference types.
// var a = (c[d], _) = new C2();
Diagnostic(ErrorCode.WRN_NullabilityMismatchInArgumentForOutput, "c[d]").WithArguments("dynamic", "int", "x", "void C2.Deconstruct(out int x, out int y)").WithLocation(10, 18)
);
string source3 = @"
#nullable enable
public class C
{
public static void Main()
{
dynamic d = 1;
var c = new C();
var a = (c[d], _) = new C2();
Print(a.Item1);
}
int? _test1 = 0;
int? this[int x]
{
get => _test1;
set => _test1 = value;
}
static void Print(dynamic b)
{
}
}
class C2
{
public void Deconstruct(out int? x, out int y)
{
(x, y) = (2, 123);
}
}
";
var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp);
// Nullability is not tracked across deconstruction, this is a pre-existing condition - https://github.com/dotnet/roslyn/issues/33011
comp3.VerifyDiagnostics();
comp3.VerifyDiagnostics(
// (10,18): warning CS8624: Argument of type 'dynamic' cannot be used as an output of type 'int?' for parameter 'x' in 'void C2.Deconstruct(out int? x, out int y)' due to differences in the nullability of reference types.
// var a = (c[d], _) = new C2();
Diagnostic(ErrorCode.WRN_NullabilityMismatchInArgumentForOutput, "c[d]").WithArguments("dynamic", "int?", "x", "void C2.Deconstruct(out int? x, out int y)").WithLocation(10, 18)
);
tree = comp3.SyntaxTrees.Single();
node = tree.GetRoot().DescendantNodes().OfType<IdentifierNameSyntax>().Where(id => id.Identifier.ValueText == "Item1").Single();
model = comp3.GetSemanticModel(tree);
typeInfo = model.GetTypeInfo(node);
AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString());
Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState);
}
Observed confusing warning:
// (10,18): warning CS8624: Argument of type 'dynamic' cannot be used as an output of type 'int' for parameter 'x' in 'void C2.Deconstruct(out int x, out int y)' due to differences in the nullability of reference types.
// var a = (c[d], _) = new C2();
Diagnostic(ErrorCode.WRN_NullabilityMismatchInArgumentForOutput, "c[d]").WithArguments("dynamic", "int", "x", "void C2.Deconstruct(out int x, out int y)").WithLocation(10, 18)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the SingleCandidate_ResultIsDynamic_Property_Assignment_Deconstruction_Method_01 test and its two VerifyDiagnostics calls. Read the deconstruction and nullable-flow behavior exercised by GetDeconstructionInfo and GetTypeInfo, then trace the diagnostic for c[d]. Done means the reported warning is no longer confusing and the test assertions accurately capture the intended diagnostic behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- compilers, testing-qa
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100