Skip to content

Parse: Require a "before: " label in @_backDeploy attribute #41893

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions docs/ReferenceGuides/UnderscoredAttributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,19 @@ Most notably, default argument expressions are implicitly
`@_alwaysEmitIntoClient`, which means that adding a default argument to a
function which did not have one previously does not break ABI.

## `@_backDeploy(availabilitySpec ...)`
## `@_backDeploy(before: ...)`

Causes the body of a function to be emitted into the module interface to be
available for inlining in clients with deployment targets lower than the formal
availability of the function. When inlined, the body of the function is
transformed such that it calls the library's copy of the function if it is
available at runtime. Otherwise, the copy of the original function body is
executed.
available for emission into clients with deployment targets lower than the
ABI availability of the function. When the client's deployment target is
before the function's ABI availability, the compiler replaces calls to that
function with a call to a thunk that checks at runtime whether the original
library function is available. If the the original is available then it is
called. Otherwise, the fallback copy of the function that was emitted into the
client is called instead.

For more details, see the [pitch thread](https://forums.swift.org/t/pitch-function-back-deployment/55769/)
in the forums.

## `@_assemblyVision`

Expand Down
4 changes: 4 additions & 0 deletions include/swift/AST/DiagnosticsParse.def
Original file line number Diff line number Diff line change
Expand Up @@ -1572,6 +1572,10 @@ ERROR(originally_defined_in_need_nonempty_module_name,none,
"original module name cannot be empty in @_originallyDefinedIn", ())

// backDeploy
ERROR(attr_back_deploy_expected_before_label,none,
"expected 'before:' in '@_backDeploy' attribute", ())
ERROR(attr_back_deploy_expected_colon_after_before,none,
"expected ':' after 'before' in '@_backDeploy' attribute", ())
ERROR(attr_back_deploy_missing_rparen,none,
"expected ')' in '@_backDeploy' argument list", ())

Expand Down
20 changes: 20 additions & 0 deletions include/swift/Parse/Parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -895,6 +895,22 @@ class Parser {
/// close brace.
SourceLoc getErrorOrMissingLoc() const;

enum class ParseListItemResult {
/// There are more list items to parse.
Continue,
/// The list ended inside a string literal interpolation context.
FinishedInStringInterpolation,
/// The list ended for another reason.
Finished,
};

/// Parses a single item from a comma separated list and updates `Status`.
ParseListItemResult
parseListItem(ParserStatus &Status, tok RightK, SourceLoc LeftLoc,
SourceLoc &RightLoc, bool AllowSepAfterLast,
SyntaxKind ElementKind,
llvm::function_ref<ParserStatus()> callback);

/// Parse a comma separated list of some elements.
ParserStatus parseList(tok RightK, SourceLoc LeftLoc, SourceLoc &RightLoc,
bool AllowSepAfterLast, Diag<> ErrorDiag,
Expand Down Expand Up @@ -1070,6 +1086,10 @@ class Parser {
ParserResult<TransposeAttr> parseTransposeAttribute(SourceLoc AtLoc,
SourceLoc Loc);

/// Parse the @_backDeploy attribute.
bool parseBackDeployAttribute(DeclAttributes &Attributes, StringRef AttrName,
SourceLoc AtLoc, SourceLoc Loc);

/// Parse a specific attribute.
ParserStatus parseDeclAttribute(DeclAttributes &Attributes, SourceLoc AtLoc,
PatternBindingInitializer *&initContext,
Expand Down
2 changes: 1 addition & 1 deletion lib/AST/Attr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1215,7 +1215,7 @@ bool DeclAttribute::printImpl(ASTPrinter &Printer, const PrintOptions &Options,

case DAK_BackDeploy: {
Printer.printAttrName("@_backDeploy");
Printer << "(";
Printer << "(before: ";
auto Attr = cast<BackDeployAttr>(this);
Printer << platformString(Attr->Platform) << " " <<
Attr->Version.getAsString();
Expand Down
111 changes: 73 additions & 38 deletions lib/Parse/ParseDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1824,6 +1824,78 @@ ParserStatus Parser::parsePlatformVersionInList(StringRef AttrName,
return makeParserSuccess();
}

bool Parser::parseBackDeployAttribute(DeclAttributes &Attributes,
StringRef AttrName, SourceLoc AtLoc,
SourceLoc Loc) {
std::string AtAttrName = (llvm::Twine("@") + AttrName).str();
auto LeftLoc = Tok.getLoc();
if (!consumeIf(tok::l_paren)) {
diagnose(Loc, diag::attr_expected_lparen, AtAttrName,
DeclAttribute::isDeclModifier(DAK_BackDeploy));
return false;
}

SourceLoc RightLoc;
ParserStatus Status;
bool SuppressLaterDiags = false;
llvm::SmallVector<PlatformAndVersion, 4> PlatformAndVersions;

{
SyntaxParsingContext SpecListListContext(
SyntaxContext, SyntaxKind::BackDeployAttributeSpecList);

// Parse 'before' ':'.
if (Tok.is(tok::identifier) && Tok.getText() == "before") {
consumeToken();
if (!consumeIf(tok::colon))
diagnose(Tok, diag::attr_back_deploy_expected_colon_after_before)
.fixItInsertAfter(PreviousLoc, ":");
} else {
diagnose(Tok, diag::attr_back_deploy_expected_before_label)
.fixItInsertAfter(PreviousLoc, "before:");
}

// Parse the version list.
if (!Tok.is(tok::r_paren)) {
SyntaxParsingContext VersionListContext(
SyntaxContext, SyntaxKind::BackDeployVersionList);

ParseListItemResult Result;
do {
Result = parseListItem(Status, tok::r_paren, LeftLoc, RightLoc,
/*AllowSepAfterLast=*/false,
SyntaxKind::BackDeployVersionArgument,
[&]() -> ParserStatus {
return parsePlatformVersionInList(
AtAttrName, PlatformAndVersions);
});
} while (Result == ParseListItemResult::Continue);
}
}

if (parseMatchingToken(tok::r_paren, RightLoc,
diag::attr_back_deploy_missing_rparen, LeftLoc))
return false;

if (Status.isErrorOrHasCompletion() || SuppressLaterDiags) {
return false;
}

if (PlatformAndVersions.empty()) {
diagnose(Loc, diag::attr_availability_need_platform_version, AtAttrName);
return false;
}

assert(!PlatformAndVersions.empty());
auto AttrRange = SourceRange(Loc, Tok.getLoc());
for (auto &Item : PlatformAndVersions) {
Attributes.add(new (Context)
BackDeployAttr(AtLoc, AttrRange, Item.first, Item.second,
/*IsImplicit*/ false));
}
return true;
}

/// Processes a parsed option name by attempting to match it to a list of
/// alternative name/value pairs provided by a chain of \c when() calls, ending
/// in either \c whenOmitted() if omitting the option is allowed, or
Expand Down Expand Up @@ -2858,45 +2930,8 @@ bool Parser::parseNewDeclAttribute(DeclAttributes &Attributes, SourceLoc AtLoc,
break;
}
case DAK_BackDeploy: {
auto LeftLoc = Tok.getLoc();
if (!consumeIf(tok::l_paren)) {
diagnose(Loc, diag::attr_expected_lparen, AttrName,
DeclAttribute::isDeclModifier(DK));
return false;
}

bool SuppressLaterDiags = false;
SourceLoc RightLoc;
llvm::SmallVector<PlatformAndVersion, 4> PlatformAndVersions;
StringRef AttrName = "@_backDeploy";
ParserStatus Status = parseList(tok::r_paren, LeftLoc, RightLoc, false,
diag::attr_back_deploy_missing_rparen,
SyntaxKind::AvailabilitySpecList,
[&]() -> ParserStatus {
ParserStatus ListItemStatus =
parsePlatformVersionInList(AttrName, PlatformAndVersions);
if (ListItemStatus.isErrorOrHasCompletion())
SuppressLaterDiags = true;
return ListItemStatus;
});

if (Status.isErrorOrHasCompletion() || SuppressLaterDiags) {
return false;
}

if (PlatformAndVersions.empty()) {
diagnose(Loc, diag::attr_availability_need_platform_version, AttrName);
if (!parseBackDeployAttribute(Attributes, AttrName, AtLoc, Loc))
return false;
}

assert(!PlatformAndVersions.empty());
AttrRange = SourceRange(Loc, Tok.getLoc());
for (auto &Item: PlatformAndVersions) {
Attributes.add(new (Context) BackDeployAttr(AtLoc, AttrRange,
Item.first,
Item.second,
/*IsImplicit*/false));
}
break;
}
}
Expand Down
126 changes: 70 additions & 56 deletions lib/Parse/Parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1061,14 +1061,72 @@ static SyntaxKind getListElementKind(SyntaxKind ListKind) {
}
}

static bool tokIsStringInterpolationEOF(Token &Tok, tok RightK) {
return Tok.is(tok::eof) && Tok.getText() == ")" && RightK == tok::r_paren;
}

Parser::ParseListItemResult
Parser::parseListItem(ParserStatus &Status, tok RightK, SourceLoc LeftLoc,
SourceLoc &RightLoc, bool AllowSepAfterLast,
SyntaxKind ElementKind,
llvm::function_ref<ParserStatus()> callback) {
while (Tok.is(tok::comma)) {
diagnose(Tok, diag::unexpected_separator, ",").fixItRemove(Tok.getLoc());
consumeToken();
}
SourceLoc StartLoc = Tok.getLoc();

SyntaxParsingContext ElementContext(SyntaxContext, ElementKind);
if (ElementKind == SyntaxKind::Unknown)
ElementContext.setTransparent();
Status |= callback();
if (Tok.is(RightK))
return ParseListItemResult::Finished;

// If the lexer stopped with an EOF token whose spelling is ")", then this
// is actually the tuple that is a string literal interpolation context.
// Just accept the ")" and build the tuple as we usually do.
if (tokIsStringInterpolationEOF(Tok, RightK)) {
RightLoc = Tok.getLoc();
return ParseListItemResult::FinishedInStringInterpolation;
}
// If we haven't made progress, or seeing any error, skip ahead.
if (Tok.getLoc() == StartLoc || Status.isErrorOrHasCompletion()) {
assert(Status.isErrorOrHasCompletion() && "no progress without error");
skipListUntilDeclRBrace(LeftLoc, RightK, tok::comma);
if (Tok.is(RightK) || Tok.isNot(tok::comma))
return ParseListItemResult::Finished;
}
if (consumeIf(tok::comma)) {
if (Tok.isNot(RightK))
return ParseListItemResult::Continue;
if (!AllowSepAfterLast) {
diagnose(Tok, diag::unexpected_separator, ",").fixItRemove(PreviousLoc);
}
return ParseListItemResult::Finished;
}
// If we're in a comma-separated list, the next token is at the
// beginning of a new line and can never start an element, break.
if (Tok.isAtStartOfLine() &&
(Tok.is(tok::r_brace) || isStartOfSwiftDecl() || isStartOfStmt())) {
return ParseListItemResult::Finished;
}
// If we found EOF or such, bailout.
if (Tok.isAny(tok::eof, tok::pound_endif)) {
IsInputIncomplete = true;
return ParseListItemResult::Finished;
}

diagnose(Tok, diag::expected_separator, ",")
.fixItInsertAfter(PreviousLoc, ",");
Status.setIsParseError();
return ParseListItemResult::Continue;
}

ParserStatus
Parser::parseList(tok RightK, SourceLoc LeftLoc, SourceLoc &RightLoc,
bool AllowSepAfterLast, Diag<> ErrorDiag, SyntaxKind Kind,
llvm::function_ref<ParserStatus()> callback) {
auto TokIsStringInterpolationEOF = [&]() -> bool {
return Tok.is(tok::eof) && Tok.getText() == ")" && RightK == tok::r_paren;
};

llvm::Optional<SyntaxParsingContext> ListContext;
ListContext.emplace(SyntaxContext, Kind);
if (Kind == SyntaxKind::Unknown)
Expand All @@ -1081,64 +1139,20 @@ Parser::parseList(tok RightK, SourceLoc LeftLoc, SourceLoc &RightLoc,
RightLoc = consumeToken(RightK);
return makeParserSuccess();
}
if (TokIsStringInterpolationEOF()) {
if (tokIsStringInterpolationEOF(Tok, RightK)) {
RightLoc = Tok.getLoc();
return makeParserSuccess();
}

ParserStatus Status;
while (true) {
while (Tok.is(tok::comma)) {
diagnose(Tok, diag::unexpected_separator, ",")
.fixItRemove(Tok.getLoc());
consumeToken();
}
SourceLoc StartLoc = Tok.getLoc();

SyntaxParsingContext ElementContext(SyntaxContext, ElementKind);
if (ElementKind == SyntaxKind::Unknown)
ElementContext.setTransparent();
Status |= callback();
if (Tok.is(RightK))
break;
// If the lexer stopped with an EOF token whose spelling is ")", then this
// is actually the tuple that is a string literal interpolation context.
// Just accept the ")" and build the tuple as we usually do.
if (TokIsStringInterpolationEOF()) {
RightLoc = Tok.getLoc();
return Status;
}
// If we haven't made progress, or seeing any error, skip ahead.
if (Tok.getLoc() == StartLoc || Status.isErrorOrHasCompletion()) {
assert(Status.isErrorOrHasCompletion() && "no progress without error");
skipListUntilDeclRBrace(LeftLoc, RightK, tok::comma);
if (Tok.is(RightK) || Tok.isNot(tok::comma))
break;
}
if (consumeIf(tok::comma)) {
if (Tok.isNot(RightK))
continue;
if (!AllowSepAfterLast) {
diagnose(Tok, diag::unexpected_separator, ",")
.fixItRemove(PreviousLoc);
}
break;
}
// If we're in a comma-separated list, the next token is at the
// beginning of a new line and can never start an element, break.
if (Tok.isAtStartOfLine() &&
(Tok.is(tok::r_brace) || isStartOfSwiftDecl() || isStartOfStmt())) {
break;
}
// If we found EOF or such, bailout.
if (Tok.isAny(tok::eof, tok::pound_endif)) {
IsInputIncomplete = true;
break;
}
ParseListItemResult Result;
do {
Result = parseListItem(Status, RightK, LeftLoc, RightLoc, AllowSepAfterLast,
ElementKind, callback);
} while (Result == ParseListItemResult::Continue);

diagnose(Tok, diag::expected_separator, ",")
.fixItInsertAfter(PreviousLoc, ",");
Status.setIsParseError();
if (Result == ParseListItemResult::FinishedInStringInterpolation) {
return Status;
}

ListContext.reset();
Expand Down
Loading