Skip to content

[AST] Add a new attribute @hasAsyncAlternative #36027

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 1 commit into from
Feb 20, 2021
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
6 changes: 6 additions & 0 deletions include/swift/AST/Attr.def
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,12 @@ SIMPLE_DECL_ATTR(reasync, AtReasync,
ABIBreakingToAdd | ABIBreakingToRemove | APIBreakingToAdd | APIBreakingToRemove,
110)

DECL_ATTR(hasAsyncAlternative, HasAsyncAlternative,
OnAbstractFunction | ConcurrencyOnly |
ABIStableToAdd | ABIStableToRemove |
APIStableToAdd | APIStableToRemove,
111)

#undef TYPE_ATTR
#undef DECL_ATTR_ALIAS
#undef CONTEXTUAL_DECL_ATTR_ALIAS
Expand Down
24 changes: 24 additions & 0 deletions include/swift/AST/Attr.h
Original file line number Diff line number Diff line change
Expand Up @@ -2140,6 +2140,30 @@ class TransposeAttr final
}
};

/// The `@hasAsyncAlternative` attribute marks a function as having an async
/// alternative, optionally providing a name (for cases when the alternative
/// has a different name).
class HasAsyncAlternativeAttr final : public DeclAttribute {
public:
/// An optional name of the async alternative function, where the name of the
/// attributed function is used otherwise.
const DeclNameRef Name;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll want type checking to resolve this to a Decl*, and serialization to serialize the Decl reference rather than the name (so clients don't have to resolve the Decl again).


HasAsyncAlternativeAttr(DeclNameRef Name, SourceLoc AtLoc, SourceRange Range)
: DeclAttribute(DAK_HasAsyncAlternative, AtLoc, Range, false),
Name(Name) {}

HasAsyncAlternativeAttr(SourceLoc AtLoc, SourceRange Range)
: DeclAttribute(DAK_HasAsyncAlternative, AtLoc, Range, false) {}

/// Determine whether this attribute has a name associated with it.
bool hasName() const { return !Name.getBaseName().empty(); }

static bool classof(const DeclAttribute *DA) {
return DA->getKind() == DAK_HasAsyncAlternative;
}
};

/// Attributes that may be applied to declarations.
class DeclAttributes {
/// Linked list of declaration attributes.
Expand Down
9 changes: 9 additions & 0 deletions include/swift/AST/DiagnosticsParse.def
Original file line number Diff line number Diff line change
Expand Up @@ -1672,6 +1672,15 @@ ERROR(sil_inst_autodiff_invalid_witness_generic_signature,PointsToFirstBadToken,
"parameters as original function generic signature '%1'",
(StringRef, StringRef))

// hasAsyncAlternative
ERROR(requires_has_async_alternative, none,
"'%0' support is required to be explicitly enabled using "
"-experimental-has-async-alternative-attribute", (StringRef))

ERROR(has_async_alternative_invalid_name, none,
"argument of '%0' attribute must be an identifier or full function name",
(StringRef))

//------------------------------------------------------------------------------
// MARK: Generics parsing diagnostics
//------------------------------------------------------------------------------
Expand Down
3 changes: 3 additions & 0 deletions include/swift/Basic/LangOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,9 @@ namespace swift {
ASTVerifierOverrideKind ASTVerifierOverride =
ASTVerifierOverrideKind::NoOverride;

/// Allow @hasAsyncAlternative attribute
bool EnableExperimentalHasAsyncAlternative = false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need a flag for this. ConcurrencyOnly ensures that it can only be used when -enable-experimental-concurrency is provided.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, wasn't sure whether it being behind concurrency only was okay :). Good to know!


/// Sets the target we are building for and updates platform conditions
/// to match.
///
Expand Down
5 changes: 5 additions & 0 deletions include/swift/Option/FrontendOptions.td
Original file line number Diff line number Diff line change
Expand Up @@ -796,4 +796,9 @@ def disable_ast_verifier : Flag<["-"], "disable-ast-verifier">,
def new_driver_path
: Separate<["-"], "new-driver-path">, MetaVarName<"<path>">,
HelpText<"Path of the new driver to be used">;

def experimental_has_async_alternative_attribute:
Flag<["-"], "experimental-has-async-alternative-attribute">,
HelpText<"Allow use of @hasAsyncAlternative">;

} // end let Flags = [FrontendOption, NoDriverOption, HelpHidden]
2 changes: 2 additions & 0 deletions lib/AST/Attr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1223,6 +1223,8 @@ StringRef DeclAttribute::getAttrName() const {
return "derivative";
case DAK_Transpose:
return "transpose";
case DAK_HasAsyncAlternative:
return "hasAsyncAlternative";
}
llvm_unreachable("bad DeclAttrKind");
}
Expand Down
3 changes: 3 additions & 0 deletions lib/Frontend/CompilerInvocation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -715,6 +715,9 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
}
}

Opts.EnableExperimentalHasAsyncAlternative |=
Args.hasArg(OPT_experimental_has_async_alternative_attribute);

return HadError || UnsupportedOS || UnsupportedArch;
}

Expand Down
54 changes: 54 additions & 0 deletions lib/Parse/ParseDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1570,6 +1570,44 @@ void Parser::parseAllAvailabilityMacroArguments() {
AvailabilityMacrosComputed = true;
}

static HasAsyncAlternativeAttr *parseAsyncAlternativeAttribute(
Parser &P, StringRef AttrName, SourceLoc AtLoc, DeclAttrKind DK) {
SourceLoc Loc = P.PreviousLoc;

// Unnamed @hasAsyncAlternative attribute
if (P.Tok.isNot(tok::l_paren))
return new (P.Context) HasAsyncAlternativeAttr(AtLoc, Loc);

P.consumeToken(tok::l_paren);

if (!P.Tok.is(tok::string_literal)) {
P.diagnose(Loc, diag::attr_expected_string_literal, AttrName);
return nullptr;
}

auto Value = P.getStringLiteralIfNotInterpolated(
Loc, ("argument of '" + AttrName + "'").str());
P.consumeToken(tok::string_literal);
if (!Value)
return nullptr;

ParsedDeclName parsedName = parseDeclName(Value.getValue());
if (!parsedName || !parsedName.ContextName.empty()) {
P.diagnose(AtLoc, diag::has_async_alternative_invalid_name, AttrName);;
return nullptr;
}

SourceRange AttrRange = SourceRange(Loc, P.Tok.getRange().getStart());
if (!P.consumeIf(tok::r_paren)) {
P.diagnose(Loc, diag::attr_expected_rparen, AttrName,
DeclAttribute::isDeclModifier(DK));
return nullptr;
}

return new (P.Context) HasAsyncAlternativeAttr(
parsedName.formDeclNameRef(P.Context), AtLoc, AttrRange);
}

bool Parser::parseNewDeclAttribute(DeclAttributes &Attributes, SourceLoc AtLoc,
DeclAttrKind DK, bool isFromClangAttribute) {
// Ok, it is a valid attribute, eat it, and then process it.
Expand Down Expand Up @@ -2648,6 +2686,22 @@ bool Parser::parseNewDeclAttribute(DeclAttributes &Attributes, SourceLoc AtLoc,
name, AtLoc, range, /*implicit*/ false));
break;
}
case DAK_HasAsyncAlternative: {
auto *attr = parseAsyncAlternativeAttribute(*this, AttrName, AtLoc, DK);
if (!attr) {
skipUntilDeclStmtRBrace(tok::r_paren);
consumeIf(tok::r_paren);
return false;
}

if (!DiscardAttribute) {
if (Context.LangOpts.EnableExperimentalHasAsyncAlternative)
Attributes.add(attr);
else
diagnose(Loc, diag::requires_has_async_alternative, AttrName);
}
break;
}
}

if (DuplicateAttribute) {
Expand Down
1 change: 1 addition & 0 deletions lib/Sema/TypeCheckAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ class AttributeChecker : public AttributeVisitor<AttributeChecker> {
IGNORED_ATTR(Effects)
IGNORED_ATTR(Exported)
IGNORED_ATTR(ForbidSerializingReference)
IGNORED_ATTR(HasAsyncAlternative)
IGNORED_ATTR(HasStorage)
IGNORED_ATTR(HasMissingDesignatedInitializers)
IGNORED_ATTR(InheritsConvenienceInitializers)
Expand Down
1 change: 1 addition & 0 deletions lib/Sema/TypeCheckDeclOverride.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1446,6 +1446,7 @@ namespace {
UNINTERESTING_ATTR(Exported)
UNINTERESTING_ATTR(ForbidSerializingReference)
UNINTERESTING_ATTR(GKInspectable)
UNINTERESTING_ATTR(HasAsyncAlternative)
UNINTERESTING_ATTR(HasMissingDesignatedInitializers)
UNINTERESTING_ATTR(IBAction)
UNINTERESTING_ATTR(IBDesignable)
Expand Down
21 changes: 21 additions & 0 deletions lib/Serialization/Deserialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4529,6 +4529,27 @@ llvm::Error DeclDeserializer::deserializeDeclAttributes() {
break;
}

case decls_block::HasAsyncAlternative_DECL_ATTR: {
bool isCompound;
ArrayRef<uint64_t> rawPieces;
serialization::decls_block::HasAsyncAlternativeDeclAttrLayout::readRecord(
scratch, isCompound, rawPieces);

DeclNameRef name;
if (!rawPieces.empty()) {
auto baseName = MF.getDeclBaseName(rawPieces[0]);
SmallVector<Identifier, 4> pieces;
for (auto rawPiece : rawPieces.drop_front())
pieces.push_back(MF.getIdentifier(rawPiece));
name = !isCompound ? DeclNameRef({baseName})
: DeclNameRef({ctx, baseName, pieces});
}

Attr = new (ctx) HasAsyncAlternativeAttr(name, SourceLoc(),
SourceRange());
break;
}

#define SIMPLE_DECL_ATTR(NAME, CLASS, ...) \
case decls_block::CLASS##_DECL_ATTR: { \
bool isImplicit; \
Expand Down
8 changes: 7 additions & 1 deletion lib/Serialization/ModuleFormat.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0;
/// describe what change you made. The content of this comment isn't important;
/// it just ensures a conflict if two people change the module format.
/// Don't worry about adhering to the 80-column limit for this line.
const uint16_t SWIFTMODULE_VERSION_MINOR = 598; // reasync
const uint16_t SWIFTMODULE_VERSION_MINOR = 599; // has-async-alternative

/// A standard hash seed used for all string hashes in a serialized module.
///
Expand Down Expand Up @@ -1913,6 +1913,12 @@ namespace decls_block {
BCArray<BCFixed<1>> // Transposed parameter indices' bitvector.
>;

using HasAsyncAlternativeDeclAttrLayout = BCRecordLayout<
HasAsyncAlternative_DECL_ATTR,
BCFixed<1>, // True if compound name
BCArray<IdentifierIDField> // Name and parameters
>;

#define SIMPLE_DECL_ATTR(X, CLASS, ...) \
using CLASS##DeclAttrLayout = BCRecordLayout< \
CLASS##_DECL_ATTR, \
Expand Down
18 changes: 18 additions & 0 deletions lib/Serialization/Serialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2614,6 +2614,24 @@ class Serializer::DeclSerializer : public DeclVisitor<DeclSerializer> {
origDeclID, paramIndicesVector);
return;
}

case DAK_HasAsyncAlternative: {
auto *attr = cast<HasAsyncAlternativeAttr>(DA);
auto abbrCode =
S.DeclTypeAbbrCodes[HasAsyncAlternativeDeclAttrLayout::Code];

SmallVector<IdentifierID, 4> pieces;
if (attr->hasName()) {
pieces.push_back(S.addDeclBaseNameRef(attr->Name.getBaseName()));
for (auto argName : attr->Name.getArgumentNames())
pieces.push_back(S.addDeclBaseNameRef(argName));
}

HasAsyncAlternativeDeclAttrLayout::emitRecord(
S.Out, S.ScratchRecord, abbrCode, attr->Name.isCompoundName(),
pieces);
return;
}
}
}

Expand Down
56 changes: 56 additions & 0 deletions test/attr/attr_hasasyncalternative.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// REQUIRES: concurrency

// RUN: %target-typecheck-verify-swift -enable-experimental-concurrency -experimental-has-async-alternative-attribute

@hasAsyncAlternative
func func1() {}

@hasAsyncAlternative("betterFunc")
func func2() {}

@hasAsyncAlternative("betterFunc(param:)")
func func3() {}

@hasAsyncAlternative("not+identifier") // expected-error {{argument of 'hasAsyncAlternative' attribute must be an identifier or full function name}}
func func4() {}

@hasAsyncAlternative("$dollarname") // expected-error {{argument of 'hasAsyncAlternative' attribute must be an identifier or full function name}}
func func5() {}

@hasAsyncAlternative("TypePrefix.func") // expected-error {{argument of 'hasAsyncAlternative' attribute must be an identifier or full function name}}
func func6() {}

@hasAsyncAlternative("interpreted \()") // expected-error {{argument of 'hasAsyncAlternative' cannot be an interpolated string literal}}
func func7() {}

@hasAsyncAlternative("missingRParen" // expected-error {{expected ')' in 'hasAsyncAlternative' attribute}}
func func8() {}

@hasAsyncAlternative // expected-note {{attribute already specified here}}
@hasAsyncAlternative("other") // expected-error {{duplicate attribute}}
func duplicate() {}

@hasAsyncAlternative // expected-error {{'@hasAsyncAlternative' attribute cannot be applied to this declaration}}
protocol SomeProto {
@hasAsyncAlternative
func protoFunc()
}

@hasAsyncAlternative // expected-error {{'@hasAsyncAlternative' attribute cannot be applied to this declaration}}
struct SomeStruct: SomeProto {
func protoFunc() { }

@hasAsyncAlternative
func structFunc() { }

@hasAsyncAlternative
static func staticStructFunc() { }
}

@hasAsyncAlternative // expected-error {{'@hasAsyncAlternative' attribute cannot be applied to this declaration}}
class SomeClass: SomeProto {
func protoFunc() { }

@hasAsyncAlternative
func classFunc() { }
}
4 changes: 4 additions & 0 deletions test/attr/attr_hasasyncalternative_conc_disabled.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// RUN: %target-typecheck-verify-swift

@hasAsyncAlternative // expected-error {{'hasAsyncAlternative' attribute is only valid when experimental concurrency is enabled}}
func func1() {}
6 changes: 6 additions & 0 deletions test/attr/attr_hasasyncalternative_disabled.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// REQUIRES: concurrency

// RUN: %target-typecheck-verify-swift -enable-experimental-concurrency

@hasAsyncAlternative // expected-error {{'hasAsyncAlternative' support is required to be explicitly enabled using -experimental-has-async-alternative-attribute}}
func func1() {}