Skip to content

Commit 37d1fc8

Browse files
committed
Tie attributes to language features
The new `DECL_ATTR_FEATURE_REQUIREMENT` macro in DeclAttr.def can be used to declare that an attribute should only be available when a related language feature is enabled. Effects: • `#if hasAttribute(someAttr)` will return `false` unless the required feature is enabled. • Code completion will not include the attribute unless the required feature is enabled. • `TypeChecker::checkDeclAttributes()` diagnoses non-implicit uses of the attribute. Add this mechanism and use it to tie @abi to the ABIAttribute feature. Also design tests for it.
1 parent 1a1988b commit 37d1fc8

File tree

11 files changed

+234
-14
lines changed

11 files changed

+234
-14
lines changed

include/swift/AST/Attr.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include "swift/AST/StorageImpl.h"
3232
#include "swift/Basic/Debug.h"
3333
#include "swift/Basic/EnumTraits.h"
34+
#include "swift/Basic/Feature.h"
3435
#include "swift/Basic/InlineBitfield.h"
3536
#include "swift/Basic/Located.h"
3637
#include "swift/Basic/OptimizationMode.h"
@@ -483,6 +484,8 @@ class DeclAttribute : public AttributeBase {
483484
LLVM_READNONE
484485
static bool canAttributeAppearOnDeclKind(DeclAttrKind DAK, DeclKind DK);
485486

487+
static std::optional<Feature> getRequiredFeature(DeclAttrKind DK);
488+
486489
/// Returns the source name of the attribute, without the @ or any arguments.
487490
StringRef getAttrName() const;
488491

include/swift/AST/DeclAttr.def

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,16 @@
4242
DECL_ATTR_ALIAS(SPELLING, CLASS)
4343
#endif
4444

45+
// Diagnose any use of the attribute CLASS without FEATURE_NAME enabled,
46+
// and also enable other special behavior. If you use this for an experimental
47+
// feature, please add test cases to:
48+
//
49+
// * test/attr/feature_requirement.swift
50+
// * test/IDE/complete_decl_attribute_feature_requirement.swift
51+
#ifndef DECL_ATTR_FEATURE_REQUIREMENT
52+
#define DECL_ATTR_FEATURE_REQUIREMENT(CLASS, FEATURE_NAME)
53+
#endif
54+
4555
#ifndef LAST_DECL_ATTR
4656
#define LAST_DECL_ATTR(CLASS)
4757
#endif
@@ -516,6 +526,7 @@ DECL_ATTR(lifetime, Lifetime,
516526
DECL_ATTR(abi, ABI,
517527
OnAbstractFunction | OnVar /* will eventually add types */ | LongAttribute | ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIStableToRemove,
518528
162)
529+
DECL_ATTR_FEATURE_REQUIREMENT(ABI, ABIAttribute)
519530

520531
LAST_DECL_ATTR(ABI)
521532

@@ -525,4 +536,5 @@ LAST_DECL_ATTR(ABI)
525536
#undef CONTEXTUAL_SIMPLE_DECL_ATTR
526537
#undef DECL_ATTR
527538
#undef CONTEXTUAL_DECL_ATTR
539+
#undef DECL_ATTR_FEATURE_REQUIREMENT
528540
#undef LAST_DECL_ATTR

include/swift/IDE/CompletionLookup.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -597,7 +597,7 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
597597
SourceLoc CodeCompletionLoc);
598598

599599
static bool canUseAttributeOnDecl(DeclAttrKind DAK, bool IsInSil,
600-
bool IsConcurrencyEnabled,
600+
const LangOptions &langOpts,
601601
std::optional<DeclKind> DK, StringRef Name);
602602

603603
void getAttributeDeclCompletions(bool IsInSil, std::optional<DeclKind> DK);

lib/AST/Attr.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1887,6 +1887,18 @@ uint64_t DeclAttribute::getOptions(DeclAttrKind DK) {
18871887
llvm_unreachable("bad DeclAttrKind");
18881888
}
18891889

1890+
std::optional<Feature> DeclAttribute::getRequiredFeature(DeclAttrKind DK) {
1891+
switch (DK) {
1892+
#define DECL_ATTR_FEATURE_REQUIREMENT(CLASS, FEATURE_NAME) \
1893+
case DeclAttrKind::CLASS: \
1894+
return Feature::FEATURE_NAME;
1895+
#include "swift/AST/DeclAttr.def"
1896+
default:
1897+
return std::nullopt;
1898+
}
1899+
llvm_unreachable("bad DeclAttrKind");
1900+
}
1901+
18901902
StringRef DeclAttribute::getAttrName() const {
18911903
switch (getKind()) {
18921904
#define SIMPLE_DECL_ATTR(NAME, CLASS, ...) \
@@ -3169,6 +3181,10 @@ static bool hasDeclAttribute(const LangOptions &langOpts,
31693181
if (DeclAttribute::isConcurrencyOnly(*kind))
31703182
return false;
31713183

3184+
if (auto feature = DeclAttribute::getRequiredFeature(*kind))
3185+
if (!langOpts.hasFeature(*feature))
3186+
return false;
3187+
31723188
return true;
31733189
}
31743190

lib/IDE/CodeCompletion.cpp

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -686,7 +686,7 @@ static void addKeyword(CodeCompletionResultSink &Sink, StringRef Name,
686686
}
687687

688688
static void addDeclKeywords(CodeCompletionResultSink &Sink, DeclContext *DC,
689-
bool IsConcurrencyEnabled) {
689+
const LangOptions &langOpts) {
690690
auto isTypeDeclIntroducer = [](CodeCompletionKeywordKind Kind,
691691
std::optional<DeclAttrKind> DAK) -> bool {
692692
switch (Kind) {
@@ -799,10 +799,16 @@ static void addDeclKeywords(CodeCompletionResultSink &Sink, DeclContext *DC,
799799
return;
800800

801801
// Remove keywords only available when concurrency is enabled.
802-
if (DAK.has_value() && !IsConcurrencyEnabled &&
802+
if (DAK.has_value() && !langOpts.EnableExperimentalConcurrency &&
803803
DeclAttribute::isConcurrencyOnly(*DAK))
804804
return;
805805

806+
// Remove experimental keywords.
807+
if (DAK.has_value())
808+
if (auto feature = DeclAttribute::getRequiredFeature(*DAK))
809+
if (!langOpts.hasFeature(*feature))
810+
return;
811+
806812
CodeCompletionFlair flair = getFlair(Kind, DAK);
807813

808814
// Special case for 'actor'. Get the same flair with 'kw_class'.
@@ -1020,8 +1026,7 @@ void CodeCompletionCallbacksImpl::addKeywords(CodeCompletionResultSink &Sink,
10201026
LLVM_FALLTHROUGH;
10211027
}
10221028
case CompletionKind::StmtOrExpr:
1023-
addDeclKeywords(Sink, CurDeclContext,
1024-
Context.LangOpts.EnableExperimentalConcurrency);
1029+
addDeclKeywords(Sink, CurDeclContext, Context.LangOpts);
10251030
addStmtKeywords(Sink, CurDeclContext, MaybeFuncBody);
10261031
addClosureSignatureKeywordsIfApplicable(Sink, CurDeclContext);
10271032

@@ -1122,8 +1127,7 @@ void CodeCompletionCallbacksImpl::addKeywords(CodeCompletionResultSink &Sink,
11221127
.Default(false);
11231128
}) != ParsedKeywords.end();
11241129
if (!HasDeclIntroducer) {
1125-
addDeclKeywords(Sink, CurDeclContext,
1126-
Context.LangOpts.EnableExperimentalConcurrency);
1130+
addDeclKeywords(Sink, CurDeclContext, Context.LangOpts);
11271131
addLetVarKeywords(Sink);
11281132
}
11291133
break;

lib/IDE/CompletionLookup.cpp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3008,7 +3008,7 @@ void CompletionLookup::getGenericRequirementCompletions(
30083008
}
30093009

30103010
bool CompletionLookup::canUseAttributeOnDecl(DeclAttrKind DAK, bool IsInSil,
3011-
bool IsConcurrencyEnabled,
3011+
const LangOptions &langOpts,
30123012
std::optional<DeclKind> DK,
30133013
StringRef Name) {
30143014
if (DeclAttribute::isUserInaccessible(DAK))
@@ -3019,8 +3019,12 @@ bool CompletionLookup::canUseAttributeOnDecl(DeclAttrKind DAK, bool IsInSil,
30193019
return false;
30203020
if (!IsInSil && DeclAttribute::isSilOnly(DAK))
30213021
return false;
3022-
if (!IsConcurrencyEnabled && DeclAttribute::isConcurrencyOnly(DAK))
3022+
if (!langOpts.EnableExperimentalConcurrency
3023+
&& DeclAttribute::isConcurrencyOnly(DAK))
30233024
return false;
3025+
if (auto feature = DeclAttribute::getRequiredFeature(DAK))
3026+
if (!langOpts.hasFeature(*feature))
3027+
return false;
30243028
if (!DK.has_value())
30253029
return true;
30263030
// Hide underscored attributes even if they are not marked as user
@@ -3044,11 +3048,10 @@ void CompletionLookup::getAttributeDeclCompletions(bool IsInSil,
30443048
#include "swift/AST/DeclNodes.def"
30453049
}
30463050
}
3047-
bool IsConcurrencyEnabled = Ctx.LangOpts.EnableExperimentalConcurrency;
30483051
std::string Description = TargetName.str() + " Attribute";
30493052
#define DECL_ATTR_ALIAS(KEYWORD, NAME) DECL_ATTR(KEYWORD, NAME, 0, 0)
30503053
#define DECL_ATTR(KEYWORD, NAME, ...) \
3051-
if (canUseAttributeOnDecl(DeclAttrKind::NAME, IsInSil, IsConcurrencyEnabled, \
3054+
if (canUseAttributeOnDecl(DeclAttrKind::NAME, IsInSil, Ctx.LangOpts, \
30523055
DK, #KEYWORD)) \
30533056
addDeclAttrKeyword(#KEYWORD, Description);
30543057
#include "swift/AST/DeclAttr.def"

lib/Sema/TypeCheckAttr.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1789,6 +1789,18 @@ void TypeChecker::checkDeclAttributes(Decl *D) {
17891789
for (auto attr : D->getExpandedAttrs()) {
17901790
if (!attr->isValid()) continue;
17911791

1792+
// If the attribute requires a feature that is not enabled, and it is not
1793+
// an implicit attribute, diagnose and disable it.
1794+
if (auto feature = DeclAttribute::getRequiredFeature(attr->getKind())) {
1795+
if (!attr->isImplicit()
1796+
&& !D->getASTContext().LangOpts.hasFeature(*feature)) {
1797+
Checker.diagnoseAndRemoveAttr(attr, diag::requires_experimental_feature,
1798+
attr->getAttrName(), false,
1799+
getFeatureName(*feature));
1800+
continue;
1801+
}
1802+
}
1803+
17921804
// If Attr.def says that the attribute cannot appear on this kind of
17931805
// declaration, diagnose it and disable it.
17941806
if (attr->canAppearOnDecl(D)) {
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// This contains code completion test cases for features covered by experimental
2+
// feature flags, and tests both the case when the feature is disabled and when
3+
// it's enabled. When a feature becomes non-experimental, move its test cases
4+
// into the normal complete_decl_attribute.swift test file.
5+
6+
// REQUIRES: asserts
7+
8+
// RUN: %batch-code-completion -filecheck-additional-suffix _DISABLED
9+
// RUN: %batch-code-completion -filecheck-additional-suffix _ENABLED -enable-experimental-feature ABIAttribute
10+
11+
// NOTE: Please do not include the ", N items" after "Begin completions". The
12+
// item count creates needless merge conflicts given that an "End completions"
13+
// line exists for each test.
14+
15+
@#^KEYWORD2^# func method(){}
16+
17+
// KEYWORD2: Begin completions
18+
// KEYWORD2_ENABLED-DAG: Keyword/None: abi[#Func Attribute#]; name=abi
19+
// KEYWORD2_DISABLED-NOT: Keyword/None: abi[#{{.*}} Attribute#]; name=abi
20+
// KEYWORD2: End completions
21+
22+
@#^KEYWORD3^# class C {}
23+
24+
// KEYWORD3: Begin completions
25+
// KEYWORD3_ENABLED-NOT: Keyword/None: abi[#{{.*}} Attribute#]; name=abi
26+
// KEYWORD3_DISABLED-NOT: Keyword/None: abi[#{{.*}} Attribute#]; name=abi
27+
// KEYWORD3: End completions
28+
29+
@#^KEYWORD3_2?check=KEYWORD3^#IB class C2 {}
30+
// Same as KEYWORD3.
31+
32+
@#^KEYWORD4^# enum E {}
33+
// KEYWORD4: Begin completions
34+
// KEYWORD4_ENABLED-NOT: Keyword/None: abi[#{{.*}} Attribute#]; name=abi
35+
// KEYWORD4_DISABLED-NOT: Keyword/None: abi[#{{.*}} Attribute#]; name=abi
36+
// KEYWORD4: End completions
37+
38+
@#^KEYWORD5^# struct S{}
39+
// KEYWORD5: Begin completions
40+
// KEYWORD5_ENABLED-NOT: Keyword/None: abi[#{{.*}} Attribute#]; name=abi
41+
// KEYWORD5_DISABLED-NOT: Keyword/None: abi[#{{.*}} Attribute#]; name=abi
42+
// KEYWORD5: End completions
43+
44+
@#^ON_GLOBALVAR^# var globalVar
45+
// ON_GLOBALVAR: Begin completions
46+
// ON_GLOBALVAR_ENABLED-DAG: Keyword/None: abi[#Var Attribute#]; name=abi
47+
// ON_GLOBALVAR_DISABLED-NOT: Keyword/None: abi[#{{.*}} Attribute#]; name=abi
48+
// ON_GLOBALVAR: End completions
49+
50+
struct _S {
51+
@#^ON_INIT^# init()
52+
// ON_INIT: Begin completions
53+
// ON_INIT_ENABLED-DAG: Keyword/None: abi[#Constructor Attribute#]; name=abi
54+
// ON_INIT_DISABLED-NOT: Keyword/None: abi[#{{.*}} Attribute#]; name=abi
55+
// ON_INIT: End completions
56+
57+
@#^ON_PROPERTY^# var foo
58+
// ON_PROPERTY: Begin completions
59+
// ON_PROPERTY_ENABLED-DAG: Keyword/None: abi[#Var Attribute#]; name=abi
60+
// ON_PROPERTY_DISABLED-NOT: Keyword/None: abi[#{{.*}} Attribute#]; name=abi
61+
// ON_PROPERTY: End completions
62+
63+
@#^ON_METHOD^# private
64+
func foo()
65+
// ON_METHOD: Begin completions
66+
// ON_METHOD_ENABLED-DAG: Keyword/None: abi[#Func Attribute#]; name=abi
67+
// ON_METHOD_DISABLED-NOT: Keyword/None: abi[#{{.*}} Attribute#]; name=abi
68+
// ON_METHOD: End completions
69+
70+
71+
func bar(@#^ON_PARAM_1?check=ON_PARAM^#)
72+
// ON_PARAM: Begin completions
73+
// ON_PARAM_ENABLED-NOT: Keyword/None: abi[#{{.*}} Attribute#]; name=abi
74+
// ON_PARAM_DISABLED-NOT: Keyword/None: abi[#{{.*}} Attribute#]; name=abi
75+
// ON_PARAM: End completions
76+
77+
func bar(
78+
@#^ON_PARAM_2?check=ON_PARAM^#
79+
80+
arg: Int
81+
)
82+
// Same as ON_PARAM.
83+
84+
@#^ON_MEMBER_INDEPENDENT_1?check=ON_MEMBER_LAST^#
85+
86+
func dummy1() {}
87+
// Same as ON_MEMBER_LAST.
88+
89+
@#^ON_MEMBER_INDEPENDENT_2?check=ON_MEMBER_LAST^#
90+
func dummy2() {}
91+
// Same as ON_MEMBER_LAST.
92+
93+
94+
@#^ON_MEMBER_LAST^#
95+
// ON_MEMBER_LAST: Begin completions
96+
// ON_MEMBER_LAST_ENABLED-DAG: Keyword/None: abi[#Declaration Attribute#]; name=abi
97+
// ON_MEMBER_LAST_DISABLED-NOT: Keyword/None: abi[#{{.*}} Attribute#]; name=abi
98+
// ON_MEMBER_LAST: End completions
99+
}
100+
101+
func takeClosure(_: () -> Void) {
102+
takeClosure { @#^IN_CLOSURE^# in
103+
print("x")
104+
}
105+
}
106+
// IN_CLOSURE: Begin completions
107+
// FIXME: Not valid in this position (but CompletionLookup can't tell that)
108+
// IN_CLOSURE_ENABLED-DAG: Keyword/None: abi[#Declaration Attribute#]; name=abi
109+
// IN_CLOSURE_DISABLED-NOT: Keyword/None: abi[#{{.*}} Attribute#]; name=abi
110+
// IN_CLOSURE: End completions
111+
112+
@#^KEYWORD_INDEPENDENT_1?check=KEYWORD_LAST^#
113+
114+
func dummy1() {}
115+
// Same as KEYWORD_LAST.
116+
117+
@#^KEYWORD_INDEPENDENT_2?check=KEYWORD_LAST^#
118+
func dummy2() {}
119+
// Same as KEYWORD_LAST.
120+
121+
@#^KEYWORD_LAST^#
122+
123+
// KEYWORD_LAST: Begin completions
124+
// KEYWORD_LAST_ENABLED-DAG: Keyword/None: abi[#Declaration Attribute#]; name=abi
125+
// KEYWORD_LAST_DISABLED-NOT: Keyword/None: abi[#Declaration Attribute#]; name=abi
126+
// KEYWORD_LAST: End completions

test/Misc/verify-swift-feature-testing.test-sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ import sys
1111
# Tests that check for the behaviour of experimental/upcoming features, so
1212
# they cannot automatically be checked.
1313
EXCEPTIONAL_FILES = [
14+
pathlib.Path("test/attr/feature_requirement.swift"),
1415
pathlib.Path("test/Frontend/experimental-features-no-asserts.swift"),
1516
pathlib.Path("test/Frontend/upcoming_feature.swift"),
17+
pathlib.Path("test/IDE/complete_decl_attribute_feature_requirement.swift"),
1618
]
1719

1820
FEATURE_USAGE_RE = re.compile(

test/attr/feature_requirement.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// RUN: %target-typecheck-verify-swift -parse-as-library -verify-additional-prefix disabled-
2+
// RUN: %target-typecheck-verify-swift -parse-as-library -verify-additional-prefix enabled- -enable-experimental-feature ABIAttribute
3+
4+
// REQUIRES: asserts
5+
6+
// This test checks whether DECL_ATTR_FEATURE_REQUIREMENT is being applied correctly.
7+
// It is expected to need occasional edits as experimental features are stabilized.
8+
9+
@abi(func fn())
10+
func fn() {} // expected-disabled-error@-1 {{'abi' attribute is only valid when experimental feature ABIAttribute is enabled}}
11+
12+
#if hasAttribute(abi)
13+
#error("does have @abi") // expected-enabled-error {{does have @abi}}
14+
#else
15+
#error("doesn't have @abi") // expected-disabled-error {{doesn't have @abi}}
16+
#endif

0 commit comments

Comments
 (0)