[Parse] Fix isEditorPlaceholder checks in the parser

Factor out `Token::isEditorPlaceholder` and make sure we're checking
the token's raw text, ensuring we don't ignore backticks.
This commit is contained in:
Hamish Knight
2025-06-24 21:47:21 +01:00
parent 85a268696e
commit a3eed77738
4 changed files with 23 additions and 7 deletions

View File

@@ -266,6 +266,9 @@ public:
}
}
/// True if the token is an editor placeholder.
bool isEditorPlaceholder() const;
/// True if the string literal token is multiline.
bool isMultilineString() const {
return MultilineString;

View File

@@ -35,6 +35,10 @@
using namespace swift;
bool Token::isEditorPlaceholder() const {
return is(tok::identifier) && Identifier::isEditorPlaceholder(getRawText());
}
/// parseExpr
///
/// expr:
@@ -2206,7 +2210,7 @@ static bool tryParseArgLabelList(Parser &P, Parser::DeclNameOptions flags,
// An argument label.
bool nextIsArgLabel = next.canBeArgumentLabel() || next.is(tok::colon);
// An editor placeholder.
bool nextIsPlaceholder = Identifier::isEditorPlaceholder(next.getText());
bool nextIsPlaceholder = next.isEditorPlaceholder();
if (!(nextIsRParen || nextIsArgLabel || nextIsPlaceholder))
return false;
@@ -2402,7 +2406,7 @@ ParserResult<Expr> Parser::parseExprIdentifier(bool allowKeyword) {
hasGenericArgumentList = true;
}
if (name.getBaseName().isEditorPlaceholder()) {
if (IdentTok.isEditorPlaceholder()) {
return makeParserResult(
status, parseExprEditorPlaceholder(IdentTok, name.getBaseIdentifier()));
}
@@ -3301,8 +3305,7 @@ static bool isStartOfLabelledTrailingClosure(Parser &P) {
return true;
// Parse editor placeholder as trailing closure so SourceKit can expand it to
// closure literal.
if (P.peekToken().is(tok::identifier) &&
Identifier::isEditorPlaceholder(P.peekToken().getText()))
if (P.peekToken().isEditorPlaceholder())
return true;
// Consider `label: <complete>` that the user is trying to write a closure.
if (P.peekToken().is(tok::code_complete) && !P.peekToken().isAtStartOfLine())
@@ -3360,8 +3363,8 @@ Parser::parseTrailingClosures(bool isExprBasic, SourceRange calleeRange,
closure = parseExprClosure();
} else if (Tok.is(tok::identifier)) {
// Parse editor placeholder as a closure literal.
assert(Identifier::isEditorPlaceholder(Tok.getText()));
Identifier name = Context.getIdentifier(Tok.getText());
assert(Tok.isEditorPlaceholder());
Identifier name = Context.getIdentifier(Tok.getRawText());
closure = makeParserResult(parseExprEditorPlaceholder(Tok, name));
consumeToken(tok::identifier);
} else if (Tok.is(tok::code_complete)) {

View File

@@ -21,6 +21,10 @@ func testRegexLiteral() {
func testEditorPlaceholder() -> Int {
func foo(_ x: String) {}
foo(<#T##x: String##String#>) // expected-error {{editor placeholder in source file}})
// Make sure we don't try to parse this as an editor placeholder.
_ = `<#foo#>` // expected-error {{cannot find '<#foo#>' in scope}}
return <#T##Int#> // expected-error {{editor placeholder in source file}}
}
@@ -79,4 +83,4 @@ using @Test
// expected-error@-1 {{default isolation can only be set to '@MainActor' or 'nonisolated'}}
using test
// expected-error@-1 {{default isolation can only be set to '@MainActor' or 'nonisolated'}}
// expected-error@-1 {{default isolation can only be set to '@MainActor' or 'nonisolated'}}

View File

@@ -27,6 +27,12 @@ f(<#T#> + 1) // expected-error{{editor placeholder in source file}}
f(<#T##Int#>) // expected-error{{editor placeholder in source file}}
f(<#T##String#>) // expected-error{{editor placeholder in source file}} expected-error{{cannot convert value of type 'String' to expected argument type 'Int'}}
<#foo#>(x:)<Int>; // expected-error {{editor placeholder in source file}}
// These are actually raw identifiers
`<#foo#>` // expected-error {{cannot find '<#foo#>' in scope}}
`<#foo#>`(x:)<Int>; // expected-error {{cannot find '<#foo#>(x:)' in scope}}
for x in <#T#> { // expected-error{{editor placeholder in source file}} expected-error{{for-in loop requires '()' to conform to 'Sequence'}}
}