-
Notifications
You must be signed in to change notification settings - Fork 4k
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
Support 'scoped' modifier for parameters and locals #61389
Conversation
@@ -412,6 +412,9 @@ | |||
<summary>Gets the optional "readonly" keyword.</summary> | |||
</PropertyComment> | |||
</Field> | |||
<Field Name="ScopedKeyword" Type="SyntaxToken" Optional="true"> | |||
<Kind Name="ScopedKeyword"/> | |||
</Field> |
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 type syntax, and not part of the parameter syntax? #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.
The feature supports both scoped ref Span<int> param
and ref scoped Span<int> param
and they mean different things. Haven't looked closely at this PR yet but it doesn't surprise me that Scoped is part of the ParameterSyntax directly.
It doesn't surprise me that scoped
goes into the Modifiers
on a ParameterSyntax
. I mentioned to Chuck offline that it would be best if our parse for readonly ref readonly Span<int> local;
has a similar parse as scoped ref scoped Span<int> local;
. I think this makes them match. Although this makes me wonder if this ReadOnlyKeyword
should "grow up" into a generalized trailing Modifiers
list.
@@ -1163,6 +1165,8 @@ internal static DeclarationModifiers GetModifier(SyntaxKind kind, SyntaxKind con | |||
return DeclarationModifiers.Partial; | |||
case SyntaxKind.AsyncKeyword: | |||
return DeclarationModifiers.Async; | |||
case SyntaxKind.ScopedKeyword: | |||
return AllowScopedModifier() ? DeclarationModifiers.Scoped : DeclarationModifiers.None; |
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.
Wouldn't it be better if the keyword was parsed regardless of language version, but in a lower version it would show an error that the feature isn't available? That would make for a better experience when users try this feature but didn't upgrade their lang version, and would also allow the language version code fix to work. #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.
I agree. I think the 'file' or 'required' parsing changes should end up looking similar to what we do in here. https://github.com/dotnet/roslyn/pull/58431/files#diff-a655d393d9799eabace5e5109383232749be8052f1fd825fc52b9f170908e838
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.
Thanks. I'd initially expected breaking changes parsing scoped
as a modifier, so I'd made the modifier conditional on language version, but the syntax may be unambiguous after all. I've updated the parser to allow scoped
as a modifier unconditionally, and moved the language version check to binding.
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.
Haven't looked at DeclarationScopeParsingTests yet. Looks like we're on a good track but did have some feedback and questions.
@@ -1163,6 +1165,8 @@ internal static DeclarationModifiers GetModifier(SyntaxKind kind, SyntaxKind con | |||
return DeclarationModifiers.Partial; | |||
case SyntaxKind.AsyncKeyword: | |||
return DeclarationModifiers.Async; | |||
case SyntaxKind.ScopedKeyword: | |||
return AllowScopedModifier() ? DeclarationModifiers.Scoped : DeclarationModifiers.None; |
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 agree. I think the 'file' or 'required' parsing changes should end up looking similar to what we do in here. https://github.com/dotnet/roslyn/pull/58431/files#diff-a655d393d9799eabace5e5109383232749be8052f1fd825fc52b9f170908e838
static unsafe void Main() | ||
{ | ||
delegate*<scoped R, void> f1 = &F1; | ||
delegate*<ref scoped int, scoped ref int, void> f2 = &F2; |
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.
let's make sure we test scenarios where 'scoped' doesn't match as well. #Pending
} | ||
|
||
// PROTOTYPE: Report error for implicit conversion between delegate types that differ by 'scoped', | ||
// and between function pointer types and methods that differ by 'scoped'. |
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.
seems like all conversions should be disallowed when there are 'scoped' differences. It actually makes me wonder if a 'modreq' is needed to prevent downlevel compilers from attempting to perform such conversions. #Pending
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've added an item to the test plan, and I'll address this in a subsequent PR.
@@ -1283,7 +1296,7 @@ bool isStructOrRecordKeyword(SyntaxToken token) | |||
|
|||
private bool ShouldAsyncBeTreatedAsModifier(bool parsingStatementNotDeclaration) | |||
{ | |||
Debug.Assert(this.CurrentToken.ContextualKind == SyntaxKind.AsyncKeyword); | |||
Debug.Assert(this.CurrentToken.Kind == SyntaxKind.IdentifierToken && GetModifier(this.CurrentToken) != DeclarationModifiers.None); |
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 updated assert is from #60823 where this method is also renamed. #Resolved
@@ -68,6 +68,10 @@ public override TResult Accept<TResult>(SymbolVisitor<TResult> visitor) | |||
|
|||
public ImmutableArray<CustomModifier> CustomModifiers => ImmutableArray.Create<CustomModifier>(); | |||
|
|||
public bool IsRefScoped => false; | |||
|
|||
public bool IsValueScoped => false; |
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.
interesting name. curious what the intuition is on 'value scoped' #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.
The name is intended to indicate that the members of the ref struct
, that is, the value of the ref struct
, does not escape the current method.
(false, false) => DeclarationScope.Unscoped, | ||
(false, true) => type.IsRefLikeType ? DeclarationScope.ValueScoped : null, | ||
(true, false) => refKind != RefKind.None ? DeclarationScope.RefScoped : null, | ||
(true, true) => null, |
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.
@@ -19,7 +18,7 @@ namespace Microsoft.CodeAnalysis.CSharp.Symbols | |||
/// <summary> | |||
/// A source parameter, potentially with a default value, attributes, etc. | |||
/// </summary> | |||
internal class SourceComplexParameterSymbol : SourceParameterSymbol, IAttributeTargetSymbol | |||
internal abstract class SourceComplexParameterSymbol : SourceParameterSymbol, IAttributeTargetSymbol |
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 do not see any advantage in making this type abstract especially that now we are forced to create a bigger type where SourceComplexParameterSymbol
would work. If the goal is to make RefCustomModifiers
property abstract, then we can rename this type to SourceComplexParameterSymbolBase and add sealed derived type called SourceComplexParameterSymbol with old implementation of the property. "Restoring" logic at creation sites. #Closed
It feels like we should add DeclarationScope to this class since we handle RefKind here as well. This way we will be able to keep the original logic above and create In reply to: 1144323560 In reply to: 1144323560 In reply to: 1144323560 In reply to: 1144323560 Refers to: src/Compilers/CSharp/Portable/Symbols/Source/SourceParameterSymbol.cs:90 in 88f0053. [](commit_id = 88f0053, deletion_comment = False) |
|
||
if (!isParams && | ||
!isExtensionMethodThis && | ||
scope == DeclarationScope.Unscoped && |
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 do not think addition of this condition is justified. We should still be able to create SourceSimpleParameterSymbol
in this case. SourceComplexParameterSymbol
doesn't add any interesting behavior around DeclarationScope
that SourceSimpleParameterSymbol
cannot handle, other than the storage, of course. As I suggested below, we should simply move the storage to this class and restore the name for SourceComplexParameterSymbolWithCustomModifiersPrecedingByRef
class. #Closed
@@ -173,5 +173,7 @@ internal override bool IsMetadataOut | |||
internal override ImmutableArray<int> InterpolatedStringHandlerArgumentIndexes => ImmutableArray<int>.Empty; | |||
|
|||
internal override bool HasInterpolatedStringHandlerArgumentError => false; | |||
|
|||
internal override DeclarationScope Scope => DeclarationScope.Unscoped; |
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.
@@ -210,12 +237,13 @@ internal override string GetDebuggerDisplay() | |||
SyntaxToken identifierToken, | |||
LocalDeclarationKind declarationKind, | |||
EqualsValueClauseSyntax initializer = null, | |||
Binder initializerBinderOpt = null) | |||
Binder initializerBinderOpt = null, | |||
bool scopedModifier = false) |
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.
@@ -313,7 +314,7 @@ internal void BuildLocalFunctions(StatementSyntax statement, ref ArrayBuilder<Lo | |||
} | |||
} | |||
|
|||
protected SourceLocalSymbol MakeLocal(VariableDeclarationSyntax declaration, VariableDeclaratorSyntax declarator, LocalDeclarationKind kind, Binder initializerBinderOpt = null) | |||
protected SourceLocalSymbol MakeLocal(VariableDeclarationSyntax declaration, VariableDeclaratorSyntax declarator, LocalDeclarationKind kind, Binder initializerBinderOpt = null, bool scopedModifier = false) |
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.
(true, true) => null, | ||
}; | ||
} | ||
|
||
public override ImmutableArray<CSharpAttributeData> GetAttributes() |
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 (commit 22) |
@@ -215,7 +215,7 @@ public static bool IsSourceParameterWithEnumeratorCancellationAttribute(this Par | |||
{ | |||
switch (parameter) | |||
{ | |||
case SourceComplexParameterSymbol source: | |||
case SourceComplexParameterSymbolBase source: |
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 (commit 23) |
@@ -980,12 +1016,12 @@ public override ImmutableArray<CSharpAttributeData> GetAttributes() | |||
} | |||
|
|||
bool filterIsReadOnlyAttribute = this.RefKind == RefKind.In; | |||
bool filterLifetimeAnnotationAttribute = Scope != DeclarationScope.Unscoped; |
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.
{ | ||
Assert.Equal(expectedRefKind, parameter.RefKind); | ||
// https://github.com/dotnet/roslyn/issues/61647: Use public API. | ||
//Assert.Equal(expectedScope == DeclarationScope.RefScoped, parameter.IsRefScoped); |
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.
The only caller is the preceding method which tests the internal API ParameterSymbol.Scope
.
{ | ||
Assert.Equal(expectedRefKind, local.RefKind); | ||
// https://github.com/dotnet/roslyn/issues/61647: Use public API. | ||
//Assert.Equal(expectedScope == DeclarationScope.RefScoped, local.IsRefScoped); |
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.
The only caller is the preceding method which tests the internal API LocalSymbol.Scope
.
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.
LGTM (commit 33), assuming CI is passing.
Proposal: low-level-struct-improvements.md
Test plan: #59194
Parse and bind
scoped
modifier for parameters and locals; report binding error forscoped
when compiling with-langversion:10
or earlier; emit modifier to metadata withLifetimeAnnotationAttribute
. Thescoped
modifier can be applied toref
or toref struct
values.The PR does not include:
scoped
variables (see spec)scoped
scoped
considered in overrides and interface implementation