[Parse] Ignore '(' on newline after attribute names

Also '#error', '#warning', and '#sourceLocation'.

Other call-like syntax (call expression, macro expansion, and custom
attribtues) requires '(' on the same line as the callee. For consistency,
built-in attributes and built-in directives should also ignore '(' on
next line.
This commit is contained in:
Rintaro Ishizaki
2025-08-01 16:00:38 -07:00
parent db8b8480e9
commit a8fba10da1
5 changed files with 38 additions and 15 deletions

View File

@@ -62,7 +62,7 @@ ERROR(pound_diagnostic_expected_string,none,
"expected string literal in %select{#warning|#error}0 directive",(bool))
ERROR(pound_diagnostic_expected,none,
"expected '%0' in %select{#warning|#error}1 directive",(StringRef,bool))
ERROR(pound_diagnostic_expected_parens,none,
ERROR(pound_diagnostic_expected_parens,PointsToFirstBadToken,
"%select{#warning|#error}0 directive requires parentheses",(bool))
ERROR(pound_diagnostic_interpolation,none,
"string interpolation is not allowed in %select{#warning|#error}0 directives",(bool))

View File

@@ -2503,7 +2503,7 @@ static std::optional<Identifier> parseSingleAttrOptionImpl(
};
bool isDeclModifier = DeclAttribute::isDeclModifier(DK);
if (!P.Tok.is(tok::l_paren)) {
if (!P.Tok.isFollowingLParen()) {
if (allowOmitted)
return Identifier();
@@ -2883,7 +2883,7 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes,
.Case("public", AccessLevel::Public)
.Case("open", AccessLevel::Open);
if (!Tok.is(tok::l_paren)) {
if (!Tok.isFollowingLParen()) {
// Normal access control attribute.
AttrRange = Loc;
@@ -3458,7 +3458,7 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes,
}
case DeclAttrKind::PrivateImport: {
// Parse the leading '('.
if (Tok.isNot(tok::l_paren)) {
if (!Tok.isFollowingLParen()) {
diagnose(Loc, diag::attr_expected_lparen, AttrName,
DeclAttribute::isDeclModifier(DK));
return makeParserSuccess();
@@ -3507,7 +3507,7 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes,
}
case DeclAttrKind::ObjC: {
// Unnamed @objc attribute.
if (Tok.isNot(tok::l_paren)) {
if (!Tok.isFollowingLParen()) {
auto attr = ObjCAttr::createUnnamed(Context, AtLoc, Loc);
Attributes.add(attr);
break;
@@ -3575,7 +3575,7 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes,
case DeclAttrKind::DynamicReplacement: {
// Parse the leading '('.
if (Tok.isNot(tok::l_paren)) {
if (!Tok.isFollowingLParen()) {
diagnose(Loc, diag::attr_expected_lparen, AttrName,
DeclAttribute::isDeclModifier(DK));
return makeParserSuccess();
@@ -3626,7 +3626,7 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes,
case DeclAttrKind::TypeEraser: {
// Parse leading '('
if (Tok.isNot(tok::l_paren)) {
if (!Tok.isFollowingLParen()) {
diagnose(Loc, diag::attr_expected_lparen, AttrName,
DeclAttribute::isDeclModifier(DK));
return makeParserSuccess();
@@ -3656,7 +3656,7 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes,
case DeclAttrKind::Specialize:
case DeclAttrKind::Specialized: {
if (Tok.isNot(tok::l_paren)) {
if (!Tok.isFollowingLParen()) {
diagnose(Loc, diag::attr_expected_lparen, AttrName,
DeclAttribute::isDeclModifier(DK));
return makeParserSuccess();
@@ -3885,7 +3885,7 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes,
break;
}
case DeclAttrKind::RawLayout: {
if (Tok.isNot(tok::l_paren)) {
if (!Tok.isFollowingLParen()) {
diagnose(Loc, diag::attr_expected_lparen, AttrName,
DeclAttribute::isDeclModifier(DK));
return makeParserSuccess();
@@ -4187,7 +4187,7 @@ ParserResult<CustomAttr> Parser::parseCustomAttribute(SourceLoc atLoc) {
// Parse a custom attribute.
auto type = parseType(diag::expected_type, ParseTypeReason::CustomAttribute);
if (type.hasCodeCompletion() || type.isNull()) {
if (Tok.is(tok::l_paren) && isCustomAttributeArgument())
if (Tok.isFollowingLParen() && isCustomAttributeArgument())
skipSingle();
return ParserResult<CustomAttr>(ParserStatus(type));
@@ -4369,7 +4369,7 @@ ParserStatus Parser::parseDeclAttribute(DeclAttributes &Attributes,
SourceLoc attrLoc = consumeToken();
// @warn_unused_result with no arguments.
if (Tok.isNot(tok::l_paren)) {
if (!Tok.isFollowingLParen()) {
diagnose(AtLoc, diag::attr_warn_unused_result_removed)
.fixItRemove(SourceRange(AtLoc, attrLoc));
@@ -4453,7 +4453,7 @@ ParserStatus Parser::parseDeclAttribute(DeclAttributes &Attributes,
// Recover by eating @foo(...) when foo is not known.
consumeToken();
if (Tok.is(tok::l_paren))
if (Tok.isFollowingLParen())
skipSingle();
return makeParserError();
@@ -5925,7 +5925,7 @@ bool Parser::isStartOfSwiftDecl(bool allowPoundIfAttributes,
// If it might be, we do some more digging.
// If this is 'unowned', check to see if it is valid.
if (Tok.getText() == "unowned" && Tok2.is(tok::l_paren)) {
if (Tok.getText() == "unowned" && Tok2.isFollowingLParen()) {
Parser::BacktrackingScope Backtrack(*this);
if (consumeIfParenthesizedUnowned(*this)) {
return isStartOfSwiftDecl(/*allowPoundIfAttributes=*/false,
@@ -5934,7 +5934,7 @@ bool Parser::isStartOfSwiftDecl(bool allowPoundIfAttributes,
}
// If this is 'nonisolated', check to see if it is valid.
if (Tok.isContextualKeyword("nonisolated") && Tok2.is(tok::l_paren)) {
if (Tok.isContextualKeyword("nonisolated") && Tok2.isFollowingLParen()) {
BacktrackingScope backtrack(*this);
if (consumeIfParenthesizedNonisolated(*this)) {
return isStartOfSwiftDecl(/*allowPoundIfAttributes=*/false,
@@ -7277,6 +7277,12 @@ ParserStatus Parser::parseDeclPoundDiagnostic() {
bool isError = Tok.is(tok::pound_error);
consumeToken(isError ? tok::pound_error : tok::pound_warning);
if (Tok.isAtStartOfLine()) {
diagnose(Tok, diag::pound_diagnostic_expected_parens, isError)
.fixItInsertAfter(PreviousLoc, "(\"<#message#>\")");
return makeParserSuccess();
}
SourceLoc lParenLoc = Tok.getLoc();
bool hadLParen = consumeIf(tok::l_paren);
@@ -7373,7 +7379,7 @@ ParserStatus Parser::parseLineDirective(bool isLine) {
unsigned StartLine = 0;
std::optional<StringRef> Filename;
if (!isLine) {
if (!isLine && !Tok.isAtStartOfLine()) {
// #sourceLocation()
// #sourceLocation(file: "foo", line: 42)
if (parseToken(tok::l_paren, diag::sourceLocation_expected, "("))

View File

@@ -18,6 +18,10 @@ struct MyPropertyWrapper {
struct PropertyWrapperTest {
@MyPropertyWrapper (param: 2) // expected-warning {{extraneous whitespace between attribute name and '('; this is an error in the Swift 6 language mode}}
var x: Int
@MyPropertyWrapper
(param: 2) // expected-error {{expected 'var' keyword in property declaration}} expected-error {{property declaration does not bind any variables}} expected-error {{expected pattern}}
var y: Int
}
let closure1 = { @MainActor (a, b) in // expected-warning {{extraneous whitespace between attribute name and '('; this is an error in the Swift 6 language mode}}
@@ -31,3 +35,10 @@ let closure2 = { @MainActor
@
MainActor
func mainActorFunc() {}
@inline // expected-error {{expected '(' in 'inline' attribute}}
(never) func neverInline() {} // expected-error {{expected declaration}}
@objc
(whatever) func whateverObjC() {} // expected-error {{expected declaration}}

View File

@@ -84,3 +84,6 @@ class I55049 {
// expected-error@+1 {{#line directive was renamed to #sourceLocation}}
#line 1_000 "issue-55049.swift"
class I55049_1 {}
#sourceLocation
(file: "nextLine.swift", line: 400) // expected-warning {{expression of type '(file: String, line: Int)' is unused}}

View File

@@ -82,3 +82,6 @@ protocol MyProtocol {
""") // expected-warning @-2 {{warnings support multi-line string literals}}
#warning(#"warnings support \(custom string delimiters)"#) // expected-warning {{warnings support \\(custom string delimiters)}}
#warning // expected-error {{#warning directive requires parentheses}} {{9-9=("<#message#>")}}
("message") // expected-warning {{string literal is unused}}