Skip to content

Commit 034c62c

Browse files
committed
AST: Generalize availability fix-its to support custom availability domains.
Resolves rdar://156118254.
1 parent aeb5a46 commit 034c62c

File tree

7 files changed

+78
-35
lines changed

7 files changed

+78
-35
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7070,9 +7070,6 @@ NOTE(availability_guard_with_version_check, none,
70707070

70717071
NOTE(availability_add_attribute, none,
70727072
"add '@available' attribute to enclosing %kindonly0", (const Decl *))
7073-
FIXIT(insert_available_attr,
7074-
"@available(%0 %1, *)\n%2",
7075-
(StringRef, StringRef, StringRef))
70767073

70777074
ERROR(availability_inout_accessor_only_in, none,
70787075
"cannot pass as inout because %0 is only available in %1"

lib/Sema/TypeCheckAvailability.cpp

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -659,10 +659,18 @@ static void fixAvailabilityForDecl(
659659
StringRef OriginalIndent =
660660
Lexer::getIndentationForLine(Context.SourceMgr, InsertLoc);
661661

662+
llvm::SmallString<64> FixItBuffer;
663+
llvm::raw_svector_ostream FixIt(FixItBuffer);
664+
665+
FixIt << "@available(" << Domain.getNameForAttributePrinting();
666+
if (Domain.isVersioned())
667+
FixIt << " " << RequiredAvailability.getVersionString();
668+
if (Domain.isPlatform())
669+
FixIt << ", *";
670+
FixIt << ")\n" << OriginalIndent;
671+
662672
D->diagnose(diag::availability_add_attribute, DeclForDiagnostic)
663-
.fixItInsert(InsertLoc, diag::insert_available_attr,
664-
Domain.getNameForAttributePrinting(),
665-
RequiredAvailability.getVersionString(), OriginalIndent);
673+
.fixItInsert(InsertLoc, FixIt.str());
666674
}
667675

668676
/// In the special case of being in an existing, nontrivial availability scope
@@ -684,7 +692,6 @@ static bool fixAvailabilityByNarrowingNearbyVersionCheck(
684692
if (!scope)
685693
return false;
686694

687-
// FIXME: [availability] Support fixing availability for versionless domains.
688695
auto ExplicitAvailability =
689696
scope->getExplicitAvailabilityRange(Domain, Context);
690697
if (ExplicitAvailability && !RequiredAvailability.isAlwaysAvailable() &&
@@ -722,8 +729,9 @@ static bool fixAvailabilityByNarrowingNearbyVersionCheck(
722729
/// Emit a diagnostic note and Fix-It to add an if #available(...) { } guard
723730
/// that checks for the given version range around the given node.
724731
static void fixAvailabilityByAddingVersionCheck(
725-
ASTNode NodeToWrap, const AvailabilityRange &RequiredAvailability,
726-
SourceRange ReferenceRange, ASTContext &Context) {
732+
ASTNode NodeToWrap, AvailabilityDomain Domain,
733+
const AvailabilityRange &RequiredAvailability, SourceRange ReferenceRange,
734+
ASTContext &Context) {
727735
// If this is an implicit variable that wraps an expression,
728736
// let's point to it's initializer. For example, result builder
729737
// transform captures expressions into implicit variables.
@@ -768,24 +776,32 @@ static void fixAvailabilityByAddingVersionCheck(
768776
StartAt += NewLine.length();
769777
}
770778

771-
PlatformKind Target = targetPlatform(Context.LangOpts);
779+
AvailabilityDomain QueryDomain = Domain;
772780

773781
// Runtime availability checks that specify app extension platforms don't
774782
// work, so only suggest checks against the base platform.
775-
if (auto TargetRemovingAppExtension =
776-
basePlatformForExtensionPlatform(Target))
777-
Target = *TargetRemovingAppExtension;
783+
if (auto CanonicalPlatform =
784+
basePlatformForExtensionPlatform(QueryDomain.getPlatformKind())) {
785+
QueryDomain = AvailabilityDomain::forPlatform(*CanonicalPlatform);
786+
}
787+
788+
Out << "if #available(" << QueryDomain.getNameForAttributePrinting();
789+
if (QueryDomain.isVersioned())
790+
Out << " " << RequiredAvailability.getVersionString();
791+
if (QueryDomain.isPlatform())
792+
Out << ", *";
778793

779-
Out << "if #available(" << platformString(Target) << " "
780-
<< RequiredAvailability.getVersionString() << ", *) {\n";
794+
Out << ") {\n";
781795

782796
Out << OriginalIndent << ExtraIndent << GuardedText << "\n";
783797

784798
// We emit an empty fallback case with a comment to encourage the developer
785799
// to think explicitly about whether fallback on earlier versions is needed.
786800
Out << OriginalIndent << "} else {\n";
787-
Out << OriginalIndent << ExtraIndent << "// Fallback on earlier versions\n";
788-
Out << OriginalIndent << "}";
801+
Out << OriginalIndent << ExtraIndent << "// Fallback";
802+
if (QueryDomain.isVersioned())
803+
Out << " on earlier versions";
804+
Out << "\n" << OriginalIndent << "}";
789805
}
790806

791807
Context.Diags.diagnose(
@@ -803,10 +819,6 @@ static void fixAvailability(SourceRange ReferenceRange,
803819
if (ReferenceRange.isInvalid())
804820
return;
805821

806-
// FIXME: [availability] Support non-platform domains.
807-
if (!Domain.isPlatform())
808-
return;
809-
810822
std::optional<ASTNode> NodeToWrapInVersionCheck;
811823
const Decl *FoundMemberDecl = nullptr;
812824
const Decl *FoundTypeLevelDecl = nullptr;
@@ -818,8 +830,8 @@ static void fixAvailability(SourceRange ReferenceRange,
818830
// Suggest wrapping in if #available(...) { ... } if possible.
819831
if (NodeToWrapInVersionCheck.has_value()) {
820832
fixAvailabilityByAddingVersionCheck(NodeToWrapInVersionCheck.value(),
821-
RequiredAvailability, ReferenceRange,
822-
Context);
833+
Domain, RequiredAvailability,
834+
ReferenceRange, Context);
823835
}
824836

825837
// Suggest adding availability attributes.

test/Availability/availability_custom_domains.swift

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,22 +29,25 @@ func unavailableInDynamicDomain() { } // expected-note * {{'unavailableInDynamic
2929
@available(UnknownDomain) // expected-error {{unrecognized platform name 'UnknownDomain'}}
3030
func availableInUnknownDomain() { }
3131

32-
func testDeployment() {
32+
func testDeployment() { // expected-note 2 {{add '@available' attribute to enclosing global function}}
3333
alwaysAvailable()
3434
availableInEnabledDomain() // expected-error {{'availableInEnabledDomain()' is only available in EnabledDomain}}
35+
// expected-note@-1 {{add 'if #available' version check}}
3536
unavailableInEnabledDomain() // expected-error {{'unavailableInEnabledDomain()' is unavailable}}
3637
unavailableInDisabledDomain() // expected-error {{'unavailableInDisabledDomain()' is unavailable}}
3738
deprecatedInDynamicDomain() // expected-warning {{'deprecatedInDynamicDomain()' is deprecated: Use something else}}
3839
unavailableInDynamicDomain() // expected-error {{'unavailableInDynamicDomain()' is unavailable}}
3940
availableInDynamicDomain() // expected-error {{'availableInDynamicDomain()' is only available in DynamicDomain}}
41+
// expected-note@-1 {{add 'if #available' version check}}
4042
availableInUnknownDomain()
4143
}
4244

43-
func testIfAvailable(_ truthy: Bool) {
45+
func testIfAvailable(_ truthy: Bool) { // expected-note 9 {{add '@available' attribute to enclosing global function}}
4446
if #available(EnabledDomain) { // expected-note {{enclosing scope here}}
4547
availableInEnabledDomain()
4648
unavailableInEnabledDomain() // expected-error {{'unavailableInEnabledDomain()' is unavailable}}
4749
availableInDynamicDomain() // expected-error {{'availableInDynamicDomain()' is only available in DynamicDomain}}
50+
// expected-note@-1 {{add 'if #available' version check}}
4851
unavailableInDynamicDomain() // expected-error {{'unavailableInDynamicDomain()' is unavailable}}
4952

5053
if #available(DynamicDomain) {
@@ -56,6 +59,7 @@ func testIfAvailable(_ truthy: Bool) {
5659
availableInEnabledDomain()
5760
unavailableInEnabledDomain() // expected-error {{'unavailableInEnabledDomain()' is unavailable}}
5861
availableInDynamicDomain() // expected-error {{'availableInDynamicDomain()' is only available in DynamicDomain}}
62+
// expected-note@-1 {{add 'if #available' version check}}
5963
unavailableInDynamicDomain()
6064
}
6165

@@ -66,12 +70,15 @@ func testIfAvailable(_ truthy: Bool) {
6670
availableInEnabledDomain()
6771
unavailableInEnabledDomain() // expected-error {{'unavailableInEnabledDomain()' is unavailable}}
6872
availableInDynamicDomain() // expected-error {{'availableInDynamicDomain()' is only available in DynamicDomain}}
73+
// expected-note@-1 {{add 'if #available' version check}}
6974
unavailableInDynamicDomain() // expected-error {{'unavailableInDynamicDomain()' is unavailable}}
7075
}
7176
} else {
7277
availableInEnabledDomain() // expected-error {{'availableInEnabledDomain()' is only available in EnabledDomain}}
78+
// expected-note@-1 {{add 'if #available' version check}}
7379
unavailableInEnabledDomain()
7480
availableInDynamicDomain() // expected-error {{'availableInDynamicDomain()' is only available in DynamicDomain}}
81+
// expected-note@-1 {{add 'if #available' version check}}
7582
unavailableInDynamicDomain() // expected-error {{'unavailableInDynamicDomain()' is unavailable}}
7683
}
7784

@@ -84,8 +91,10 @@ func testIfAvailable(_ truthy: Bool) {
8491
// In this branch, we only know that one of the domains is unavailable,
8592
// but we don't know which.
8693
availableInEnabledDomain() // expected-error {{'availableInEnabledDomain()' is only available in EnabledDomain}}
94+
// expected-note@-1 {{add 'if #available' version check}}
8795
unavailableInEnabledDomain() // expected-error {{'unavailableInEnabledDomain()' is unavailable}}
8896
availableInDynamicDomain() // expected-error {{'availableInDynamicDomain()' is only available in DynamicDomain}}
97+
// expected-note@-1 {{add 'if #available' version check}}
8998
unavailableInDynamicDomain() // expected-error {{'unavailableInDynamicDomain()' is unavailable}}
9099
}
91100

@@ -96,11 +105,13 @@ func testIfAvailable(_ truthy: Bool) {
96105
// In this branch, the state of EnabledDomain remains unknown since
97106
// execution will reach here if "truthy" is false.
98107
availableInEnabledDomain() // expected-error {{'availableInEnabledDomain()' is only available in EnabledDomain}}
108+
// expected-note@-1 {{add 'if #available' version check}}
99109
unavailableInEnabledDomain() // expected-error {{'unavailableInEnabledDomain()' is unavailable}}
100110
}
101111

102112
if #unavailable(EnabledDomain) {
103113
availableInEnabledDomain() // expected-error {{'availableInEnabledDomain()' is only available in EnabledDomain}}
114+
// expected-note@-1 {{add 'if #available' version check}}
104115
unavailableInEnabledDomain()
105116
} else {
106117
availableInEnabledDomain()
@@ -113,7 +124,7 @@ func testIfAvailable(_ truthy: Bool) {
113124
}
114125
}
115126

116-
func testWhileAvailable() {
127+
func testWhileAvailable() { // expected-note {{add '@available' attribute to enclosing global function}}
117128
while #available(EnabledDomain) { // expected-note {{enclosing scope here}}
118129
availableInEnabledDomain()
119130
unavailableInEnabledDomain() // expected-error {{'unavailableInEnabledDomain()' is unavailable}}
@@ -124,32 +135,36 @@ func testWhileAvailable() {
124135

125136
while #unavailable(EnabledDomain) {
126137
availableInEnabledDomain() // expected-error {{'availableInEnabledDomain()' is only available in EnabledDomain}}
138+
// expected-note@-1 {{add 'if #available' version check}}
127139
unavailableInEnabledDomain()
128140

129141
if #available(EnabledDomain) {} // FIXME: [availability] Diagnose as unreachable
130142
if #unavailable(EnabledDomain) {} // FIXME: [availability] Diagnose as redundant
131143
}
132144
}
133145

134-
func testGuardAvailable() {
146+
func testGuardAvailable() { // expected-note 3 {{add '@available' attribute to enclosing global function}}
135147
guard #available(EnabledDomain) else { // expected-note {{enclosing scope here}}
136148
availableInEnabledDomain() // expected-error {{'availableInEnabledDomain()' is only available in EnabledDomain}}
149+
// expected-note@-1 {{add 'if #available' version check}}
137150
unavailableInEnabledDomain()
138151
availableInDynamicDomain() // expected-error {{'availableInDynamicDomain()' is only available in DynamicDomain}}
152+
// expected-note@-1 {{add 'if #available' version check}}
139153

140154
return
141155
}
142156

143157
availableInEnabledDomain()
144158
unavailableInEnabledDomain() // expected-error {{'unavailableInEnabledDomain()' is unavailable}}
145159
availableInDynamicDomain() // expected-error {{'availableInDynamicDomain()' is only available in DynamicDomain}}
160+
// expected-note@-1 {{add 'if #available' version check}}
146161

147162
if #available(EnabledDomain) {} // expected-warning {{unnecessary check for 'EnabledDomain'; enclosing scope ensures guard will always be true}}
148163
if #unavailable(EnabledDomain) {} // FIXME: [availability] Diagnose as unreachable
149164
}
150165

151166
@available(EnabledDomain)
152-
func testEnabledDomainAvailable() { // expected-note {{enclosing scope here}}
167+
func testEnabledDomainAvailable() { // expected-note {{add '@available' attribute to enclosing global function}} expected-note {{enclosing scope here}}
153168
availableInEnabledDomain()
154169
unavailableInEnabledDomain() // expected-error {{'unavailableInEnabledDomain()' is unavailable}}
155170

@@ -160,12 +175,14 @@ func testEnabledDomainAvailable() { // expected-note {{enclosing scope here}}
160175
unavailableInDisabledDomain() // expected-error {{'unavailableInDisabledDomain()' is unavailable}}
161176
deprecatedInDynamicDomain() // expected-warning {{'deprecatedInDynamicDomain()' is deprecated: Use something else}}
162177
availableInDynamicDomain() // expected-error {{'availableInDynamicDomain()' is only available in DynamicDomain}}
178+
// expected-note@-1 {{add 'if #available' version check}}
163179
availableInUnknownDomain()
164180
}
165181

166182
@available(EnabledDomain, unavailable)
167-
func testEnabledDomainUnavailable() {
183+
func testEnabledDomainUnavailable() { // expected-note {{add '@available' attribute to enclosing global function}}
168184
availableInEnabledDomain() // expected-error {{'availableInEnabledDomain()' is only available in EnabledDomain}}
185+
// expected-note@-1 {{add 'if #available' version check}}
169186
unavailableInEnabledDomain()
170187

171188
if #available(EnabledDomain) {} // FIXME: [availability] Diagnose as unreachable
@@ -175,6 +192,7 @@ func testEnabledDomainUnavailable() {
175192
unavailableInDisabledDomain() // expected-error {{'unavailableInDisabledDomain()' is unavailable}}
176193
deprecatedInDynamicDomain() // expected-warning {{'deprecatedInDynamicDomain()' is deprecated: Use something else}}
177194
availableInDynamicDomain() // expected-error {{'availableInDynamicDomain()' is only available in DynamicDomain}}
195+
// expected-note@-1 {{add 'if #available' version check}}
178196
availableInUnknownDomain()
179197
}
180198

@@ -184,9 +202,11 @@ func testUniversallyUnavailable() {
184202
// FIXME: [availability] Diagnostic consistency: potentially unavailable declaration shouldn't be diagnosed
185203
// in contexts that are unavailable to broader domains
186204
availableInEnabledDomain() // expected-error {{'availableInEnabledDomain()' is only available in EnabledDomain}}
205+
// expected-note@-1 {{add 'if #available' version check}}
187206
unavailableInDisabledDomain()
188207
deprecatedInDynamicDomain() // expected-warning {{'deprecatedInDynamicDomain()' is deprecated: Use something else}}
189208
availableInDynamicDomain() // expected-error {{'availableInDynamicDomain()' is only available in DynamicDomain}}
209+
// expected-note@-1 {{add 'if #available' version check}}
190210
availableInUnknownDomain()
191211

192212
if #available(EnabledDomain) {} // FIXME: [availability] Diagnose?
@@ -205,6 +225,12 @@ struct EnabledDomainAvailable {
205225
}
206226
}
207227

228+
func testFixIts() {
229+
// expected-note@-1 {{add '@available' attribute to enclosing global function}}{{1-1=@available(EnabledDomain)\n}}
230+
availableInEnabledDomain() // expected-error {{'availableInEnabledDomain()' is only available in EnabledDomain}}
231+
// expected-note@-1 {{add 'if #available' version check}}{{3-29=if #available(EnabledDomain) {\n availableInEnabledDomain()\n \} else {\n // Fallback\n \}}}
232+
}
233+
208234
protocol P { }
209235

210236
@available(EnabledDomain)

test/ClangImporter/Inputs/availability_custom_domains_other.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ func availableInArctic() { }
66
@available(Mediterranean)
77
func availableInMediterranean() { }
88

9-
func testOtherClangDecls() {
9+
func testOtherClangDecls() { // expected-note {{add '@available' attribute to enclosing global function}}
1010
available_in_baltic() // expected-error {{'available_in_baltic()' is only available in Baltic}}
11+
// expected-note@-1 {{add 'if #available' version check}}
1112
}

test/ClangImporter/availability_custom_domains.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,14 @@
2323

2424
import Oceans // re-exports Rivers
2525

26-
func testClangDecls() {
26+
func testClangDecls() { // expected-note 3 {{add '@available' attribute to enclosing global function}}
2727
available_in_arctic() // expected-error {{'available_in_arctic()' is only available in Arctic}}
28+
// expected-note@-1 {{add 'if #available' version check}}
2829
unavailable_in_pacific() // expected-error {{'unavailable_in_pacific()' is unavailable}}
2930
available_in_colorado_river_delta() // expected-error {{'available_in_colorado_river_delta()' is only available in Pacific}}
31+
// expected-note@-1 {{add 'if #available' version check}}
3032
available_in_colorado() // expected-error {{'available_in_colorado()' is only available in Colorado}}
33+
// expected-note@-1 {{add 'if #available' version check}}
3134
available_in_baltic() // expected-error {{cannot find 'available_in_baltic' in scope}}
3235
}
3336

@@ -47,12 +50,15 @@ func unavailableInColorado() { } // expected-note {{'unavailableInColorado()' ha
4750
@available(Baltic) // expected-error {{unrecognized platform name 'Baltic'}}
4851
func availableInBaltic() { } // expected-note {{did you mean 'availableInBaltic'}}
4952

50-
func testSwiftDecls() {
53+
func testSwiftDecls() { // expected-note 3 {{add '@available' attribute to enclosing global function}}
5154
availableInBayBridge() // expected-error {{'availableInBayBridge()' is only available in BayBridge}}
55+
// expected-note@-1 {{add 'if #available' version check}}
5256
unavailableInBayBridge() // expected-error {{'unavailableInBayBridge()' is unavailable}}
5357
availableInArctic()
5458
availableInPacific() // expected-error {{'availableInPacific()' is only available in Pacific}}
59+
// expected-note@-1 {{add 'if #available' version check}}
5560
unavailableInColorado() // expected-error {{'unavailableInColorado()' is unavailable}}
5661
availableInBaltic()
5762
availableInMediterranean() // expected-error {{'availableInMediterranean()' is only available in Mediterranean}}
63+
// expected-note@-1 {{add 'if #available' version check}}
5864
}

test/Serialization/availability_custom_domains.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ public func unavailableInColorado() { }
3131

3232
import lib
3333

34-
func test() {
34+
func test() { // expected-note {{add '@available' attribute to enclosing global function}}
3535
availableInPacific() // expected-error {{'availableInPacific()' is only available in Pacific}}
36+
// expected-note@-1 {{add 'if #available' version check}}
3637
unavailableInColorado() // expected-error {{'unavailableInColorado()' is unavailable}}
3738
}

0 commit comments

Comments
 (0)