-
Notifications
You must be signed in to change notification settings - Fork 4.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement scoping and shadowing rules for extension parameter and type parameters #77704
Changes from all commits
6050758
048e179
57cc979
e8db8ca
b467715
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,10 +30,10 @@ internal void ValidateParameterNameConflicts( | |
bool allowShadowingNames, | ||
BindingDiagnosticBag diagnostics) | ||
{ | ||
PooledHashSet<string>? tpNames = null; | ||
PooledDictionary<string, TypeParameterSymbol>? tpNames = null; | ||
if (!typeParameters.IsDefaultOrEmpty) | ||
{ | ||
tpNames = PooledHashSet<string>.GetInstance(); | ||
tpNames = PooledDictionary<string, TypeParameterSymbol>.GetInstance(); | ||
foreach (var tp in typeParameters) | ||
{ | ||
var name = tp.Name; | ||
|
@@ -42,7 +42,7 @@ internal void ValidateParameterNameConflicts( | |
continue; | ||
} | ||
|
||
if (!tpNames.Add(name)) | ||
if (!tpNames.TryAdd(name, tp)) | ||
{ | ||
// Type parameter declaration name conflicts are detected elsewhere | ||
} | ||
|
@@ -65,16 +65,37 @@ internal void ValidateParameterNameConflicts( | |
continue; | ||
} | ||
|
||
if (tpNames != null && tpNames.Contains(name)) | ||
if (tpNames != null && tpNames.TryGetValue(name, out TypeParameterSymbol? tp)) | ||
{ | ||
// CS0412: 'X': a parameter or local variable cannot have the same name as a method type parameter | ||
diagnostics.Add(ErrorCode.ERR_LocalSameNameAsTypeParam, GetLocation(p), name); | ||
if (tp.ContainingSymbol is NamedTypeSymbol { IsExtension: true }) | ||
{ | ||
if (p.ContainingSymbol != (object)tp.ContainingSymbol) // Otherwise, SynthesizedExtensionMarker is going to report an error about this conflict | ||
{ | ||
diagnostics.Add(ErrorCode.ERR_LocalSameNameAsExtensionTypeParameter, GetLocation(p), name); | ||
} | ||
} | ||
else if (p.ContainingSymbol is NamedTypeSymbol { IsExtension: true }) | ||
{ | ||
diagnostics.Add(ErrorCode.ERR_TypeParameterSameNameAsExtensionParameter, tp.GetFirstLocationOrNone(), name); | ||
} | ||
else | ||
{ | ||
// CS0412: 'X': a parameter or local variable cannot have the same name as a method type parameter | ||
diagnostics.Add(ErrorCode.ERR_LocalSameNameAsTypeParam, GetLocation(p), name); | ||
} | ||
} | ||
|
||
if (!pNames.Add(name)) | ||
{ | ||
// The parameter name '{0}' is a duplicate | ||
diagnostics.Add(ErrorCode.ERR_DuplicateParamName, GetLocation(p), name); | ||
if (parameters[0] is { ContainingSymbol: NamedTypeSymbol { IsExtension: true }, Name: var receiverName } && receiverName == name) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
{ | ||
diagnostics.Add(ErrorCode.ERR_LocalSameNameAsExtensionParameter, GetLocation(p), name); | ||
} | ||
else | ||
{ | ||
// The parameter name '{0}' is a duplicate | ||
diagnostics.Add(ErrorCode.ERR_DuplicateParamName, GetLocation(p), name); | ||
} | ||
} | ||
else if (!allowShadowingNames) | ||
{ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
using System.Diagnostics; | ||
using Microsoft.CodeAnalysis.CSharp.Symbols; | ||
using Roslyn.Utilities; | ||
|
||
namespace Microsoft.CodeAnalysis.CSharp | ||
{ | ||
/// <summary> | ||
/// Binder used to place extension parameter, if any, in scope. | ||
/// </summary> | ||
internal sealed class WithExtensionParameterBinder : Binder | ||
{ | ||
private readonly NamedTypeSymbol _type; | ||
|
||
internal WithExtensionParameterBinder(NamedTypeSymbol type, Binder next) | ||
: base(next) | ||
{ | ||
_type = type; | ||
} | ||
|
||
internal override void AddLookupSymbolsInfoInSingleBinder(LookupSymbolsInfo result, LookupOptions options, Binder originalBinder) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We probably need some direct LookupSymbols tests to cover this method #Resolved |
||
{ | ||
if (options.CanConsiderMembers()) | ||
{ | ||
if (_type.ExtensionParameter is { Name: not "" } parameter && | ||
originalBinder.CanAddLookupSymbolInfo(parameter, options, result, null)) | ||
{ | ||
result.AddSymbol(parameter, parameter.Name, 0); | ||
} | ||
} | ||
} | ||
|
||
internal override void LookupSymbolsInSingleBinder( | ||
LookupResult result, string name, int arity, ConsList<TypeSymbol> basesBeingResolved, LookupOptions options, Binder originalBinder, bool diagnose, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo) | ||
{ | ||
Debug.Assert(result.IsClear); | ||
|
||
if ((options & (LookupOptions.NamespaceAliasesOnly | LookupOptions.NamespacesOrTypesOnly)) != 0) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Why do the work if we know that parameters do not satisfy the request. Especially, when we are binding types names in source, we want to avoid having to create symbols for members, etc. This helps with avoiding cycles. In terms of implementation, I used |
||
{ | ||
return; | ||
} | ||
|
||
if (_type.ExtensionParameter is { Name: not "" } parameter && parameter.Name == name) | ||
{ | ||
result.MergeEqual(originalBinder.CheckViability(parameter, arity, options, null, diagnose, ref useSiteInfo)); | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8092,4 +8092,28 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ | |
<data name="ERR_ExtensionResolutionFailed" xml:space="preserve"> | ||
<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> | ||
</data> | ||
<data name="ERR_ReceiverParameterSameNameAsTypeParameter" xml:space="preserve"> | ||
<value>'{0}': a receiver parameter cannot have the same name as an extension container type parameter</value> | ||
</data> | ||
<data name="ERR_LocalSameNameAsExtensionTypeParameter" xml:space="preserve"> | ||
<value>'{0}': a parameter, local variable, or local function cannot have the same name as an extension container type parameter</value> | ||
</data> | ||
<data name="ERR_TypeParameterSameNameAsExtensionTypeParameter" xml:space="preserve"> | ||
<value>Type parameter '{0}' has the same name as an extension container type parameter</value> | ||
</data> | ||
<data name="ERR_LocalSameNameAsExtensionParameter" xml:space="preserve"> | ||
<value>'{0}': a parameter, local variable, or local function cannot have the same name as an extension parameter</value> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I used the following message as a template "'{0}': a parameter, local variable, or local function cannot have the same name as a method type parameter" |
||
</data> | ||
<data name="ERR_ValueParameterSameNameAsExtensionParameter" xml:space="preserve"> | ||
<value>'value': an automatically-generated parameter name conflicts with an extension parameter name</value> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The terminology was "copied" from |
||
</data> | ||
<data name="ERR_TypeParameterSameNameAsExtensionParameter" xml:space="preserve"> | ||
<value>Type parameter '{0}' has the same name as an extension parameter</value> | ||
</data> | ||
<data name="ERR_InvalidExtensionParameterReference" xml:space="preserve"> | ||
<value>Cannot use extension parameter '{0}' in this context.</value> | ||
</data> | ||
<data name="ERR_ValueParameterSameNameAsExtensionTypeParameter" xml:space="preserve"> | ||
<value>'value': an automatically-generated parameter name conflicts with an extension type parameter name</value> | ||
</data> | ||
</root> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this condition necessary? #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is for references in nested types.
IsInDeclaringTypeInstanceMember
does the same. Declaring a nested type in extension is an error, but I prefer to be consistentThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider adding a test for this