Skip to content

Speculation within attributes on local functions is broken #60801

@jcouv

Description

@jcouv

The binder from GetBinder all in TryBindNameofOperator should never be null.
But currently, it could be during speculation using a MethodBodySemanticModel (see test below, which would crash without this mitigation).
This also happens in completion scenarios in the IDE (see tests that use skipSpeculation and are marked with the current issue).

In short, the model returned by TryGetSpeculativeSemanticModelForMethodBody should not directly try to bind the expression, it should first create an AttributeSemanticModel first in some cases.

This affects new nameof scenarios (test plan), but likely other existing scenarios too.

        private bool TryBindNameofOperator(InvocationExpressionSyntax node, BindingDiagnosticBag diagnostics, out BoundExpression result)
        {
            if (node.MayBeNameofOperator())
            {
                var binder = this.GetBinder(node);
                if (binder is null)
                {
                    // This could happen during speculation due to a bug, but should be removed
                    // Tracked by https://github.com/dotnet/roslyn/issues/60801
                    result = null;
                    return false;
                }
                if (binder.EnclosingNameofArgument == node.ArgumentList.Arguments[0].Expression)
                {
                    result = binder.BindNameofOperatorInternal(node, diagnostics);
                    return true;
                }
            }

            result = null;
            return false;
        }
        [Fact]
        public void ParameterScope_InMethodAttributeNameOf_GetSymbolInfoOnSpeculativeMethodBodySemanticModel()
        {
            var source = @"
class C
{
    void M()
    {
        [My(nameof(parameter))]
        void local(int parameter) { }
    }
}

public class MyAttribute : System.Attribute
{
    public MyAttribute(string name1) { }
}
";

            var comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext);
            comp.VerifyDiagnostics(
                // (7,14): warning CS8321: The local function 'local' is declared but never used
                //         void local(int parameter) { }
                Diagnostic(ErrorCode.WRN_UnreferencedLocalFunction, "local").WithArguments("local").WithLocation(7, 14)
                );

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

            var tree2 = CSharpSyntaxTree.ParseText(source);
            var method = tree2.GetRoot().DescendantNodes().OfType<MethodDeclarationSyntax>().First();
            Assert.True(model.TryGetSpeculativeSemanticModelForMethodBody(method.Body.SpanStart, method, out var speculativeModel));

            var invocation = tree2.GetRoot().DescendantNodes().OfType<InvocationExpressionSyntax>().Single();
            Assert.Equal("nameof(parameter)", invocation.ToString());
            var symbolInfo = speculativeModel.GetSymbolInfo(invocation);
            Assert.Null(symbolInfo.Symbol);
        }

Note to self: I have some notes from brainstorming with Aleksey on how to fix this.

Metadata

Metadata

Assignees

Type

No type

Projects

Status

Done

Relationships

None yet

Development

No branches or pull requests

Issue actions