Skip to content

Commit 1c0c397

Browse files
committed
[Parse] Improve recovery from and diagnostics for invalid operator names
1 parent 5a8e3ea commit 1c0c397

File tree

6 files changed

+69
-20
lines changed

6 files changed

+69
-20
lines changed

include/swift/AST/DiagnosticsParse.def

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -428,8 +428,14 @@ ERROR(operator_decl_inner_scope,none,
428428
"'operator' may only be declared at file scope", ())
429429
ERROR(expected_operator_name_after_operator,PointsToFirstBadToken,
430430
"expected operator name in operator declaration", ())
431-
ERROR(identifier_when_expecting_operator,PointsToFirstBadToken,
432-
"%0 is considered to be an identifier, not an operator", (Identifier))
431+
ERROR(identifier_within_operator_name,PointsToFirstBadToken,
432+
"'%0' is considered an identifier and must not "
433+
"appear within an operator name", (StringRef))
434+
ERROR(operator_name_invalid_char,PointsToFirstBadToken,
435+
"'%0' is not allowed in operator names", (StringRef))
436+
ERROR(postfix_operator_name_cannot_start_with_unwrap,PointsToFirstBadToken,
437+
"postfix operator names starting with '?' or '!' are disallowed "
438+
"to avoid collisions with built-in unwrapping operators", ())
433439

434440
ERROR(deprecated_operator_body,PointsToFirstBadToken,
435441
"operator should no longer be declared with body", ())

lib/Parse/ParseDecl.cpp

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7401,24 +7401,50 @@ Parser::parseDeclOperator(ParseDeclOptions Flags, DeclAttributes &Attributes) {
74017401
SourceLoc OperatorLoc = consumeToken(tok::kw_operator);
74027402
bool AllowTopLevel = Flags.contains(PD_AllowTopLevel);
74037403

7404-
if (!Tok.isAnyOperator() && !Tok.is(tok::exclaim_postfix)) {
7405-
// A common error is to try to define an operator with something in the
7406-
// unicode plane considered to be an operator, or to try to define an
7407-
// operator like "not". Diagnose this specifically.
7408-
if (Tok.is(tok::identifier))
7409-
diagnose(Tok, diag::identifier_when_expecting_operator,
7410-
Context.getIdentifier(Tok.getText()));
7411-
else
7404+
const auto maybeDiagnoseInvalidCharInOperatorName = [this](const Token &Tk) {
7405+
if (Tk.is(tok::identifier) &&
7406+
DeclAttribute::getAttrKindFromString(Tk.getText()) ==
7407+
DeclAttrKind::DAK_Count) {
7408+
diagnose(Tk, diag::identifier_within_operator_name, Tk.getText());
7409+
return true;
7410+
} else if (Tk.isNot(tok::colon, tok::l_brace, tok::semi) &&
7411+
Tk.isPunctuation()) {
7412+
diagnose(Tk, diag::operator_name_invalid_char,
7413+
Tk.getText().take_front());
7414+
return true;
7415+
}
7416+
return false;
7417+
};
7418+
7419+
// A common error is to try to define an operator with something in the
7420+
// unicode plane considered to be an operator, or to try to define an
7421+
// operator like "not". Analyze and diagnose this specifically.
7422+
if (Tok.isAnyOperator()) {
7423+
if (peekToken().getLoc() == Tok.getRange().getEnd() &&
7424+
maybeDiagnoseInvalidCharInOperatorName(peekToken())) {
7425+
consumeToken();
7426+
7427+
// If there's a deprecated body, skip it to improve recovery.
7428+
if (peekToken().is(tok::l_brace)) {
7429+
consumeToken();
7430+
skipSingle();
7431+
}
7432+
return nullptr;
7433+
}
7434+
// We will handle these either below or in Sema.
7435+
} else if (Tok.isNot(tok::exclaim_postfix, tok::question_infix,
7436+
tok::question_postfix)) {
7437+
if (maybeDiagnoseInvalidCharInOperatorName(Tok)) {
7438+
// We're done diagnosing.
7439+
} else {
74127440
diagnose(Tok, diag::expected_operator_name_after_operator);
7441+
}
74137442

7414-
// To improve recovery, check to see if we have a { right after this token.
7415-
// If so, swallow until the end } to avoid tripping over the body of the
7416-
// malformed operator decl.
7443+
// If there's a deprecated body, skip it to improve recovery.
74177444
if (peekToken().is(tok::l_brace)) {
74187445
consumeToken();
74197446
skipSingle();
74207447
}
7421-
74227448
return nullptr;
74237449
}
74247450

@@ -7427,9 +7453,11 @@ Parser::parseDeclOperator(ParseDeclOptions Flags, DeclAttributes &Attributes) {
74277453
Identifier Name = Context.getIdentifier(Tok.getText());
74287454
SourceLoc NameLoc = consumeToken();
74297455

7456+
// Postfix operators starting with ? or ! conflict with builtin
7457+
// unwrapping operators.
74307458
if (Attributes.hasAttribute<PostfixAttr>()) {
74317459
if (!Name.empty() && (Name.get()[0] == '?' || Name.get()[0] == '!'))
7432-
diagnose(NameLoc, diag::expected_operator_name_after_operator);
7460+
diagnose(NameLoc, diag::postfix_operator_name_cannot_start_with_unwrap);
74337461
}
74347462

74357463
auto Result = parseDeclOperatorImpl(OperatorLoc, Name, NameLoc, Attributes);

lib/Sema/TypeCheckAttr.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1503,6 +1503,9 @@ void AttributeChecker::visitFinalAttr(FinalAttr *attr) {
15031503
static bool isBuiltinOperator(StringRef name, DeclAttribute *attr) {
15041504
return ((isa<PrefixAttr>(attr) && name == "&") || // lvalue to inout
15051505
(isa<PostfixAttr>(attr) && name == "!") || // optional unwrapping
1506+
// FIXME: Not actually a builtin operator, but should probably
1507+
// be allowed and accounted for in Sema?
1508+
(isa<PrefixAttr>(attr) && name == "?") ||
15061509
(isa<PostfixAttr>(attr) && name == "?") || // optional chaining
15071510
(isa<InfixAttr>(attr) && name == "?") || // ternary operator
15081511
(isa<PostfixAttr>(attr) && name == ">") || // generic argument list

test/Parse/operator_decl.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,16 @@ prefix operator // expected-error {{expected operator name in operator declarati
4040
prefix operator %%+
4141

4242
prefix operator ??
43-
postfix operator ?? // expected-error {{expected operator name in operator declaration}}
43+
postfix operator ?? // expected-error {{postfix operator names starting with '?' or '!' are disallowed}}
4444
prefix operator !!
45-
postfix operator !! // expected-error {{expected operator name in operator declaration}}
45+
postfix operator !! // expected-error {{postfix operator names starting with '?' or '!' are disallowed}}
46+
47+
infix operator --aa // expected-error {{'aa' is considered an identifier and must not appear within an operator name}}
48+
infix operator aa--: A // expected-error {{'aa' is considered an identifier and must not appear within an operator name}}
49+
infix operator <<$$@< // expected-error {{'$$' is considered an identifier and must not appear within an operator name}}
50+
infix operator !!@aa // expected-error {{'@' is not allowed in operator names}}
51+
infix operator #++= // expected-error {{'#' is not allowed in operator names}}
52+
infix operator ++=# // expected-error {{'#' is not allowed in operator names}}
4653

4754
infix operator +++=
4855
infix operator *** : A

test/Parse/recovery.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -818,10 +818,13 @@ func test23719432() {
818818
}
819819

820820
// <rdar://problem/19911096> QoI: terrible recovery when using '·' for an operator
821-
infix operator · { // expected-error {{'·' is considered to be an identifier, not an operator}}
821+
infix operator · { // expected-error {{'·' is considered an identifier and must not appear within an operator name}}
822822
associativity none precedence 150
823823
}
824824

825+
infix operator -@-class Recover1 {} // expected-error {{'@' is not allowed in operator names}}
826+
prefix operator -фф--class Recover2 {} // expected-error {{'фф' is considered an identifier and must not appear within an operator name}}
827+
825828
// <rdar://problem/21712891> Swift Compiler bug: String subscripts with range should require closing bracket.
826829
func r21712891(s : String) -> String {
827830
let a = s.startIndex..<s.startIndex

test/decl/func/operator.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ infix prefix func +-+(x: Double, y: Double) {} // expected-error {{'infix' modif
172172

173173
// Don't allow one to define a postfix '!'; it's built into the
174174
// language. Also illegal to have any postfix operator starting with '!'.
175-
postfix operator ! // expected-error {{cannot declare a custom postfix '!' operator}} expected-error {{expected operator name in operator declaration}}
175+
postfix operator ! // expected-error {{cannot declare a custom postfix '!' operator}} expected-error {{postfix operator names starting with '?' or '!' are disallowed}}
176176
prefix operator & // expected-error {{cannot declare a custom prefix '&' operator}}
177177

178178
// <rdar://problem/14607026> Restrict use of '<' and '>' as prefix/postfix operator names
@@ -188,7 +188,9 @@ func operator_in_func_bad () {
188188
prefix func + (input: String) -> String { return "+" + input } // expected-error {{operator functions can only be declared at global or in type scope}}
189189
}
190190

191-
infix operator ? // expected-error {{expected operator name in operator declaration}}
191+
infix operator ? // expected-error {{cannot declare a custom infix '?' operator}}
192+
193+
prefix operator ? // expected-error {{cannot declare a custom prefix '?' operator}}
192194

193195
infix operator ??=
194196

0 commit comments

Comments
 (0)