Skip to content

SemanticModel ignores evaluation context introduced by checked/unchecked expression #60419

@AlekseyTs

Description

@AlekseyTs

Comments in the repro indicate unexpected behavior:


        [Fact]
        public void ClassifyConversion_01()
        {
            var source1 =
@"
public class C0 
{
    public static explicit operator checked long(C0 x)
    {
        System.Console.WriteLine(""checked C0"");
        return 0;
    }

    public static explicit operator long(C0 x)
    {
        System.Console.WriteLine(""regular C0"");
        return 0;
    }
}
";
            var source2 =
@"
class Program
{
    static void Main()
    {
        TestExplicitLong1(new C0());
        TestExplicitLong2(new C0());
    }

    public static long TestExplicitLong1(C0 x)
    {
        checked { return (long)x; }
    }

    public static long TestExplicitLong2(C0 y)
    {
        return checked( (long)y);
    }
}
";
            var compilation1 = CreateCompilation(source1 + source2, options: TestOptions.DebugExe, parseOptions: TestOptions.RegularPreview);
            CompileAndVerify(compilation1, expectedOutput: @"
checked C0
checked C0
").VerifyDiagnostics();

            var tree = compilation1.SyntaxTrees.Single();
            var model = compilation1.GetSemanticModel(tree);

            var xNode = tree.GetRoot().DescendantNodes().OfType<IdentifierNameSyntax>().Where(id => id.Identifier.ValueText == "x").Single();
            var yNode = tree.GetRoot().DescendantNodes().OfType<IdentifierNameSyntax>().Where(id => id.Identifier.ValueText == "y").Single();

            Assert.Equal("System.Int64 C0.op_CheckedExplicit(C0 x)", model.GetSymbolInfo(xNode.Parent).Symbol.ToTestDisplayString());
            Assert.Equal("System.Int64 C0.op_CheckedExplicit(C0 x)", model.GetSymbolInfo(yNode.Parent).Symbol.ToTestDisplayString());

            var int64 = ((IMethodSymbol)model.GetSymbolInfo(xNode.Parent).Symbol).ReturnType;
            Assert.Equal("System.Int64", int64.ToTestDisplayString());

            Assert.Equal("System.Int64 C0.op_CheckedExplicit(C0 x)", model.ClassifyConversion(xNode.SpanStart, xNode, int64, isExplicitInSource: false).Method.ToTestDisplayString());
            Assert.Equal("System.Int64 C0.op_CheckedExplicit(C0 x)", model.ClassifyConversion(xNode.SpanStart, xNode, int64, isExplicitInSource: true).Method.ToTestDisplayString());

            // !!! Expected System.Int64 C0.op_CheckedExplicit(C0 x) !!!
            Assert.Equal("System.Int64 C0.op_Explicit(C0 x)", model.ClassifyConversion(yNode.SpanStart, yNode, int64, isExplicitInSource: false).Method.ToTestDisplayString());

            // !!! Expected System.Int64 C0.op_CheckedExplicit(C0 x) !!!
            Assert.Equal("System.Int64 C0.op_Explicit(C0 x)", model.ClassifyConversion(yNode.SpanStart, yNode, int64, isExplicitInSource: true).Method.ToTestDisplayString());

            Assert.Equal("System.Int64 C0.op_CheckedExplicit(C0 x)", model.ClassifyConversion(xNode, int64, isExplicitInSource: false).Method.ToTestDisplayString());
            Assert.Equal("System.Int64 C0.op_CheckedExplicit(C0 x)", model.ClassifyConversion(xNode, int64, isExplicitInSource: true).Method.ToTestDisplayString());

            // !!! Expected System.Int64 C0.op_CheckedExplicit(C0 x) !!!
            Assert.Equal("System.Int64 C0.op_Explicit(C0 x)", model.ClassifyConversion(yNode, int64, isExplicitInSource: false).Method.ToTestDisplayString());

            // !!! Expected System.Int64 C0.op_CheckedExplicit(C0 x) !!!
            Assert.Equal("System.Int64 C0.op_Explicit(C0 x)", model.ClassifyConversion(yNode, int64, isExplicitInSource: true).Method.ToTestDisplayString());
        }

        [Fact]
        public void ClassifyConversion_03()
        {
            var source1 =
@"
public class C0 
{
    public static explicit operator checked long(C0 x)
    {
        System.Console.WriteLine(""checked C0"");
        return 0;
    }

    public static explicit operator long(C0 x)
    {
        System.Console.WriteLine(""regular C0"");
        return 0;
    }
}
";
            var source2 =
@"
class Program
{
    static void Main()
    {
        TestExplicitLong1(new C0());
        TestExplicitLong2(new C0());
    }

    public static long TestExplicitLong1(C0 x)
    {
        checked { unchecked { return (long)x; }}
    }

    public static long TestExplicitLong2(C0 y)
    {
        checked { return unchecked( (long)y); }
    }
}
";
            var compilation1 = CreateCompilation(source1 + source2, options: TestOptions.DebugExe, parseOptions: TestOptions.RegularPreview);
            CompileAndVerify(compilation1, expectedOutput: @"
regular C0
regular C0
").VerifyDiagnostics();

            var tree = compilation1.SyntaxTrees.Single();
            var model = compilation1.GetSemanticModel(tree);

            var xNode = tree.GetRoot().DescendantNodes().OfType<IdentifierNameSyntax>().Where(id => id.Identifier.ValueText == "x").Single();
            var yNode = tree.GetRoot().DescendantNodes().OfType<IdentifierNameSyntax>().Where(id => id.Identifier.ValueText == "y").Single();

            Assert.Equal("System.Int64 C0.op_Explicit(C0 x)", model.GetSymbolInfo(xNode.Parent).Symbol.ToTestDisplayString());
            Assert.Equal("System.Int64 C0.op_Explicit(C0 x)", model.GetSymbolInfo(yNode.Parent).Symbol.ToTestDisplayString());

            var int64 = ((IMethodSymbol)model.GetSymbolInfo(xNode.Parent).Symbol).ReturnType;
            Assert.Equal("System.Int64", int64.ToTestDisplayString());

            Assert.Equal("System.Int64 C0.op_Explicit(C0 x)", model.ClassifyConversion(xNode.SpanStart, xNode, int64, isExplicitInSource: false).Method.ToTestDisplayString());
            Assert.Equal("System.Int64 C0.op_Explicit(C0 x)", model.ClassifyConversion(xNode.SpanStart, xNode, int64, isExplicitInSource: true).Method.ToTestDisplayString());

            // !!! Expected System.Int64 C0.op_Explicit(C0 x) !!!
            Assert.Equal("System.Int64 C0.op_CheckedExplicit(C0 x)", model.ClassifyConversion(yNode.SpanStart, yNode, int64, isExplicitInSource: false).Method.ToTestDisplayString());

            // !!! Expected System.Int64 C0.op_Explicit(C0 x) !!!
            Assert.Equal("System.Int64 C0.op_CheckedExplicit(C0 x)", model.ClassifyConversion(yNode.SpanStart, yNode, int64, isExplicitInSource: true).Method.ToTestDisplayString());

            Assert.Equal("System.Int64 C0.op_Explicit(C0 x)", model.ClassifyConversion(xNode, int64, isExplicitInSource: false).Method.ToTestDisplayString());
            Assert.Equal("System.Int64 C0.op_Explicit(C0 x)", model.ClassifyConversion(xNode, int64, isExplicitInSource: true).Method.ToTestDisplayString());

            // !!! Expected System.Int64 C0.op_Explicit(C0 x) !!!
            Assert.Equal("System.Int64 C0.op_CheckedExplicit(C0 x)", model.ClassifyConversion(yNode, int64, isExplicitInSource: false).Method.ToTestDisplayString());

            // !!! Expected System.Int64 C0.op_Explicit(C0 x) !!!
            Assert.Equal("System.Int64 C0.op_CheckedExplicit(C0 x)", model.ClassifyConversion(yNode, int64, isExplicitInSource: true).Method.ToTestDisplayString());
        }

        [Fact]
        public void GetSpeculativeSymbolInfo_01()
        {
            var source1 =
@"
public class C0 
{
    public static C0 operator checked -(C0 a)
    {
        System.Console.WriteLine(""checked C0"");
        return a;
    }

    public static C0 operator -(C0 a)
    {
        System.Console.WriteLine(""regular C0"");
        return a;
    }
}
";
            var source2 =
@"
class Program
{
    static void Main()
    {
        Test1(new C0());
        Test2(new C0());
    }

    public static C0 Test1(C0 x)
    {
        checked { return -x; }
    }

    public static C0 Test2(C0 y)
    {
        return checked( -y);
    }
}
";
            var compilation1 = CreateCompilation(source1 + source2, options: TestOptions.DebugExe, parseOptions: TestOptions.RegularPreview);
            CompileAndVerify(compilation1, expectedOutput: @"
checked C0
checked C0
").VerifyDiagnostics();

            var tree = compilation1.SyntaxTrees.Single();
            var model = compilation1.GetSemanticModel(tree);

            var xNode = tree.GetRoot().DescendantNodes().OfType<IdentifierNameSyntax>().Where(id => id.Identifier.ValueText == "x").Single();
            var yNode = tree.GetRoot().DescendantNodes().OfType<IdentifierNameSyntax>().Where(id => id.Identifier.ValueText == "y").Single();

            Assert.Equal("C0 C0.op_CheckedUnaryNegation(C0 a)", model.GetSymbolInfo(xNode.Parent).Symbol.ToTestDisplayString());
            Assert.Equal("C0 C0.op_CheckedUnaryNegation(C0 a)", model.GetSymbolInfo(yNode.Parent).Symbol.ToTestDisplayString());

            var xNodeToSpeculate = SyntaxFactory.ParseExpression("-x");
            var yNodeToSpeculate = SyntaxFactory.ParseExpression("-y");

            Assert.Equal("C0 C0.op_CheckedUnaryNegation(C0 a)", model.GetSpeculativeSymbolInfo(xNode.SpanStart, xNodeToSpeculate, SpeculativeBindingOption.BindAsExpression).Symbol.ToTestDisplayString());

            // !!! Expected C0 C0.op_CheckedUnaryNegation(C0 a) !!!
            Assert.Equal("C0 C0.op_UnaryNegation(C0 a)", model.GetSpeculativeSymbolInfo(yNode.SpanStart, yNodeToSpeculate, SpeculativeBindingOption.BindAsExpression).Symbol.ToTestDisplayString());
        }

        [Fact]
        public void GetSpeculativeSymbolInfo_02()
        {
            var source1 =
@"
public class C0 
{
    public static C0 operator checked -(C0 a)
    {
        System.Console.WriteLine(""checked C0"");
        return a;
    }

    public static C0 operator -(C0 a)
    {
        System.Console.WriteLine(""regular C0"");
        return a;
    }
}
";
            var source2 =
@"
class Program
{
    static void Main()
    {
        Test1(new C0());
        Test2(new C0());
    }

    public static C0 Test1(C0 x)
    {
        checked { unchecked { return -x; } }
    }

    public static C0 Test2(C0 y)
    {
        checked { return unchecked( -y); }
    }
}
";
            var compilation1 = CreateCompilation(source1 + source2, options: TestOptions.DebugExe, parseOptions: TestOptions.RegularPreview);
            CompileAndVerify(compilation1, expectedOutput: @"
regular C0
regular C0
").VerifyDiagnostics();

            var tree = compilation1.SyntaxTrees.Single();
            var model = compilation1.GetSemanticModel(tree);

            var xNode = tree.GetRoot().DescendantNodes().OfType<IdentifierNameSyntax>().Where(id => id.Identifier.ValueText == "x").Single();
            var yNode = tree.GetRoot().DescendantNodes().OfType<IdentifierNameSyntax>().Where(id => id.Identifier.ValueText == "y").Single();

            Assert.Equal("C0 C0.op_UnaryNegation(C0 a)", model.GetSymbolInfo(xNode.Parent).Symbol.ToTestDisplayString());
            Assert.Equal("C0 C0.op_UnaryNegation(C0 a)", model.GetSymbolInfo(yNode.Parent).Symbol.ToTestDisplayString());

            var xNodeToSpeculate = SyntaxFactory.ParseExpression("-x");
            var yNodeToSpeculate = SyntaxFactory.ParseExpression("-y");

            Assert.Equal("C0 C0.op_UnaryNegation(C0 a)", model.GetSpeculativeSymbolInfo(xNode.SpanStart, xNodeToSpeculate, SpeculativeBindingOption.BindAsExpression).Symbol.ToTestDisplayString());

            // !!! Expected C0 C0.op_UnaryNegation(C0 a) !!!
            Assert.Equal("C0 C0.op_CheckedUnaryNegation(C0 a)", model.GetSpeculativeSymbolInfo(yNode.SpanStart, yNodeToSpeculate, SpeculativeBindingOption.BindAsExpression).Symbol.ToTestDisplayString());
        }

Metadata

Metadata

Assignees

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions