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

Produce specific error for misplaced 'record' keyword #59622

Merged
merged 7 commits into from
Mar 1, 2022
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions src/Compilers/CSharp/Portable/CSharpResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -6989,4 +6989,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="ERR_LineContainsDifferentWhitespace" xml:space="preserve">
<value>Line contains different whitespace than the closing line of the raw string literal: '{0}' versus '{1}'</value>
</data>
<data name="ERR_MisplacedRecord" xml:space="preserve">
<value>Unexpected keyword 'record'. Did you mean 'record struct' or 'record class'?</value>
</data>
</root>
2 changes: 2 additions & 0 deletions src/Compilers/CSharp/Portable/Errors/ErrorCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2046,6 +2046,8 @@ internal enum ErrorCode
ERR_IllegalAtSequence = 9008,
ERR_StringMustStartWithQuoteCharacter = 9009,

ERR_MisplacedRecord = 9012,

#endregion

// Note: you will need to re-generate compiler code after adding warnings (eng\generate-compiler-code.cmd)
Expand Down
56 changes: 39 additions & 17 deletions src/Compilers/CSharp/Portable/Parser/LanguageParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,16 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.CSharp.Syntax.InternalSyntax
{
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Syntax.InternalSyntax;

internal partial class LanguageParser : SyntaxParser
Expand Down Expand Up @@ -1542,22 +1543,21 @@ private void CheckForVersionSpecificModifiers(SyntaxListBuilder modifiers, Synta

private TypeDeclarationSyntax ParseClassOrStructOrInterfaceDeclaration(SyntaxList<AttributeListSyntax> attributes, SyntaxListBuilder modifiers)
{
Debug.Assert(this.CurrentToken.Kind == SyntaxKind.ClassKeyword ||
this.CurrentToken.Kind == SyntaxKind.StructKeyword ||
this.CurrentToken.Kind == SyntaxKind.InterfaceKeyword ||
CurrentToken.ContextualKind == SyntaxKind.RecordKeyword);
Debug.Assert(this.CurrentToken.Kind is SyntaxKind.ClassKeyword or SyntaxKind.StructKeyword or SyntaxKind.InterfaceKeyword ||
this.CurrentToken.ContextualKind == SyntaxKind.RecordKeyword);

// "top-level" expressions and statements should never occur inside an asynchronous context
Debug.Assert(!IsInAsync);

var keyword = ConvertToKeyword(this.EatToken());

var outerSaveTerm = _termState;
SyntaxToken? recordModifier = null;
if (keyword.Kind == SyntaxKind.RecordKeyword)

if (tryScanRecordStart(out var keyword, out var recordModifier))
{
_termState |= TerminatorState.IsEndOfRecordSignature;
recordModifier = eatRecordModifierIfAvailable();
}
else
{
keyword = ConvertToKeyword(this.EatToken());
}

var saveTerm = _termState;
Expand Down Expand Up @@ -1675,17 +1675,39 @@ private TypeDeclarationSyntax ParseClassOrStructOrInterfaceDeclaration(SyntaxLis
}
}

SyntaxToken? eatRecordModifierIfAvailable()
bool tryScanRecordStart([NotNullWhen(true)] out SyntaxToken? keyword, out SyntaxToken? recordModifier)
{
Debug.Assert(keyword.Kind == SyntaxKind.RecordKeyword);
if (CurrentToken.Kind is SyntaxKind.ClassKeyword or SyntaxKind.StructKeyword)
if (this.CurrentToken.ContextualKind == SyntaxKind.RecordKeyword)
{
var result = EatToken();
result = CheckFeatureAvailability(result, MessageID.IDS_FeatureRecordStructs);
return result;
keyword = ConvertToKeyword(this.EatToken());
recordModifier = this.CurrentToken.Kind is SyntaxKind.ClassKeyword or SyntaxKind.StructKeyword
? CheckFeatureAvailability(EatToken(), MessageID.IDS_FeatureRecordStructs)
: null;

return true;
}

return null;
if (this.CurrentToken.Kind is SyntaxKind.StructKeyword or SyntaxKind.ClassKeyword &&
this.PeekToken(1).ContextualKind == SyntaxKind.RecordKeyword &&
IsFeatureEnabled(MessageID.IDS_FeatureRecordStructs) &&
PeekToken(2).Kind is SyntaxKind.IdentifierToken)
{
// Provide a specific diagnostic on `struct record S` or `class record C`
var misplacedToken = this.EatToken();

// Parse out 'record' but place 'struct/class' as leading skipped trivia on it.
keyword = AddLeadingSkippedSyntax(
this.AddError(ConvertToKeyword(this.EatToken()), ErrorCode.ERR_MisplacedRecord),
misplacedToken);

// Treat `struct record` as a RecordStructDeclaration, and `class record` as a RecordDeclaration.
recordModifier = SyntaxFactory.MissingToken(misplacedToken.Kind);
return true;
}

keyword = null;
recordModifier = null;
return false;
}

static TypeDeclarationSyntax constructTypeDeclaration(ContextAwareSyntax syntaxFactory, SyntaxList<AttributeListSyntax> attributes, SyntaxListBuilder modifiers, SyntaxToken keyword, SyntaxToken? recordModifier,
Expand Down
5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf

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

5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf

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

5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf

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

5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf

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

5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf

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

5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf

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

5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf

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

5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf

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

5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf

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

5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf

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

5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf

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

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

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

Loading