Skip to content

Commit 07b86e9

Browse files
committed
Lenient prefix / parsing
1 parent 493b4c5 commit 07b86e9

File tree

5 files changed

+92
-58
lines changed

5 files changed

+92
-58
lines changed

include/swift/Parse/Parser.h

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1763,10 +1763,7 @@ class Parser {
17631763
/// Try re-lex a '/' operator character as a regex literal. This should be
17641764
/// called when parsing in an expression position to ensure a regex literal is
17651765
/// 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);
1766+
void tryLexRegexLiteral(bool forUnappliedOperator);
17701767

17711768
void validateCollectionElement(ParserResult<Expr> element);
17721769

lib/Parse/ParseExpr.cpp

Lines changed: 55 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -513,7 +513,7 @@ ParserResult<Expr> Parser::parseExprUnary(Diag<> Message, bool isExprBasic) {
513513
UnresolvedDeclRefExpr *Operator;
514514

515515
// First check to see if we have the start of a regex literal `/.../`.
516-
tryLexRegexLiteral(/*mustBeRegex*/ true);
516+
tryLexRegexLiteral(/*forUnappliedOperator*/ false);
517517

518518
switch (Tok.getKind()) {
519519
default:
@@ -880,56 +880,70 @@ UnresolvedDeclRefExpr *Parser::parseExprOperator() {
880880
return new (Context) UnresolvedDeclRefExpr(name, refKind, DeclNameLoc(loc));
881881
}
882882

883-
void Parser::tryLexRegexLiteral(bool mustBeRegex) {
883+
void Parser::tryLexRegexLiteral(bool forUnappliedOperator) {
884884
if (!Context.LangOpts.EnableBareSlashRegexLiterals)
885885
return;
886886

887+
// Never a regex literal.
888+
if (Tok.isEscapedOperator())
889+
return;
890+
887891
// Check to see if we have a regex literal `/.../`, optionally with a prefix
888892
// operator e.g `!/.../`.
893+
bool mustBeRegex = false;
889894
switch (Tok.getKind()) {
890895
case tok::oper_prefix:
896+
// Prefix operators may contain `/` characters, so this may not be a regex,
897+
// and as such need to make sure we have a closing `/`. The first character
898+
// heuristics aren't relevant here as a right-bound operator will not have
899+
// a space, tab, or `)` character.
900+
break;
891901
case tok::oper_binary_spaced:
892-
case tok::oper_binary_unspaced: {
893-
// Check to see if we have an operator containing '/'.
894-
auto slashIdx = Tok.getText().find("/");
895-
if (slashIdx == StringRef::npos)
896-
break;
902+
case tok::oper_binary_unspaced:
903+
// When re-lexing for a 'proper' expression, binary operators are always
904+
// invalid, so we can be confident in always lexing a regex literal.
905+
mustBeRegex = !forUnappliedOperator;
906+
break;
907+
default:
908+
// We only re-lex regex literals for operator tokens.
909+
return;
910+
}
897911

898-
CancellableBacktrackingScope backtrack(*this);
899-
{
900-
Optional<Lexer::ForwardSlashRegexRAII> regexScope;
901-
regexScope.emplace(*L, mustBeRegex);
902-
903-
// Try re-lex as a `/.../` regex literal, this will split an operator if
904-
// necessary.
905-
L->restoreState(getParserPosition().LS, /*enableDiagnostics*/ true);
906-
907-
// If we didn't split a prefix operator, reset the regex lexing scope.
908-
// Otherwise, we want to keep it in place for the next token.
909-
auto didSplit = L->peekNextToken().getLength() == slashIdx;
910-
if (!didSplit)
911-
regexScope.reset();
912-
913-
// Discard the current token, which will be replaced by the re-lexed
914-
// token, which will either be a regex literal token, a prefix operator,
915-
// or the original unchanged token.
916-
discardToken();
917-
918-
// If we split a prefix operator from the regex literal, and are not sure
919-
// whether this should be a regex, backtrack if we didn't end up lexing a
920-
// regex literal.
921-
if (didSplit && !mustBeRegex &&
922-
!L->peekNextToken().is(tok::regex_literal)) {
923-
return;
924-
}
912+
// Check to see if we have an operator containing '/'.
913+
auto slashIdx = Tok.getText().find("/");
914+
if (slashIdx == StringRef::npos)
915+
return;
916+
917+
CancellableBacktrackingScope backtrack(*this);
918+
{
919+
Optional<Lexer::ForwardSlashRegexRAII> regexScope;
920+
regexScope.emplace(*L, mustBeRegex);
921+
922+
// Try re-lex as a `/.../` regex literal, this will split an operator if
923+
// necessary.
924+
L->restoreState(getParserPosition().LS, /*enableDiagnostics*/ true);
925+
926+
// If we didn't split a prefix operator, reset the regex lexing scope.
927+
// Otherwise, we want to keep it in place for the next token.
928+
auto didSplit = L->peekNextToken().getLength() == slashIdx;
929+
if (!didSplit)
930+
regexScope.reset();
931+
932+
// Discard the current token, which will be replaced by the re-lexed
933+
// token, which will either be a regex literal token, a prefix operator,
934+
// or the original unchanged token.
935+
discardToken();
925936

926-
// Otherwise, accept the result.
927-
backtrack.cancelBacktrack();
937+
// If we split a prefix operator from the regex literal, and are not sure
938+
// whether this should be a regex, backtrack if we didn't end up lexing a
939+
// regex literal.
940+
if (didSplit && !mustBeRegex &&
941+
!L->peekNextToken().is(tok::regex_literal)) {
942+
return;
928943
}
929-
break;
930-
}
931-
default:
932-
break;
944+
945+
// Otherwise, accept the result.
946+
backtrack.cancelBacktrack();
933947
}
934948
}
935949

@@ -3226,7 +3240,7 @@ ParserStatus Parser::parseExprList(tok leftTok, tok rightTok,
32263240
// First check to see if we have the start of a regex literal `/.../`. We
32273241
// need to do this before handling unapplied operator references, as e.g
32283242
// `(/, /)` might be a regex literal.
3229-
tryLexRegexLiteral(/*mustBeRegex*/ false);
3243+
tryLexRegexLiteral(/*forUnappliedOperator*/ true);
32303244

32313245
// See if we have an operator decl ref '(<op>)'. The operator token in
32323246
// this case lexes as a binary operator because it neither leads nor

test/StringProcessing/Frontend/enable-flag.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
// REQUIRES: swift_in_compiler
66

7-
prefix operator / // expected-error {{prefix operator may not contain '/'}}
7+
prefix operator /
88

99
_ = /x/
1010
_ = #/x/#

test/StringProcessing/Parse/forward-slash-regex.swift

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
// REQUIRES: swift_in_compiler
33
// REQUIRES: concurrency
44

5-
prefix operator / // expected-error {{prefix operator may not contain '/'}}
6-
prefix operator ^/ // expected-error {{prefix operator may not contain '/'}}
7-
prefix operator /^/ // expected-error {{prefix operator may not contain '/'}}
5+
prefix operator /
6+
prefix operator ^/
7+
prefix operator /^/
8+
9+
prefix func ^/ <T> (_ x: T) -> T { x } // expected-note {{'^/' declared here}}
810

911
prefix operator !!
1012
prefix func !! <T>(_ x: T) -> T { x }
@@ -53,8 +55,9 @@ do {
5355
// expected-error@-3 {{'/' is not a postfix unary operator}}
5456
}
5557

58+
// No closing '/' so a prefix operator.
5659
_ = /x
57-
// expected-error@-1 {{unterminated regex literal}}
60+
// expected-error@-1 {{'/' is not a prefix unary operator}}
5861

5962
_ = !/x/
6063
// expected-error@-1 {{cannot convert value of type 'Regex<Substring>' to expected argument type 'Bool'}}
@@ -250,13 +253,15 @@ _ = await /x / // expected-warning {{no 'async' operations occur within 'await'
250253
// written a comment and is still in the middle of writing the characters before
251254
// it.
252255
_ = /x// comment
253-
// expected-error@-1 {{unterminated regex literal}}
256+
// expected-error@-1 {{'/' is not a prefix unary operator}}
254257

255258
_ = /x // comment
256-
// expected-error@-1 {{unterminated regex literal}}
259+
// expected-error@-1 {{'/' is not a prefix unary operator}}
257260

258261
_ = /x/*comment*/
259-
// expected-error@-1 {{unterminated regex literal}}
262+
// expected-error@-1 {{'/' is not a prefix unary operator}}
263+
264+
// MARK: Unapplied operators
260265

261266
// These become regex literals, unless surrounded in parens.
262267
func baz(_ x: (Int, Int) -> Int, _ y: (Int, Int) -> Int) {} // expected-note 4{{'baz' declared here}}
@@ -320,6 +325,26 @@ let arr: [Double] = [2, 3, 4]
320325
_ = arr.reduce(1, /) / 3
321326
_ = arr.reduce(1, /) + arr.reduce(1, /)
322327

328+
// MARK: Backticks behavior
329+
330+
// This is a prefix operator, even if there is a closing '/'.
331+
_ = `/`x
332+
// expected-error@-1 {{'/' is not a prefix unary operator}}
333+
334+
_ = `/`x/
335+
// expected-error@-1 {{'/' is not a prefix unary operator}}
336+
// expected-error@-2 {{'/' is not a postfix unary operator}}
337+
338+
_ = ^/x/
339+
// expected-error@-1 {{'^' is not a prefix unary operator}}
340+
341+
_ = `^/`x/
342+
// expected-error@-1 {{'/' is not a postfix unary operator}}
343+
344+
_ = `!!`/x/
345+
346+
// MARK: Starting characters
347+
323348
// Fine.
324349
_ = /./
325350

test/StringProcessing/Parse/regex_parse_error.swift

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,15 @@ _ = #/\(?'abc/#
3030
do {
3131
_ = /\
3232
/
33-
// expected-error@-2:7 {{unterminated regex literal}}
34-
// expected-error@-3:9 {{expected escape sequence}}
35-
} // expected-error@:1 {{expected expression after operator}}
33+
// expected-error@-1:3 {{expected expression path in Swift key path}}
34+
}
3635

3736
do {
3837
_ = #/\
3938
/#
4039
// expected-error@-2:7 {{unterminated regex literal}}
4140
// expected-error@-3:10 {{expected escape sequence}}
42-
// expected-error@-3:3 {{unterminated regex literal}}
43-
// expected-warning@-4:3 {{regular expression literal is unused}}
41+
// expected-error@-3:4 {{expected expression}}
4442
}
4543

4644
func foo<T>(_ x: T, _ y: T) {}

0 commit comments

Comments
 (0)