Skip to content
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

Required modifier parsing #58431

Merged
merged 5 commits into from
Jan 10, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ internal enum DeclarationModifiers : uint
Async = 1 << 20,
Ref = 1 << 21, // used only for structs

Required = 1 << 22, // Used only for properties and fields

All = (1 << 23) - 1, // all modifiers
Unset = 1 << 23, // used when a modifiers value hasn't yet been computed

Expand Down
4 changes: 4 additions & 0 deletions src/Compilers/CSharp/Portable/Errors/MessageID.cs
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,9 @@ internal enum MessageID

IDS_FeatureGenericAttributes = MessageBase + 12812,

// PROTOTYPE(req): here for avoiding merge conflicts. Move to the end and condense before merge.
IDS_FeatureRequiredMembers = MessageBase + 13000,

IDS_FeatureNewLinesInInterpolations = MessageBase + 12813,
IDS_FeatureListPattern = MessageBase + 12814,
IDS_ParameterNullChecking = MessageBase + 12815,
Expand Down Expand Up @@ -354,6 +357,7 @@ internal static LanguageVersion RequiredVersion(this MessageID feature)
case MessageID.IDS_FeatureGenericAttributes: // semantic check
case MessageID.IDS_FeatureNewLinesInInterpolations: // semantic check
case MessageID.IDS_FeatureListPattern: // semantic check
case MessageID.IDS_FeatureRequiredMembers: // semantic check
333fred marked this conversation as resolved.
Show resolved Hide resolved
case MessageID.IDS_ParameterNullChecking: // syntax check
return LanguageVersion.Preview;

Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 23 additions & 6 deletions src/Compilers/CSharp/Portable/Parser/LanguageParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1160,6 +1160,8 @@ internal static DeclarationModifiers GetModifier(SyntaxKind kind, SyntaxKind con
return DeclarationModifiers.Partial;
case SyntaxKind.AsyncKeyword:
return DeclarationModifiers.Async;
case SyntaxKind.RequiredKeyword:
return DeclarationModifiers.Required;
}

goto default;
Expand Down Expand Up @@ -1241,7 +1243,7 @@ private void ParseModifiers(SyntaxListBuilder tokens, bool forAccessors)
}

case DeclarationModifiers.Async:
if (!ShouldAsyncBeTreatedAsModifier(parsingStatementNotDeclaration: false))
if (!ShouldAsyncOrRequiredBeTreatedAsModifier(parsingStatementNotDeclaration: false))
{
return;
}
Expand All @@ -1250,6 +1252,19 @@ private void ParseModifiers(SyntaxListBuilder tokens, bool forAccessors)
modTok = CheckFeatureAvailability(modTok, MessageID.IDS_FeatureAsync);
break;

case DeclarationModifiers.Required:
// In C# 11, required in a modifier position is always a keyword if not escaped. Otherwise, we reuse the async detection
// machinery to make a conservative guess as to whether the user meant required to be a keyword, so that they get a good langver
// diagnostic and all the machinery to upgrade their project kicks in.
if (!IsFeatureEnabled(MessageID.IDS_FeatureRequiredMembers) && !ShouldAsyncOrRequiredBeTreatedAsModifier(parsingStatementNotDeclaration: false))
333fred marked this conversation as resolved.
Show resolved Hide resolved
{
return;
}

modTok = ConvertToKeyword(this.EatToken());

break;

default:
modTok = this.EatToken();
break;
Expand Down Expand Up @@ -1278,16 +1293,16 @@ bool isStructOrRecordKeyword(SyntaxToken token)
}
}

private bool ShouldAsyncBeTreatedAsModifier(bool parsingStatementNotDeclaration)
private bool ShouldAsyncOrRequiredBeTreatedAsModifier(bool parsingStatementNotDeclaration)
{
Debug.Assert(this.CurrentToken.ContextualKind == SyntaxKind.AsyncKeyword);
Debug.Assert(this.CurrentToken.ContextualKind is SyntaxKind.AsyncKeyword or SyntaxKind.RequiredKeyword);

// Adapted from CParser::IsAsyncMethod.

if (IsNonContextualModifier(PeekToken(1)))
{
// If the next token is a (non-contextual) modifier keyword, then this token is
// definitely the async keyword
// definitely the async or required keyword
return true;
}

Expand All @@ -1298,7 +1313,7 @@ private bool ShouldAsyncBeTreatedAsModifier(bool parsingStatementNotDeclaration)

try
{
this.EatToken(); //move past contextual 'async'
this.EatToken(); //move past contextual 'async' or 'required'

if (!parsingStatementNotDeclaration &&
(this.CurrentToken.ContextualKind == SyntaxKind.PartialKeyword))
Expand All @@ -1315,6 +1330,8 @@ private bool ShouldAsyncBeTreatedAsModifier(bool parsingStatementNotDeclaration)
// ... 'async' [partial] <typename> <membername> ...
// DEVNOTE: Although we parse async user defined conversions, operators, etc. here,
// anything other than async methods are detected as erroneous later, during the define phase
// The comments in general were not updated to add "async or required" everywhere to preserve
// history, but generally wherever async occurs, it can also be required.

if (!parsingStatementNotDeclaration)
{
Expand Down Expand Up @@ -7843,7 +7860,7 @@ private bool IsPossibleLocalDeclarationStatement(bool isGlobalScriptLevel)
tk = this.CurrentToken.ContextualKind;

var isPossibleAttributeOrModifier = (IsAdditionalLocalFunctionModifier(tk) || tk == SyntaxKind.OpenBracketToken)
&& (tk != SyntaxKind.AsyncKeyword || ShouldAsyncBeTreatedAsModifier(parsingStatementNotDeclaration: true));
&& (tk != SyntaxKind.AsyncKeyword || ShouldAsyncOrRequiredBeTreatedAsModifier(parsingStatementNotDeclaration: true));
if (isPossibleAttributeOrModifier)
{
return true;
Expand Down
1 change: 1 addition & 0 deletions src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ Microsoft.CodeAnalysis.CSharp.Syntax.SubpatternSyntax.WithExpressionColon(Micros
Microsoft.CodeAnalysis.CSharp.SyntaxKind.ExpressionColon = 9069 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind
Microsoft.CodeAnalysis.CSharp.SyntaxKind.LineDirectivePosition = 9070 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind
Microsoft.CodeAnalysis.CSharp.SyntaxKind.LineSpanDirectiveTrivia = 9071 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind
Microsoft.CodeAnalysis.CSharp.SyntaxKind.RequiredKeyword = 8447 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind
*REMOVED*Microsoft.CodeAnalysis.CSharp.Syntax.NamespaceDeclarationSyntax.Externs.get -> Microsoft.CodeAnalysis.SyntaxList<Microsoft.CodeAnalysis.CSharp.Syntax.ExternAliasDirectiveSyntax>
*REMOVED*Microsoft.CodeAnalysis.CSharp.Syntax.NamespaceDeclarationSyntax.Members.get -> Microsoft.CodeAnalysis.SyntaxList<Microsoft.CodeAnalysis.CSharp.Syntax.MemberDeclarationSyntax>
*REMOVED*Microsoft.CodeAnalysis.CSharp.Syntax.NamespaceDeclarationSyntax.Name.get -> Microsoft.CodeAnalysis.CSharp.Syntax.NameSyntax
Expand Down
2 changes: 2 additions & 0 deletions src/Compilers/CSharp/Portable/Syntax/SyntaxKind.cs
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,8 @@ public enum SyntaxKind : ushort
ManagedKeyword = 8445,
/// <summary>Represents <see langword="unmanaged"/>.</summary>
UnmanagedKeyword = 8446,
/// <summary>Represents <see langword="required"/>.</summary>
RequiredKeyword = 8447,

// when adding a contextual keyword following functions must be adapted:
// <see cref="SyntaxFacts.GetContextualKeywordKinds"/>
Expand Down
7 changes: 6 additions & 1 deletion src/Compilers/CSharp/Portable/Syntax/SyntaxKindFacts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1075,7 +1075,7 @@ public static SyntaxKind GetPreprocessorKeywordKind(string text)

public static IEnumerable<SyntaxKind> GetContextualKeywordKinds()
{
for (int i = (int)SyntaxKind.YieldKeyword; i <= (int)SyntaxKind.UnmanagedKeyword; i++)
for (int i = (int)SyntaxKind.YieldKeyword; i <= (int)SyntaxKind.RequiredKeyword; i++)
{
yield return (SyntaxKind)i;
}
Expand Down Expand Up @@ -1128,6 +1128,7 @@ public static bool IsContextualKeyword(SyntaxKind kind)
case SyntaxKind.RecordKeyword:
case SyntaxKind.ManagedKeyword:
case SyntaxKind.UnmanagedKeyword:
case SyntaxKind.RequiredKeyword:
return true;
default:
return false;
Expand Down Expand Up @@ -1247,6 +1248,8 @@ public static SyntaxKind GetContextualKeywordKind(string text)
return SyntaxKind.ManagedKeyword;
case "unmanaged":
return SyntaxKind.UnmanagedKeyword;
case "required":
return SyntaxKind.RequiredKeyword;
default:
return SyntaxKind.None;
}
Expand Down Expand Up @@ -1684,6 +1687,8 @@ public static string GetText(SyntaxKind kind)
return "managed";
case SyntaxKind.UnmanagedKeyword:
return "unmanaged";
case SyntaxKind.RequiredKeyword:
return "required";
default:
return string.Empty;
}
Expand Down
Loading