Skip to content

Commit

Permalink
Required modifier parsing (#58431)
Browse files Browse the repository at this point in the history
Implements parsing for required members.

Test plan: #57046
Spec: dotnet/csharplang#3630
  • Loading branch information
333fred authored Jan 10, 2022
1 parent d019178 commit 8212481
Show file tree
Hide file tree
Showing 9 changed files with 648 additions and 7 deletions.
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
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.

30 changes: 24 additions & 6 deletions src/Compilers/CSharp/Portable/Parser/LanguageParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1162,6 +1162,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 @@ -1243,7 +1245,7 @@ private void ParseModifiers(SyntaxListBuilder tokens, bool forAccessors)
}

case DeclarationModifiers.Async:
if (!ShouldAsyncBeTreatedAsModifier(parsingStatementNotDeclaration: false))
if (!ShouldAsyncOrRequiredBeTreatedAsModifier(parsingStatementNotDeclaration: false))
{
return;
}
Expand All @@ -1252,6 +1254,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))
{
return;
}

modTok = ConvertToKeyword(this.EatToken());

break;

default:
modTok = this.EatToken();
break;
Expand Down Expand Up @@ -1280,16 +1295,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 @@ -1300,7 +1315,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 @@ -1317,6 +1332,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 @@ -2627,6 +2644,7 @@ private bool IsMisplacedModifier(SyntaxListBuilder modifiers, SyntaxList<Attribu
if (GetModifier(this.CurrentToken) != DeclarationModifiers.None &&
this.CurrentToken.ContextualKind != SyntaxKind.PartialKeyword &&
this.CurrentToken.ContextualKind != SyntaxKind.AsyncKeyword &&
this.CurrentToken.ContextualKind != SyntaxKind.RequiredKeyword &&
IsComplete(type))
{
var misplacedModifier = this.CurrentToken;
Expand Down Expand Up @@ -7906,7 +7924,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 @@ -79,6 +79,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

0 comments on commit 8212481

Please sign in to comment.