Skip to content

Commit 2a646dc

Browse files
committed
Parse: Require a "before: " label in the first item of the list in the @_backDeploy attribute in order to match the pitched syntax for the attribute. Refactor existing comma separated list parsing code to take advantage of part of it in the attribute parsing.
1 parent 59b62c2 commit 2a646dc

File tree

9 files changed

+250
-103
lines changed

9 files changed

+250
-103
lines changed

docs/ReferenceGuides/UnderscoredAttributes.md

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,19 @@ Most notably, default argument expressions are implicitly
3939
`@_alwaysEmitIntoClient`, which means that adding a default argument to a
4040
function which did not have one previously does not break ABI.
4141

42-
## `@_backDeploy(availabilitySpec ...)`
42+
## `@_backDeploy(before: ...)`
4343

4444
Causes the body of a function to be emitted into the module interface to be
45-
available for inlining in clients with deployment targets lower than the formal
46-
availability of the function. When inlined, the body of the function is
47-
transformed such that it calls the library's copy of the function if it is
48-
available at runtime. Otherwise, the copy of the original function body is
49-
executed.
45+
available for emission into clients with deployment targets lower than the
46+
ABI availability of the function. When the client's deployment target is
47+
before the function's ABI availability, the compiler replaces calls to that
48+
function with a call to a thunk that checks at runtime whether the original
49+
library function is available. If the the original is available then it is
50+
called. Otherwise, the fallback copy of the function that was emitted into the
51+
client is called instead.
52+
53+
For more details, see the [pitch thread](https://forums.swift.org/t/pitch-function-back-deployment/55769/)
54+
in the forums.
5055

5156
## `@_assemblyVision`
5257

include/swift/AST/DiagnosticsParse.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1572,6 +1572,10 @@ ERROR(originally_defined_in_need_nonempty_module_name,none,
15721572
"original module name cannot be empty in @_originallyDefinedIn", ())
15731573

15741574
// backDeploy
1575+
ERROR(attr_back_deploy_expected_before_label,none,
1576+
"expected 'before:' in '@_backDeploy' attribute", ())
1577+
ERROR(attr_back_deploy_expected_colon_after_before,none,
1578+
"expected ':' after 'before' in '@_backDeploy' attribute", ())
15751579
ERROR(attr_back_deploy_missing_rparen,none,
15761580
"expected ')' in '@_backDeploy' argument list", ())
15771581

include/swift/Parse/Parser.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -895,6 +895,22 @@ class Parser {
895895
/// close brace.
896896
SourceLoc getErrorOrMissingLoc() const;
897897

898+
enum class ParseListItemResult {
899+
/// There are more list items to parse.
900+
Continue,
901+
/// The list ended inside a string literal interpolation context.
902+
FinishedInStringInterpolation,
903+
/// The list ended for another reason.
904+
Finished,
905+
};
906+
907+
/// Parses a single item from a comma separated list and updates `Status`.
908+
ParseListItemResult
909+
parseListItem(ParserStatus &Status, tok RightK, SourceLoc LeftLoc,
910+
SourceLoc &RightLoc, bool AllowSepAfterLast,
911+
SyntaxKind ElementKind,
912+
llvm::function_ref<ParserStatus()> callback);
913+
898914
/// Parse a comma separated list of some elements.
899915
ParserStatus parseList(tok RightK, SourceLoc LeftLoc, SourceLoc &RightLoc,
900916
bool AllowSepAfterLast, Diag<> ErrorDiag,
@@ -1070,6 +1086,10 @@ class Parser {
10701086
ParserResult<TransposeAttr> parseTransposeAttribute(SourceLoc AtLoc,
10711087
SourceLoc Loc);
10721088

1089+
/// Parse the @_backDeploy attribute.
1090+
bool parseBackDeployAttribute(DeclAttributes &Attributes, StringRef AttrName,
1091+
SourceLoc AtLoc, SourceLoc Loc);
1092+
10731093
/// Parse a specific attribute.
10741094
ParserStatus parseDeclAttribute(DeclAttributes &Attributes, SourceLoc AtLoc,
10751095
PatternBindingInitializer *&initContext,

lib/AST/Attr.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1215,7 +1215,7 @@ bool DeclAttribute::printImpl(ASTPrinter &Printer, const PrintOptions &Options,
12151215

12161216
case DAK_BackDeploy: {
12171217
Printer.printAttrName("@_backDeploy");
1218-
Printer << "(";
1218+
Printer << "(before: ";
12191219
auto Attr = cast<BackDeployAttr>(this);
12201220
Printer << platformString(Attr->Platform) << " " <<
12211221
Attr->Version.getAsString();

lib/Parse/ParseDecl.cpp

Lines changed: 71 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1824,6 +1824,76 @@ ParserStatus Parser::parsePlatformVersionInList(StringRef AttrName,
18241824
return makeParserSuccess();
18251825
}
18261826

1827+
bool Parser::parseBackDeployAttribute(DeclAttributes &Attributes,
1828+
StringRef AttrName, SourceLoc AtLoc,
1829+
SourceLoc Loc) {
1830+
std::string AtAttrName = (llvm::Twine("@") + AttrName).str();
1831+
auto LeftLoc = Tok.getLoc();
1832+
if (!consumeIf(tok::l_paren)) {
1833+
diagnose(Loc, diag::attr_expected_lparen, AtAttrName,
1834+
DeclAttribute::isDeclModifier(DAK_BackDeploy));
1835+
return false;
1836+
}
1837+
1838+
SourceLoc RightLoc;
1839+
ParserStatus Status;
1840+
bool SuppressLaterDiags = false;
1841+
llvm::SmallVector<PlatformAndVersion, 4> PlatformAndVersions;
1842+
1843+
{
1844+
SyntaxParsingContext SpecListListContext(
1845+
SyntaxContext, SyntaxKind::BackDeployAttributeSpecList);
1846+
1847+
// Parse 'before' ':'.
1848+
if (Tok.is(tok::identifier) && Tok.getText() == "before") {
1849+
consumeToken();
1850+
if (!consumeIf(tok::colon))
1851+
diagnose(Tok, diag::attr_back_deploy_expected_colon_after_before);
1852+
} else {
1853+
diagnose(Tok, diag::attr_back_deploy_expected_before_label);
1854+
}
1855+
1856+
// Parse the version list.
1857+
if (!Tok.is(tok::r_paren)) {
1858+
SyntaxParsingContext VersionListContext(
1859+
SyntaxContext, SyntaxKind::BackDeployVersionList);
1860+
1861+
ParseListItemResult Result;
1862+
do {
1863+
Result = parseListItem(Status, tok::r_paren, LeftLoc, RightLoc,
1864+
/*AllowSepAfterLast=*/false,
1865+
SyntaxKind::BackDeployVersionArgument,
1866+
[&]() -> ParserStatus {
1867+
return parsePlatformVersionInList(
1868+
AtAttrName, PlatformAndVersions);
1869+
});
1870+
} while (Result == ParseListItemResult::Continue);
1871+
}
1872+
}
1873+
1874+
if (parseMatchingToken(tok::r_paren, RightLoc,
1875+
diag::attr_back_deploy_missing_rparen, LeftLoc))
1876+
return false;
1877+
1878+
if (Status.isErrorOrHasCompletion() || SuppressLaterDiags) {
1879+
return false;
1880+
}
1881+
1882+
if (PlatformAndVersions.empty()) {
1883+
diagnose(Loc, diag::attr_availability_need_platform_version, AtAttrName);
1884+
return false;
1885+
}
1886+
1887+
assert(!PlatformAndVersions.empty());
1888+
auto AttrRange = SourceRange(Loc, Tok.getLoc());
1889+
for (auto &Item : PlatformAndVersions) {
1890+
Attributes.add(new (Context)
1891+
BackDeployAttr(AtLoc, AttrRange, Item.first, Item.second,
1892+
/*IsImplicit*/ false));
1893+
}
1894+
return true;
1895+
}
1896+
18271897
/// Processes a parsed option name by attempting to match it to a list of
18281898
/// alternative name/value pairs provided by a chain of \c when() calls, ending
18291899
/// in either \c whenOmitted() if omitting the option is allowed, or
@@ -2858,45 +2928,8 @@ bool Parser::parseNewDeclAttribute(DeclAttributes &Attributes, SourceLoc AtLoc,
28582928
break;
28592929
}
28602930
case DAK_BackDeploy: {
2861-
auto LeftLoc = Tok.getLoc();
2862-
if (!consumeIf(tok::l_paren)) {
2863-
diagnose(Loc, diag::attr_expected_lparen, AttrName,
2864-
DeclAttribute::isDeclModifier(DK));
2865-
return false;
2866-
}
2867-
2868-
bool SuppressLaterDiags = false;
2869-
SourceLoc RightLoc;
2870-
llvm::SmallVector<PlatformAndVersion, 4> PlatformAndVersions;
2871-
StringRef AttrName = "@_backDeploy";
2872-
ParserStatus Status = parseList(tok::r_paren, LeftLoc, RightLoc, false,
2873-
diag::attr_back_deploy_missing_rparen,
2874-
SyntaxKind::AvailabilitySpecList,
2875-
[&]() -> ParserStatus {
2876-
ParserStatus ListItemStatus =
2877-
parsePlatformVersionInList(AttrName, PlatformAndVersions);
2878-
if (ListItemStatus.isErrorOrHasCompletion())
2879-
SuppressLaterDiags = true;
2880-
return ListItemStatus;
2881-
});
2882-
2883-
if (Status.isErrorOrHasCompletion() || SuppressLaterDiags) {
2884-
return false;
2885-
}
2886-
2887-
if (PlatformAndVersions.empty()) {
2888-
diagnose(Loc, diag::attr_availability_need_platform_version, AttrName);
2931+
if (!parseBackDeployAttribute(Attributes, AttrName, AtLoc, Loc))
28892932
return false;
2890-
}
2891-
2892-
assert(!PlatformAndVersions.empty());
2893-
AttrRange = SourceRange(Loc, Tok.getLoc());
2894-
for (auto &Item: PlatformAndVersions) {
2895-
Attributes.add(new (Context) BackDeployAttr(AtLoc, AttrRange,
2896-
Item.first,
2897-
Item.second,
2898-
/*IsImplicit*/false));
2899-
}
29002933
break;
29012934
}
29022935
}

lib/Parse/Parser.cpp

Lines changed: 70 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1061,14 +1061,72 @@ static SyntaxKind getListElementKind(SyntaxKind ListKind) {
10611061
}
10621062
}
10631063

1064+
static bool tokIsStringInterpolationEOF(Token &Tok, tok RightK) {
1065+
return Tok.is(tok::eof) && Tok.getText() == ")" && RightK == tok::r_paren;
1066+
}
1067+
1068+
Parser::ParseListItemResult
1069+
Parser::parseListItem(ParserStatus &Status, tok RightK, SourceLoc LeftLoc,
1070+
SourceLoc &RightLoc, bool AllowSepAfterLast,
1071+
SyntaxKind ElementKind,
1072+
llvm::function_ref<ParserStatus()> callback) {
1073+
while (Tok.is(tok::comma)) {
1074+
diagnose(Tok, diag::unexpected_separator, ",").fixItRemove(Tok.getLoc());
1075+
consumeToken();
1076+
}
1077+
SourceLoc StartLoc = Tok.getLoc();
1078+
1079+
SyntaxParsingContext ElementContext(SyntaxContext, ElementKind);
1080+
if (ElementKind == SyntaxKind::Unknown)
1081+
ElementContext.setTransparent();
1082+
Status |= callback();
1083+
if (Tok.is(RightK))
1084+
return ParseListItemResult::Finished;
1085+
1086+
// If the lexer stopped with an EOF token whose spelling is ")", then this
1087+
// is actually the tuple that is a string literal interpolation context.
1088+
// Just accept the ")" and build the tuple as we usually do.
1089+
if (tokIsStringInterpolationEOF(Tok, RightK)) {
1090+
RightLoc = Tok.getLoc();
1091+
return ParseListItemResult::FinishedInStringInterpolation;
1092+
}
1093+
// If we haven't made progress, or seeing any error, skip ahead.
1094+
if (Tok.getLoc() == StartLoc || Status.isErrorOrHasCompletion()) {
1095+
assert(Status.isErrorOrHasCompletion() && "no progress without error");
1096+
skipListUntilDeclRBrace(LeftLoc, RightK, tok::comma);
1097+
if (Tok.is(RightK) || Tok.isNot(tok::comma))
1098+
return ParseListItemResult::Finished;
1099+
}
1100+
if (consumeIf(tok::comma)) {
1101+
if (Tok.isNot(RightK))
1102+
return ParseListItemResult::Continue;
1103+
if (!AllowSepAfterLast) {
1104+
diagnose(Tok, diag::unexpected_separator, ",").fixItRemove(PreviousLoc);
1105+
}
1106+
return ParseListItemResult::Finished;
1107+
}
1108+
// If we're in a comma-separated list, the next token is at the
1109+
// beginning of a new line and can never start an element, break.
1110+
if (Tok.isAtStartOfLine() &&
1111+
(Tok.is(tok::r_brace) || isStartOfSwiftDecl() || isStartOfStmt())) {
1112+
return ParseListItemResult::Finished;
1113+
}
1114+
// If we found EOF or such, bailout.
1115+
if (Tok.isAny(tok::eof, tok::pound_endif)) {
1116+
IsInputIncomplete = true;
1117+
return ParseListItemResult::Finished;
1118+
}
1119+
1120+
diagnose(Tok, diag::expected_separator, ",")
1121+
.fixItInsertAfter(PreviousLoc, ",");
1122+
Status.setIsParseError();
1123+
return ParseListItemResult::Continue;
1124+
}
1125+
10641126
ParserStatus
10651127
Parser::parseList(tok RightK, SourceLoc LeftLoc, SourceLoc &RightLoc,
10661128
bool AllowSepAfterLast, Diag<> ErrorDiag, SyntaxKind Kind,
10671129
llvm::function_ref<ParserStatus()> callback) {
1068-
auto TokIsStringInterpolationEOF = [&]() -> bool {
1069-
return Tok.is(tok::eof) && Tok.getText() == ")" && RightK == tok::r_paren;
1070-
};
1071-
10721130
llvm::Optional<SyntaxParsingContext> ListContext;
10731131
ListContext.emplace(SyntaxContext, Kind);
10741132
if (Kind == SyntaxKind::Unknown)
@@ -1081,64 +1139,20 @@ Parser::parseList(tok RightK, SourceLoc LeftLoc, SourceLoc &RightLoc,
10811139
RightLoc = consumeToken(RightK);
10821140
return makeParserSuccess();
10831141
}
1084-
if (TokIsStringInterpolationEOF()) {
1142+
if (tokIsStringInterpolationEOF(Tok, RightK)) {
10851143
RightLoc = Tok.getLoc();
10861144
return makeParserSuccess();
10871145
}
10881146

10891147
ParserStatus Status;
1090-
while (true) {
1091-
while (Tok.is(tok::comma)) {
1092-
diagnose(Tok, diag::unexpected_separator, ",")
1093-
.fixItRemove(Tok.getLoc());
1094-
consumeToken();
1095-
}
1096-
SourceLoc StartLoc = Tok.getLoc();
1097-
1098-
SyntaxParsingContext ElementContext(SyntaxContext, ElementKind);
1099-
if (ElementKind == SyntaxKind::Unknown)
1100-
ElementContext.setTransparent();
1101-
Status |= callback();
1102-
if (Tok.is(RightK))
1103-
break;
1104-
// If the lexer stopped with an EOF token whose spelling is ")", then this
1105-
// is actually the tuple that is a string literal interpolation context.
1106-
// Just accept the ")" and build the tuple as we usually do.
1107-
if (TokIsStringInterpolationEOF()) {
1108-
RightLoc = Tok.getLoc();
1109-
return Status;
1110-
}
1111-
// If we haven't made progress, or seeing any error, skip ahead.
1112-
if (Tok.getLoc() == StartLoc || Status.isErrorOrHasCompletion()) {
1113-
assert(Status.isErrorOrHasCompletion() && "no progress without error");
1114-
skipListUntilDeclRBrace(LeftLoc, RightK, tok::comma);
1115-
if (Tok.is(RightK) || Tok.isNot(tok::comma))
1116-
break;
1117-
}
1118-
if (consumeIf(tok::comma)) {
1119-
if (Tok.isNot(RightK))
1120-
continue;
1121-
if (!AllowSepAfterLast) {
1122-
diagnose(Tok, diag::unexpected_separator, ",")
1123-
.fixItRemove(PreviousLoc);
1124-
}
1125-
break;
1126-
}
1127-
// If we're in a comma-separated list, the next token is at the
1128-
// beginning of a new line and can never start an element, break.
1129-
if (Tok.isAtStartOfLine() &&
1130-
(Tok.is(tok::r_brace) || isStartOfSwiftDecl() || isStartOfStmt())) {
1131-
break;
1132-
}
1133-
// If we found EOF or such, bailout.
1134-
if (Tok.isAny(tok::eof, tok::pound_endif)) {
1135-
IsInputIncomplete = true;
1136-
break;
1137-
}
1148+
ParseListItemResult Result;
1149+
do {
1150+
Result = parseListItem(Status, RightK, LeftLoc, RightLoc, AllowSepAfterLast,
1151+
ElementKind, callback);
1152+
} while (Result == ParseListItemResult::Continue);
11381153

1139-
diagnose(Tok, diag::expected_separator, ",")
1140-
.fixItInsertAfter(PreviousLoc, ",");
1141-
Status.setIsParseError();
1154+
if (Result == ParseListItemResult::FinishedInStringInterpolation) {
1155+
return Status;
11421156
}
11431157

11441158
ListContext.reset();

test/attr/attr_backDeploy.swift

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -304,10 +304,34 @@ public func unknownMacroVersionned() {}
304304
@_backDeploy(before: _unknownMacro 1.0, _myProject 2.0) // expected-warning {{unknown platform '_unknownMacro' for attribute '@_backDeploy'}}
305305
public func knownAndUnknownMacroVersionned() {}
306306

307-
@_backDeploy() // expected-error {{expected at least one platform version in '@_backDeploy' attribute}}
307+
@_backDeploy() // expected-error {{expected 'before:' in '@_backDeploy' attribute}}
308+
// expected-error@-1 {{expected at least one platform version in '@_backDeploy' attribute}}
309+
public func emptyAttributeFunc() {}
310+
311+
@available(macOS 11.0, *)
312+
@_backDeploy(macOS 12.0) // expected-error {{expected 'before:' in '@_backDeploy' attribute}}
313+
public func missingBeforeFunc() {}
314+
315+
@_backDeploy(before) // expected-error {{expected ':' after 'before' in '@_backDeploy' attribute}}
316+
// expected-error@-1 {{expected at least one platform version in '@_backDeploy' attribute}}
317+
public func missingColonAfterBeforeFunc() {}
318+
319+
@available(macOS 11.0, *)
320+
@_backDeploy(before macOS 12.0) // expected-error {{expected ':' after 'before' in '@_backDeploy' attribute}}
321+
public func missingColonBetweenBeforeAndPlatformFunc() {}
322+
323+
@available(macOS 11.0, *)
324+
@_backDeploy(before: macOS 12.0,) // expected-error {{unexpected ',' separator}}
325+
public func unexpectedTrailingCommaFunc() {}
326+
327+
@available(macOS 11.0, iOS 14.0, *)
328+
@_backDeploy(before: macOS 12.0,, iOS 15.0) // expected-error {{unexpected ',' separator}}
329+
public func extraCommaFunc() {}
330+
331+
@_backDeploy(before:) // expected-error {{expected at least one platform version in '@_backDeploy' attribute}}
308332
public func emptyPlatformVersionsFunc() {}
309333

310-
@_backDeploy // expected-error {{expected '(' in '_backDeploy' attribute}}
334+
@_backDeploy // expected-error {{expected '(' in '@_backDeploy' attribute}}
311335
public func expectedLeftParenFunc() {}
312336

313337
@_backDeploy(before: macOS 12.0 // expected-note {{to match this opening '('}}

0 commit comments

Comments
 (0)