-
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
Static local functions #32067
Static local functions #32067
Conversation
|
}"; | ||
var comp = CreateCompilation(source); | ||
comp.VerifyEmitDiagnostics(); | ||
} |
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.
Since you're merging straight to master, some test ideas:
- test IOperation
- test the semantic model (there is likely a bug there, as regular local functions used to report
IsStatic=true
, issue LocalFunctionSymbol.IsStatic always returns true #27719) - add a test using a local function with a delegate type (delegate probably still won't get cached at this point though)
- can you verify that the IDE recommends "static"? File an issue if it doesn't.
In my mind, fixing any of those is not blocking this PR though.
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.
Consider testing static static local() ...
#Closed
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.
Another test idea: a static local function should not allow a this
parameter (extension method). #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.
Logged #32174 for static
in IDE.
@@ -51,7 +52,7 @@ private static Location GetLocation(Symbol symbol) | |||
{ | |||
// Type parameter declaration name conflicts are detected elsewhere | |||
} | |||
else | |||
else if (!isLocalFunction) |
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.
I found this surprising. Should we just change the behavior for static local functions? #Closed
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.
I believe the expected behavior is to allow shadowing for all local functions.
In reply to: 244605104 [](ancestors = 244605104)
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.
Part of the motivation for the shadowing decisions was that I should be able to take a normal function and copy + paste it as a local function and it should just compile. No hassle. Given that I would expect all forms of shadowing to work.
This is my memory of the meeting at least. #Closed
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.
notes were mentioning lambdas too, would it make more sense to do this for both at the same time? nevermind, it's been separated out. (#32069 (comment)) #Closed
@@ -111,13 +112,28 @@ protected bool ValidateNameConflictsInScope(Symbol symbol, Location location, st | |||
break; | |||
} | |||
|
|||
// symbols inside local function shadow names in scopes outside local function |
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.
I thought this new behavior would be only for static local functions too.
Do you have some tests with nesting: a local function inside a static local function that shadows type parameters or parameters?
void M(int x)
{
static void local()
{
void local2(int x) { } // allowed or not?
}
}
``` #Closed
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.
Debug.Assert(!error); // If the assert fails, add a corresponding test. | ||
goto End; | ||
} | ||
} |
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.
Do we have a test where the name of the local function is in conflict with a variable in scope?
public void M(int local, int local2)
{
// both should be errors
void local() { }
static void local2() { }
}
``` #Closed
Test Plan Link: #32069 |
comp.VerifyDiagnostics( | ||
// (11,26): error CS0136: A local or parameter named 'x' cannot be declared in this scope because that name is used in an enclosing local scope to define a local or parameter | ||
// void F3() { void x() { } } // method | ||
Diagnostic(ErrorCode.ERR_LocalIllegallyOverrides, "x").WithArguments("x").WithLocation(11, 26), |
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.
This should be allowed correct? #Resolved
Diagnostic(ErrorCode.ERR_LocalIllegallyOverrides, "x").WithArguments("x").WithLocation(11, 26), | ||
// (13,30): error CS1931: The range variable 'x' conflicts with a previous declaration of 'x' | ||
// void F5() { _ = from x in new[] { 1, 2, 3 } select x; } // range variable | ||
Diagnostic(ErrorCode.ERR_QueryRangeVariableOverrides, "x").WithArguments("x").WithLocation(13, 30)); |
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.
This should be allowed correct? #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.
Done with review pass (iteration 1).
We need to clarify the expected behavior (are we changing shadowing rules for regular local functions). Also a few test ideas.
|
||
UsingDeclaration(text, options: TestOptions.Regular7_3, | ||
// (5,9): error CS8421: To use the 'static' modifier for local functions, please use language version 8.0 or greater. | ||
// static static void F1() { } |
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.
static static [](start = 27, length = 13)
nit: the error recovery seems worse than on regular methods. I'd expect a single diagnostic: "error CS1004: Duplicate 'static' modifier".
Consider filing an issue.
Example #Closed
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.
Refers to: src/Compilers/CSharp/Test/Semantic/Semantics/OutVarTests.cs:4417 in 3118ed4. [](commit_id = 3118ed4ffe76cc6f9580d6c367d36da939cbc2fa, deletion_comment = False) |
return true; | ||
} | ||
|
||
if (newSymbolKind == SymbolKind.TypeParameter) |
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.
if [](start = 16, length = 2)
nit: Consider using a switch
on newSymbolKind
for the whole block. #Closed
@@ -8568,6 +8568,7 @@ private static bool IsAdditionalLocalFunctionModifier(SyntaxKind kind) | |||
case SyntaxKind.InternalKeyword: | |||
case SyntaxKind.ProtectedKeyword: | |||
case SyntaxKind.PrivateKeyword: | |||
case SyntaxKind.StaticKeyword: |
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.
case SyntaxKind.StaticKeyword: [](start = 16, length = 30)
Should this be in the bucket of "valid" modifiers at the beginning of this switch
? #Closed
} | ||
badBuilder[i] = this.AddError(modifiers[i], ErrorCode.ERR_BadMemberFlag, ((SyntaxToken)modifiers[i]).Text); | ||
modifier = this.AddError(modifier, ErrorCode.ERR_StaticLocalFunctionModifier, new CSharpRequiredLanguageVersion(MessageID.IDS_FeatureStaticLocalFunctions.RequiredVersion())); |
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.
modifier = this.AddError(modifier, ErrorCode.ERR_StaticLocalFunctionModifier, new CSharpRequiredLanguageVersion(MessageID.IDS_FeatureStaticLocalFunctions.RequiredVersion())); [](start = 24, length = 174)
This doesn't feel right. It looks like this is going to have effect on syntax nodes that goes beyond just an additional diagnostics. For example, an async/unsafe modifiers are likely to be dropped. It is probably worth adjusting the logic so it would never drop original modifiers from the list, only add errors to them at most. #Closed
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.
The loop should be dropping modifiers, only modifying modifiers that have errors.
In reply to: 244893785 [](ancestors = 244893785)
@@ -286,6 +286,8 @@ public override bool IsExtensionMethod | |||
} | |||
} | |||
|
|||
internal bool IsStaticLocalFunction => _syntax.Modifiers.Any(SyntaxKind.StaticKeyword); |
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.
internal bool IsStaticLocalFunction => _syntax.Modifiers.Any(SyntaxKind.StaticKeyword); [](start = 8, length = 87)
Would IsStatic property work? #Closed
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.
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.
What would be the public API to answer the same question? It feels like we should have one at the time we ship this feature.
In reply to: 245043955 [](ancestors = 245043955,244894319)
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.
The public API is ISymbol.IsStatic
which is implemented by LocalFunctionSymbol.IsStatic
. In short, the public API relies on fixing 27719.
In reply to: 245086567 [](ancestors = 245086567,245043955,244894319)
@@ -469,25 +469,32 @@ private bool ReportConflictWithLocal(Symbol local, Symbol newSymbol, string name | |||
return true; | |||
} | |||
|
|||
if (newSymbolKind == SymbolKind.Local || newSymbolKind == SymbolKind.Parameter || newSymbolKind == SymbolKind.Method || newSymbolKind == SymbolKind.TypeParameter) |
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.
if (newSymbolKind == SymbolKind.Local || newSymbolKind == SymbolKind.Parameter || newSymbolKind == SymbolKind.Method || newSymbolKind == SymbolKind.TypeParameter) [](start = 12, length = 162)
Instead of making changes to this function would it be simpler to make changes to ValidateNameConflictsInScope
method to get out of the loop once we are done with a body of a local function and the language version is right? #Closed
Similar comment as for ReportConflictWithLocal. Otherwise we are likely missing changes in WithLambdaParametersBinder #Closed Refers to: src/Compilers/CSharp/Portable/Binder/InMethodBinder.cs:257 in da7cd13. [](commit_id = da7cd13b29df6cc832fa782bf2b3916de7e0e682, deletion_comment = False) |
// avoid checking for conflicts outside of the local function. | ||
var stopAtLocalFunction = | ||
symbol?.DeclaringCompilation.IsFeatureEnabled(MessageID.IDS_FeatureStaticLocalFunctions) == true ? | ||
symbol.ContainingSymbol as LocalFunctionSymbol : |
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.
symbol.ContainingSymbol as LocalFunctionSymbol [](start = 16, length = 46)
Is this condition really necessary? For example, what if the symbol is a local or parameter in a lambda within a local function? #Closed
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.
@@ -53,7 +48,7 @@ private static Location GetLocation(Symbol symbol) | |||
} | |||
else | |||
{ | |||
ValidateDeclarationNameConflictsInScope(tp, diagnostics); | |||
ValidateDeclarationNameConflictsInScope(tp, diagnostics, outsideContainingSymbol: true); |
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.
outsideContainingSymbol: true [](start = 81, length = 29)
It looks like there is only one call site for the ValidateParameterNameConflicts
function, it feels like it would be simpler to pass a flag to suppress this call altogether rather than adding a parameter to ValidateDeclarationNameConflictsInScope
and to ValidateNameConflictsInScope
to deal with this special case there. #Closed
@@ -83,7 +78,7 @@ private static Location GetLocation(Symbol symbol) | |||
} | |||
else | |||
{ | |||
ValidateDeclarationNameConflictsInScope(p, diagnostics); | |||
ValidateDeclarationNameConflictsInScope(p, diagnostics, outsideContainingSymbol: true); |
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.
outsideContainingSymbol: true [](start = 80, length = 29)
Same comment as for the previous ValidateDeclarationNameConflictsInScope
call #Closed
I marked the PR as "personal" for now, since you're addressing blocking issues from design review. Please ping when ready for review. Thanks #Closed |
// void F5() { _ = from x in new[] { 1, 2, 3 } select x; } // range variable | ||
Diagnostic(ErrorCode.ERR_QueryRangeVariableOverrides, "x").WithArguments("x").WithLocation(12, 30)); | ||
|
||
comp = CreateCompilation(source, parseOptions: TestOptions.Regular8); |
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.
parseOptions: TestOptions.Regular8 [](start = 45, length = 34)
We should also always test with the latest version. Consider relying on implicit value instead. #Closed
void F1() | ||
{ | ||
object x = null; | ||
Action a1 = () => { int x = 0; }; |
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.
() => { int x = 0; }; [](start = 23, length = 22)
It looks like we are missing a test for a scenario when a lambda parameter or a local inside a lambda shadow something from outside of the local function. #Closed
Diagnostic(ErrorCode.ERR_StaticLocalFunctionCannotCaptureThis, "_f").WithLocation(18, 30)); | ||
|
||
comp = CreateCompilation(source, parseOptions: TestOptions.Regular8); | ||
// PROTOTYPE: Should we report the same errors when "MyDefine" is not set? |
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.
// PROTOTYPE: [](start = 12, length = 13)
Does this PR target a feature branch? #Closed
Yay! 🎉 |
Allow
static
modifier for local functions in C#8; report errors forstatic
local functions that referencethis
or a variable in an outer scope; and allow shadowing of local and parameter names inside local functions.[jcouv update:]
Championed issue: dotnet/csharplang#1565
Last LDM notes: https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-09-10.md
Test plan: #32069