From cc5e79b5212e3dfa27e3484d816dfbc66e7cd53d Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Mon, 29 Jan 2024 16:54:54 -0800 Subject: [PATCH 1/2] [Parse] Disallow space between `@` and attribute name in Swift 6 mode --- include/swift/AST/DiagnosticsParse.def | 4 +++ include/swift/Parse/Parser.h | 2 ++ lib/ClangImporter/ImportDecl.cpp | 13 ++++++---- lib/Parse/ParseDecl.cpp | 34 ++++++++++++++++++-------- test/Parse/attribute_spacing.swift | 6 +++++ 5 files changed, 44 insertions(+), 15 deletions(-) create mode 100644 test/Parse/attribute_spacing.swift diff --git a/include/swift/AST/DiagnosticsParse.def b/include/swift/AST/DiagnosticsParse.def index bf43b9b9d673a..da93ad193fae1 100644 --- a/include/swift/AST/DiagnosticsParse.def +++ b/include/swift/AST/DiagnosticsParse.def @@ -1510,6 +1510,10 @@ ERROR(type_attribute_applied_to_decl,none, ERROR(decl_attribute_applied_to_type,none, "attribute can only be applied to declarations, not types", ()) + +ERROR(attr_extra_whitespace_after_at,PointsToFirstBadToken, + "extraneous whitespace between '@' and attribute name", ()) + ERROR(attr_expected_lparen,none, "expected '(' in '%0' %select{attribute|modifier}1", (StringRef, bool)) diff --git a/include/swift/Parse/Parser.h b/include/swift/Parse/Parser.h index ae99092cbdf3e..19afa8c0d9216 100644 --- a/include/swift/Parse/Parser.h +++ b/include/swift/Parse/Parser.h @@ -1147,6 +1147,7 @@ class Parser { /// Parse a specific attribute. ParserStatus parseDeclAttribute(DeclAttributes &Attributes, SourceLoc AtLoc, + SourceLoc AtEndLoc, PatternBindingInitializer *&initContext, bool isFromClangAttribute = false); @@ -1256,6 +1257,7 @@ class Parser { bool justChecking); ParserStatus parseTypeAttribute(TypeOrCustomAttr &result, SourceLoc AtLoc, + SourceLoc AtEndLoc, PatternBindingInitializer *&initContext, bool justChecking = false); diff --git a/lib/ClangImporter/ImportDecl.cpp b/lib/ClangImporter/ImportDecl.cpp index 946f47869be1f..1dbf883c4e42d 100644 --- a/lib/ClangImporter/ImportDecl.cpp +++ b/lib/ClangImporter/ImportDecl.cpp @@ -8055,11 +8055,14 @@ ClangImporter::Implementation::importSwiftAttrAttributes(Decl *MappedDecl) { parser.consumeTokenWithoutFeedingReceiver(); bool hadError = false; - SourceLoc atLoc; - if (parser.consumeIf(tok::at_sign, atLoc)) { - hadError = parser.parseDeclAttribute( - MappedDecl->getAttrs(), atLoc, initContext, - /*isFromClangAttribute=*/true).isError(); + if (parser.Tok.is(tok::at_sign)) { + SourceLoc atEndLoc = parser.Tok.getRange().getEnd(); + SourceLoc atLoc = parser.consumeToken(tok::at_sign); + hadError = parser + .parseDeclAttribute(MappedDecl->getAttrs(), atLoc, + atEndLoc, initContext, + /*isFromClangAttribute=*/true) + .isError(); } else { SourceLoc staticLoc; StaticSpellingKind staticSpelling; diff --git a/lib/Parse/ParseDecl.cpp b/lib/Parse/ParseDecl.cpp index af0c007808188..83c9035cd01f4 100644 --- a/lib/Parse/ParseDecl.cpp +++ b/lib/Parse/ParseDecl.cpp @@ -4202,10 +4202,15 @@ ParserResult Parser::parseCustomAttribute( /// Note that various attributes (like mutating, weak, and unowned) are parsed /// but rejected since they have context-sensitive keywords. /// -ParserStatus Parser::parseDeclAttribute( - DeclAttributes &Attributes, SourceLoc AtLoc, - PatternBindingInitializer *&initContext, - bool isFromClangAttribute) { +ParserStatus Parser::parseDeclAttribute(DeclAttributes &Attributes, + SourceLoc AtLoc, SourceLoc AtEndLoc, + PatternBindingInitializer *&initContext, + bool isFromClangAttribute) { + if (AtEndLoc != Tok.getLoc()) { + diagnose(AtEndLoc, diag::attr_extra_whitespace_after_at) + .warnUntilSwiftVersion(6); + } + // If this not an identifier, the attribute is malformed. if (Tok.isNot(tok::identifier) && Tok.isNot(tok::kw_in) && @@ -4404,8 +4409,10 @@ ParserStatus Parser::parseDeclAttribute( bool Parser::canParseTypeAttribute() { TypeOrCustomAttr result; // ignored PatternBindingInitializer *initContext = nullptr; - return !parseTypeAttribute(result, /*atLoc=*/SourceLoc(), initContext, - /*justChecking*/ true).isError(); + return !parseTypeAttribute(result, /*atLoc=*/SourceLoc(), + /*atEndLoc=*/SourceLoc(), initContext, + /*justChecking*/ true) + .isError(); } /// Parses the '@differentiable' type attribute argument (no argument list, @@ -4602,9 +4609,14 @@ bool Parser::parseUUIDString(UUID &uuid, Diag<> diagnostic, bool justChecking) { /// canParseTypeAttribute; don't emit any diagnostics, and there's /// no need to actually record the attribute ParserStatus Parser::parseTypeAttribute(TypeOrCustomAttr &result, - SourceLoc AtLoc, + SourceLoc AtLoc, SourceLoc AtEndLoc, PatternBindingInitializer *&initContext, bool justChecking) { + if (AtEndLoc != Tok.getLoc()) { + diagnose(AtEndLoc, diag::attr_extra_whitespace_after_at) + .warnUntilSwiftVersion(6); + } + // If this not an identifier, the attribute is malformed. if (Tok.isNot(tok::identifier) && // These are keywords that we accept as attribute names. @@ -5038,8 +5050,9 @@ ParserStatus Parser::parseDeclAttributeList( ParserStatus Status; while (Tok.isAny(tok::at_sign, tok::pound_if)) { if (Tok.is(tok::at_sign)) { - SourceLoc AtLoc = consumeToken(); - Status |= parseDeclAttribute(Attributes, AtLoc, initContext); + SourceLoc AtEndLoc = Tok.getRange().getEnd(); + SourceLoc AtLoc = consumeToken(); + Status |= parseDeclAttribute(Attributes, AtLoc, AtEndLoc, initContext); } else { if (!ifConfigsAreDeclAttrs && !ifConfigContainsOnlyAttributes()) break; @@ -5330,8 +5343,9 @@ ParserStatus Parser::ParsedTypeAttributeList::slowParse(Parser &P) { return status; TypeOrCustomAttr result; + SourceLoc AtEndLoc = Tok.getRange().getEnd(); SourceLoc AtLoc = P.consumeToken(); - status |= P.parseTypeAttribute(result, AtLoc, initContext); + status |= P.parseTypeAttribute(result, AtLoc, AtEndLoc, initContext); if (status.isError()) return status; if (result) diff --git a/test/Parse/attribute_spacing.swift b/test/Parse/attribute_spacing.swift new file mode 100644 index 0000000000000..45c0a0dd9bd06 --- /dev/null +++ b/test/Parse/attribute_spacing.swift @@ -0,0 +1,6 @@ +// RUN: %target-typecheck-verify-swift -swift-version 6 + +@ MainActor // expected-error {{extraneous whitespace between '@' and attribute name}} +class Foo { + func foo(_ x: @ escaping () -> Int) {} // expected-error {{extraneous whitespace between '@' and attribute name}} +} From c6e425a55938dba840416e2ef1ae1491fc048082 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Mon, 29 Jan 2024 22:56:25 -0800 Subject: [PATCH 2/2] [Parse] Disallow space between attribute name and '(' in Swift 6 mode This allows us to resolve disambiguities of whether a parenthesis belong to an argument to the attribute or if it is eg. the start of a tuple. --- include/swift/AST/DiagnosticsParse.def | 3 + include/swift/Parse/Parser.h | 12 ++++ lib/Parse/ParseDecl.cpp | 86 +++++++++++++++----------- lib/Parse/Parser.cpp | 17 +++++ test/Parse/attribute_spacing.swift | 28 ++++++++- test/attr/attributes.swift | 4 +- 6 files changed, 108 insertions(+), 42 deletions(-) diff --git a/include/swift/AST/DiagnosticsParse.def b/include/swift/AST/DiagnosticsParse.def index da93ad193fae1..3130d0293f21b 100644 --- a/include/swift/AST/DiagnosticsParse.def +++ b/include/swift/AST/DiagnosticsParse.def @@ -1514,6 +1514,9 @@ ERROR(decl_attribute_applied_to_type,none, ERROR(attr_extra_whitespace_after_at,PointsToFirstBadToken, "extraneous whitespace between '@' and attribute name", ()) +ERROR(attr_extra_whitespace_before_lparen,PointsToFirstBadToken, + "extraneous whitespace between attribute name and '('", ()) + ERROR(attr_expected_lparen,none, "expected '(' in '%0' %select{attribute|modifier}1", (StringRef, bool)) diff --git a/include/swift/Parse/Parser.h b/include/swift/Parse/Parser.h index 19afa8c0d9216..14a37a2ca0689 100644 --- a/include/swift/Parse/Parser.h +++ b/include/swift/Parse/Parser.h @@ -602,6 +602,18 @@ class Parser { return true; } + /// Consume a '('. If it is not directly following the previous token, emit an + /// error (Swift 6) or warning (Swift <6) that attribute name and parentheses + /// must not be separated by a space. + SourceLoc consumeAttributeLParen(); + + /// If the next token is a '(' that's not on a new line consume it, and error + /// (Swift 6) or warn (Swift <6) that the attribute must not be separted from + /// the '(' by a space. + /// + /// If the next token is not '(' or it's on a new line, return false. + bool consumeIfAttributeLParen(); + bool consumeIfNotAtStartOfLine(tok K) { if (Tok.isAtStartOfLine()) return false; return consumeIf(K); diff --git a/lib/Parse/ParseDecl.cpp b/lib/Parse/ParseDecl.cpp index 83c9035cd01f4..2d5ff3815b879 100644 --- a/lib/Parse/ParseDecl.cpp +++ b/lib/Parse/ParseDecl.cpp @@ -1093,7 +1093,14 @@ bool Parser::parseSpecializeAttribute( llvm::function_ref parseSILSIPModule) { assert(ClosingBrace == tok::r_paren || ClosingBrace == tok::r_square); - SourceLoc lParenLoc = consumeToken(); + SourceLoc lParenLoc; + if (Tok.is(tok::l_paren)) { + lParenLoc = consumeAttributeLParen(); + } else { + // SIL parsing is positioned at _specialize when entering this and parses + // the location of the _specialize keyword as the lParenLoc. + lParenLoc = consumeToken(); + } bool DiscardAttribute = false; StringRef AttrName = "_specialize"; @@ -1157,7 +1164,7 @@ Parser::parseStorageRestrictionsAttribute(SourceLoc AtLoc, SourceLoc Loc) { } // Consume '(' - SourceLoc lParenLoc = consumeToken(); + SourceLoc lParenLoc = consumeAttributeLParen(); SmallVector initializesProperties; SmallVector accessesProperties; @@ -1319,7 +1326,7 @@ Parser::parseImplementsAttribute(SourceLoc AtLoc, SourceLoc Loc) { return Status; } - SourceLoc lParenLoc = consumeToken(); + SourceLoc lParenLoc = consumeAttributeLParen(); DeclNameLoc MemberNameLoc; DeclNameRef MemberName; @@ -1379,13 +1386,13 @@ Parser::parseImplementsAttribute(SourceLoc AtLoc, SourceLoc Loc) { ParserResult Parser::parseDifferentiableAttribute(SourceLoc atLoc, SourceLoc loc) { StringRef AttrName = "differentiable"; - SourceLoc lParenLoc = loc, rParenLoc = loc; + SourceLoc rParenLoc = loc; DifferentiabilityKind diffKind = DifferentiabilityKind::Normal; SmallVector parameters; TrailingWhereClause *whereClause = nullptr; // Parse '('. - if (consumeIf(tok::l_paren, lParenLoc)) { + if (consumeIfAttributeLParen()) { // Parse @differentiable attribute arguments. if (parseDifferentiableAttributeArguments( diffKind, parameters, whereClause)) @@ -1415,7 +1422,7 @@ bool Parser::parseExternAttribute(DeclAttributes &Attributes, SourceLoc lParenLoc = Tok.getLoc(), rParenLoc; // Parse @_extern(, ...) - if (!consumeIf(tok::l_paren)) { + if (!consumeIfAttributeLParen()) { diagnose(Loc, diag::attr_expected_lparen, AttrName, DeclAttribute::isDeclModifier(DAK_Extern)); return false; @@ -1857,7 +1864,7 @@ static bool parseQualifiedDeclName(Parser &P, Diag<> nameParseError, ParserResult Parser::parseDerivativeAttribute(SourceLoc atLoc, SourceLoc loc) { StringRef AttrName = "derivative"; - SourceLoc lParenLoc = loc, rParenLoc = loc; + SourceLoc rParenLoc = loc; TypeRepr *baseType = nullptr; DeclNameRefWithLoc original; SmallVector parameters; @@ -1885,7 +1892,7 @@ ParserResult Parser::parseDerivativeAttribute(SourceLoc atLoc, return errorAndSkipUntilConsumeRightParen(*this, AttrName); }; // Parse '('. - if (!consumeIf(tok::l_paren, lParenLoc)) { + if (!consumeIfAttributeLParen()) { diagnose(getEndOfPreviousLoc(), diag::attr_expected_lparen, AttrName, /*DeclModifier*/ false); return makeParserError(); @@ -1937,7 +1944,7 @@ ParserResult Parser::parseDerivativeAttribute(SourceLoc atLoc, ParserResult Parser::parseTransposeAttribute(SourceLoc atLoc, SourceLoc loc) { StringRef AttrName = "transpose"; - SourceLoc lParenLoc = loc, rParenLoc = loc; + SourceLoc rParenLoc = loc; TypeRepr *baseType = nullptr; DeclNameRefWithLoc original; SmallVector parameters; @@ -1966,7 +1973,7 @@ ParserResult Parser::parseTransposeAttribute(SourceLoc atLoc, }; // Parse '('. - if (!consumeIf(tok::l_paren, lParenLoc)) { + if (!consumeIfAttributeLParen()) { diagnose(getEndOfPreviousLoc(), diag::attr_expected_lparen, AttrName, /*DeclModifier*/ false); return makeParserError(); @@ -2270,7 +2277,7 @@ bool Parser::parseBackDeployedAttribute(DeclAttributes &Attributes, SourceLoc Loc) { std::string AtAttrName = (llvm::Twine("@") + AttrName).str(); auto LeftLoc = Tok.getLoc(); - if (!consumeIf(tok::l_paren)) { + if (!consumeIfAttributeLParen()) { diagnose(Loc, diag::attr_expected_lparen, AtAttrName, DeclAttribute::isDeclModifier(DAK_BackDeployed)); return false; @@ -2418,7 +2425,7 @@ Parser::parseDocumentationAttribute(SourceLoc AtLoc, SourceLoc Loc) { llvm::Optional Visibility = llvm::None; llvm::Optional Metadata = llvm::None; - if (!consumeIf(tok::l_paren)) { + if (!consumeIfAttributeLParen()) { diagnose(Loc, diag::attr_expected_lparen, AttrName, declModifier); return makeParserError(); @@ -2503,7 +2510,7 @@ Parser::parseMacroRoleAttribute( } // Parse the argments. - SourceLoc lParenLoc = consumeToken(); + SourceLoc lParenLoc = consumeAttributeLParen(); SourceLoc rParenLoc; llvm::Optional role; bool sawRole = false; @@ -2750,7 +2757,7 @@ static llvm::Optional parseSingleAttrOptionImpl( return llvm::None; } - P.consumeToken(tok::l_paren); + P.consumeAttributeLParen(); StringRef parsedName = P.Tok.getText(); if (!P.consumeIf(tok::identifier)) { @@ -2913,7 +2920,7 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes, break; case DAK_Effects: { - if (!consumeIf(tok::l_paren)) { + if (!consumeIfAttributeLParen()) { diagnose(Loc, diag::attr_expected_lparen, AttrName, DeclAttribute::isDeclModifier(DK)); return makeParserSuccess(); @@ -3093,7 +3100,7 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes, break; } - consumeToken(tok::l_paren); + consumeAttributeLParen(); // Parse the subject. if (Tok.isContextualKeyword("set")) { @@ -3145,7 +3152,7 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes, } case DAK_SPIAccessControl: { - if (!consumeIf(tok::l_paren)) { + if (!consumeIfAttributeLParen()) { diagnose(Loc, diag::attr_expected_lparen, AttrName, DeclAttribute::isDeclModifier(DK)); return makeParserSuccess(); @@ -3186,7 +3193,7 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes, case DAK_CDecl: case DAK_Expose: case DAK_SILGenName: { - if (!consumeIf(tok::l_paren)) { + if (!consumeIfAttributeLParen()) { diagnose(Loc, diag::attr_expected_lparen, AttrName, DeclAttribute::isDeclModifier(DK)); return makeParserSuccess(); @@ -3293,7 +3300,7 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes, } case DAK_Section: { - if (!consumeIf(tok::l_paren)) { + if (!consumeIfAttributeLParen()) { diagnose(Loc, diag::attr_expected_lparen, AttrName, DeclAttribute::isDeclModifier(DK)); return makeParserSuccess(); @@ -3333,12 +3340,12 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes, } case DAK_Alignment: { - if (!consumeIf(tok::l_paren)) { + if (!consumeIfAttributeLParen()) { diagnose(Loc, diag::attr_expected_lparen, AttrName, DeclAttribute::isDeclModifier(DK)); return makeParserSuccess(); } - + if (Tok.isNot(tok::integer_literal)) { diagnose(Loc, diag::alignment_must_be_positive_integer); return makeParserSuccess(); @@ -3380,7 +3387,7 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes, } case DAK_Semantics: { - if (!consumeIf(tok::l_paren)) { + if (!consumeIfAttributeLParen()) { diagnose(Loc, diag::attr_expected_lparen, AttrName, DeclAttribute::isDeclModifier(DK)); return makeParserSuccess(); @@ -3415,7 +3422,7 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes, } case DAK_OriginallyDefinedIn: { auto LeftLoc = Tok.getLoc(); - if (!consumeIf(tok::l_paren)) { + if (!consumeIfAttributeLParen()) { diagnose(Loc, diag::attr_expected_lparen, AttrName, DeclAttribute::isDeclModifier(DK)); return makeParserSuccess(); @@ -3505,7 +3512,7 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes, break; } case DAK_Available: { - if (!consumeIf(tok::l_paren)) { + if (!consumeIfAttributeLParen()) { diagnose(Loc, diag::attr_expected_lparen, AttrName, DeclAttribute::isDeclModifier(DK)); return makeParserSuccess(); @@ -3523,7 +3530,7 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes, DeclAttribute::isDeclModifier(DK)); return makeParserSuccess(); } - SourceLoc LParenLoc = consumeToken(tok::l_paren); + SourceLoc LParenLoc = consumeAttributeLParen(); llvm::Optional filename; { // Parse 'sourceFile'. @@ -3574,7 +3581,7 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes, } // Parse the leading '('. - SourceLoc LParenLoc = consumeToken(tok::l_paren); + SourceLoc LParenLoc = consumeAttributeLParen(); // Parse the names, with trailing colons (if there are present) and populate // the inout parameters @@ -3640,7 +3647,7 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes, return makeParserSuccess(); } - SourceLoc LParenLoc = consumeToken(tok::l_paren); + SourceLoc LParenLoc = consumeAttributeLParen(); DeclNameRef replacedFunction; { // Parse 'for'. @@ -3691,7 +3698,7 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes, return makeParserSuccess(); } - SourceLoc LParenLoc = consumeToken(tok::l_paren); + SourceLoc LParenLoc = consumeAttributeLParen(); ParserResult ErasedType; bool invalid = false; { @@ -3792,7 +3799,7 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes, case DAK_UnavailableFromAsync: { StringRef message; - if (consumeIf(tok::l_paren)) { + if (consumeIfAttributeLParen()) { if (!Tok.is(tok::identifier)) { llvm_unreachable("Flag must start with an identifier"); } @@ -3882,7 +3889,7 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes, return makeParserSuccess(); } - consumeToken(tok::l_paren); + consumeAttributeLParen(); if (!Tok.canBeArgumentLabel()) { diagnose(Loc, diag::attr_rawlayout_expected_label, "'size', 'like', or 'likeArrayOf'"); @@ -4160,6 +4167,10 @@ ParserResult Parser::parseCustomAttribute( initParser.emplace(*this, initContext); } + if (getEndOfPreviousLoc() != Tok.getLoc()) { + diagnose(getEndOfPreviousLoc(), diag::attr_extra_whitespace_before_lparen) + .warnUntilSwiftVersion(6); + } auto result = parseArgumentList(tok::l_paren, tok::r_paren, /*isExprBasic*/ true, /*allowTrailingClosure*/ false); @@ -4427,10 +4438,11 @@ static bool parseDifferentiableTypeAttributeArgument( SourceLoc &diffKindLocResult, bool emitDiagnostics) { Parser::CancellableBacktrackingScope backtrack(P); - SourceLoc beginLoc, kindLoc, endLoc; + SourceLoc beginLoc = P.Tok.getLoc(); + SourceLoc kindLoc, endLoc; // Match '( )', and store the identifier token to `argument`. - if (!P.consumeIf(tok::l_paren, beginLoc)) + if (!P.consumeIfAttributeLParen()) return false; auto argument = P.Tok; if (!P.consumeIf(tok::identifier, kindLoc)) @@ -4498,7 +4510,7 @@ bool Parser::parseConventionAttributeInternal(SourceLoc atLoc, SourceLoc attrLoc ConventionTypeAttr *&result, bool justChecking) { SourceLoc LPLoc = Tok.getLoc(); - if (!consumeIfNotAtStartOfLine(tok::l_paren)) { + if (!consumeIfAttributeLParen()) { if (!justChecking) diagnose(Tok, diag::convention_attribute_expected_lparen); return true; @@ -4764,7 +4776,7 @@ ParserStatus Parser::parseTypeAttribute(TypeOrCustomAttr &result, case TAK_opened: { // Parse the opened existential ID string in parens SourceLoc beginLoc = Tok.getLoc(), idLoc, endLoc; - if (!consumeIfNotAtStartOfLine(tok::l_paren)) { + if (!consumeAttributeLParen()) { if (!justChecking) diagnose(Tok, diag::opened_attribute_expected_lparen); return makeParserError(); @@ -4817,7 +4829,7 @@ ParserStatus Parser::parseTypeAttribute(TypeOrCustomAttr &result, // Parse the opened ID string in parens SourceLoc beginLoc = Tok.getLoc(), idLoc, endLoc; - if (!consumeIfNotAtStartOfLine(tok::l_paren)) { + if (!consumeIfAttributeLParen()) { if (!justChecking) diagnose(Tok, diag::pack_element_attribute_expected_lparen); return makeParserError(); @@ -4892,12 +4904,12 @@ ParserStatus Parser::parseTypeAttribute(TypeOrCustomAttr &result, case TAK__opaqueReturnTypeOf: { // Parse the mangled decl name and index. auto beginLoc = Tok.getLoc(); - if (!consumeIfNotAtStartOfLine(tok::l_paren)) { + if (!consumeIfAttributeLParen()) { if (!justChecking) diagnose(Tok, diag::attr_expected_lparen, "_opaqueReturnTypeOf", false); return makeParserError(); } - + if (!Tok.is(tok::string_literal)) { if (!justChecking) diagnose(Tok, diag::opened_attribute_id_value); diff --git a/lib/Parse/Parser.cpp b/lib/Parse/Parser.cpp index 5f0bdf4dd029a..61b8944104e11 100644 --- a/lib/Parse/Parser.cpp +++ b/lib/Parse/Parser.cpp @@ -576,6 +576,23 @@ SourceLoc Parser::getEndOfPreviousLoc() const { return Lexer::getLocForEndOfToken(SourceMgr, PreviousLoc); } +SourceLoc Parser::consumeAttributeLParen() { + SourceLoc LastTokenEndLoc = getEndOfPreviousLoc(); + if (LastTokenEndLoc != Tok.getLoc() && !isInSILMode()) { + diagnose(LastTokenEndLoc, diag::attr_extra_whitespace_before_lparen) + .warnUntilSwiftVersion(6); + } + return consumeToken(tok::l_paren); +} + +bool Parser::consumeIfAttributeLParen() { + if (!Tok.isFollowingLParen()) { + return false; + } + consumeAttributeLParen(); + return true; +} + SourceLoc Parser::consumeStartingCharacterOfCurrentToken(tok Kind, size_t Len) { // Consumes prefix of token and returns its location. // (like '?', '<', '>' or '!' immediately followed by '<') diff --git a/test/Parse/attribute_spacing.swift b/test/Parse/attribute_spacing.swift index 45c0a0dd9bd06..152b1d1033637 100644 --- a/test/Parse/attribute_spacing.swift +++ b/test/Parse/attribute_spacing.swift @@ -1,6 +1,28 @@ -// RUN: %target-typecheck-verify-swift -swift-version 6 +// RUN: %target-typecheck-verify-swift -@ MainActor // expected-error {{extraneous whitespace between '@' and attribute name}} +@ MainActor // expected-warning {{extraneous whitespace between '@' and attribute name; this is an error in Swift 6}} class Foo { - func foo(_ x: @ escaping () -> Int) {} // expected-error {{extraneous whitespace between '@' and attribute name}} + func funcWithEscapingClosure(_ x: @ escaping () -> Int) {} // expected-warning {{extraneous whitespace between '@' and attribute name; this is an error in Swift 6}} +} + +@available (*, deprecated) // expected-warning {{extraneous whitespace between attribute name and '('; this is an error in Swift 6}} +func deprecated() {} + +@propertyWrapper +struct MyPropertyWrapper { + var wrappedValue: Int = 1 + + init(param: Int) {} +} + +struct PropertyWrapperTest { + @MyPropertyWrapper (param: 2) // expected-warning {{extraneous whitespace between attribute name and '('; this is an error in Swift 6}} + var x: Int +} + +let closure1 = { @MainActor (a, b) in // expected-warning {{extraneous whitespace between attribute name and '('; this is an error in Swift 6}} +} + +let closure2 = { @MainActor + (a: Int, b: Int) in } diff --git a/test/attr/attributes.swift b/test/attr/attributes.swift index da50239ab74bf..9a36fa41b3f84 100644 --- a/test/attr/attributes.swift +++ b/test/attr/attributes.swift @@ -210,9 +210,9 @@ func func_with_unknown_attr3(x: @unknown(Int) -> Int) {} // expected-error {{unk func func_with_unknown_attr4(x: @unknown(Int) throws -> Int) {} // expected-error {{unknown attribute 'unknown'}} func func_with_unknown_attr5(x: @unknown (x: Int, y: Int)) {} // expected-error {{unknown attribute 'unknown'}} func func_with_unknown_attr6(x: @unknown(x: Int, y: Int)) {} // expected-error {{unknown attribute 'unknown'}} -func func_with_unknown_attr7(x: @unknown (Int) () -> Int) {} // expected-error {{unknown attribute 'unknown'}} +func func_with_unknown_attr7(x: @unknown (Int) () -> Int) {} // expected-error {{unknown attribute 'unknown'}} expected-warning {{extraneous whitespace between attribute name and '('; this is an error in Swift 6}} -func func_type_attribute_with_space(x: @convention (c) () -> Int) {} // OK. Known attributes can have space before its paren. +func func_type_attribute_with_space(x: @convention(c) () -> Int) {} // OK. Known attributes can have space before its paren. // @thin and @pseudogeneric are not supported except in SIL. var thinFunc : @thin () -> () // expected-error {{unknown attribute 'thin'}}