diff --git a/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 7.md b/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 7.md index 0ca44ace66fc4..c7d3410263060 100644 --- a/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 7.md +++ b/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 7.md @@ -5,9 +5,12 @@ ***Introduced in .NET SDK 7.0.400, Visual Studio 2022 version 17.3.*** When the language version is C# 11 or later, a `nameof` operator in an attribute on a method -brings the type parameters of that method in scope. The same applies for local functions. +brings the type parameters of that method in scope. The same applies for local functions. +A `nameof` operator in an attribute on a method, its type parameters or parameters brings +the parameters of that method in scope. The same applies to local functions, lambdas, +delegates and indexers. -For instance, this will now be an error: +For instance, these will now be errors: ```csharp class C { @@ -20,11 +23,28 @@ class C } ``` +```csharp +class C +{ + class parameter + { + internal const string Constant = """"; + } + [MyAttribute(nameof(parameter.Constant))] + void M(int parameter) { } +} +``` + Possible workarounds are: -1. Rename the type parameter to avoid shadowing the name from outer scope. +1. Rename the type parameter or parameter to avoid shadowing the name from outer scope. +1. Use a string literal instead of the `nameof` operator. 1. Downgrade the `` element to 9.0 or earlier. +Note: The break will also apply to C# 10 and earlier when .NET 7 ships, but is +currently scoped down to users of LangVer=preview. +Tracked by https://github.com/dotnet/roslyn/issues/60640 + ## UTF8 String Literal conversion ***Introduced in .NET SDK 6.0.400, Visual Studio 2022 version 17.3.*** diff --git a/src/Compilers/CSharp/Portable/Binder/LocalBinderFactory.cs b/src/Compilers/CSharp/Portable/Binder/LocalBinderFactory.cs index 080e396518e4a..bb8c6252f9b3b 100644 --- a/src/Compilers/CSharp/Portable/Binder/LocalBinderFactory.cs +++ b/src/Compilers/CSharp/Portable/Binder/LocalBinderFactory.cs @@ -199,18 +199,31 @@ public override void VisitOperatorDeclaration(OperatorDeclarationSyntax node) Visit(node.ExpressionBody); } +#nullable enable public override void VisitInvocationExpression(InvocationExpressionSyntax node) { if (node.MayBeNameofOperator()) { var oldEnclosing = _enclosing; - WithTypeParametersBinder withTypeParametersBinder = ((_enclosing.Flags & BinderFlags.InContextualAttributeBinder) != 0) && - _enclosing.Compilation.IsFeatureEnabled(MessageID.IDS_FeatureExtendedNameofScope) - ? getExtraWithTypeParametersBinder(_enclosing, getAttributeTarget(_enclosing)) - : null; + + WithTypeParametersBinder? withTypeParametersBinder; + WithParametersBinder? withParametersBinder; + // The LangVer check will be removed before shipping .NET 7. + // Tracked by https://github.com/dotnet/roslyn/issues/60640 + if (((_enclosing.Flags & BinderFlags.InContextualAttributeBinder) != 0) && _enclosing.Compilation.IsFeatureEnabled(MessageID.IDS_FeatureExtendedNameofScope)) + { + var attributeTarget = getAttributeTarget(_enclosing); + withTypeParametersBinder = getExtraWithTypeParametersBinder(_enclosing, attributeTarget); + withParametersBinder = getExtraWithParametersBinder(_enclosing, attributeTarget); + } + else + { + withTypeParametersBinder = null; + withParametersBinder = null; + } var argumentExpression = node.ArgumentList.Arguments[0].Expression; - var possibleNameofBinder = new NameofBinder(argumentExpression, _enclosing, withTypeParametersBinder); + var possibleNameofBinder = new NameofBinder(argumentExpression, _enclosing, withTypeParametersBinder, withParametersBinder); AddToMap(node, possibleNameofBinder); _enclosing = possibleNameofBinder; @@ -231,9 +244,77 @@ static Symbol getAttributeTarget(Binder current) return contextualAttributeBinder.AttributeTarget; } - static WithTypeParametersBinder getExtraWithTypeParametersBinder(Binder enclosing, Symbol target) + static WithTypeParametersBinder? getExtraWithTypeParametersBinder(Binder enclosing, Symbol target) => target.Kind == SymbolKind.Method ? new WithMethodTypeParametersBinder((MethodSymbol)target, enclosing) : null; + + // We're bringing parameters in scope inside `nameof` in attributes on methods, their type parameters and parameters. + // This also applies to local functions, lambdas, indexers and delegates. + static WithParametersBinder? getExtraWithParametersBinder(Binder enclosing, Symbol target) + { + var parameters = target switch + { + SourcePropertyAccessorSymbol { MethodKind: MethodKind.PropertySet } setter => getSetterParameters(setter), + MethodSymbol methodSymbol => methodSymbol.Parameters, + ParameterSymbol parameter => getAllParameters(parameter), + TypeParameterSymbol typeParameter => getMethodParametersFromTypeParameter(typeParameter), + PropertySymbol property => property.Parameters, + NamedTypeSymbol namedType when namedType.IsDelegateType() => getDelegateParameters(namedType), + _ => default + }; + + return parameters.IsDefaultOrEmpty + ? null + : new WithParametersBinder(parameters, enclosing); + } + + static ImmutableArray getAllParameters(ParameterSymbol parameter) + { + switch (parameter.ContainingSymbol) + { + case MethodSymbol method: + return method.Parameters; + case PropertySymbol property: + return property.Parameters; + default: + Debug.Assert(false); + return default; + } + } + + static ImmutableArray getMethodParametersFromTypeParameter(TypeParameterSymbol typeParameter) + { + switch (typeParameter.ContainingSymbol) + { + case MethodSymbol method: + return method.Parameters; + case NamedTypeSymbol namedType when namedType.IsDelegateType(): + return getDelegateParameters(namedType); + default: + Debug.Assert(false); + return default; + } + } + + static ImmutableArray getDelegateParameters(NamedTypeSymbol delegateType) + { + Debug.Assert(delegateType.IsDelegateType()); + if (delegateType.DelegateInvokeMethod is { } invokeMethod) + { + return invokeMethod.Parameters; + } + + Debug.Assert(false); + return default; + } + + static ImmutableArray getSetterParameters(SourcePropertyAccessorSymbol setter) + { + var parameters = setter.Parameters; + Debug.Assert(parameters[^1] is SynthesizedAccessorValueParameterSymbol); + return parameters.RemoveAt(parameters.Length - 1); + } } +#nullable disable public override void VisitSimpleLambdaExpression(SimpleLambdaExpressionSyntax node) { diff --git a/src/Compilers/CSharp/Portable/Binder/NameofBinder.cs b/src/Compilers/CSharp/Portable/Binder/NameofBinder.cs index 54b79acf1debe..9e24e3d374abf 100644 --- a/src/Compilers/CSharp/Portable/Binder/NameofBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/NameofBinder.cs @@ -21,13 +21,15 @@ internal sealed class NameofBinder : Binder { private readonly SyntaxNode _nameofArgument; private readonly WithTypeParametersBinder? _withTypeParametersBinder; + private readonly WithParametersBinder? _withParametersBinder; private ThreeState _lazyIsNameofOperator; - internal NameofBinder(SyntaxNode nameofArgument, Binder next, WithTypeParametersBinder? withTypeParametersBinder) + internal NameofBinder(SyntaxNode nameofArgument, Binder next, WithTypeParametersBinder? withTypeParametersBinder, WithParametersBinder? withParametersBinder) : base(next) { _nameofArgument = nameofArgument; _withTypeParametersBinder = withTypeParametersBinder; + _withParametersBinder = withParametersBinder; } private bool IsNameofOperator @@ -49,14 +51,42 @@ private bool IsNameofOperator internal override void LookupSymbolsInSingleBinder(LookupResult result, string name, int arity, ConsList basesBeingResolved, LookupOptions options, Binder originalBinder, bool diagnose, ref CompoundUseSiteInfo useSiteInfo) { + bool foundParameter = false; + if (_withParametersBinder is not null && IsNameofOperator) + { + _withParametersBinder.LookupSymbolsInSingleBinder(result, name, arity, basesBeingResolved, options, originalBinder, diagnose, ref useSiteInfo); + if (!result.IsClear) + { + if (result.IsMultiViable) + { + return; + } + + foundParameter = true; + } + } + if (_withTypeParametersBinder is not null && IsNameofOperator) { - _withTypeParametersBinder.LookupSymbolsInSingleBinder(result, name, arity, basesBeingResolved, options, originalBinder, diagnose, ref useSiteInfo); + if (foundParameter) + { + var tmp = LookupResult.GetInstance(); + _withTypeParametersBinder.LookupSymbolsInSingleBinder(tmp, name, arity, basesBeingResolved, options, originalBinder, diagnose, ref useSiteInfo); + result.MergeEqual(tmp); + } + else + { + _withTypeParametersBinder.LookupSymbolsInSingleBinder(result, name, arity, basesBeingResolved, options, originalBinder, diagnose, ref useSiteInfo); + } } } internal override void AddLookupSymbolsInfoInSingleBinder(LookupSymbolsInfo info, LookupOptions options, Binder originalBinder) { + if (_withParametersBinder is not null && IsNameofOperator) + { + _withParametersBinder.AddLookupSymbolsInfoInSingleBinder(info, options, originalBinder); + } if (_withTypeParametersBinder is not null && IsNameofOperator) { _withTypeParametersBinder.AddLookupSymbolsInfoInSingleBinder(info, options, originalBinder); diff --git a/src/Compilers/CSharp/Portable/Binder/WithParametersBinder.cs b/src/Compilers/CSharp/Portable/Binder/WithParametersBinder.cs index 73e6e00c5371c..deac5a964225e 100644 --- a/src/Compilers/CSharp/Portable/Binder/WithParametersBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/WithParametersBinder.cs @@ -14,7 +14,8 @@ namespace Microsoft.CodeAnalysis.CSharp { /// /// Binder used to place the parameters of a method, property, indexer, or delegate - /// in scope when binding <param> tags inside of XML documentation comments. + /// in scope when binding <param> tags inside of XML documentation comments + /// and `nameof` in certain attribute positions. /// internal sealed class WithParametersBinder : Binder { diff --git a/src/Compilers/CSharp/Portable/Compilation/SyntaxTreeSemanticModel.cs b/src/Compilers/CSharp/Portable/Compilation/SyntaxTreeSemanticModel.cs index ca93a10f5d420..1d56e63bbf85e 100644 --- a/src/Compilers/CSharp/Portable/Compilation/SyntaxTreeSemanticModel.cs +++ b/src/Compilers/CSharp/Portable/Compilation/SyntaxTreeSemanticModel.cs @@ -1274,7 +1274,7 @@ private AttributeSemanticModel CreateModelForAttribute(Binder enclosingBinder, A var attributeType = (NamedTypeSymbol)enclosingBinder.BindType(attribute.Name, BindingDiagnosticBag.Discarded, out aliasOpt).Type; // For attributes where a nameof could introduce some type parameters, we need to track the attribute target - Symbol attributeTarget = GetAttributeTargetForExtraTypeParameters(attribute.Parent.Parent); + Symbol attributeTarget = getAttributeTarget(attribute.Parent.Parent); return AttributeSemanticModel.Create( this, @@ -1284,16 +1284,22 @@ private AttributeSemanticModel CreateModelForAttribute(Binder enclosingBinder, A attributeTarget, enclosingBinder.WithAdditionalFlags(BinderFlags.AttributeArgument), containingModel?.GetRemappedSymbols()); - } - Symbol GetAttributeTargetForExtraTypeParameters(SyntaxNode targetSyntax) - { - return targetSyntax switch + Symbol getAttributeTarget(SyntaxNode targetSyntax) { - MethodDeclarationSyntax methodDeclaration => GetDeclaredMemberSymbol(methodDeclaration), - LocalFunctionStatementSyntax localFunction => GetMemberModel(localFunction)?.GetDeclaredLocalFunction(localFunction), - _ => null - }; + return targetSyntax switch + { + BaseMethodDeclarationSyntax methodDeclaration => GetDeclaredMemberSymbol(methodDeclaration), + LocalFunctionStatementSyntax localFunction => GetMemberModel(localFunction)?.GetDeclaredLocalFunction(localFunction), + ParameterSyntax parameterSyntax => ((Symbols.PublicModel.ParameterSymbol)GetDeclaredSymbol(parameterSyntax)).UnderlyingSymbol, + TypeParameterSyntax typeParameterSyntax => ((Symbols.PublicModel.TypeParameterSymbol)GetDeclaredSymbol(typeParameterSyntax)).UnderlyingSymbol, + IndexerDeclarationSyntax indexerSyntax => ((Symbols.PublicModel.PropertySymbol)GetDeclaredSymbol(indexerSyntax)).UnderlyingSymbol, + AccessorDeclarationSyntax accessorSyntax => ((Symbols.PublicModel.MethodSymbol)GetDeclaredSymbol(accessorSyntax)).UnderlyingSymbol, + AnonymousFunctionExpressionSyntax anonymousFunction => ((Symbols.PublicModel.Symbol)GetSymbolInfo(anonymousFunction).Symbol).UnderlyingSymbol, + DelegateDeclarationSyntax delegateSyntax => ((Symbols.PublicModel.NamedTypeSymbol)GetDeclaredSymbol(delegateSyntax)).UnderlyingSymbol, + _ => null + }; + } } private FieldSymbol GetDeclaredFieldSymbol(VariableDeclaratorSyntax variableDecl) diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/LambdaTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/LambdaTests.cs index 80e3ad4c285ec..6f653c9c5fe67 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/LambdaTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/LambdaTests.cs @@ -6310,42 +6310,6 @@ class A : Attribute { } ); } - [Fact] - public void ParameterScope_NotInMethodAttributeNameOf() - { - var comp = CreateCompilation(@" -class C -{ - void M() - { - - var _ = - [My(nameof(parameter))] // 1 - void(int parameter) => { }; - } - - [My(nameof(parameter))] // 2 - void M2(int parameter) { } -} - -public class MyAttribute : System.Attribute -{ - public MyAttribute(string name1) { } -} -"); - comp.VerifyDiagnostics( - // (8,24): error CS0103: The name 'parameter' does not exist in the current context - // [My(nameof(parameter))] // 1 - Diagnostic(ErrorCode.ERR_NameNotInContext, "parameter").WithArguments("parameter").WithLocation(8, 24), - // (12,16): error CS0103: The name 'parameter' does not exist in the current context - // [My(nameof(parameter))] // 2 - Diagnostic(ErrorCode.ERR_NameNotInContext, "parameter").WithArguments("parameter").WithLocation(12, 16) - ); - - VerifyParameter(comp, 0); - VerifyParameter(comp, 1); - } - /// /// Look for usages of "parameter" and verify the index-th one. /// @@ -6538,38 +6502,6 @@ public MyAttribute(string name1) { } VerifyParameter(comp, 1); } - [Fact] - public void ParameterScope_NotInParameterAttributeNameOf() - { - var comp = CreateCompilation(@" -class C -{ - void M() - { - var _ = void ([My(nameof(parameter))] int parameter) => throw null; - } - - void M2([My(nameof(parameter))] int parameter) => throw null; -} - -public class MyAttribute : System.Attribute -{ - public MyAttribute(string name1) { } -} -"); - comp.VerifyDiagnostics( - // (6,34): error CS0103: The name 'parameter' does not exist in the current context - // var _ = void ([My(nameof(parameter))] int parameter) => throw null; - Diagnostic(ErrorCode.ERR_NameNotInContext, "parameter").WithArguments("parameter").WithLocation(6, 34), - // (9,24): error CS0103: The name 'parameter' does not exist in the current context - // void M2([My(nameof(parameter))] int parameter) => throw null; - Diagnostic(ErrorCode.ERR_NameNotInContext, "parameter").WithArguments("parameter").WithLocation(9, 24) - ); - - VerifyParameter(comp, 0); - VerifyParameter(comp, 1); - } - [Fact] public void ParameterScope_InParameterDefaultValueNameOf() { diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/LocalFunctionTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/LocalFunctionTests.cs index 6c9623d81d13f..06a5d7dcf2e23 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/LocalFunctionTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/LocalFunctionTests.cs @@ -7612,6 +7612,9 @@ public class MyAttribute : System.Attribute public MyAttribute(string name1) { } } "; + // The break will also apply to C# 10 and earlier when .NET 7 ships, + // but is currently scoped down to users of LangVer=preview. + // Tracked by https://github.com/dotnet/roslyn/issues/60640 var comp = CreateCompilation(source, parseOptions: TestOptions.Regular10); comp.VerifyDiagnostics(); @@ -8648,9 +8651,9 @@ public interface I } [Fact] - public void ParameterScope_NotInMethodAttributeNameOf() + public void ParameterScope_InMethodAttributeNameOf() { - var comp = CreateCompilation(@" + var source = @" class C { void M() @@ -8669,7 +8672,8 @@ public class MyAttribute : System.Attribute { public MyAttribute(string name1) { } } -"); +"; + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular10); comp.VerifyDiagnostics( // (8,20): error CS0103: The name 'parameter' does not exist in the current context // [My(nameof(parameter))] // 1 @@ -8681,238 +8685,1337 @@ public MyAttribute(string name1) { } VerifyParameter(comp, 0, null); VerifyParameter(comp, 1, null); + + comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics(); + + VerifyParameter(comp, 0, "void local(System.Int32 parameter)"); + VerifyParameter(comp, 1, "void C.M2(System.Int32 parameter)"); } /// /// Look for usages of "parameter" and verify the index-th one. /// - private void VerifyParameter(CSharpCompilation comp, int index, string expectedMethod) + private void VerifyParameter(CSharpCompilation comp, int index, string expectedMethod, string parameterName = "parameter") { - var tree = comp.SyntaxTrees.Single(); + var tree = comp.SyntaxTrees.First(); var model = comp.GetSemanticModel(tree); var parameterUsages = tree.GetRoot().DescendantNodes().OfType() - .Where(i => i.Identifier.ValueText == "parameter") + .Where(i => i.Identifier.ValueText == parameterName) .Where(i => i.Ancestors().Any(a => a.IsKind(SyntaxKind.Attribute) || a.IsKind(SyntaxKind.TypeConstraint) || a.IsKind(SyntaxKind.DefaultExpression) || a.IsKind(SyntaxKind.InvocationExpression))) .ToArray(); var parameterUsage = parameterUsages[index]; var symbol = model.GetSymbolInfo(parameterUsage).Symbol; - Debug.Assert(expectedMethod is null); - Assert.Null(symbol); - Assert.True(model.GetTypeInfo(parameterUsage).Type.IsErrorType()); - Assert.DoesNotContain("parameter", model.LookupSymbols(parameterUsage.Position).ToTestDisplayStrings()); + if (expectedMethod is null) + { + Assert.Null(symbol); + Assert.True(model.GetTypeInfo(parameterUsage).Type.IsErrorType()); + Assert.DoesNotContain("parameter", model.LookupSymbols(parameterUsage.Position).ToTestDisplayStrings()); + } + else + { + Assert.Equal(expectedMethod, symbol.ContainingSymbol.ToTestDisplayString()); + Assert.Equal("System.Int32", model.GetTypeInfo(parameterUsage).Type.ToTestDisplayString()); + + var lookupResults = model.LookupSymbols(parameterUsage.Position).ToTestDisplayStrings(); + Assert.Contains($"System.Int32 {parameterName}", lookupResults); + } } [Fact] - public void ParameterScope_NotInMethodAttribute() + public void ParameterScope_InMethodAttributeNameOf_ConflictingNames() { - var comp = CreateCompilation(@" + var source = @" class C { void M() { - local(0); + local(0); - [My(parameter)] // 1 - void local(int parameter) { } + [My(nameof(parameter))] + void local<@parameter>(int parameter) => throw null; } - [My(parameter)] // 2 - void M2(int parameter) { } + [My(nameof(parameter))] + void M2<@parameter>(int parameter) => throw null; } public class MyAttribute : System.Attribute { - public MyAttribute(object o) { } + public MyAttribute(string name1) { } } -"); +"; + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular10); comp.VerifyDiagnostics( - // (8,13): error CS0103: The name 'parameter' does not exist in the current context - // [My(parameter)] // 1 - Diagnostic(ErrorCode.ERR_NameNotInContext, "parameter").WithArguments("parameter").WithLocation(8, 13), - // (12,9): error CS0103: The name 'parameter' does not exist in the current context - // [My(parameter)] // 2 - Diagnostic(ErrorCode.ERR_NameNotInContext, "parameter").WithArguments("parameter").WithLocation(12, 9) + // (8,20): error CS0103: The name 'parameter' does not exist in the current context + // [My(nameof(parameter))] + Diagnostic(ErrorCode.ERR_NameNotInContext, "parameter").WithArguments("parameter").WithLocation(8, 20), + // (9,36): error CS0412: 'parameter': a parameter, local variable, or local function cannot have the same name as a method type parameter + // void local<@parameter>(int parameter) => throw null; + Diagnostic(ErrorCode.ERR_LocalSameNameAsTypeParam, "parameter").WithArguments("parameter").WithLocation(9, 36), + // (12,16): error CS0103: The name 'parameter' does not exist in the current context + // [My(nameof(parameter))] + Diagnostic(ErrorCode.ERR_NameNotInContext, "parameter").WithArguments("parameter").WithLocation(12, 16), + // (13,29): error CS0412: 'parameter': a parameter, local variable, or local function cannot have the same name as a method type parameter + // void M2<@parameter>(int parameter) => throw null; + Diagnostic(ErrorCode.ERR_LocalSameNameAsTypeParam, "parameter").WithArguments("parameter").WithLocation(13, 29) ); VerifyParameter(comp, 0, null); VerifyParameter(comp, 1, null); + + comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics( + // (9,36): error CS0412: 'parameter': a parameter, local variable, or local function cannot have the same name as a method type parameter + // void local<@parameter>(int parameter) => throw null; + Diagnostic(ErrorCode.ERR_LocalSameNameAsTypeParam, "parameter").WithArguments("parameter").WithLocation(9, 36), + // (13,29): error CS0412: 'parameter': a parameter, local variable, or local function cannot have the same name as a method type parameter + // void M2<@parameter>(int parameter) => throw null; + Diagnostic(ErrorCode.ERR_LocalSameNameAsTypeParam, "parameter").WithArguments("parameter").WithLocation(13, 29) + ); + + VerifyParameter(comp, 0, "void local(System.Int32 parameter)"); + VerifyParameter(comp, 1, "void C.M2(System.Int32 parameter)"); } [Fact] - public void ParameterScope_NotInMethodAttributeTypeArgument() + public void ParameterScope_InMethodAttributeNameOf_CompatBreak() { - var comp = CreateCompilation(@" + var source = @" class C { + class @parameter + { + internal const int Constant = 0; + } + void M() { local(0); - [My] // 1 + [My(nameof(parameter.Constant))] // 1 void local(int parameter) { } } - [My] // 2 + [My(nameof(parameter.Constant))] // 2 void M2(int parameter) { } } -public class MyAttribute : System.Attribute +public class MyAttribute : System.Attribute { + public MyAttribute(string name1) { } } -"); +"; + // The break will also apply to C# 10 and earlier when .NET 7 ships, + // but is currently scoped down to users of LangVer=preview. + // Tracked by https://github.com/dotnet/roslyn/issues/60640 + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular10); + comp.VerifyDiagnostics(); + + comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext); comp.VerifyDiagnostics( - // (8,13): error CS0246: The type or namespace name 'parameter' could not be found (are you missing a using directive or an assembly reference?) - // [My] // 1 - Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "parameter").WithArguments("parameter").WithLocation(8, 13), - // (12,9): error CS0246: The type or namespace name 'parameter' could not be found (are you missing a using directive or an assembly reference?) - // [My] // 2 - Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "parameter").WithArguments("parameter").WithLocation(12, 9) + // (13,30): error CS1061: 'int' does not contain a definition for 'Constant' and no accessible extension method 'Constant' accepting a first argument of type 'int' could be found (are you missing a using directive or an assembly reference?) + // [My(nameof(parameter.Constant))] // 1 + Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "Constant").WithArguments("int", "Constant").WithLocation(13, 30), + // (17,26): error CS1061: 'int' does not contain a definition for 'Constant' and no accessible extension method 'Constant' accepting a first argument of type 'int' could be found (are you missing a using directive or an assembly reference?) + // [My(nameof(parameter.Constant))] // 2 + Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "Constant").WithArguments("int", "Constant").WithLocation(17, 26) ); - - VerifyParameter(comp, 0, null); - VerifyParameter(comp, 1, null); } [Fact] - public void ParameterScope_NotAsMethodAttributeType() + public void ParameterScope_InMethodAttributeNameOf_WithReturnTarget() { - var comp = CreateCompilation(@" + var source = @" class C { void M() { - local(null); + local(0); - [parameter] // 1 - void local(System.Attribute parameter) { } + [return: My(nameof(parameter))] // 1 + void local(int parameter) { } } - [parameter] // 2 - void M2(System.Attribute parameter) { } + [return: My(nameof(parameter))] // 2 + void M2(int parameter) { } } -"); + +public class MyAttribute : System.Attribute +{ + public MyAttribute(string name1) { } +} +"; + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular10); comp.VerifyDiagnostics( - // (8,10): error CS0246: The type or namespace name 'parameterAttribute' could not be found (are you missing a using directive or an assembly reference?) - // [parameter] // 1 - Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "parameter").WithArguments("parameterAttribute").WithLocation(8, 10), - // (8,10): error CS0246: The type or namespace name 'parameter' could not be found (are you missing a using directive or an assembly reference?) - // [parameter] // 1 - Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "parameter").WithArguments("parameter").WithLocation(8, 10), - // (12,6): error CS0246: The type or namespace name 'parameterAttribute' could not be found (are you missing a using directive or an assembly reference?) - // [parameter] // 2 - Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "parameter").WithArguments("parameterAttribute").WithLocation(12, 6), - // (12,6): error CS0246: The type or namespace name 'parameter' could not be found (are you missing a using directive or an assembly reference?) - // [parameter] // 2 - Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "parameter").WithArguments("parameter").WithLocation(12, 6) + // (8,28): error CS0103: The name 'parameter' does not exist in the current context + // [return: My(nameof(parameter))] // 1 + Diagnostic(ErrorCode.ERR_NameNotInContext, "parameter").WithArguments("parameter").WithLocation(8, 28), + // (12,24): error CS0103: The name 'parameter' does not exist in the current context + // [return: My(nameof(parameter))] // 2 + Diagnostic(ErrorCode.ERR_NameNotInContext, "parameter").WithArguments("parameter").WithLocation(12, 24) ); VerifyParameter(comp, 0, null); VerifyParameter(comp, 1, null); + + comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics(); + + VerifyParameter(comp, 0, "void local(System.Int32 parameter)"); + VerifyParameter(comp, 1, "void C.M2(System.Int32 parameter)"); } [Fact] - public void ParameterScope_NotInParameterAttribute() + public void ParameterScope_InMethodAttributeNameOf_SpeculatingWithReplacementAttributeInsideExisting() { - var comp = CreateCompilation(@" + var source = @" class C { void M() { local(0); - void local([My(parameter)] int parameter) => throw null; + [My(positionA)] + void local(int parameter) { } } - void M2([My(parameter)] int parameter) => throw null; + [My(positionB)] + void M2(int parameter) { } } public class MyAttribute : System.Attribute { public MyAttribute(string name1) { } } -"); +"; + // C# 10 + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular10); comp.VerifyDiagnostics( - // (8,24): error CS0103: The name 'parameter' does not exist in the current context - // void local([My(parameter)] int parameter) => throw null; - Diagnostic(ErrorCode.ERR_NameNotInContext, "parameter").WithArguments("parameter").WithLocation(8, 24), - // (11,17): error CS0103: The name 'parameter' does not exist in the current context - // void M2([My(parameter)] int parameter) => throw null; - Diagnostic(ErrorCode.ERR_NameNotInContext, "parameter").WithArguments("parameter").WithLocation(11, 17) + // (8,13): error CS0103: The name 'positionA' does not exist in the current context + // [My(positionA)] + Diagnostic(ErrorCode.ERR_NameNotInContext, "positionA").WithArguments("positionA").WithLocation(8, 13), + // (12,9): error CS0103: The name 'positionB' does not exist in the current context + // [My(positionB)] + Diagnostic(ErrorCode.ERR_NameNotInContext, "positionB").WithArguments("positionB").WithLocation(12, 9) ); - VerifyParameter(comp, 0, null); - VerifyParameter(comp, 1, null); + var tree = comp.SyntaxTrees.Single(); + var parentModel = comp.GetSemanticModel(tree); + var localFuncPosition = tree.GetText().ToString().IndexOf("positionA", StringComparison.Ordinal); + var methodPosition = tree.GetText().ToString().IndexOf("positionB", StringComparison.Ordinal); + + var attr = parseAttributeSyntax("[My(nameof(parameter))]", TestOptions.Regular10); + VerifyParameterSpeculation(parentModel, localFuncPosition, attr, found: false); + VerifyParameterSpeculation(parentModel, methodPosition, attr, found: false); + + attr = parseAttributeSyntax("[My(parameter)]", TestOptions.Regular10); + VerifyParameterSpeculation(parentModel, localFuncPosition, attr, found: false); + VerifyParameterSpeculation(parentModel, methodPosition, attr, found: false); + + // C# 11 + comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics( + // (8,13): error CS0103: The name 'positionA' does not exist in the current context + // [My(positionA)] + Diagnostic(ErrorCode.ERR_NameNotInContext, "positionA").WithArguments("positionA").WithLocation(8, 13), + // (12,9): error CS0103: The name 'positionB' does not exist in the current context + // [My(positionB)] + Diagnostic(ErrorCode.ERR_NameNotInContext, "positionB").WithArguments("positionB").WithLocation(12, 9) + ); + + tree = comp.SyntaxTrees.Single(); + parentModel = comp.GetSemanticModel(tree); + + attr = parseAttributeSyntax("[My(nameof(parameter))]", TestOptions.Regular10); + VerifyParameterSpeculation(parentModel, localFuncPosition, attr); + VerifyParameterSpeculation(parentModel, methodPosition, attr); + + attr = parseAttributeSyntax("[My(parameter)]", TestOptions.Regular10); + VerifyParameterSpeculation(parentModel, localFuncPosition, attr, found: false); + VerifyParameterSpeculation(parentModel, methodPosition, attr, found: false); + + return; + + static AttributeSyntax parseAttributeSyntax(string source, CSharpParseOptions parseOptions) + => SyntaxFactory.ParseCompilationUnit($@"class X {{ {source} void M() {{ }} }}", options: parseOptions).DescendantNodes().OfType().Single(); + } + + static void VerifyParameterSpeculation(SemanticModel parentModel, int localFuncPosition, AttributeSyntax attr1, bool found = true) + { + SemanticModel speculativeModel; + var success = parentModel.TryGetSpeculativeSemanticModel(localFuncPosition, attr1, out speculativeModel); + Assert.True(success); + Assert.NotNull(speculativeModel); + + var symbolInfo = speculativeModel.GetSymbolInfo(getParameter(attr1)); + if (found) + { + Assert.Equal(SymbolKind.Parameter, symbolInfo.Symbol.Kind); + } + else + { + Assert.Null(symbolInfo.Symbol); + } + return; + + static IdentifierNameSyntax getParameter(CSharpSyntaxNode node) + { + return node.DescendantNodes().OfType().Where(i => i.Identifier.ValueText == "parameter").Single(); + } } [Fact] - public void ParameterScope_NotInParameterAttributeNameOf() + public void ParameterScope_InIndexerAttributeNameOf() { - var comp = CreateCompilation(@" + var source = @" class C { - void M() - { - local(0); + [My(nameof(parameter))] + int this[int parameter] => 0; +} - void local([My(nameof(parameter))] int parameter) => throw null; - } +public class MyAttribute : System.Attribute +{ + public MyAttribute(string name1) { } +} +"; + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular10); + comp.VerifyDiagnostics( + // (4,16): error CS0103: The name 'parameter' does not exist in the current context + // [My(nameof(parameter))] + Diagnostic(ErrorCode.ERR_NameNotInContext, "parameter").WithArguments("parameter").WithLocation(4, 16) + ); - void M2([My(nameof(parameter))] int parameter) => throw null; + VerifyParameter(comp, 0, null); + + comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics(); + + VerifyParameter(comp, 0, "System.Int32 C.this[System.Int32 parameter] { get; }"); + } + + [Fact] + public void ParameterScope_InIndexerAttributeNameOf_SetterOnly() + { + var source = @" +class C +{ + [My(nameof(parameter))] + int this[int parameter] { set => throw null; } } public class MyAttribute : System.Attribute { public MyAttribute(string name1) { } } -"); +"; + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular10); comp.VerifyDiagnostics( - // (8,31): error CS0103: The name 'parameter' does not exist in the current context - // void local([My(nameof(parameter))] int parameter) => throw null; - Diagnostic(ErrorCode.ERR_NameNotInContext, "parameter").WithArguments("parameter").WithLocation(8, 31), - // (11,24): error CS0103: The name 'parameter' does not exist in the current context - // void M2([My(nameof(parameter))] int parameter) => throw null; - Diagnostic(ErrorCode.ERR_NameNotInContext, "parameter").WithArguments("parameter").WithLocation(11, 24) + // (4,16): error CS0103: The name 'parameter' does not exist in the current context + // [My(nameof(parameter))] + Diagnostic(ErrorCode.ERR_NameNotInContext, "parameter").WithArguments("parameter").WithLocation(4, 16) ); VerifyParameter(comp, 0, null); - VerifyParameter(comp, 1, null); + + comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics(); + + VerifyParameter(comp, 0, "System.Int32 C.this[System.Int32 parameter] { set; }"); } [Fact] - public void ParameterScope_NotAsParameterAttributeType() + public void ParameterScope_InIndexerGetterAttributeNameOf() { - var comp = CreateCompilation(@" + var source = @" class C { - void M() + int this[int parameter] { - local(null); - - void local([parameter] System.Attribute parameter) => throw null; + [My(nameof(parameter))] + get => throw null; } +} - void M2([parameter] System.Attribute parameter) => throw null; +public class MyAttribute : System.Attribute +{ + public MyAttribute(string name1) { } } -"); +"; + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular10); comp.VerifyDiagnostics( - // (8,21): error CS0246: The type or namespace name 'parameterAttribute' could not be found (are you missing a using directive or an assembly reference?) - // void local([parameter] System.Attribute parameter) => throw null; - Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "parameter").WithArguments("parameterAttribute").WithLocation(8, 21), - // (8,21): error CS0246: The type or namespace name 'parameter' could not be found (are you missing a using directive or an assembly reference?) - // void local([parameter] System.Attribute parameter) => throw null; - Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "parameter").WithArguments("parameter").WithLocation(8, 21), - // (11,14): error CS0246: The type or namespace name 'parameterAttribute' could not be found (are you missing a using directive or an assembly reference?) - // void M2([parameter] System.Attribute parameter) => throw null; - Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "parameter").WithArguments("parameterAttribute").WithLocation(11, 14), - // (11,14): error CS0246: The type or namespace name 'parameter' could not be found (are you missing a using directive or an assembly reference?) - // void M2([parameter] System.Attribute parameter) => throw null; - Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "parameter").WithArguments("parameter").WithLocation(11, 14) + // (6,20): error CS0103: The name 'parameter' does not exist in the current context + // [My(nameof(parameter))] + Diagnostic(ErrorCode.ERR_NameNotInContext, "parameter").WithArguments("parameter").WithLocation(6, 20) + ); + + VerifyParameter(comp, 0, null); + + comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + + VerifyParameter(comp, 0, "System.Int32 C.this[System.Int32 parameter].get"); + } + + [Fact] + public void ParameterScope_InIndexerSetterAttributeNameOf() + { + var source = @" +class C +{ + int this[int parameter] + { + [My(nameof(parameter))] + set => throw null; + } +} + +public class MyAttribute : System.Attribute +{ + public MyAttribute(string name1) { } +} +"; + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular10); + comp.VerifyDiagnostics( + // (6,20): error CS0103: The name 'parameter' does not exist in the current context + // [My(nameof(parameter))] + Diagnostic(ErrorCode.ERR_NameNotInContext, "parameter").WithArguments("parameter").WithLocation(6, 20) + ); + + VerifyParameter(comp, 0, null); + + comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics(); + + VerifyParameter(comp, 0, "void C.this[System.Int32 parameter].set"); + } + + [Fact] + public void ParameterScope_InIndexerInitSetterAttributeNameOf() + { + var source = @" +class C +{ + int this[int parameter] + { + [My(nameof(parameter))] + init => throw null; + } +} + +public class MyAttribute : System.Attribute +{ + public MyAttribute(string name1) { } +} +"; + var comp = CreateCompilation(new[] { source, IsExternalInitTypeDefinition }, parseOptions: TestOptions.Regular10); + comp.VerifyDiagnostics( + // (6,20): error CS0103: The name 'parameter' does not exist in the current context + // [My(nameof(parameter))] + Diagnostic(ErrorCode.ERR_NameNotInContext, "parameter").WithArguments("parameter").WithLocation(6, 20) + ); + + VerifyParameter(comp, 0, null); + + comp = CreateCompilation(new[] { source, IsExternalInitTypeDefinition }, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics(); + + VerifyParameter(comp, 0, "void modreq(System.Runtime.CompilerServices.IsExternalInit) C.this[System.Int32 parameter].init"); + } + + [Fact] + public void ParameterScope_InMethodAttributeNameOf_Lambda() + { + var source = @" +class C +{ + void M() + { + var x = [My(nameof(parameter))] int (int parameter) => 0; + } +} + +public class MyAttribute : System.Attribute +{ + public MyAttribute(string name1) { } +} +"; + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular10); + comp.VerifyDiagnostics( + // (6,28): error CS0103: The name 'parameter' does not exist in the current context + // var x = [My(nameof(parameter))] int (int parameter) => 0; + Diagnostic(ErrorCode.ERR_NameNotInContext, "parameter").WithArguments("parameter").WithLocation(6, 28) + ); + + VerifyParameter(comp, 0, null); + + comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics(); + + VerifyParameter(comp, 0, "lambda expression"); + } + + [Fact] + public void ParameterScope_InMethodAttributeNameOf_AnonymousFunctionWithImplicitParameters() + { + var source = @" +class C +{ + void M() + { + System.Func x = [My(nameof(parameter))] delegate { return 1; } + } +} + +public class MyAttribute : System.Attribute +{ + public MyAttribute(string name1) { } +} +"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (6,35): error CS1525: Invalid expression term '[' + // System.Func x = [My(nameof(parameter))] delegate { return 1; } + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "[").WithArguments("[").WithLocation(6, 35), + // (6,36): error CS0103: The name 'My' does not exist in the current context + // System.Func x = [My(nameof(parameter))] delegate { return 1; } + Diagnostic(ErrorCode.ERR_NameNotInContext, "My").WithArguments("My").WithLocation(6, 36), + // (6,46): error CS0103: The name 'parameter' does not exist in the current context + // System.Func x = [My(nameof(parameter))] delegate { return 1; } + Diagnostic(ErrorCode.ERR_NameNotInContext, "parameter").WithArguments("parameter").WithLocation(6, 46), + // (6,59): error CS1002: ; expected + // System.Func x = [My(nameof(parameter))] delegate { return 1; } + Diagnostic(ErrorCode.ERR_SemicolonExpected, "delegate").WithLocation(6, 59), + // (6,81): error CS1002: ; expected + // System.Func x = [My(nameof(parameter))] delegate { return 1; } + Diagnostic(ErrorCode.ERR_SemicolonExpected, "").WithLocation(6, 81) + ); + } + + [Fact] + public void ParameterScope_InMethodAttributeNameOf_Delegate() + { + var source = @" +[My(nameof(parameter))] delegate int MyDelegate(int parameter); + +public class MyAttribute : System.Attribute +{ + public MyAttribute(string name1) { } +} +"; + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular10); + comp.VerifyDiagnostics( + // (2,12): error CS0103: The name 'parameter' does not exist in the current context + // [My(nameof(parameter))] delegate int MyDelegate(int parameter); + Diagnostic(ErrorCode.ERR_NameNotInContext, "parameter").WithArguments("parameter").WithLocation(2, 12) + ); + + VerifyParameter(comp, 0, null); + + comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics(); + + VerifyParameter(comp, 0, "System.Int32 MyDelegate.Invoke(System.Int32 parameter)"); + } + + [Fact] + public void ParameterScope_InMethodAttributeNameOf_Delegate_ConflictingName() + { + var source = @" +[My(nameof(TParameter))] delegate int MyDelegate(int TParameter); + +public class MyAttribute : System.Attribute +{ + public MyAttribute(string name1) { } +} +"; + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular10); + comp.VerifyDiagnostics(); + + VerifyTParameter(comp, 0, "MyDelegate"); + + comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics(); + + VerifyParameter(comp, 0, "System.Int32 MyDelegate.Invoke(System.Int32 TParameter)", parameterName: "TParameter"); + } + + [Fact] + public void ParameterScope_InMethodAttributeNameOf_Constructor() + { + var source = @" +class C +{ + [My(nameof(parameter))] C(int parameter) { } +} + +public class MyAttribute : System.Attribute +{ + public MyAttribute(string name1) { } +} +"; + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular10); + comp.VerifyDiagnostics( + // (4,16): error CS0103: The name 'parameter' does not exist in the current context + // [My(nameof(parameter))] C(int parameter) { } + Diagnostic(ErrorCode.ERR_NameNotInContext, "parameter").WithArguments("parameter").WithLocation(4, 16) + ); + + VerifyParameter(comp, 0, null); + + comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics(); + + VerifyParameter(comp, 0, "C..ctor(System.Int32 parameter)"); + } + + [Fact] + public void ParameterScope_NotInMethodAttribute() + { + var comp = CreateCompilation(@" +class C +{ + void M() + { + local(0); + + [My(parameter)] // 1 + void local(int parameter) { } + } + + [My(parameter)] // 2 + void M2(int parameter) { } +} + +public class MyAttribute : System.Attribute +{ + public MyAttribute(object o) { } +} +"); + comp.VerifyDiagnostics( + // (8,13): error CS0103: The name 'parameter' does not exist in the current context + // [My(parameter)] // 1 + Diagnostic(ErrorCode.ERR_NameNotInContext, "parameter").WithArguments("parameter").WithLocation(8, 13), + // (12,9): error CS0103: The name 'parameter' does not exist in the current context + // [My(parameter)] // 2 + Diagnostic(ErrorCode.ERR_NameNotInContext, "parameter").WithArguments("parameter").WithLocation(12, 9) + ); + + VerifyParameter(comp, 0, null); + VerifyParameter(comp, 1, null); + } + + [Fact] + public void ParameterScope_NotInMethodAttributeTypeArgument() + { + var comp = CreateCompilation(@" +class C +{ + void M() + { + local(0); + + [My] // 1 + void local(int parameter) { } + } + + [My] // 2 + void M2(int parameter) { } +} + +public class MyAttribute : System.Attribute +{ +} +"); + comp.VerifyDiagnostics( + // (8,13): error CS0246: The type or namespace name 'parameter' could not be found (are you missing a using directive or an assembly reference?) + // [My] // 1 + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "parameter").WithArguments("parameter").WithLocation(8, 13), + // (12,9): error CS0246: The type or namespace name 'parameter' could not be found (are you missing a using directive or an assembly reference?) + // [My] // 2 + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "parameter").WithArguments("parameter").WithLocation(12, 9) + ); + + VerifyParameter(comp, 0, null); + VerifyParameter(comp, 1, null); + } + + [Fact] + public void ParameterScope_NotAsMethodAttributeType() + { + var comp = CreateCompilation(@" +class C +{ + void M() + { + local(null); + + [parameter] // 1 + void local(System.Attribute parameter) { } + } + + [parameter] // 2 + void M2(System.Attribute parameter) { } +} +"); + comp.VerifyDiagnostics( + // (8,10): error CS0246: The type or namespace name 'parameterAttribute' could not be found (are you missing a using directive or an assembly reference?) + // [parameter] // 1 + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "parameter").WithArguments("parameterAttribute").WithLocation(8, 10), + // (8,10): error CS0246: The type or namespace name 'parameter' could not be found (are you missing a using directive or an assembly reference?) + // [parameter] // 1 + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "parameter").WithArguments("parameter").WithLocation(8, 10), + // (12,6): error CS0246: The type or namespace name 'parameterAttribute' could not be found (are you missing a using directive or an assembly reference?) + // [parameter] // 2 + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "parameter").WithArguments("parameterAttribute").WithLocation(12, 6), + // (12,6): error CS0246: The type or namespace name 'parameter' could not be found (are you missing a using directive or an assembly reference?) + // [parameter] // 2 + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "parameter").WithArguments("parameter").WithLocation(12, 6) + ); + + VerifyParameter(comp, 0, null); + VerifyParameter(comp, 1, null); + } + + [Fact] + public void ParameterScope_NotInParameterAttribute() + { + var comp = CreateCompilation(@" +class C +{ + void M() + { + local(0); + + void local([My(parameter)] int parameter) => throw null; + } + + void M2([My(parameter)] int parameter) => throw null; +} + +public class MyAttribute : System.Attribute +{ + public MyAttribute(string name1) { } +} +"); + comp.VerifyDiagnostics( + // (8,24): error CS0103: The name 'parameter' does not exist in the current context + // void local([My(parameter)] int parameter) => throw null; + Diagnostic(ErrorCode.ERR_NameNotInContext, "parameter").WithArguments("parameter").WithLocation(8, 24), + // (11,17): error CS0103: The name 'parameter' does not exist in the current context + // void M2([My(parameter)] int parameter) => throw null; + Diagnostic(ErrorCode.ERR_NameNotInContext, "parameter").WithArguments("parameter").WithLocation(11, 17) + ); + + VerifyParameter(comp, 0, null); + VerifyParameter(comp, 1, null); + } + + [Fact] + public void ParameterScope_InParameterAttributeNameOf() + { + var source = @" +class C +{ + void M() + { + local(0); + + void local([My(nameof(parameter))] int parameter) => throw null; + } + + void M2([My(nameof(parameter))] int parameter) => throw null; +} + +public class MyAttribute : System.Attribute +{ + public MyAttribute(string name1) { } +} +"; + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular10); + comp.VerifyDiagnostics( + // (8,31): error CS0103: The name 'parameter' does not exist in the current context + // void local([My(nameof(parameter))] int parameter) => throw null; + Diagnostic(ErrorCode.ERR_NameNotInContext, "parameter").WithArguments("parameter").WithLocation(8, 31), + // (11,24): error CS0103: The name 'parameter' does not exist in the current context + // void M2([My(nameof(parameter))] int parameter) => throw null; + Diagnostic(ErrorCode.ERR_NameNotInContext, "parameter").WithArguments("parameter").WithLocation(11, 24) + ); + + VerifyParameter(comp, 0, null); + VerifyParameter(comp, 1, null); + + comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics(); + + VerifyParameter(comp, 0, "void local(System.Int32 parameter)"); + VerifyParameter(comp, 1, "void C.M2(System.Int32 parameter)"); + } + + [Fact] + public void ParameterScope_InParameterAttributeNameOf_ConflictingNames() + { + var source = @" +class C +{ + void M() + { + local(0); + + void local([My(nameof(TParameter))] int TParameter) => throw null; + } + + void M2([My(nameof(TParameter))] int TParameter) => throw null; +} + +public class MyAttribute : System.Attribute +{ + public MyAttribute(string name1) { } +} +"; + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular10); + comp.VerifyDiagnostics( + // (8,61): error CS0412: 'TParameter': a parameter, local variable, or local function cannot have the same name as a method type parameter + // void local([My(nameof(TParameter))] int TParameter) => throw null; + Diagnostic(ErrorCode.ERR_LocalSameNameAsTypeParam, "TParameter").WithArguments("TParameter").WithLocation(8, 61), + // (11,54): error CS0412: 'TParameter': a parameter, local variable, or local function cannot have the same name as a method type parameter + // void M2([My(nameof(TParameter))] int TParameter) => throw null; + Diagnostic(ErrorCode.ERR_LocalSameNameAsTypeParam, "TParameter").WithArguments("TParameter").WithLocation(11, 54) + ); + + VerifyTParameter(comp, 0, "void local(System.Int32 TParameter)"); + VerifyTParameter(comp, 1, "void C.M2(System.Int32 TParameter)"); + + comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics( + // (8,61): error CS0412: 'TParameter': a parameter, local variable, or local function cannot have the same name as a method type parameter + // void local([My(nameof(TParameter))] int TParameter) => throw null; + Diagnostic(ErrorCode.ERR_LocalSameNameAsTypeParam, "TParameter").WithArguments("TParameter").WithLocation(8, 61), + // (11,54): error CS0412: 'TParameter': a parameter, local variable, or local function cannot have the same name as a method type parameter + // void M2([My(nameof(TParameter))] int TParameter) => throw null; + Diagnostic(ErrorCode.ERR_LocalSameNameAsTypeParam, "TParameter").WithArguments("TParameter").WithLocation(11, 54) + ); + + VerifyParameter(comp, 0, "void local(System.Int32 TParameter)", parameterName: "TParameter"); + VerifyParameter(comp, 1, "void C.M2(System.Int32 TParameter)", parameterName: "TParameter"); + } + + [Fact] + public void ParameterScope_InParameterAttributeNameOf_SpeculatingWithReplacementAttributeInsideExisting() + { + var source = @" +class C +{ + void M() + { + local(0); + + void local([My(positionA)] int parameter) { } + } + + void M2([My(positionB)] int parameter) { } +} + +public class MyAttribute : System.Attribute +{ + public MyAttribute(string name1) { } +} +"; + // C# 10 + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular10); + comp.VerifyDiagnostics( + // (8,24): error CS0103: The name 'positionA' does not exist in the current context + // void local([My(positionA)] int parameter) { } + Diagnostic(ErrorCode.ERR_NameNotInContext, "positionA").WithArguments("positionA").WithLocation(8, 24), + // (11,17): error CS0103: The name 'positionB' does not exist in the current context + // void M2([My(positionB)] int parameter) { } + Diagnostic(ErrorCode.ERR_NameNotInContext, "positionB").WithArguments("positionB").WithLocation(11, 17) + ); + + var tree = comp.SyntaxTrees.Single(); + var parentModel = comp.GetSemanticModel(tree); + var localFuncPosition = tree.GetText().ToString().IndexOf("positionA", StringComparison.Ordinal); + var methodPosition = tree.GetText().ToString().IndexOf("positionB", StringComparison.Ordinal); + + var attr = parseAttributeSyntax("[My(nameof(parameter))]", TestOptions.Regular10); + VerifyParameterSpeculation(parentModel, localFuncPosition, attr, found: false); + VerifyParameterSpeculation(parentModel, methodPosition, attr, found: false); + + attr = parseAttributeSyntax("[My(parameter)]", TestOptions.Regular10); + VerifyParameterSpeculation(parentModel, localFuncPosition, attr, found: false); + VerifyParameterSpeculation(parentModel, methodPosition, attr, found: false); + + // C# 11 + comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics( + // (8,24): error CS0103: The name 'positionA' does not exist in the current context + // void local([My(positionA)] int parameter) { } + Diagnostic(ErrorCode.ERR_NameNotInContext, "positionA").WithArguments("positionA").WithLocation(8, 24), + // (11,17): error CS0103: The name 'positionB' does not exist in the current context + // void M2([My(positionB)] int parameter) { } + Diagnostic(ErrorCode.ERR_NameNotInContext, "positionB").WithArguments("positionB").WithLocation(11, 17) + ); + + tree = comp.SyntaxTrees.Single(); + parentModel = comp.GetSemanticModel(tree); + + attr = parseAttributeSyntax("[My(nameof(parameter))]", TestOptions.Regular10); + VerifyParameterSpeculation(parentModel, localFuncPosition, attr); + VerifyParameterSpeculation(parentModel, methodPosition, attr); + + attr = parseAttributeSyntax("[My(parameter)]", TestOptions.Regular10); + VerifyParameterSpeculation(parentModel, localFuncPosition, attr, found: false); + VerifyParameterSpeculation(parentModel, methodPosition, attr, found: false); + + return; + + static AttributeSyntax parseAttributeSyntax(string source, CSharpParseOptions parseOptions) + => SyntaxFactory.ParseCompilationUnit($@"class X {{ {source} void M() {{ }} }}", options: parseOptions).DescendantNodes().OfType().Single(); + } + + [Fact] + public void ParameterScope_InParameterAttributeNameOf_Indexer() + { + var source = @" +class C +{ + int this[[My(nameof(parameter))] int parameter] => throw null; +} + +public class MyAttribute : System.Attribute +{ + public MyAttribute(string name1) { } +} +"; + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular10); + comp.VerifyDiagnostics( + // (4,25): error CS0103: The name 'parameter' does not exist in the current context + // int this[[My(nameof(parameter))] int parameter] => throw null; + Diagnostic(ErrorCode.ERR_NameNotInContext, "parameter").WithArguments("parameter").WithLocation(4, 25) + ); + + VerifyParameter(comp, 0, null); + + comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics(); + + VerifyParameter(comp, 0, "System.Int32 C.this[System.Int32 parameter] { get; }"); + } + + [Fact] + public void ParameterScope_InParameterAttributeNameOf_Indexer_SetterOnly() + { + var source = @" +class C +{ + int this[[My(nameof(parameter))] int parameter] { set => throw null; } +} + +public class MyAttribute : System.Attribute +{ + public MyAttribute(string name1) { } +} +"; + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular10); + comp.VerifyDiagnostics( + // (4,25): error CS0103: The name 'parameter' does not exist in the current context + // int this[[My(nameof(parameter))] int parameter] => throw null; + Diagnostic(ErrorCode.ERR_NameNotInContext, "parameter").WithArguments("parameter").WithLocation(4, 25) + ); + + VerifyParameter(comp, 0, null); + + comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics(); + + VerifyParameter(comp, 0, "System.Int32 C.this[System.Int32 parameter] { set; }"); + } + + [Fact] + public void ParameterScope_ValueLocalNotInPropertyOrAccessorAttributeNameOf() + { + var source = @" +class C +{ + [My(nameof(value))] + int Property { set => throw null; } + + int Property2 { [My(nameof(value))] get => throw null; } + + int Property3 { [My(nameof(value))] set => throw null; } + + int Property4 { [My(nameof(value))] init => throw null; } +} + +public class MyAttribute : System.Attribute +{ + public MyAttribute(string name1) { } +} +"; + var comp = CreateCompilation(new[] { source, IsExternalInitTypeDefinition }); + comp.VerifyDiagnostics( + // (4,16): error CS0103: The name 'value' does not exist in the current context + // [My(nameof(value))] + Diagnostic(ErrorCode.ERR_NameNotInContext, "value").WithArguments("value").WithLocation(4, 16), + // (7,32): error CS0103: The name 'value' does not exist in the current context + // int Property2 { [My(nameof(value))] get => throw null; } + Diagnostic(ErrorCode.ERR_NameNotInContext, "value").WithArguments("value").WithLocation(7, 32), + // (9,32): error CS0103: The name 'value' does not exist in the current context + // int Property3 { [My(nameof(value))] set => throw null; } + Diagnostic(ErrorCode.ERR_NameNotInContext, "value").WithArguments("value").WithLocation(9, 32), + // (11,32): error CS0103: The name 'value' does not exist in the current context + // int Property4 { [My(nameof(value))] init => throw null; } + Diagnostic(ErrorCode.ERR_NameNotInContext, "value").WithArguments("value").WithLocation(11, 32) + ); + } + + [Fact] + public void ParameterScope_InParameterAttributeNameOf_Constructor() + { + var source = @" +class C +{ + C([My(nameof(parameter))] int parameter) => throw null; +} + +public class MyAttribute : System.Attribute +{ + public MyAttribute(string name1) { } +} +"; + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular10); + comp.VerifyDiagnostics( + // (4,18): error CS0103: The name 'parameter' does not exist in the current context + // C([My(nameof(parameter))] int parameter) => throw null; + Diagnostic(ErrorCode.ERR_NameNotInContext, "parameter").WithArguments("parameter").WithLocation(4, 18) + ); + + VerifyParameter(comp, 0, null); + + comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics(); + + VerifyParameter(comp, 0, "C..ctor(System.Int32 parameter)"); + } + + [Fact] + public void ParameterScope_InParameterAttributeNameOf_Delegate() + { + var source = @" +delegate void MyDelegate([My(nameof(parameter))] int parameter); + +public class MyAttribute : System.Attribute +{ + public MyAttribute(string name1) { } +} +"; + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular10); + comp.VerifyDiagnostics( + // (2,37): error CS0103: The name 'parameter' does not exist in the current context + // delegate void MyDelegate([My(nameof(parameter))] int parameter); + Diagnostic(ErrorCode.ERR_NameNotInContext, "parameter").WithArguments("parameter").WithLocation(2, 37) + ); + + VerifyParameter(comp, 0, null); + + comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics(); + + VerifyParameter(comp, 0, "void MyDelegate.Invoke(System.Int32 parameter)"); + } + + [Fact] + public void ParameterScope_InParameterAttributeNameOf_ConversionOperator() + { + var source = @" +class C +{ + public static implicit operator C([My(nameof(parameter))] int parameter) => throw null; +} + +public class MyAttribute : System.Attribute +{ + public MyAttribute(string name1) { } +} +"; + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular10); + comp.VerifyDiagnostics( + // (4,50): error CS0103: The name 'parameter' does not exist in the current context + // public static implicit operator C([My(nameof(parameter))] int parameter) => throw null; + Diagnostic(ErrorCode.ERR_NameNotInContext, "parameter").WithArguments("parameter").WithLocation(4, 50) + ); + + VerifyParameter(comp, 0, null); + + comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics(); + + VerifyParameter(comp, 0, "C C.op_Implicit(System.Int32 parameter)"); + } + + [Fact] + public void ParameterScope_InParameterAttributeNameOf_Operator() + { + var source = @" +class C +{ + public static C operator +([My(nameof(parameter))] int parameter, C other) => throw null; +} + +public class MyAttribute : System.Attribute +{ + public MyAttribute(string name1) { } +} +"; + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular10); + comp.VerifyDiagnostics( + // (4,43): error CS0103: The name 'parameter' does not exist in the current context + // public static C operator +([My(nameof(parameter))] int parameter, C other) => throw null; + Diagnostic(ErrorCode.ERR_NameNotInContext, "parameter").WithArguments("parameter").WithLocation(4, 43) + ); + + VerifyParameter(comp, 0, null); + + comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics(); + + VerifyParameter(comp, 0, "C C.op_Addition(System.Int32 parameter, C other)"); + } + + [Fact] + public void ParameterScope_InParameterAttributeNameOf_Lambda() + { + var source = @" +class C +{ + void M() + { + var x = ([My(nameof(parameter))] int parameter) => 0; + } +} + +public class MyAttribute : System.Attribute +{ + public MyAttribute(string name1) { } +} +"; + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular10); + comp.VerifyDiagnostics( + // (6,29): error CS0103: The name 'parameter' does not exist in the current context + // var x = ([My(nameof(parameter))] int parameter) => 0; + Diagnostic(ErrorCode.ERR_NameNotInContext, "parameter").WithArguments("parameter").WithLocation(6, 29) + ); + + VerifyParameter(comp, 0, null); + + comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics(); + + VerifyParameter(comp, 0, "lambda expression"); + } + + [Fact] + public void ParameterScope_InParameterAttributeNameOf_AnonymousDelegate() + { + var source = @" +class C +{ + void M() + { + var x = delegate ([My(nameof(parameter))] int parameter) { return 0; }; + } +} + +public class MyAttribute : System.Attribute +{ + public MyAttribute(string name1) { } +} +"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (6,27): error CS7014: Attributes are not valid in this context. + // var x = delegate ([My(nameof(parameter))] int parameter) { return 0; }; + Diagnostic(ErrorCode.ERR_AttributesNotAllowed, "[My(nameof(parameter))]").WithLocation(6, 27) + ); + + VerifyParameter(comp, 0, "lambda expression"); + } + + [Fact] + public void ParameterScope_InTypeParameterAttributeNameOf() + { + var source = @" +class C +{ + void M() + { + local(0); + + void local<[My(nameof(parameter))] T>(int parameter) { } + } + + void M2<[My(nameof(parameter))] T>(int parameter) { } +} + +public class MyAttribute : System.Attribute +{ + public MyAttribute(string name1) { } +} +"; + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular10); + comp.VerifyDiagnostics( + // (8,31): error CS0103: The name 'parameter' does not exist in the current context + // void local<[My(nameof(parameter))] T>(int parameter) { } + Diagnostic(ErrorCode.ERR_NameNotInContext, "parameter").WithArguments("parameter").WithLocation(8, 31), + // (11,24): error CS0103: The name 'parameter' does not exist in the current context + // void M2<[My(nameof(parameter))] T>(int parameter) { } + Diagnostic(ErrorCode.ERR_NameNotInContext, "parameter").WithArguments("parameter").WithLocation(11, 24) + ); + + VerifyParameter(comp, 0, null); + VerifyParameter(comp, 1, null); + + comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics(); + + VerifyParameter(comp, 0, "void local(System.Int32 parameter)"); + VerifyParameter(comp, 1, "void C.M2(System.Int32 parameter)"); + } + + [Fact] + public void ParameterScope_InTypeParameterAttributeNameOf_SpeculatingWithReplacementAttributeInsideExisting() + { + var source = @" +class C +{ + void M() + { + local(0); + + void local<[My(positionA)] T>(int parameter) { } + } + + void M2<[My(positionB)] T>(int parameter) { } +} + +public class MyAttribute : System.Attribute +{ + public MyAttribute(string name1) { } +} +"; + // C# 10 + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular10); + comp.VerifyDiagnostics( + // (8,24): error CS0103: The name 'positionA' does not exist in the current context + // void local([My(positionA)] int parameter) { } + Diagnostic(ErrorCode.ERR_NameNotInContext, "positionA").WithArguments("positionA").WithLocation(8, 24), + // (11,17): error CS0103: The name 'positionB' does not exist in the current context + // void M2([My(positionB)] int parameter) { } + Diagnostic(ErrorCode.ERR_NameNotInContext, "positionB").WithArguments("positionB").WithLocation(11, 17) + ); + + var tree = comp.SyntaxTrees.Single(); + var parentModel = comp.GetSemanticModel(tree); + var localFuncPosition = tree.GetText().ToString().IndexOf("positionA", StringComparison.Ordinal); + var methodPosition = tree.GetText().ToString().IndexOf("positionB", StringComparison.Ordinal); + + var attr = parseAttributeSyntax("[My(nameof(parameter))]", TestOptions.Regular10); + VerifyParameterSpeculation(parentModel, localFuncPosition, attr, found: false); + VerifyParameterSpeculation(parentModel, methodPosition, attr, found: false); + + attr = parseAttributeSyntax("[My(parameter)]", TestOptions.Regular10); + VerifyParameterSpeculation(parentModel, localFuncPosition, attr, found: false); + VerifyParameterSpeculation(parentModel, methodPosition, attr, found: false); + + // C# 11 + comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics( + // (8,24): error CS0103: The name 'positionA' does not exist in the current context + // void local([My(positionA)] int parameter) { } + Diagnostic(ErrorCode.ERR_NameNotInContext, "positionA").WithArguments("positionA").WithLocation(8, 24), + // (11,17): error CS0103: The name 'positionB' does not exist in the current context + // void M2([My(positionB)] int parameter) { } + Diagnostic(ErrorCode.ERR_NameNotInContext, "positionB").WithArguments("positionB").WithLocation(11, 17) + ); + + tree = comp.SyntaxTrees.Single(); + parentModel = comp.GetSemanticModel(tree); + + attr = parseAttributeSyntax("[My(nameof(parameter))]", TestOptions.Regular10); + VerifyParameterSpeculation(parentModel, localFuncPosition, attr); + VerifyParameterSpeculation(parentModel, methodPosition, attr); + + attr = parseAttributeSyntax("[My(parameter)]", TestOptions.Regular10); + VerifyParameterSpeculation(parentModel, localFuncPosition, attr, found: false); + VerifyParameterSpeculation(parentModel, methodPosition, attr, found: false); + + return; + + static AttributeSyntax parseAttributeSyntax(string source, CSharpParseOptions parseOptions) + => SyntaxFactory.ParseCompilationUnit($@"class X {{ {source} void M() {{ }} }}", options: parseOptions).DescendantNodes().OfType().Single(); + } + + [Fact] + public void ParameterScope_InTypeParameterAttributeNameOf_Delegate() + { + var source = @" +delegate int MyDelegate<[My(nameof(parameter))] T>(int parameter); + +public class MyAttribute : System.Attribute +{ + public MyAttribute(string name1) { } +} +"; + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular10); + comp.VerifyDiagnostics( + // (2,36): error CS0103: The name 'parameter' does not exist in the current context + // delegate int MyDelegate<[My(nameof(parameter))] T>(int parameter); + Diagnostic(ErrorCode.ERR_NameNotInContext, "parameter").WithArguments("parameter").WithLocation(2, 36) + ); + + VerifyParameter(comp, 0, null); + + comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics(); + + VerifyParameter(comp, 0, "System.Int32 MyDelegate.Invoke(System.Int32 parameter)"); + } + + [Fact] + public void ParameterScope_NotAsParameterAttributeType() + { + var comp = CreateCompilation(@" +class C +{ + void M() + { + local(null); + + void local([parameter] System.Attribute parameter) => throw null; + } + + void M2([parameter] System.Attribute parameter) => throw null; +} +"); + comp.VerifyDiagnostics( + // (8,21): error CS0246: The type or namespace name 'parameterAttribute' could not be found (are you missing a using directive or an assembly reference?) + // void local([parameter] System.Attribute parameter) => throw null; + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "parameter").WithArguments("parameterAttribute").WithLocation(8, 21), + // (8,21): error CS0246: The type or namespace name 'parameter' could not be found (are you missing a using directive or an assembly reference?) + // void local([parameter] System.Attribute parameter) => throw null; + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "parameter").WithArguments("parameter").WithLocation(8, 21), + // (11,14): error CS0246: The type or namespace name 'parameterAttribute' could not be found (are you missing a using directive or an assembly reference?) + // void M2([parameter] System.Attribute parameter) => throw null; + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "parameter").WithArguments("parameterAttribute").WithLocation(11, 14), + // (11,14): error CS0246: The type or namespace name 'parameter' could not be found (are you missing a using directive or an assembly reference?) + // void M2([parameter] System.Attribute parameter) => throw null; + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "parameter").WithArguments("parameter").WithLocation(11, 14) ); VerifyParameter(comp, 0, null); - VerifyParameter(comp, 1, null); } [Fact] @@ -9009,7 +10112,7 @@ public MyAttribute(string name1) { } } [Fact] - public void ParameterScope_NotAsParameterDefaultDefaultValue() + public void ParameterScope_NotInParameterDefaultDefaultValue() { var comp = CreateCompilation(@" class C @@ -9049,7 +10152,7 @@ public MyAttribute(string name1) { } } [Fact] - public void ParameterScope_NotAsParameterNameOfDefaultValue() + public void ParameterScope_NotInParameterNameOfDefaultValue() { var comp = CreateCompilation(@" class C