diff --git a/include/swift/Parse/Parser.h b/include/swift/Parse/Parser.h index ed88d1180ddeb..58817a7cc0850 100644 --- a/include/swift/Parse/Parser.h +++ b/include/swift/Parse/Parser.h @@ -900,11 +900,15 @@ class Parser { // Decl Parsing /// Returns true if parser is at the start of a Swift decl or decl-import. - bool isStartOfSwiftDecl(bool allowPoundIfAttributes = true); + bool isStartOfSwiftDecl(bool allowPoundIfAttributes = true, + bool hadAttrsOrModifiers = false); /// Returns true if the parser is at the start of a SIL decl. bool isStartOfSILDecl(); + /// Returns true if the parser is at a freestanding macro expansion. + bool isStartOfFreestandingMacroExpansion(); + /// Parse the top-level Swift items into the provided vector. /// /// Each item will be a declaration, statement, or expression. diff --git a/lib/AST/Attr.cpp b/lib/AST/Attr.cpp index 15af95676c4f6..12e0833b70d5d 100644 --- a/lib/AST/Attr.cpp +++ b/lib/AST/Attr.cpp @@ -855,7 +855,7 @@ OrigDeclAttrFilter::operator()(const DeclAttribute *Attr) const { auto declLoc = decl->getStartLoc(); auto *mod = decl->getModuleContext(); auto *declFile = mod->getSourceFileContainingLocation(declLoc); - auto *attrFile = mod->getSourceFileContainingLocation(Attr->AtLoc); + auto *attrFile = mod->getSourceFileContainingLocation(Attr->getLocation()); if (!declFile || !attrFile) return Attr; diff --git a/lib/Parse/ParseDecl.cpp b/lib/Parse/ParseDecl.cpp index 403e05e635cd7..9c12e800f4374 100644 --- a/lib/Parse/ParseDecl.cpp +++ b/lib/Parse/ParseDecl.cpp @@ -4786,7 +4786,8 @@ static void skipAttribute(Parser &P) { } } -bool Parser::isStartOfSwiftDecl(bool allowPoundIfAttributes) { +bool Parser::isStartOfSwiftDecl(bool allowPoundIfAttributes, + bool hadAttrsOrModifiers) { if (Tok.is(tok::at_sign) && peekToken().is(tok::kw_rethrows)) { // @rethrows does not follow the general rule of @ so // it is needed to short circuit this else there will be an infinite @@ -4835,13 +4836,25 @@ bool Parser::isStartOfSwiftDecl(bool allowPoundIfAttributes) { (Tok.is(tok::pound_endif) && !allowPoundIfAttributes)) return true; - return isStartOfSwiftDecl(allowPoundIfAttributes); + return isStartOfSwiftDecl(allowPoundIfAttributes, + /*hadAttrsOrModifiers=*/true); } - if (Tok.is(tok::pound) && peekToken().is(tok::identifier)) { - // Macro expansions at the top level are declarations. - return !isInSILMode() && SF.Kind != SourceFileKind::Interface && - CurDeclContext->isModuleScopeContext() && !allowTopLevelCode(); + if (Tok.is(tok::pound)) { + if (isStartOfFreestandingMacroExpansion()) { + if (isInSILMode() || SF.Kind == SourceFileKind::Interface) + return false; + + // Parse '#' after attrs/modifiers as a macro expansion decl. + if (hadAttrsOrModifiers) + return true; + + // Macro expansions at the top level of non-script file are declarations. + return CurDeclContext->isModuleScopeContext() && !allowTopLevelCode(); + } + + // Otherwise, prefer parsing it as an expression. + return false; } // Skip a #if that contains only attributes in all branches. These will be @@ -4849,8 +4862,14 @@ bool Parser::isStartOfSwiftDecl(bool allowPoundIfAttributes) { if (Tok.is(tok::pound_if) && allowPoundIfAttributes) { BacktrackingScope backtrack(*this); bool sawAnyAttributes = false; - return skipIfConfigOfAttributes(sawAnyAttributes) && - (Tok.is(tok::eof) || (sawAnyAttributes && isStartOfSwiftDecl())); + if (!skipIfConfigOfAttributes(sawAnyAttributes)) + return false; + if (Tok.is(tok::eof)) + return true; + if (!sawAnyAttributes) + return false; + return isStartOfSwiftDecl(/*allowPoundIfAttributes=*/true, + /*hadAttrsOrModifiers=*/true); } // If we have a decl modifying keyword, check if the next token is a valid @@ -4872,13 +4891,15 @@ bool Parser::isStartOfSwiftDecl(bool allowPoundIfAttributes) { // If we found the start of a decl while trying to skip over the // paren, then we have something incomplete like 'private('. Return // true for better recovery. - if (isStartOfSwiftDecl(/*allowPoundIfAttributes=*/false)) + if (isStartOfSwiftDecl(/*allowPoundIfAttributes=*/false, + /*hadAttrsOrModifiers=*/true)) return true; skipSingle(); } } - return isStartOfSwiftDecl(/*allowPoundIfAttributes=*/false); + return isStartOfSwiftDecl(/*allowPoundIfAttributes=*/false, + /*hadAttrsOrModifiers=*/true); } } @@ -4905,7 +4926,8 @@ bool Parser::isStartOfSwiftDecl(bool allowPoundIfAttributes) { consumeToken(tok::l_paren); consumeToken(tok::identifier); consumeToken(tok::r_paren); - return isStartOfSwiftDecl(/*allowPoundIfAttributes=*/false); + return isStartOfSwiftDecl(/*allowPoundIfAttributes=*/false, + /*hadAttrsOrModifiers=*/true); } if (Tok.isContextualKeyword("actor")) { @@ -4917,7 +4939,8 @@ bool Parser::isStartOfSwiftDecl(bool allowPoundIfAttributes) { // it's an actor declaration, otherwise, it isn't. do { consumeToken(); - } while (isStartOfSwiftDecl(/*allowPoundIfAttributes=*/false)); + } while (isStartOfSwiftDecl(/*allowPoundIfAttributes=*/false, + /*hadAttrsOrModifiers=*/true)); return Tok.is(tok::identifier); } @@ -4960,12 +4983,14 @@ bool Parser::isStartOfSwiftDecl(bool allowPoundIfAttributes) { // If we found the start of a decl while trying to skip over the // paren, then we have something incomplete like 'package('. Return // true for better recovery. - if (isStartOfSwiftDecl(/*allowPoundIfAttributes=*/false)) + if (isStartOfSwiftDecl(/*allowPoundIfAttributes=*/false, + /*hadAttrsOrModifiers=*/true)) return true; skipSingle(); } } - return isStartOfSwiftDecl(/*allowPoundIfAttributes=*/false); + return isStartOfSwiftDecl(/*allowPoundIfAttributes=*/false, + /*hadAttrsOrModifiers=*/true); } } @@ -4976,7 +5001,8 @@ bool Parser::isStartOfSwiftDecl(bool allowPoundIfAttributes) { // Otherwise, do a recursive parse. Parser::BacktrackingScope Backtrack(*this); consumeToken(tok::identifier); - return isStartOfSwiftDecl(/*allowPoundIfAttributes=*/false); + return isStartOfSwiftDecl(/*allowPoundIfAttributes=*/false, + /*hadAttrsOrModifiers=*/true); } bool Parser::isStartOfSILDecl() { @@ -5004,6 +5030,19 @@ bool Parser::isStartOfSILDecl() { llvm_unreachable("Unhandled case in switch"); } +bool Parser::isStartOfFreestandingMacroExpansion() { + // Check if "'#' " without any whitespace between them. + if (!Tok.is(tok::pound)) + return false; + if (Tok.getRange().getEnd() != peekToken().getLoc()) + return false; + if (!peekToken().isAny(tok::identifier, tok::code_complete) && + // allow keywords right after '#' so we can diagnose it when parsing. + !peekToken().isKeyword()) + return false; + return true; +} + void Parser::consumeDecl(ParserPosition BeginParserPosition, ParseDeclOptions Flags, bool IsTopLevel) { @@ -5256,9 +5295,15 @@ Parser::parseDecl(ParseDeclOptions Flags, // Handled below. break; case tok::pound: - if (Tok.isAtStartOfLine() && - peekToken().is(tok::code_complete) && - Tok.getLoc().getAdvancedLoc(1) == peekToken().getLoc()) { + if (!isStartOfFreestandingMacroExpansion()) { + consumeToken(tok::pound); + diagnose(Tok.getLoc(), + diag::macro_expansion_decl_expected_macro_identifier); + DeclResult = makeParserError(); + break; + } + + if (peekToken().is(tok::code_complete)) { consumeToken(); if (CodeCompletionCallbacks) { CodeCompletionCallbacks->completeAfterPoundDirective(); @@ -5270,6 +5315,7 @@ Parser::parseDecl(ParseDeclOptions Flags, // Parse as a macro expansion. DeclResult = parseDeclMacroExpansion(Flags, Attributes); + StaticLoc = SourceLoc(); // Ignore 'static' on macro expansion break; case tok::pound_if: @@ -9803,10 +9849,10 @@ Parser::parseDeclMacroExpansion(ParseDeclOptions flags, } } - return makeParserResult( - status, - new (Context) MacroExpansionDecl( - CurDeclContext, poundLoc, macroNameRef, macroNameLoc, - leftAngleLoc, Context.AllocateCopy(genericArgs), rightAngleLoc, - argList)); + auto *med = new (Context) MacroExpansionDecl( + CurDeclContext, poundLoc, macroNameRef, macroNameLoc, leftAngleLoc, + Context.AllocateCopy(genericArgs), rightAngleLoc, argList); + med->getAttrs() = attributes; + + return makeParserResult(status, med); } diff --git a/lib/Parse/ParseExpr.cpp b/lib/Parse/ParseExpr.cpp index 18cedb538fc09..964914ee9483a 100644 --- a/lib/Parse/ParseExpr.cpp +++ b/lib/Parse/ParseExpr.cpp @@ -1841,6 +1841,12 @@ ParserResult Parser::parseExprPrimary(Diag<> ID, bool isExprBasic) { } case tok::pound: + if (!isStartOfFreestandingMacroExpansion()) { + consumeToken(tok::pound); + diagnose(Tok.getLoc(), + diag::macro_expansion_expr_expected_macro_identifier); + return makeParserError(); + } if (peekToken().is(tok::code_complete) && Tok.getLoc().getAdvancedLoc(1) == peekToken().getLoc()) { return parseExprPoundCodeCompletion(/*ParentKind*/None); diff --git a/lib/Sema/TypeCheckDeclPrimary.cpp b/lib/Sema/TypeCheckDeclPrimary.cpp index 5fe1591e1bc4f..7b423bb500b7a 100644 --- a/lib/Sema/TypeCheckDeclPrimary.cpp +++ b/lib/Sema/TypeCheckDeclPrimary.cpp @@ -2058,6 +2058,10 @@ class DeclChecker : public DeclVisitor { } void visitMacroExpansionDecl(MacroExpansionDecl *MED) { + // TODO: Type check attributes. + // Type checking arguments should reflect the attributes. + // e.g. '@available(macOS 999) #Future { newAPIFrom999() }'. + // Assign a discriminator. (void)MED->getDiscriminator(); // Decls in expansion already visited as auxiliary decls. diff --git a/test/Parse/line-directive.swift b/test/Parse/line-directive.swift index fcf6df3f2d45e..fd0675bb75909 100644 --- a/test/Parse/line-directive.swift +++ b/test/Parse/line-directive.swift @@ -30,8 +30,8 @@ public struct S { // expected-error@+5{{operators must have one or two arguments}} // expected-error@+4{{member operator '/()' must have at least one argument of type 'S'}} // expected-error@+3{{expected '{' in body of function declaration}} -// expected-error@+2 2 {{consecutive declarations on a line must be separated by ';}} -// expected-error@+1 2 {{expected a macro identifier}} +// expected-error@+2 {{consecutive declarations on a line must be separated by ';}} +// expected-error@+1 {{expected a macro identifier}} / ###line 25 "line-directive.swift" } // expected-error@+1{{#line directive was renamed to #sourceLocation}} diff --git a/test/Parse/macro_decl.swift b/test/Parse/macro_decl.swift index 26767524bf435..d5fa9c3737dc8 100644 --- a/test/Parse/macro_decl.swift +++ b/test/Parse/macro_decl.swift @@ -16,6 +16,11 @@ extension String { #memberwiseInit(flavor: .chocolate, haha: true) { "abc" } + @available(macOS 999, *) + public #memberwiseInit() + + static internal #memberwiseInit + struct Foo { #memberwiseInit @@ -26,3 +31,18 @@ extension String { // expected-error @+1 {{expected a macro identifier for a pound literal declaration}} #() } + +@RandomAttr #someFunc + +public #someFunc + +#someFunc + +func test() { + @discardableResult #someFunc + + dynamic #someFunc + + @CustomAttr + isolated #someFunc +} diff --git a/test/Parse/object_literals.swift b/test/Parse/object_literals.swift index 76171ff64e671..da567e499b35a 100644 --- a/test/Parse/object_literals.swift +++ b/test/Parse/object_literals.swift @@ -4,5 +4,4 @@ let _ = #notAPound // expected-error {{no macro named 'notAPound'}} let _ = #notAPound(1, 2) // expected-error {{no macro named 'notAPound'}} let _ = #Color // expected-error {{no macro named 'Color'}} -let _ = [##] // expected-error 2 {{expected a macro identifier}} {{none}} -// expected-error @-1 {{consecutive statements on a line must be separated by ';'}} +let _ = [##] // expected-error {{expected a macro identifier}} {{none}} diff --git a/test/Parse/operator_decl.swift b/test/Parse/operator_decl.swift index cbe3945a64277..1daec13369d74 100644 --- a/test/Parse/operator_decl.swift +++ b/test/Parse/operator_decl.swift @@ -52,7 +52,6 @@ infix operator aa--: A // expected-error {{'aa' is considered an identifier and infix operator <<$$@< // expected-error {{'$$' is considered an identifier and must not appear within an operator name}} infix operator !!@aa // expected-error {{'@' is not allowed in operator names}} infix operator #++= // expected-error {{'#' is not allowed in operator names}} -// expected-error@-1 {{expected a macro identifier}} infix operator ++=# // expected-error {{'#' is not allowed in operator names}} infix operator -># // expected-error {{'#' is not allowed in operator names}} @@ -61,7 +60,6 @@ infix operator -># // expected-error {{'#' is not allowed in operator names}} infix operator =#= // expected-error@-1 {{'#' is not allowed in operator names}} // expected-error@-2 {{'=' must have consistent whitespace on both sides}} -// expected-error@-3 {{expected a macro identifier}} infix operator +++= infix operator *** : A diff --git a/test/Parse/raw_string_errors.swift b/test/Parse/raw_string_errors.swift index b9feab1b377b6..a7ae88b0b2c7f 100644 --- a/test/Parse/raw_string_errors.swift +++ b/test/Parse/raw_string_errors.swift @@ -12,7 +12,7 @@ let _ = #"\##("invalid")"# let _ = ###"""invalid"###### // expected-error@-1{{too many '#' characters in closing delimiter}}{{26-29=}} // expected-error@-2{{consecutive statements on a line must be separated by ';'}} -// expected-error@-3 3 {{expected a macro identifier}} +// expected-error@-3{{expected a macro identifier}} let _ = ####"invalid"### // expected-error@-1{{unterminated string literal}} @@ -20,7 +20,7 @@ let _ = ####"invalid"### let _ = ###"invalid"###### // expected-error@-1{{too many '#' characters in closing delimiter}}{{24-27=}} // expected-error@-2{{consecutive statements on a line must be separated by ';'}} -// expected-error@-3 3 {{expected a macro identifier}} +// expected-error@-3{{expected a macro identifier}} let _ = ##"""aa foobar diff --git a/test/StringProcessing/Frontend/disable-flag.swift b/test/StringProcessing/Frontend/disable-flag.swift index 5c9c237d04357..a9a9d41042177 100644 --- a/test/StringProcessing/Frontend/disable-flag.swift +++ b/test/StringProcessing/Frontend/disable-flag.swift @@ -9,6 +9,6 @@ _ = /x/ // expected-error@-2 {{cannot find 'x' in scope}} // expected-error@-3 {{'/' is not a postfix unary operator}} -_ = #/x/# // expected-error 2 {{expected a macro identifier}} +_ = #/x/# // expected-error {{expected a macro identifier}} func foo(_ x: Regex) {} // expected-error {{cannot find type 'Regex' in scope}}