Skip to content

Commit 28bd13d

Browse files
authored
Merge pull request #42119 from hamishknight/regular-grammar
2 parents 624c07b + f1a7990 commit 28bd13d

22 files changed

+730
-84
lines changed

include/swift/AST/DiagnosticEngine.h

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -848,6 +848,7 @@ namespace swift {
848848
friend class DiagnosticTransaction;
849849
friend class CompoundDiagnosticTransaction;
850850
friend class DiagnosticStateRAII;
851+
friend class DiagnosticQueue;
851852

852853
public:
853854
explicit DiagnosticEngine(SourceManager &SourceMgr)
@@ -1137,10 +1138,20 @@ namespace swift {
11371138
/// Send \c diag to all diagnostic consumers.
11381139
void emitDiagnostic(const Diagnostic &diag);
11391140

1141+
/// Handle a new diagnostic, which will either be emitted, or added to an
1142+
/// active transaction.
1143+
void handleDiagnostic(Diagnostic &&diag);
1144+
1145+
/// Clear any tentative diagnostics.
1146+
void clearTentativeDiagnostics();
1147+
11401148
/// Send all tentative diagnostics to all diagnostic consumers and
11411149
/// delete them.
11421150
void emitTentativeDiagnostics();
11431151

1152+
/// Forward all tentative diagnostics to a different diagnostic engine.
1153+
void forwardTentativeDiagnosticsTo(DiagnosticEngine &targetEngine);
1154+
11441155
public:
11451156
DiagnosticKind declaredDiagnosticKindFor(const DiagID id);
11461157

@@ -1333,6 +1344,78 @@ namespace swift {
13331344
}
13341345
};
13351346

1347+
/// Represents a queue of diagnostics that have their emission delayed until
1348+
/// the queue is destroyed. This is similar to DiagnosticTransaction, but
1349+
/// with a few key differences:
1350+
///
1351+
/// - The queue maintains its own diagnostic engine (which may be accessed
1352+
/// through `getDiags()`), and diagnostics must be specifically emitted
1353+
/// using that engine to be enqueued.
1354+
/// - It allows for non-LIFO transactions, as each queue operates
1355+
/// independently.
1356+
/// - A queue can be drained multiple times without having to be recreated
1357+
/// (unlike DiagnosticTransaction, it has no concept of "closing").
1358+
///
1359+
/// Note you may add DiagnosticTransactions to the queue's diagnostic engine,
1360+
/// but they must be closed before attempting to clear or emit the diagnostics
1361+
/// in the queue.
1362+
///
1363+
class DiagnosticQueue final {
1364+
/// The underlying diagnostic engine that the diagnostics will be emitted
1365+
/// by.
1366+
DiagnosticEngine &UnderlyingEngine;
1367+
1368+
/// A temporary engine used to queue diagnostics.
1369+
DiagnosticEngine QueueEngine;
1370+
1371+
/// Whether the queued diagnostics should be emitted on the destruction of
1372+
/// the queue, or whether they should be cleared.
1373+
bool EmitOnDestruction;
1374+
1375+
public:
1376+
DiagnosticQueue(const DiagnosticQueue &) = delete;
1377+
DiagnosticQueue &operator=(const DiagnosticQueue &) = delete;
1378+
1379+
/// Create a new diagnostic queue with a given engine to forward the
1380+
/// diagnostics to.
1381+
explicit DiagnosticQueue(DiagnosticEngine &engine, bool emitOnDestruction)
1382+
: UnderlyingEngine(engine), QueueEngine(engine.SourceMgr),
1383+
EmitOnDestruction(emitOnDestruction) {
1384+
// Open a transaction to avoid emitting any diagnostics for the temporary
1385+
// engine.
1386+
QueueEngine.TransactionCount++;
1387+
}
1388+
1389+
/// Retrieve the engine which may be used to enqueue diagnostics.
1390+
DiagnosticEngine &getDiags() { return QueueEngine; }
1391+
1392+
/// Retrieve the underlying engine which will receive the diagnostics.
1393+
DiagnosticEngine &getUnderlyingDiags() { return UnderlyingEngine; }
1394+
1395+
/// Clear this queue and erase all diagnostics recorded.
1396+
void clear() {
1397+
assert(QueueEngine.TransactionCount == 1 &&
1398+
"Must close outstanding DiagnosticTransactions before draining");
1399+
QueueEngine.clearTentativeDiagnostics();
1400+
}
1401+
1402+
/// Emit all the diagnostics recorded by this queue.
1403+
void emit() {
1404+
assert(QueueEngine.TransactionCount == 1 &&
1405+
"Must close outstanding DiagnosticTransactions before draining");
1406+
QueueEngine.forwardTentativeDiagnosticsTo(UnderlyingEngine);
1407+
}
1408+
1409+
~DiagnosticQueue() {
1410+
if (EmitOnDestruction) {
1411+
emit();
1412+
} else {
1413+
clear();
1414+
}
1415+
QueueEngine.TransactionCount--;
1416+
}
1417+
};
1418+
13361419
inline void
13371420
DiagnosticEngine::diagnoseWithNotes(InFlightDiagnostic parentDiag,
13381421
llvm::function_ref<void(void)> builder) {

include/swift/AST/DiagnosticsParse.def

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ ERROR(forbidden_extended_escaping_string,none,
9494
ERROR(regex_literal_parsing_error,none,
9595
"%0", (StringRef))
9696

97+
ERROR(prefix_slash_not_allowed,none,
98+
"prefix operator may not contain '/'", ())
99+
97100
//------------------------------------------------------------------------------
98101
// MARK: Lexer diagnostics
99102
//------------------------------------------------------------------------------
@@ -140,7 +143,10 @@ ERROR(lex_invalid_escape_delimiter,none,
140143
ERROR(lex_invalid_closing_delimiter,none,
141144
"too many '#' characters in closing delimiter", ())
142145

143-
ERROR(lex_unterminated_regex,none,
146+
ERROR(lex_regex_literal_invalid_starting_char,none,
147+
"regex literal may not start with %0; add backslash to escape",
148+
(StringRef))
149+
ERROR(lex_regex_literal_unterminated,none,
144150
"unterminated regex literal", ())
145151

146152
ERROR(lex_invalid_unicode_scalar,none,

include/swift/Basic/LangOptions.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -564,8 +564,9 @@ namespace swift {
564564
/// Enables dumping type witness systems from associated type inference.
565565
bool DumpTypeWitnessSystems = false;
566566

567-
/// Enables `/.../` syntax regular-expression literals
568-
bool EnableForwardSlashRegexLiterals = false;
567+
/// Enables `/.../` syntax regular-expression literals. This requires
568+
/// experimental string processing. Note this does not affect `#/.../#`.
569+
bool EnableBareSlashRegexLiterals = false;
569570

570571
/// Sets the target we are building for and updates platform conditions
571572
/// to match.

include/swift/Parse/Lexer.h

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,16 @@ enum class LexerMode {
6161
SIL
6262
};
6363

64+
/// Whether or not the lexer should attempt to lex a `/.../` regex literal.
65+
enum class LexerForwardSlashRegexMode {
66+
/// No `/.../` regex literals will be lexed.
67+
None,
68+
/// A `/.../` regex literal will be lexed, but only if successful.
69+
Tentative,
70+
/// A `/.../` regex literal will always be lexed for a '/' character.
71+
Always
72+
};
73+
6474
/// Kinds of conflict marker which the lexer might encounter.
6575
enum class ConflictMarkerKind {
6676
/// A normal or diff3 conflict marker, initiated by at least 7 "<"s,
@@ -75,7 +85,10 @@ class Lexer {
7585
const LangOptions &LangOpts;
7686
const SourceManager &SourceMgr;
7787
const unsigned BufferID;
78-
DiagnosticEngine *Diags;
88+
89+
/// A queue of diagnostics to emit when a token is consumed. We want to queue
90+
/// them, as the parser may backtrack and re-lex a token.
91+
Optional<DiagnosticQueue> DiagQueue;
7992

8093
using State = LexerState;
8194

@@ -109,6 +122,10 @@ class Lexer {
109122
/// a .sil file.
110123
const LexerMode LexMode;
111124

125+
/// Whether or not a `/.../` literal will be lexed.
126+
LexerForwardSlashRegexMode ForwardSlashRegexMode =
127+
LexerForwardSlashRegexMode::None;
128+
112129
/// True if we should skip past a `#!` line at the start of the file.
113130
const bool IsHashbangAllowed;
114131

@@ -154,6 +171,19 @@ class Lexer {
154171

155172
void initialize(unsigned Offset, unsigned EndOffset);
156173

174+
/// Retrieve the diagnostic engine for emitting diagnostics for the current
175+
/// token.
176+
DiagnosticEngine *getTokenDiags() {
177+
return DiagQueue ? &DiagQueue->getDiags() : nullptr;
178+
}
179+
180+
/// Retrieve the underlying diagnostic engine we emit diagnostics to. Note
181+
/// this should only be used for diagnostics not concerned with the current
182+
/// token.
183+
DiagnosticEngine *getUnderlyingDiags() {
184+
return DiagQueue ? &DiagQueue->getUnderlyingDiags() : nullptr;
185+
}
186+
157187
public:
158188
/// Create a normal lexer that scans the whole source buffer.
159189
///
@@ -209,6 +239,10 @@ class Lexer {
209239
LeadingTriviaResult = LeadingTrivia;
210240
TrailingTriviaResult = TrailingTrivia;
211241
}
242+
// Emit any diagnostics recorded for this token.
243+
if (DiagQueue)
244+
DiagQueue->emit();
245+
212246
if (Result.isNot(tok::eof))
213247
lexImpl();
214248
}
@@ -298,12 +332,12 @@ class Lexer {
298332
void restoreState(State S, bool enableDiagnostics = false) {
299333
assert(S.isValid());
300334
CurPtr = getBufferPtrForSourceLoc(S.Loc);
301-
// Don't reemit diagnostics while readvancing the lexer.
302-
llvm::SaveAndRestore<DiagnosticEngine*>
303-
D(Diags, enableDiagnostics ? Diags : nullptr);
304-
305335
lexImpl();
306336

337+
// Don't re-emit diagnostics from readvancing the lexer.
338+
if (DiagQueue && !enableDiagnostics)
339+
DiagQueue->clear();
340+
307341
// Restore Trivia.
308342
if (TriviaRetention == TriviaRetentionMode::WithTrivia)
309343
LeadingTrivia = S.LeadingTrivia;
@@ -505,7 +539,7 @@ class Lexer {
505539

506540
void getStringLiteralSegments(const Token &Str,
507541
SmallVectorImpl<StringSegment> &Segments) {
508-
return getStringLiteralSegments(Str, Segments, Diags);
542+
return getStringLiteralSegments(Str, Segments, getTokenDiags());
509543
}
510544

511545
static SourceLoc getSourceLoc(const char *Loc) {
@@ -531,6 +565,11 @@ class Lexer {
531565
void operator=(const SILBodyRAII&) = delete;
532566
};
533567

568+
/// Attempt to re-lex a regex literal with forward slashes `/.../` from a
569+
/// given lexing state. If \p mustBeRegex is set to true, a regex literal will
570+
/// always be lexed. Otherwise, it will not be lexed if it may be ambiguous.
571+
void tryLexForwardSlashRegexLiteralFrom(State S, bool mustBeRegex);
572+
534573
private:
535574
/// Nul character meaning kind.
536575
enum class NulCharacterKind {
@@ -595,8 +634,8 @@ class Lexer {
595634
void lexStringLiteral(unsigned CustomDelimiterLen = 0);
596635
void lexEscapedIdentifier();
597636

598-
/// Attempt to lex a regex literal, returning true if a regex literal was
599-
/// lexed, false if this is not a regex literal.
637+
/// Attempt to lex a regex literal, returning true if lexing should continue,
638+
/// false if this is not a regex literal.
600639
bool tryLexRegexLiteral(const char *TokStart);
601640

602641
void tryLexEditorPlaceholder();

include/swift/Parse/Parser.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,11 @@ class Parser {
559559
return f(backtrackScope);
560560
}
561561

562+
/// Discard the current token. This will avoid interface hashing or updating
563+
/// the previous loc. Only should be used if you've completely re-lexed
564+
/// a different token at that position.
565+
SourceLoc discardToken();
566+
562567
/// Consume a token that we created on the fly to correct the original token
563568
/// stream from lexer.
564569
void consumeExtraToken(Token K);
@@ -1752,8 +1757,17 @@ class Parser {
17521757
ParserResult<Expr>
17531758
parseExprPoundCodeCompletion(Optional<StmtKind> ParentKind);
17541759

1760+
UnresolvedDeclRefExpr *makeExprOperator(const Token &opToken);
17551761
UnresolvedDeclRefExpr *parseExprOperator();
17561762

1763+
/// Try re-lex a '/' operator character as a regex literal. This should be
1764+
/// called when parsing in an expression position to ensure a regex literal is
1765+
/// correctly parsed.
1766+
///
1767+
/// If \p mustBeRegex is set to true, a regex literal will always be lexed if
1768+
/// enabled. Otherwise, it will not be lexed if it may be ambiguous.
1769+
void tryLexRegexLiteral(bool mustBeRegex);
1770+
17571771
void validateCollectionElement(ParserResult<Expr> element);
17581772

17591773
//===--------------------------------------------------------------------===//

lib/AST/DiagnosticEngine.cpp

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1040,24 +1040,40 @@ DiagnosticBehavior DiagnosticState::determineBehavior(const Diagnostic &diag) {
10401040

10411041
void DiagnosticEngine::flushActiveDiagnostic() {
10421042
assert(ActiveDiagnostic && "No active diagnostic to flush");
1043+
handleDiagnostic(std::move(*ActiveDiagnostic));
1044+
ActiveDiagnostic.reset();
1045+
}
1046+
1047+
void DiagnosticEngine::handleDiagnostic(Diagnostic &&diag) {
10431048
if (TransactionCount == 0) {
1044-
emitDiagnostic(*ActiveDiagnostic);
1049+
emitDiagnostic(diag);
10451050
WrappedDiagnostics.clear();
10461051
WrappedDiagnosticArgs.clear();
10471052
} else {
1048-
onTentativeDiagnosticFlush(*ActiveDiagnostic);
1049-
TentativeDiagnostics.emplace_back(std::move(*ActiveDiagnostic));
1053+
onTentativeDiagnosticFlush(diag);
1054+
TentativeDiagnostics.emplace_back(std::move(diag));
10501055
}
1051-
ActiveDiagnostic.reset();
1056+
}
1057+
1058+
void DiagnosticEngine::clearTentativeDiagnostics() {
1059+
TentativeDiagnostics.clear();
1060+
WrappedDiagnostics.clear();
1061+
WrappedDiagnosticArgs.clear();
10521062
}
10531063

10541064
void DiagnosticEngine::emitTentativeDiagnostics() {
10551065
for (auto &diag : TentativeDiagnostics) {
10561066
emitDiagnostic(diag);
10571067
}
1058-
TentativeDiagnostics.clear();
1059-
WrappedDiagnostics.clear();
1060-
WrappedDiagnosticArgs.clear();
1068+
clearTentativeDiagnostics();
1069+
}
1070+
1071+
void DiagnosticEngine::forwardTentativeDiagnosticsTo(
1072+
DiagnosticEngine &targetEngine) {
1073+
for (auto &diag : TentativeDiagnostics) {
1074+
targetEngine.handleDiagnostic(std::move(diag));
1075+
}
1076+
clearTentativeDiagnostics();
10611077
}
10621078

10631079
/// Returns the access level of the least accessible PrettyPrintedDeclarations

lib/Frontend/CompilerInvocation.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,13 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
504504
Opts.EnableExperimentalStringProcessing |=
505505
Args.hasArg(OPT_enable_experimental_string_processing);
506506

507+
// Whether '/.../' regex literals are enabled. This implies experimental
508+
// string processing.
509+
if (Args.hasArg(OPT_enable_bare_slash_regex)) {
510+
Opts.EnableBareSlashRegexLiterals = true;
511+
Opts.EnableExperimentalStringProcessing = true;
512+
}
513+
507514
Opts.EnableExperimentalBoundGenericExtensions |=
508515
Args.hasArg(OPT_enable_experimental_bound_generic_extensions);
509516

@@ -1010,9 +1017,6 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
10101017
if (Args.hasArg(OPT_disable_requirement_machine_reuse))
10111018
Opts.EnableRequirementMachineReuse = false;
10121019

1013-
if (Args.hasArg(OPT_enable_bare_slash_regex))
1014-
Opts.EnableForwardSlashRegexLiterals = true;
1015-
10161020
if (Args.hasArg(OPT_enable_requirement_machine_opaque_archetypes))
10171021
Opts.EnableRequirementMachineOpaqueArchetypes = true;
10181022

0 commit comments

Comments
 (0)