Skip to content

Commit 90f7724

Browse files
committed
Fix parsing of @_unavailableFromAsync
There’s a very easy to reach `llvm_unreachable()` in this code which ought to be a diagnostic, as well as a couple of other issues. Rework it into something that’s a bit better at handling the edge cases.
1 parent 31b6aee commit 90f7724

File tree

5 files changed

+106
-31
lines changed

5 files changed

+106
-31
lines changed

include/swift/AST/KnownIdentifiers.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ IDENTIFIER(AsyncIterator)
124124
IDENTIFIER(load)
125125
IDENTIFIER(main)
126126
IDENTIFIER_WITH_NAME(MainEntryPoint, "$main")
127+
IDENTIFIER(message)
127128
IDENTIFIER(next)
128129
IDENTIFIER_(nsErrorDomain)
129130
IDENTIFIER(objectAtIndexedSubscript)

include/swift/Parse/Parser.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1782,7 +1782,10 @@ class Parser {
17821782
/// \param name The parsed name of the label (empty if it doesn't exist, or is
17831783
/// _)
17841784
/// \param loc The location of the label (empty if it doesn't exist)
1785-
void parseOptionalArgumentLabel(Identifier &name, SourceLoc &loc);
1785+
/// \param isAttr True if this is an argument label for an attribute (allows, but deprecates, use of
1786+
/// \c '=' instead of \c ':').
1787+
void parseOptionalArgumentLabel(Identifier &name, SourceLoc &loc,
1788+
bool isAttr = false);
17861789

17871790
enum class DeclNameFlag : uint8_t {
17881791
/// If passed, operator basenames are allowed.

lib/Parse/ParseDecl.cpp

Lines changed: 46 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3780,35 +3780,60 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes,
37803780
case DeclAttrKind::UnavailableFromAsync: {
37813781
StringRef message;
37823782
if (consumeIfAttributeLParen()) {
3783-
if (!Tok.is(tok::identifier)) {
3784-
llvm_unreachable("Flag must start with an identifier");
3785-
}
3786-
3787-
StringRef flag = Tok.getText();
3783+
auto tokMayBeArgument = [&]() -> bool {
3784+
return Tok.isNot(tok::r_paren, tok::comma) &&
3785+
!isKeywordPossibleDeclStart(Context.LangOpts, Tok);
3786+
};
37883787

3789-
if (flag != "message") {
3790-
diagnose(Tok.getLoc(), diag::attr_unknown_option, flag, AttrName);
3791-
return makeParserError();
3792-
}
3793-
consumeToken();
3794-
if (!consumeIf(tok::colon)) {
3795-
if (!Tok.is(tok::equal)) {
3796-
diagnose(Tok.getLoc(), diag::attr_expected_colon_after_label, flag);
3797-
return makeParserSuccess();
3788+
Identifier label;
3789+
SourceLoc labelLoc;
3790+
parseOptionalArgumentLabel(label, labelLoc, /*isAttr=*/true);
3791+
3792+
if (label.empty()) {
3793+
// If we have the identifier 'message', assume the user forgot the
3794+
// colon.
3795+
if (Tok.isContextualKeyword("message")) {
3796+
labelLoc = consumeToken();
3797+
auto diag = diagnose(Tok, diag::attr_expected_colon_after_label,
3798+
"message");
3799+
if (Tok.is(tok::string_literal))
3800+
diag.fixItInsertAfter(labelLoc, ":");
3801+
else
3802+
return makeParserError();
37983803
}
3799-
diagnose(Tok.getLoc(), diag::replace_equal_with_colon_for_value)
3800-
.fixItReplace(Tok.getLoc(), ": ");
3801-
consumeToken();
3804+
// If the argument list just abruptly cuts off, handle that as a
3805+
// missing argument (below). Otherwise, diagnose the missing label.
3806+
else if (tokMayBeArgument()) {
3807+
if (labelLoc.isValid())
3808+
// The user wrote an explicitly omitted label (`_:`).
3809+
diagnose(labelLoc, diag::attr_expected_label, "message", AttrName)
3810+
.fixItReplace(labelLoc, "message");
3811+
else
3812+
diagnose(Tok, diag::attr_expected_label, "message", AttrName)
3813+
.fixItInsert(Tok.getLoc(), "message: ");
3814+
}
3815+
// Fall through to parse the argument.
3816+
} else if (label != Context.Id_message) {
3817+
diagnose(labelLoc, diag::attr_unknown_option, label.str(), AttrName)
3818+
.fixItReplace(labelLoc, "message");
3819+
return makeParserError();
38023820
}
3821+
38033822
if (!Tok.is(tok::string_literal)) {
3804-
diagnose(Tok.getLoc(), diag::attr_expected_string_literal, AttrName);
3805-
return makeParserSuccess();
3823+
// If this token looks like an argument, replace it; otherwise insert
3824+
// before it.
3825+
auto endLoc = tokMayBeArgument() ? peekToken().getLoc() : Tok.getLoc();
3826+
3827+
diagnose(Tok, diag::attr_expected_string_literal, AttrName)
3828+
.fixItReplaceChars(Tok.getLoc(), endLoc, "\"<#error message#>\"");
3829+
3830+
return makeParserError();
38063831
}
38073832

38083833
std::optional<StringRef> value =
3809-
getStringLiteralIfNotInterpolated(Tok.getLoc(), flag);
3834+
getStringLiteralIfNotInterpolated(Tok.getLoc(), "message");
38103835
if (!value)
3811-
return makeParserSuccess();
3836+
return makeParserError();
38123837
Token stringTok = Tok;
38133838
consumeToken();
38143839
message = *value;

lib/Parse/ParseExpr.cpp

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2129,9 +2129,14 @@ ParserResult<Expr> Parser::parseExprStringLiteral() {
21292129
AppendingExpr));
21302130
}
21312131

2132-
void Parser::parseOptionalArgumentLabel(Identifier &name, SourceLoc &loc) {
2132+
void Parser::parseOptionalArgumentLabel(Identifier &name, SourceLoc &loc,
2133+
bool isAttr) {
2134+
/// A token that has the same meaning as colon, but is deprecated, if one exists for this call.
2135+
auto altColon = isAttr ? tok::equal : tok::NUM_TOKENS;
2136+
21332137
// Check to see if there is an argument label.
2134-
if (Tok.canBeArgumentLabel() && peekToken().is(tok::colon)) {
2138+
if (Tok.canBeArgumentLabel() && peekToken().isAny(tok::colon, altColon)) {
2139+
// Label found, including colon.
21352140
auto text = Tok.getText();
21362141

21372142
// If this was an escaped identifier that need not have been escaped, say
@@ -2149,11 +2154,23 @@ void Parser::parseOptionalArgumentLabel(Identifier &name, SourceLoc &loc) {
21492154
}
21502155

21512156
loc = consumeArgumentLabel(name, /*diagnoseDollarPrefix=*/false);
2152-
consumeToken(tok::colon);
2153-
} else if (Tok.is(tok::colon)) {
2154-
diagnose(Tok, diag::expected_label_before_colon);
2155-
consumeToken(tok::colon);
2157+
} else if (Tok.isAny(tok::colon, altColon)) {
2158+
// Found only the colon.
2159+
diagnose(Tok, diag::expected_label_before_colon)
2160+
.fixItInsert(Tok.getLoc(), "<#label#>");
2161+
} else {
2162+
// No label here.
2163+
return;
21562164
}
2165+
2166+
// If we get here, we ought to be on the colon.
2167+
assert(Tok.isAny(tok::colon, altColon));
2168+
2169+
if (Tok.is(altColon))
2170+
diagnose(Tok, diag::replace_equal_with_colon_for_value)
2171+
.fixItReplace(Tok.getLoc(), ": ");
2172+
2173+
consumeToken();
21572174
}
21582175

21592176
static bool tryParseArgLabelList(Parser &P, Parser::DeclNameOptions flags,

test/Concurrency/unavailable_from_async.swift

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,25 +140,54 @@ func asyncFunc() async { // expected-error{{asynchronous global function 'asyncF
140140

141141
// Parsing tests
142142

143+
// expected-error@+1 {{expected label 'message:' in '@_unavailableFromAsync' attribute}} {{24-24=message: }}
144+
@_unavailableFromAsync("almost right, but not quite")
145+
func blarp0() {}
146+
147+
// expected-error@+1 {{expected label 'message:' in '@_unavailableFromAsync' attribute}} {{24-25=message}}
148+
@_unavailableFromAsync(_: "almost right, but not quite")
149+
func blarp0a() {}
150+
143151
// expected-error@+2 {{expected declaration}}
144-
// expected-error@+1:24{{unknown option 'nope' for attribute '_unavailableFromAsync'}}
152+
// expected-error@+1:24{{unknown option 'nope' for attribute '_unavailableFromAsync'}} {{24-28=message}}
145153
@_unavailableFromAsync(nope: "almost right, but not quite")
146154
func blarp1() {}
147155

148156
// expected-error@+2 {{expected declaration}}
149-
// expected-error@+1 {{expected ':' after label 'message'}}
157+
// expected-error@+1 {{expected ':' after label 'message'}} {{none}}
150158
@_unavailableFromAsync(message; "almost right, but not quite")
151159
func blarp2() {}
152160

161+
// expected-error@+1 {{expected ':' after label 'message'}} {{31-31=:}}
162+
@_unavailableFromAsync(message "almost right, but not quite")
163+
func blarp2a() {}
164+
153165
// expected-error@+1:31 {{'=' has been replaced with ':' in attribute arguments}}{{31-32=: }}
154166
@_unavailableFromAsync(message="almost right, but not quite")
155167
func blarp3() {}
156168

157169
// expected-error@+2 {{expected declaration}}
158-
// expected-error@+1 {{expected string literal in '_unavailableFromAsync' attribute}}
170+
// expected-error@+1 {{expected string literal in '_unavailableFromAsync' attribute}} {{33-35="<#error message#>"}}
159171
@_unavailableFromAsync(message: 32)
160172
func blarp4() {}
161173

174+
// expected-error@+2 {{expected declaration}}
175+
// expected-error@+1:24{{unknown option 'fnord' for attribute '_unavailableFromAsync'}} {{24-29=message}}
176+
@_unavailableFromAsync(fnord: 32)
177+
func blarp4a() {}
178+
179+
// expected-error@+3 {{expected declaration}}
180+
// expected-error@+2 {{expected label 'message:' in '@_unavailableFromAsync' attribute}} {{24-25=message}}
181+
// expected-error@+1 {{expected string literal in '_unavailableFromAsync' attribute}} {{27-29="<#error message#>"}}
182+
@_unavailableFromAsync(_: 32)
183+
func blarp4b() {}
184+
185+
// expected-error@+3 {{expected declaration}}
186+
// expected-error@+2 {{expected string literal in '_unavailableFromAsync' attribute}} {{24-26="<#error message#>"}}
187+
// expected-error@+1 {{expected label 'message:' in '@_unavailableFromAsync' attribute}} {{24-24=message: }}
188+
@_unavailableFromAsync(32)
189+
func blarp4c() {}
190+
162191
// expected-error@+2 {{expected declaration}}
163192
// expected-error@+1 {{message cannot be an interpolated string}}
164193
@_unavailableFromAsync(message: "blarppy blarp \(31 + 10)")

0 commit comments

Comments
 (0)