Skip to content

Commit 4482c72

Browse files
authored
Implement scoping and shadowing rules for extension parameter and type parameters (#77704)
See dotnet/csharplang#9229 (commit 9)
1 parent 5ca519f commit 4482c72

33 files changed

+2701
-286
lines changed

src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,6 @@ public override Binder VisitMethodDeclaration(MethodDeclarationSyntax methodDecl
183183
{
184184
method = method ?? GetMethodSymbol(methodDecl, resultBinder);
185185
isIteratorBody = method.IsIterator;
186-
resultBinder = WithExtensionReceiverParameterBinderIfNecessary(resultBinder, method);
187186
resultBinder = new InMethodBinder(method, resultBinder);
188187
}
189188

@@ -194,19 +193,6 @@ public override Binder VisitMethodDeclaration(MethodDeclarationSyntax methodDecl
194193
return resultBinder;
195194
}
196195

197-
private static Binder WithExtensionReceiverParameterBinderIfNecessary(Binder resultBinder, MethodSymbol method)
198-
{
199-
if (method is { IsStatic: false, ContainingType: SourceNamedTypeSymbol { IsExtension: true, ExtensionParameter: { } parameter } })
200-
{
201-
// PROTOTYPE: Depending on whether we consider method parameters and receiver parameter in the same scope and
202-
// what are the name conflict/shadowing rules, we might consider to adjust behavior of InMethodBinder instead.
203-
// If we decide to keep usage of WithParametersBinder, we might want to update XML doc comment for it.
204-
return new WithParametersBinder([parameter], resultBinder);
205-
}
206-
207-
return resultBinder;
208-
}
209-
210196
public override Binder VisitConstructorDeclaration(ConstructorDeclarationSyntax parent)
211197
{
212198
// If the position isn't in the scope of the method, then proceed to the parent syntax node.
@@ -327,7 +313,6 @@ public override Binder VisitAccessorDeclaration(AccessorDeclarationSyntax parent
327313

328314
if ((object)accessor != null)
329315
{
330-
resultBinder = WithExtensionReceiverParameterBinderIfNecessary(resultBinder, accessor);
331316
resultBinder = new InMethodBinder(accessor, resultBinder);
332317

333318
resultBinder = resultBinder.SetOrClearUnsafeRegionIfNecessary(
@@ -437,7 +422,6 @@ private Binder VisitPropertyOrIndexerExpressionBody(BasePropertyDeclarationSynta
437422
// `isIteratorBody` to `SetOrClearUnsafeRegionIfNecessary` above.
438423
Debug.Assert(!accessor.IsIterator);
439424

440-
resultBinder = WithExtensionReceiverParameterBinderIfNecessary(resultBinder, accessor);
441425
resultBinder = new InMethodBinder(accessor, resultBinder);
442426
}
443427

@@ -797,6 +781,11 @@ internal Binder VisitTypeDeclarationCore(TypeDeclarationSyntax parent, NodeUsage
797781
{
798782
resultBinder = new WithClassTypeParametersBinder(typeSymbol, resultBinder);
799783
}
784+
785+
if (typeSymbol.IsExtension)
786+
{
787+
resultBinder = new WithExtensionParameterBinder(typeSymbol, resultBinder);
788+
}
800789
}
801790
}
802791

src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2116,6 +2116,14 @@ private BoundExpression BindNonMethod(SimpleNameSyntax node, Symbol symbol, Bind
21162116
{
21172117
Error(diagnostics, ErrorCode.ERR_InvalidPrimaryConstructorParameterReference, node, parameter);
21182118
}
2119+
else if (parameter.ContainingSymbol is NamedTypeSymbol { IsExtension: true } &&
2120+
(InParameterDefaultValue || InAttributeArgument ||
2121+
this.ContainingMember() is not { Kind: not SymbolKind.NamedType, IsStatic: false } || // We are not in an instance member
2122+
(object)this.ContainingMember().ContainingSymbol != parameter.ContainingSymbol) &&
2123+
!IsInsideNameof)
2124+
{
2125+
Error(diagnostics, ErrorCode.ERR_InvalidExtensionParameterReference, node, parameter);
2126+
}
21192127
else
21202128
{
21212129
// Records never capture parameters within the type

src/Compilers/CSharp/Portable/Binder/Binder_NameConflicts.cs

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ internal void ValidateParameterNameConflicts(
3030
bool allowShadowingNames,
3131
BindingDiagnosticBag diagnostics)
3232
{
33-
PooledHashSet<string>? tpNames = null;
33+
PooledDictionary<string, TypeParameterSymbol>? tpNames = null;
3434
if (!typeParameters.IsDefaultOrEmpty)
3535
{
36-
tpNames = PooledHashSet<string>.GetInstance();
36+
tpNames = PooledDictionary<string, TypeParameterSymbol>.GetInstance();
3737
foreach (var tp in typeParameters)
3838
{
3939
var name = tp.Name;
@@ -42,7 +42,7 @@ internal void ValidateParameterNameConflicts(
4242
continue;
4343
}
4444

45-
if (!tpNames.Add(name))
45+
if (!tpNames.TryAdd(name, tp))
4646
{
4747
// Type parameter declaration name conflicts are detected elsewhere
4848
}
@@ -65,16 +65,37 @@ internal void ValidateParameterNameConflicts(
6565
continue;
6666
}
6767

68-
if (tpNames != null && tpNames.Contains(name))
68+
if (tpNames != null && tpNames.TryGetValue(name, out TypeParameterSymbol? tp))
6969
{
70-
// CS0412: 'X': a parameter or local variable cannot have the same name as a method type parameter
71-
diagnostics.Add(ErrorCode.ERR_LocalSameNameAsTypeParam, GetLocation(p), name);
70+
if (tp.ContainingSymbol is NamedTypeSymbol { IsExtension: true })
71+
{
72+
if (p.ContainingSymbol != (object)tp.ContainingSymbol) // Otherwise, SynthesizedExtensionMarker is going to report an error about this conflict
73+
{
74+
diagnostics.Add(ErrorCode.ERR_LocalSameNameAsExtensionTypeParameter, GetLocation(p), name);
75+
}
76+
}
77+
else if (p.ContainingSymbol is NamedTypeSymbol { IsExtension: true })
78+
{
79+
diagnostics.Add(ErrorCode.ERR_TypeParameterSameNameAsExtensionParameter, tp.GetFirstLocationOrNone(), name);
80+
}
81+
else
82+
{
83+
// CS0412: 'X': a parameter or local variable cannot have the same name as a method type parameter
84+
diagnostics.Add(ErrorCode.ERR_LocalSameNameAsTypeParam, GetLocation(p), name);
85+
}
7286
}
7387

7488
if (!pNames.Add(name))
7589
{
76-
// The parameter name '{0}' is a duplicate
77-
diagnostics.Add(ErrorCode.ERR_DuplicateParamName, GetLocation(p), name);
90+
if (parameters[0] is { ContainingSymbol: NamedTypeSymbol { IsExtension: true }, Name: var receiverName } && receiverName == name)
91+
{
92+
diagnostics.Add(ErrorCode.ERR_LocalSameNameAsExtensionParameter, GetLocation(p), name);
93+
}
94+
else
95+
{
96+
// The parameter name '{0}' is a duplicate
97+
diagnostics.Add(ErrorCode.ERR_DuplicateParamName, GetLocation(p), name);
98+
}
7899
}
79100
else if (!allowShadowingNames)
80101
{

src/Compilers/CSharp/Portable/Binder/InMethodBinder.cs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -292,8 +292,16 @@ private static bool ReportConflictWithParameter(Symbol parameter, Symbol newSymb
292292
{
293293
case SymbolKind.Parameter:
294294
case SymbolKind.Local:
295-
// CS0412: '{0}': a parameter, local variable, or local function cannot have the same name as a method type parameter
296-
diagnostics.Add(ErrorCode.ERR_LocalSameNameAsTypeParam, newLocation, name);
295+
if (parameter.ContainingSymbol is NamedTypeSymbol { IsExtension: true })
296+
{
297+
diagnostics.Add(ErrorCode.ERR_LocalSameNameAsExtensionTypeParameter, newLocation, name);
298+
}
299+
else
300+
{
301+
// CS0412: '{0}': a parameter, local variable, or local function cannot have the same name as a method type parameter
302+
diagnostics.Add(ErrorCode.ERR_LocalSameNameAsTypeParam, newLocation, name);
303+
}
304+
297305
return true;
298306

299307
case SymbolKind.Method:
@@ -324,6 +332,16 @@ internal override bool EnsureSingleDefinition(Symbol symbol, string name, Locati
324332
var parameters = _methodSymbol.Parameters;
325333
var typeParameters = _methodSymbol.TypeParameters;
326334

335+
if (_methodSymbol.GetIsNewExtensionMember())
336+
{
337+
typeParameters = _methodSymbol.ContainingType.TypeParameters.Concat(typeParameters);
338+
339+
if (_methodSymbol.ContainingType.ExtensionParameter is { Name: not "" } receiver)
340+
{
341+
parameters = parameters.Insert(0, receiver);
342+
}
343+
}
344+
327345
if (parameters.IsEmpty && typeParameters.IsEmpty)
328346
{
329347
return false;

src/Compilers/CSharp/Portable/Binder/WithClassTypeParametersBinder.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,5 +62,19 @@ internal override void AddLookupSymbolsInfoInSingleBinder(LookupSymbolsInfo resu
6262
}
6363
}
6464
}
65+
66+
protected override LookupOptions LookupMask
67+
{
68+
get
69+
{
70+
if (_namedType.IsExtension)
71+
{
72+
// Extension type parameters should get the same treatment as method type parameters
73+
return WithMethodTypeParametersBinder.MethodTypeParameterLookupMask;
74+
}
75+
76+
return base.LookupMask;
77+
}
78+
}
6579
}
6680
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Diagnostics;
6+
using Microsoft.CodeAnalysis.CSharp.Symbols;
7+
using Roslyn.Utilities;
8+
9+
namespace Microsoft.CodeAnalysis.CSharp
10+
{
11+
/// <summary>
12+
/// Binder used to place extension parameter, if any, in scope.
13+
/// </summary>
14+
internal sealed class WithExtensionParameterBinder : Binder
15+
{
16+
private readonly NamedTypeSymbol _type;
17+
18+
internal WithExtensionParameterBinder(NamedTypeSymbol type, Binder next)
19+
: base(next)
20+
{
21+
_type = type;
22+
}
23+
24+
internal override void AddLookupSymbolsInfoInSingleBinder(LookupSymbolsInfo result, LookupOptions options, Binder originalBinder)
25+
{
26+
if (options.CanConsiderMembers())
27+
{
28+
if (_type.ExtensionParameter is { Name: not "" } parameter &&
29+
originalBinder.CanAddLookupSymbolInfo(parameter, options, result, null))
30+
{
31+
result.AddSymbol(parameter, parameter.Name, 0);
32+
}
33+
}
34+
}
35+
36+
internal override void LookupSymbolsInSingleBinder(
37+
LookupResult result, string name, int arity, ConsList<TypeSymbol> basesBeingResolved, LookupOptions options, Binder originalBinder, bool diagnose, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
38+
{
39+
Debug.Assert(result.IsClear);
40+
41+
if ((options & (LookupOptions.NamespaceAliasesOnly | LookupOptions.NamespacesOrTypesOnly)) != 0)
42+
{
43+
return;
44+
}
45+
46+
if (_type.ExtensionParameter is { Name: not "" } parameter && parameter.Name == name)
47+
{
48+
result.MergeEqual(originalBinder.CheckViability(parameter, arity, options, null, diagnose, ref useSiteInfo));
49+
}
50+
}
51+
}
52+
}

src/Compilers/CSharp/Portable/Binder/WithMethodTypeParametersBinder.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ namespace Microsoft.CodeAnalysis.CSharp
1515
/// </summary>
1616
internal sealed class WithMethodTypeParametersBinder : WithTypeParametersBinder
1717
{
18+
internal const LookupOptions MethodTypeParameterLookupMask = LookupOptions.NamespaceAliasesOnly | LookupOptions.MustNotBeMethodTypeParameter;
1819
private readonly MethodSymbol _methodSymbol;
1920
private MultiDictionary<string, TypeParameterSymbol> _lazyTypeParameterMap;
2021

@@ -57,7 +58,7 @@ protected override LookupOptions LookupMask
5758
{
5859
get
5960
{
60-
return LookupOptions.NamespaceAliasesOnly | LookupOptions.MustNotBeMethodTypeParameter;
61+
return MethodTypeParameterLookupMask;
6162
}
6263
}
6364

src/Compilers/CSharp/Portable/Binder/WithTypeParametersBinder.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ internal WithTypeParametersBinder(Binder next)
2121
// TODO: Change this to a data structure that won't allocate enumerators
2222
protected abstract MultiDictionary<string, TypeParameterSymbol> TypeParameterMap { get; }
2323

24-
// This is only overridden by WithMethodTypeParametersBinder.
2524
protected virtual LookupOptions LookupMask
2625
{
2726
get

src/Compilers/CSharp/Portable/CSharpResources.resx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8092,4 +8092,28 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
80928092
<data name="ERR_ExtensionResolutionFailed" xml:space="preserve">
80938093
<value>'{0}' does not contain a definition for '{1}' and no accessible extension member '{1}' for receiver of type '{0}' could be found (are you missing a using directive or an assembly reference?)</value>
80948094
</data>
8095+
<data name="ERR_ReceiverParameterSameNameAsTypeParameter" xml:space="preserve">
8096+
<value>'{0}': a receiver parameter cannot have the same name as an extension container type parameter</value>
8097+
</data>
8098+
<data name="ERR_LocalSameNameAsExtensionTypeParameter" xml:space="preserve">
8099+
<value>'{0}': a parameter, local variable, or local function cannot have the same name as an extension container type parameter</value>
8100+
</data>
8101+
<data name="ERR_TypeParameterSameNameAsExtensionTypeParameter" xml:space="preserve">
8102+
<value>Type parameter '{0}' has the same name as an extension container type parameter</value>
8103+
</data>
8104+
<data name="ERR_LocalSameNameAsExtensionParameter" xml:space="preserve">
8105+
<value>'{0}': a parameter, local variable, or local function cannot have the same name as an extension parameter</value>
8106+
</data>
8107+
<data name="ERR_ValueParameterSameNameAsExtensionParameter" xml:space="preserve">
8108+
<value>'value': an automatically-generated parameter name conflicts with an extension parameter name</value>
8109+
</data>
8110+
<data name="ERR_TypeParameterSameNameAsExtensionParameter" xml:space="preserve">
8111+
<value>Type parameter '{0}' has the same name as an extension parameter</value>
8112+
</data>
8113+
<data name="ERR_InvalidExtensionParameterReference" xml:space="preserve">
8114+
<value>Cannot use extension parameter '{0}' in this context.</value>
8115+
</data>
8116+
<data name="ERR_ValueParameterSameNameAsExtensionTypeParameter" xml:space="preserve">
8117+
<value>'value': an automatically-generated parameter name conflicts with an extension type parameter name</value>
8118+
</data>
80958119
</root>

src/Compilers/CSharp/Portable/Errors/ErrorCode.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2376,6 +2376,14 @@ internal enum ErrorCode
23762376
ERR_ExtensionParameterDisallowsDefaultValue = 9503,
23772377
ERR_ReceiverParameterOnlyOne = 9504,
23782378
ERR_ExtensionResolutionFailed = 9505,
2379+
ERR_ReceiverParameterSameNameAsTypeParameter = 9506,
2380+
ERR_LocalSameNameAsExtensionTypeParameter = 9507,
2381+
ERR_TypeParameterSameNameAsExtensionTypeParameter = 9508,
2382+
ERR_LocalSameNameAsExtensionParameter = 9509,
2383+
ERR_ValueParameterSameNameAsExtensionParameter = 9510,
2384+
ERR_TypeParameterSameNameAsExtensionParameter = 9511,
2385+
ERR_InvalidExtensionParameterReference = 9512,
2386+
ERR_ValueParameterSameNameAsExtensionTypeParameter = 9513,
23792387

23802388
// Note: you will need to do the following after adding errors:
23812389
// 1) Update ErrorFacts.IsBuildOnlyDiagnostic (src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs)

0 commit comments

Comments
 (0)