Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Confusing nullability warning is reported for a deconstruction #73374

Open
AlekseyTs opened this issue May 7, 2024 · 0 comments
Open

Confusing nullability warning is reported for a deconstruction #73374

AlekseyTs opened this issue May 7, 2024 · 0 comments
Labels
Area-Compilers New Language Feature - Nullable Reference Types Nullable Reference Types untriaged Issues and PRs which have not yet been triaged by a lead
Milestone

Comments

@AlekseyTs
Copy link
Contributor

        [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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Compilers New Language Feature - Nullable Reference Types Nullable Reference Types untriaged Issues and PRs which have not yet been triaged by a lead
Projects
None yet
Development

No branches or pull requests

2 participants