Skip to content

Commit 3a7bf23

Browse files
committed
[AST] Add a new attribute @hasAsyncAlternative
This attribute marks a function has having an async alternative, optionally providing the name of that function as a string. Intended to be used to allow warnings when using a function with an async alternative in an asynchronous context, to make the async refactorings more accurate, and for documentation.
1 parent 077d96c commit 3a7bf23

16 files changed

+220
-1
lines changed

include/swift/AST/Attr.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,12 @@ SIMPLE_DECL_ATTR(reasync, AtReasync,
624624
ABIBreakingToAdd | ABIBreakingToRemove | APIBreakingToAdd | APIBreakingToRemove,
625625
110)
626626

627+
DECL_ATTR(hasAsyncAlternative, HasAsyncAlternative,
628+
OnAbstractFunction | ConcurrencyOnly |
629+
ABIStableToAdd | ABIStableToRemove |
630+
APIStableToAdd | APIStableToRemove,
631+
111)
632+
627633
#undef TYPE_ATTR
628634
#undef DECL_ATTR_ALIAS
629635
#undef CONTEXTUAL_DECL_ATTR_ALIAS

include/swift/AST/Attr.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2140,6 +2140,30 @@ class TransposeAttr final
21402140
}
21412141
};
21422142

2143+
/// The `@hasAsyncAlternative` attribute marks a function as having an async
2144+
/// alternative, optionally providing a name (for cases when the alternative
2145+
/// has a different name).
2146+
class HasAsyncAlternativeAttr final : public DeclAttribute {
2147+
public:
2148+
/// An optional name of the async alternative function, where the name of the
2149+
/// attributed function is used otherwise.
2150+
const DeclNameRef Name;
2151+
2152+
HasAsyncAlternativeAttr(DeclNameRef Name, SourceLoc AtLoc, SourceRange Range)
2153+
: DeclAttribute(DAK_HasAsyncAlternative, AtLoc, Range, false),
2154+
Name(Name) {}
2155+
2156+
HasAsyncAlternativeAttr(SourceLoc AtLoc, SourceRange Range)
2157+
: DeclAttribute(DAK_HasAsyncAlternative, AtLoc, Range, false) {}
2158+
2159+
/// Determine whether this attribute has a name associated with it.
2160+
bool hasName() const { return !Name.getBaseName().empty(); }
2161+
2162+
static bool classof(const DeclAttribute *DA) {
2163+
return DA->getKind() == DAK_HasAsyncAlternative;
2164+
}
2165+
};
2166+
21432167
/// Attributes that may be applied to declarations.
21442168
class DeclAttributes {
21452169
/// Linked list of declaration attributes.

include/swift/AST/DiagnosticsParse.def

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1672,6 +1672,15 @@ ERROR(sil_inst_autodiff_invalid_witness_generic_signature,PointsToFirstBadToken,
16721672
"parameters as original function generic signature '%1'",
16731673
(StringRef, StringRef))
16741674

1675+
// hasAsyncAlternative
1676+
ERROR(requires_has_async_alternative, none,
1677+
"'%0' support is required to be explicitly enabled using "
1678+
"-experimental-has-async-alternative-attribute", (StringRef))
1679+
1680+
ERROR(has_async_alternative_invalid_name, none,
1681+
"argument of '%0' attribute must be an identifier or full function name",
1682+
(StringRef))
1683+
16751684
//------------------------------------------------------------------------------
16761685
// MARK: Generics parsing diagnostics
16771686
//------------------------------------------------------------------------------

include/swift/Basic/LangOptions.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,9 @@ namespace swift {
387387
ASTVerifierOverrideKind ASTVerifierOverride =
388388
ASTVerifierOverrideKind::NoOverride;
389389

390+
/// Allow @hasAsyncAlternative attribute
391+
bool EnableExperimentalHasAsyncAlternative = false;
392+
390393
/// Sets the target we are building for and updates platform conditions
391394
/// to match.
392395
///

include/swift/Option/FrontendOptions.td

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -796,4 +796,9 @@ def disable_ast_verifier : Flag<["-"], "disable-ast-verifier">,
796796
def new_driver_path
797797
: Separate<["-"], "new-driver-path">, MetaVarName<"<path>">,
798798
HelpText<"Path of the new driver to be used">;
799+
800+
def experimental_has_async_alternative_attribute:
801+
Flag<["-"], "experimental-has-async-alternative-attribute">,
802+
HelpText<"Allow use of @hasAsyncAlternative">;
803+
799804
} // end let Flags = [FrontendOption, NoDriverOption, HelpHidden]

lib/AST/Attr.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1223,6 +1223,8 @@ StringRef DeclAttribute::getAttrName() const {
12231223
return "derivative";
12241224
case DAK_Transpose:
12251225
return "transpose";
1226+
case DAK_HasAsyncAlternative:
1227+
return "hasAsyncAlternative";
12261228
}
12271229
llvm_unreachable("bad DeclAttrKind");
12281230
}

lib/Frontend/CompilerInvocation.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,9 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
715715
}
716716
}
717717

718+
Opts.EnableExperimentalHasAsyncAlternative |=
719+
Args.hasArg(OPT_experimental_has_async_alternative_attribute);
720+
718721
return HadError || UnsupportedOS || UnsupportedArch;
719722
}
720723

lib/Parse/ParseDecl.cpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1570,6 +1570,44 @@ void Parser::parseAllAvailabilityMacroArguments() {
15701570
AvailabilityMacrosComputed = true;
15711571
}
15721572

1573+
static HasAsyncAlternativeAttr *parseAsyncAlternativeAttribute(
1574+
Parser &P, StringRef AttrName, SourceLoc AtLoc, DeclAttrKind DK) {
1575+
SourceLoc Loc = P.PreviousLoc;
1576+
1577+
// Unnamed @hasAsyncAlternative attribute
1578+
if (P.Tok.isNot(tok::l_paren))
1579+
return new (P.Context) HasAsyncAlternativeAttr(AtLoc, Loc);
1580+
1581+
P.consumeToken(tok::l_paren);
1582+
1583+
if (!P.Tok.is(tok::string_literal)) {
1584+
P.diagnose(Loc, diag::attr_expected_string_literal, AttrName);
1585+
return nullptr;
1586+
}
1587+
1588+
auto Value = P.getStringLiteralIfNotInterpolated(
1589+
Loc, ("argument of '" + AttrName + "'").str());
1590+
P.consumeToken(tok::string_literal);
1591+
if (!Value)
1592+
return nullptr;
1593+
1594+
ParsedDeclName parsedName = parseDeclName(Value.getValue());
1595+
if (!parsedName || !parsedName.ContextName.empty()) {
1596+
P.diagnose(AtLoc, diag::has_async_alternative_invalid_name, AttrName);;
1597+
return nullptr;
1598+
}
1599+
1600+
SourceRange AttrRange = SourceRange(Loc, P.Tok.getRange().getStart());
1601+
if (!P.consumeIf(tok::r_paren)) {
1602+
P.diagnose(Loc, diag::attr_expected_rparen, AttrName,
1603+
DeclAttribute::isDeclModifier(DK));
1604+
return nullptr;
1605+
}
1606+
1607+
return new (P.Context) HasAsyncAlternativeAttr(
1608+
parsedName.formDeclNameRef(P.Context), AtLoc, AttrRange);
1609+
}
1610+
15731611
bool Parser::parseNewDeclAttribute(DeclAttributes &Attributes, SourceLoc AtLoc,
15741612
DeclAttrKind DK, bool isFromClangAttribute) {
15751613
// Ok, it is a valid attribute, eat it, and then process it.
@@ -2648,6 +2686,22 @@ bool Parser::parseNewDeclAttribute(DeclAttributes &Attributes, SourceLoc AtLoc,
26482686
name, AtLoc, range, /*implicit*/ false));
26492687
break;
26502688
}
2689+
case DAK_HasAsyncAlternative: {
2690+
auto *attr = parseAsyncAlternativeAttribute(*this, AttrName, AtLoc, DK);
2691+
if (!attr) {
2692+
skipUntilDeclStmtRBrace(tok::r_paren);
2693+
consumeIf(tok::r_paren);
2694+
return false;
2695+
}
2696+
2697+
if (!DiscardAttribute) {
2698+
if (Context.LangOpts.EnableExperimentalHasAsyncAlternative)
2699+
Attributes.add(attr);
2700+
else
2701+
diagnose(Loc, diag::requires_has_async_alternative, AttrName);
2702+
}
2703+
break;
2704+
}
26512705
}
26522706

26532707
if (DuplicateAttribute) {

lib/Sema/TypeCheckAttr.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ class AttributeChecker : public AttributeVisitor<AttributeChecker> {
105105
IGNORED_ATTR(Effects)
106106
IGNORED_ATTR(Exported)
107107
IGNORED_ATTR(ForbidSerializingReference)
108+
IGNORED_ATTR(HasAsyncAlternative)
108109
IGNORED_ATTR(HasStorage)
109110
IGNORED_ATTR(HasMissingDesignatedInitializers)
110111
IGNORED_ATTR(InheritsConvenienceInitializers)

lib/Sema/TypeCheckDeclOverride.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1446,6 +1446,7 @@ namespace {
14461446
UNINTERESTING_ATTR(Exported)
14471447
UNINTERESTING_ATTR(ForbidSerializingReference)
14481448
UNINTERESTING_ATTR(GKInspectable)
1449+
UNINTERESTING_ATTR(HasAsyncAlternative)
14491450
UNINTERESTING_ATTR(HasMissingDesignatedInitializers)
14501451
UNINTERESTING_ATTR(IBAction)
14511452
UNINTERESTING_ATTR(IBDesignable)

lib/Serialization/Deserialization.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4529,6 +4529,27 @@ llvm::Error DeclDeserializer::deserializeDeclAttributes() {
45294529
break;
45304530
}
45314531

4532+
case decls_block::HasAsyncAlternative_DECL_ATTR: {
4533+
bool isCompound;
4534+
ArrayRef<uint64_t> rawPieces;
4535+
serialization::decls_block::HasAsyncAlternativeDeclAttrLayout::readRecord(
4536+
scratch, isCompound, rawPieces);
4537+
4538+
DeclNameRef name;
4539+
if (!rawPieces.empty()) {
4540+
auto baseName = MF.getDeclBaseName(rawPieces[0]);
4541+
SmallVector<Identifier, 4> pieces;
4542+
for (auto rawPiece : rawPieces.drop_front())
4543+
pieces.push_back(MF.getIdentifier(rawPiece));
4544+
name = !isCompound ? DeclNameRef({baseName})
4545+
: DeclNameRef({ctx, baseName, pieces});
4546+
}
4547+
4548+
Attr = new (ctx) HasAsyncAlternativeAttr(name, SourceLoc(),
4549+
SourceRange());
4550+
break;
4551+
}
4552+
45324553
#define SIMPLE_DECL_ATTR(NAME, CLASS, ...) \
45334554
case decls_block::CLASS##_DECL_ATTR: { \
45344555
bool isImplicit; \

lib/Serialization/ModuleFormat.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0;
5656
/// describe what change you made. The content of this comment isn't important;
5757
/// it just ensures a conflict if two people change the module format.
5858
/// Don't worry about adhering to the 80-column limit for this line.
59-
const uint16_t SWIFTMODULE_VERSION_MINOR = 598; // reasync
59+
const uint16_t SWIFTMODULE_VERSION_MINOR = 599; // has-async-alternative
6060

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

1916+
using HasAsyncAlternativeDeclAttrLayout = BCRecordLayout<
1917+
HasAsyncAlternative_DECL_ATTR,
1918+
BCFixed<1>, // True if compound name
1919+
BCArray<IdentifierIDField> // Name and parameters
1920+
>;
1921+
19161922
#define SIMPLE_DECL_ATTR(X, CLASS, ...) \
19171923
using CLASS##DeclAttrLayout = BCRecordLayout< \
19181924
CLASS##_DECL_ATTR, \

lib/Serialization/Serialization.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2614,6 +2614,24 @@ class Serializer::DeclSerializer : public DeclVisitor<DeclSerializer> {
26142614
origDeclID, paramIndicesVector);
26152615
return;
26162616
}
2617+
2618+
case DAK_HasAsyncAlternative: {
2619+
auto *attr = cast<HasAsyncAlternativeAttr>(DA);
2620+
auto abbrCode =
2621+
S.DeclTypeAbbrCodes[HasAsyncAlternativeDeclAttrLayout::Code];
2622+
2623+
SmallVector<IdentifierID, 4> pieces;
2624+
if (attr->hasName()) {
2625+
pieces.push_back(S.addDeclBaseNameRef(attr->Name.getBaseName()));
2626+
for (auto argName : attr->Name.getArgumentNames())
2627+
pieces.push_back(S.addDeclBaseNameRef(argName));
2628+
}
2629+
2630+
HasAsyncAlternativeDeclAttrLayout::emitRecord(
2631+
S.Out, S.ScratchRecord, abbrCode, attr->Name.isCompoundName(),
2632+
pieces);
2633+
return;
2634+
}
26172635
}
26182636
}
26192637

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// REQUIRES: concurrency
2+
3+
// RUN: %target-typecheck-verify-swift -enable-experimental-concurrency -experimental-has-async-alternative-attribute
4+
5+
@hasAsyncAlternative
6+
func func1() {}
7+
8+
@hasAsyncAlternative("betterFunc")
9+
func func2() {}
10+
11+
@hasAsyncAlternative("betterFunc(param:)")
12+
func func3() {}
13+
14+
@hasAsyncAlternative("not+identifier") // expected-error {{argument of 'hasAsyncAlternative' attribute must be an identifier or full function name}}
15+
func func4() {}
16+
17+
@hasAsyncAlternative("$dollarname") // expected-error {{argument of 'hasAsyncAlternative' attribute must be an identifier or full function name}}
18+
func func5() {}
19+
20+
@hasAsyncAlternative("TypePrefix.func") // expected-error {{argument of 'hasAsyncAlternative' attribute must be an identifier or full function name}}
21+
func func6() {}
22+
23+
@hasAsyncAlternative("interpreted \()") // expected-error {{argument of 'hasAsyncAlternative' cannot be an interpolated string literal}}
24+
func func7() {}
25+
26+
@hasAsyncAlternative("missingRParen" // expected-error {{expected ')' in 'hasAsyncAlternative' attribute}}
27+
func func8() {}
28+
29+
@hasAsyncAlternative // expected-note {{attribute already specified here}}
30+
@hasAsyncAlternative("other") // expected-error {{duplicate attribute}}
31+
func duplicate() {}
32+
33+
@hasAsyncAlternative // expected-error {{'@hasAsyncAlternative' attribute cannot be applied to this declaration}}
34+
protocol SomeProto {
35+
@hasAsyncAlternative
36+
func protoFunc()
37+
}
38+
39+
@hasAsyncAlternative // expected-error {{'@hasAsyncAlternative' attribute cannot be applied to this declaration}}
40+
struct SomeStruct: SomeProto {
41+
func protoFunc() { }
42+
43+
@hasAsyncAlternative
44+
func structFunc() { }
45+
46+
@hasAsyncAlternative
47+
static func staticStructFunc() { }
48+
}
49+
50+
@hasAsyncAlternative // expected-error {{'@hasAsyncAlternative' attribute cannot be applied to this declaration}}
51+
class SomeClass: SomeProto {
52+
func protoFunc() { }
53+
54+
@hasAsyncAlternative
55+
func classFunc() { }
56+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// RUN: %target-typecheck-verify-swift
2+
3+
@hasAsyncAlternative // expected-error {{'hasAsyncAlternative' attribute is only valid when experimental concurrency is enabled}}
4+
func func1() {}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// REQUIRES: concurrency
2+
3+
// RUN: %target-typecheck-verify-swift -enable-experimental-concurrency
4+
5+
@hasAsyncAlternative // expected-error {{'hasAsyncAlternative' support is required to be explicitly enabled using -experimental-has-async-alternative-attribute}}
6+
func func1() {}

0 commit comments

Comments
 (0)