Skip to content

Commit 571d09a

Browse files
authored
Merge pull request #29955 from AnthonyLatsis/operator-diag-qoi
[Parse] Improve recovery from and diagnostics for invalid operator names
2 parents 0a318a3 + a11cc4f commit 571d09a

File tree

7 files changed

+93
-27
lines changed

7 files changed

+93
-27
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/Lexer.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -829,7 +829,8 @@ void Lexer::lexOperatorIdentifier() {
829829
if (CurPtr-TokStart == 1) {
830830
switch (TokStart[0]) {
831831
case '=':
832-
if (leftBound != rightBound) {
832+
// Refrain from emitting this message in operator name position.
833+
if (NextToken.isNot(tok::kw_operator) && leftBound != rightBound) {
833834
auto d = diagnose(TokStart, diag::lex_unary_equal);
834835
if (leftBound)
835836
d.fixItInsert(getSourceLoc(TokStart), " ");

lib/Parse/ParseDecl.cpp

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7485,24 +7485,58 @@ Parser::parseDeclOperator(ParseDeclOptions Flags, DeclAttributes &Attributes) {
74857485
SourceLoc OperatorLoc = consumeToken(tok::kw_operator);
74867486
bool AllowTopLevel = Flags.contains(PD_AllowTopLevel);
74877487

7488-
if (!Tok.isAnyOperator() && !Tok.is(tok::exclaim_postfix)) {
7489-
// A common error is to try to define an operator with something in the
7490-
// unicode plane considered to be an operator, or to try to define an
7491-
// operator like "not". Diagnose this specifically.
7492-
if (Tok.is(tok::identifier))
7493-
diagnose(Tok, diag::identifier_when_expecting_operator,
7494-
Context.getIdentifier(Tok.getText()));
7495-
else
7488+
const auto maybeDiagnoseInvalidCharInOperatorName = [this](const Token &Tk) {
7489+
if (Tk.is(tok::identifier) &&
7490+
DeclAttribute::getAttrKindFromString(Tk.getText()) ==
7491+
DeclAttrKind::DAK_Count) {
7492+
diagnose(Tk, diag::identifier_within_operator_name, Tk.getText());
7493+
return true;
7494+
} else if (Tk.isNot(tok::colon, tok::l_brace, tok::semi) &&
7495+
Tk.isPunctuation()) {
7496+
diagnose(Tk, diag::operator_name_invalid_char,
7497+
Tk.getText().take_front());
7498+
return true;
7499+
}
7500+
return false;
7501+
};
7502+
7503+
// Postfix operators starting with ? or ! conflict with builtin
7504+
// unwrapping operators.
7505+
if (Attributes.hasAttribute<PostfixAttr>())
7506+
if (!Tok.getText().empty() && (Tok.getRawText().front() == '?' ||
7507+
Tok.getRawText().front() == '!'))
7508+
diagnose(Tok, diag::postfix_operator_name_cannot_start_with_unwrap);
7509+
7510+
// A common error is to try to define an operator with something in the
7511+
// unicode plane considered to be an operator, or to try to define an
7512+
// operator like "not". Analyze and diagnose this specifically.
7513+
if (Tok.isAnyOperator() || Tok.isAny(tok::exclaim_postfix,
7514+
tok::question_infix,
7515+
tok::question_postfix,
7516+
tok::equal, tok::arrow)) {
7517+
if (peekToken().getLoc() == Tok.getRange().getEnd() &&
7518+
maybeDiagnoseInvalidCharInOperatorName(peekToken())) {
7519+
consumeToken();
7520+
7521+
// If there's a deprecated body, skip it to improve recovery.
7522+
if (peekToken().is(tok::l_brace)) {
7523+
consumeToken();
7524+
skipSingle();
7525+
}
7526+
return nullptr;
7527+
}
7528+
} else {
7529+
if (maybeDiagnoseInvalidCharInOperatorName(Tok)) {
7530+
// We're done diagnosing.
7531+
} else {
74967532
diagnose(Tok, diag::expected_operator_name_after_operator);
7533+
}
74977534

7498-
// To improve recovery, check to see if we have a { right after this token.
7499-
// If so, swallow until the end } to avoid tripping over the body of the
7500-
// malformed operator decl.
7535+
// If there's a deprecated body, skip it to improve recovery.
75017536
if (peekToken().is(tok::l_brace)) {
75027537
consumeToken();
75037538
skipSingle();
75047539
}
7505-
75067540
return nullptr;
75077541
}
75087542

@@ -7511,11 +7545,6 @@ Parser::parseDeclOperator(ParseDeclOptions Flags, DeclAttributes &Attributes) {
75117545
Identifier Name = Context.getIdentifier(Tok.getText());
75127546
SourceLoc NameLoc = consumeToken();
75137547

7514-
if (Attributes.hasAttribute<PostfixAttr>()) {
7515-
if (!Name.empty() && (Name.get()[0] == '?' || Name.get()[0] == '!'))
7516-
diagnose(NameLoc, diag::expected_operator_name_after_operator);
7517-
}
7518-
75197548
auto Result = parseDeclOperatorImpl(OperatorLoc, Name, NameLoc, Attributes);
75207549

75217550
if (!DCC.movedToTopLevel() && !AllowTopLevel) {

lib/Sema/TypeCheckAttr.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1518,10 +1518,16 @@ void AttributeChecker::visitFinalAttr(FinalAttr *attr) {
15181518
static bool isBuiltinOperator(StringRef name, DeclAttribute *attr) {
15191519
return ((isa<PrefixAttr>(attr) && name == "&") || // lvalue to inout
15201520
(isa<PostfixAttr>(attr) && name == "!") || // optional unwrapping
1521+
// FIXME: Not actually a builtin operator, but should probably
1522+
// be allowed and accounted for in Sema?
1523+
(isa<PrefixAttr>(attr) && name == "?") ||
15211524
(isa<PostfixAttr>(attr) && name == "?") || // optional chaining
1522-
(isa<InfixAttr>(attr) && name == "?") || // ternary operator
1525+
(isa<InfixAttr>(attr) && name == "?") || // ternary operator
15231526
(isa<PostfixAttr>(attr) && name == ">") || // generic argument list
1524-
(isa<PrefixAttr>(attr) && name == "<")); // generic argument list
1527+
(isa<PrefixAttr>(attr) && name == "<") || // generic argument list
1528+
name == "=" || // Assignment
1529+
// FIXME: Should probably be allowed in expression position?
1530+
name == "->");
15251531
}
15261532

15271533
void AttributeChecker::checkOperatorAttribute(DeclAttribute *attr) {

test/Parse/operator_decl.swift

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,26 @@ 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 to avoid collisions with built-in unwrapping operators}}
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 to avoid collisions with built-in unwrapping operators}}
46+
postfix operator ?$$
47+
// expected-error@-1 {{postfix operator names starting with '?' or '!' are disallowed}}
48+
// expected-error@-2 {{'$$' is considered an identifier}}
49+
50+
infix operator --aa // expected-error {{'aa' is considered an identifier and must not appear within an operator name}}
51+
infix operator aa--: A // expected-error {{'aa' is considered an identifier and must not appear within an operator name}}
52+
infix operator <<$$@< // expected-error {{'$$' is considered an identifier and must not appear within an operator name}}
53+
infix operator !!@aa // expected-error {{'@' is not allowed in operator names}}
54+
infix operator #++= // expected-error {{'#' is not allowed in operator names}}
55+
infix operator ++=# // expected-error {{'#' is not allowed in operator names}}
56+
infix operator -># // expected-error {{'#' is not allowed in operator names}}
57+
58+
// FIXME: Ideally, we shouldn't emit the «consistent whitespace» diagnostic
59+
// where = cannot possibly mean an assignment.
60+
infix operator =#=
61+
// expected-error@-1 {{'#' is not allowed in operator names}}
62+
// expected-error@-2 {{'=' must have consistent whitespace on both sides}}
4663

4764
infix operator +++=
4865
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: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,13 +172,16 @@ 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
179179
postfix operator > // expected-error {{cannot declare a custom postfix '>' operator}}
180180
prefix operator < // expected-error {{cannot declare a custom prefix '<' operator}}
181181

182+
infix operator = // expected-error {{cannot declare a custom infix '=' operator}}
183+
infix operator -> // expected-error {{cannot declare a custom infix '->' operator}}
184+
182185
postfix func !(x: Int) { } // expected-error{{cannot declare a custom postfix '!' operator}}
183186
postfix func!(x: Int8) { } // expected-error{{cannot declare a custom postfix '!' operator}}
184187
prefix func & (x: Int) {} // expected-error {{cannot declare a custom prefix '&' operator}}
@@ -188,7 +191,8 @@ func operator_in_func_bad () {
188191
prefix func + (input: String) -> String { return "+" + input } // expected-error {{operator functions can only be declared at global or in type scope}}
189192
}
190193

191-
infix operator ? // expected-error {{expected operator name in operator declaration}}
194+
infix operator ? // expected-error {{cannot declare a custom infix '?' operator}}
195+
prefix operator ? // expected-error {{cannot declare a custom prefix '?' operator}}
192196

193197
infix operator ??=
194198

0 commit comments

Comments
 (0)