[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.
This commit is contained in:
Alex Hoppen
2024-01-29 22:56:25 -08:00
parent cc5e79b521
commit c6e425a559
6 changed files with 108 additions and 42 deletions

View File

@@ -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))

View File

@@ -602,6 +602,18 @@ public:
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);

View File

@@ -1093,7 +1093,14 @@ bool Parser::parseSpecializeAttribute(
llvm::function_ref<bool(Parser &)> 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<Identifier> initializesProperties;
SmallVector<Identifier> 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<DifferentiableAttr>
Parser::parseDifferentiableAttribute(SourceLoc atLoc, SourceLoc loc) {
StringRef AttrName = "differentiable";
SourceLoc lParenLoc = loc, rParenLoc = loc;
SourceLoc rParenLoc = loc;
DifferentiabilityKind diffKind = DifferentiabilityKind::Normal;
SmallVector<ParsedAutoDiffParameter, 8> 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(<language>, ...)
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<DerivativeAttr> Parser::parseDerivativeAttribute(SourceLoc atLoc,
SourceLoc loc) {
StringRef AttrName = "derivative";
SourceLoc lParenLoc = loc, rParenLoc = loc;
SourceLoc rParenLoc = loc;
TypeRepr *baseType = nullptr;
DeclNameRefWithLoc original;
SmallVector<ParsedAutoDiffParameter, 8> parameters;
@@ -1885,7 +1892,7 @@ ParserResult<DerivativeAttr> 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<DerivativeAttr> Parser::parseDerivativeAttribute(SourceLoc atLoc,
ParserResult<TransposeAttr> Parser::parseTransposeAttribute(SourceLoc atLoc,
SourceLoc loc) {
StringRef AttrName = "transpose";
SourceLoc lParenLoc = loc, rParenLoc = loc;
SourceLoc rParenLoc = loc;
TypeRepr *baseType = nullptr;
DeclNameRefWithLoc original;
SmallVector<ParsedAutoDiffParameter, 8> parameters;
@@ -1966,7 +1973,7 @@ ParserResult<TransposeAttr> 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<AccessLevel> Visibility = llvm::None;
llvm::Optional<StringRef> 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<MacroRole> role;
bool sawRole = false;
@@ -2750,7 +2757,7 @@ static llvm::Optional<Identifier> 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<StringRef> 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<TypeRepr> 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<CustomAttr> 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 '( <identifier> )', 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);

View File

@@ -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 '<')

View File

@@ -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
}

View File

@@ -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'}}