Skip to content

Commit f4df1a8

Browse files
authored
Merge pull request #77316 from tshortli/unavailable-in-embedded
Sema: Allow calls to @_unavailableInEmbedded functions in compatible contexts
2 parents e3dff64 + 6ac729e commit f4df1a8

File tree

7 files changed

+126
-57
lines changed

7 files changed

+126
-57
lines changed

include/swift/AST/AvailabilityContext.h

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,19 +44,19 @@ class AvailabilityContext {
4444

4545
AvailabilityContext(const Storage *info) : Info(info) { assert(info); };
4646

47-
public:
48-
/// Retrieves the default `AvailabilityContext`, which is maximally available.
49-
/// The platform availability range will be set to the deployment target (or
50-
/// minimum inlining target when applicable).
51-
static AvailabilityContext getDefault(ASTContext &ctx);
52-
5347
/// Retrieves a uniqued `AvailabilityContext` with the given platform
5448
/// availability parameters.
5549
static AvailabilityContext
5650
get(const AvailabilityRange &platformAvailability,
5751
std::optional<PlatformKind> unavailablePlatform, bool deprecated,
5852
ASTContext &ctx);
5953

54+
public:
55+
/// Retrieves the default `AvailabilityContext`, which is maximally available.
56+
/// The platform availability range will be set to the deployment target (or
57+
/// minimum inlining target when applicable).
58+
static AvailabilityContext getDefault(ASTContext &ctx);
59+
6060
/// Returns the range of platform versions which may execute code in the
6161
/// availability context, starting at its introduction version.
6262
AvailabilityRange getPlatformRange() const;
@@ -74,6 +74,9 @@ class AvailabilityContext {
7474
/// Returns true if this context is deprecated on the current platform.
7575
bool isDeprecated() const;
7676

77+
/// Returns true if this context is `@_unavailableInEmbedded`.
78+
bool isUnavailableInEmbedded() const;
79+
7780
/// Constrain with another `AvailabilityContext`.
7881
void constrainWithContext(const AvailabilityContext &other, ASTContext &ctx);
7982

include/swift/AST/AvailabilityContextStorage.h

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ struct AvailabilityContext::PlatformInfo {
3636
/// platform.
3737
unsigned IsUnavailable : 1;
3838

39+
/// Whether or not the context is `@_unavailableInEmbedded`.
40+
unsigned IsUnavailableInEmbedded : 1;
41+
3942
/// Whether or not the context is considered deprecated on the current
4043
/// platform.
4144
unsigned IsDeprecated : 1;
@@ -49,24 +52,15 @@ struct AvailabilityContext::PlatformInfo {
4952
/// availability is more restrictive. Returns true if any field was updated.
5053
bool constrainWith(const Decl *decl);
5154

52-
bool constrainRange(const AvailabilityRange &other) {
53-
if (!other.isContainedIn(Range))
54-
return false;
55-
56-
Range = other;
57-
return true;
58-
}
59-
6055
bool constrainUnavailability(std::optional<PlatformKind> unavailablePlatform);
6156

62-
bool constrainDeprecated(bool deprecated);
63-
6457
/// Returns true if `other` is as available or is more available.
6558
bool isContainedIn(const PlatformInfo &other) const;
6659

6760
void Profile(llvm::FoldingSetNodeID &ID) const {
6861
Range.getRawVersionRange().Profile(ID);
6962
ID.AddBoolean(IsUnavailable);
63+
ID.AddBoolean(IsUnavailableInEmbedded);
7064
ID.AddInteger(static_cast<uint8_t>(UnavailablePlatform));
7165
ID.AddBoolean(IsDeprecated);
7266
}

lib/AST/AvailabilityContext.cpp

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,34 @@
1818

1919
using namespace swift;
2020

21+
// Defined as a macro because you can't take the reference of a bitfield.
22+
#define CONSTRAIN_BOOL(_old, _new) \
23+
[&]() { \
24+
if (_old || !_new) \
25+
return false; \
26+
_old = true; \
27+
return true; \
28+
}()
29+
30+
static bool constrainRange(AvailabilityRange &existing,
31+
const AvailabilityRange &other) {
32+
if (!other.isContainedIn(existing))
33+
return false;
34+
35+
existing = other;
36+
return true;
37+
}
38+
2139
bool AvailabilityContext::PlatformInfo::constrainWith(
2240
const PlatformInfo &other) {
2341
bool isConstrained = false;
24-
isConstrained |= constrainRange(other.Range);
42+
isConstrained |= constrainRange(Range, other.Range);
2543
if (other.IsUnavailable) {
2644
isConstrained |= constrainUnavailability(other.UnavailablePlatform);
45+
isConstrained |=
46+
CONSTRAIN_BOOL(IsUnavailableInEmbedded, other.IsUnavailableInEmbedded);
2747
}
28-
isConstrained |= constrainDeprecated(other.IsDeprecated);
48+
isConstrained |= CONSTRAIN_BOOL(IsDeprecated, other.IsDeprecated);
2949

3050
return isConstrained;
3151
}
@@ -35,13 +55,16 @@ bool AvailabilityContext::PlatformInfo::constrainWith(const Decl *decl) {
3555
auto &ctx = decl->getASTContext();
3656

3757
if (auto range = AvailabilityInference::annotatedAvailableRange(decl))
38-
isConstrained |= constrainRange(*range);
58+
isConstrained |= constrainRange(Range, *range);
3959

40-
if (auto *attr = decl->getAttrs().getUnavailable(ctx))
60+
if (auto *attr = decl->getAttrs().getUnavailable(ctx)) {
4161
isConstrained |= constrainUnavailability(attr->Platform);
62+
isConstrained |=
63+
CONSTRAIN_BOOL(IsUnavailableInEmbedded, attr->isForEmbedded());
64+
}
4265

43-
if (!IsDeprecated)
44-
isConstrained |= constrainDeprecated(decl->getAttrs().isDeprecated(ctx));
66+
isConstrained |=
67+
CONSTRAIN_BOOL(IsDeprecated, decl->getAttrs().isDeprecated(ctx));
4568

4669
return isConstrained;
4770
}
@@ -72,14 +95,6 @@ bool AvailabilityContext::PlatformInfo::constrainUnavailability(
7295
return true;
7396
}
7497

75-
bool AvailabilityContext::PlatformInfo::constrainDeprecated(bool deprecated) {
76-
if (IsDeprecated || !deprecated)
77-
return false;
78-
79-
IsDeprecated = true;
80-
return true;
81-
}
82-
8398
bool AvailabilityContext::PlatformInfo::isContainedIn(
8499
const PlatformInfo &other) const {
85100
if (!Range.isContainedIn(other.Range))
@@ -94,6 +109,9 @@ bool AvailabilityContext::PlatformInfo::isContainedIn(
94109
inheritsAvailabilityFromPlatform(UnavailablePlatform,
95110
other.UnavailablePlatform))
96111
return false;
112+
113+
if (IsUnavailableInEmbedded && !other.IsUnavailableInEmbedded)
114+
return false;
97115
}
98116

99117
if (!IsDeprecated && other.IsDeprecated)
@@ -110,6 +128,7 @@ AvailabilityContext AvailabilityContext::getDefault(ASTContext &ctx) {
110128
PlatformInfo platformInfo{AvailabilityRange::forInliningTarget(ctx),
111129
PlatformKind::none,
112130
/*IsUnavailable*/ false,
131+
/*IsUnavailableInEmbedded*/ false,
113132
/*IsDeprecated*/ false};
114133
return AvailabilityContext(Storage::get(platformInfo, ctx));
115134
}
@@ -122,7 +141,8 @@ AvailabilityContext::get(const AvailabilityRange &platformAvailability,
122141
unavailablePlatform.has_value()
123142
? *unavailablePlatform
124143
: PlatformKind::none,
125-
unavailablePlatform.has_value(), deprecated};
144+
unavailablePlatform.has_value(),
145+
/*IsUnavailableInEmbedded*/ false, deprecated};
126146
return AvailabilityContext(Storage::get(platformInfo, ctx));
127147
}
128148

@@ -137,6 +157,10 @@ AvailabilityContext::getUnavailablePlatformKind() const {
137157
return std::nullopt;
138158
}
139159

160+
bool AvailabilityContext::isUnavailableInEmbedded() const {
161+
return Info->Platform.IsUnavailableInEmbedded;
162+
}
163+
140164
bool AvailabilityContext::isDeprecated() const {
141165
return Info->Platform.IsDeprecated;
142166
}
@@ -156,7 +180,7 @@ void AvailabilityContext::constrainWithDecl(const Decl *decl) {
156180
void AvailabilityContext::constrainWithPlatformRange(
157181
const AvailabilityRange &platformRange, ASTContext &ctx) {
158182
PlatformInfo platformAvailability{Info->Platform};
159-
if (!platformAvailability.constrainRange(platformRange))
183+
if (!constrainRange(platformAvailability.Range, platformRange))
160184
return;
161185

162186
Info = Storage::get(platformAvailability, ctx);
@@ -167,7 +191,7 @@ void AvailabilityContext::constrainWithDeclAndPlatformRange(
167191
PlatformInfo platformAvailability{Info->Platform};
168192
bool isConstrained = false;
169193
isConstrained |= platformAvailability.constrainWith(decl);
170-
isConstrained |= platformAvailability.constrainRange(platformRange);
194+
isConstrained |= constrainRange(platformAvailability.Range, platformRange);
171195

172196
if (!isConstrained)
173197
return;

lib/Sema/TypeCheckAvailability.cpp

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -364,19 +364,20 @@ static bool isInsideCompatibleUnavailableDeclaration(
364364
if (!referencedPlatform)
365365
return false;
366366

367-
if (!attr->isUnconditionallyUnavailable()) {
367+
if (!attr->isUnconditionallyUnavailable())
368368
return false;
369-
}
370369

371-
// Unless in embedded Swift mode, refuse calling unavailable functions from
372-
// unavailable code, but allow the use of types.
370+
// Refuse calling universally unavailable functions from unavailable code,
371+
// but allow the use of types.
373372
PlatformKind platform = attr->Platform;
374-
if (!D->getASTContext().LangOpts.hasFeature(Feature::Embedded)) {
375-
if (platform == PlatformKind::none && !isa<TypeDecl>(D) &&
376-
!isa<ExtensionDecl>(D)) {
377-
return false;
378-
}
379-
}
373+
if (platform == PlatformKind::none && !attr->isForEmbedded() &&
374+
!isa<TypeDecl>(D) && !isa<ExtensionDecl>(D))
375+
return false;
376+
377+
// @_unavailableInEmbedded declarations may be used in contexts that are
378+
// also @_unavailableInEmbedded.
379+
if (attr->isForEmbedded())
380+
return availabilityContext.isUnavailableInEmbedded();
380381

381382
return (*referencedPlatform == platform ||
382383
inheritsAvailabilityFromPlatform(platform, *referencedPlatform));

stdlib/public/Concurrency/TaskLocal.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ public final class TaskLocal<Value: Sendable>: Sendable, CustomStringConvertible
311311

312312
@available(*, unavailable, message: "use '$myTaskLocal.withValue(_:do:)' instead")
313313
set {
314-
fatalError("Illegal attempt to set a \(Self.self) value, use `withValue(...) { ... }` instead.")
314+
fatalError("Illegal attempt to set a TaskLocal value, use `withValue(...) { ... }` instead.")
315315
}
316316
}
317317

test/Sema/availability.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,12 @@
33
@available(*, unavailable)
44
func unavailable_foo() {} // expected-note {{'unavailable_foo()' has been explicitly marked unavailable here}}
55

6+
@_unavailableInEmbedded // no-op without -enable-experimental-feature Embedded
7+
public func unavailable_in_embedded() { }
8+
69
func test() {
710
unavailable_foo() // expected-error {{'unavailable_foo()' is unavailable}}
11+
unavailable_in_embedded() // ok
812
}
913

1014
@available(*,unavailable,message: "use 'Int' instead")

test/embedded/availability.swift

Lines changed: 56 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,67 @@
1-
// Building with regular Swift should succeed
2-
// RUN: %target-swift-emit-ir %s -parse-stdlib -wmo
3-
4-
// Building with embedded Swift should produce unavailability errors
5-
// RUN: %target-typecheck-verify-swift -parse-stdlib -enable-experimental-feature Embedded -wmo
1+
// RUN: %target-typecheck-verify-swift -parse-stdlib -enable-experimental-feature Embedded
62

73
// REQUIRES: swift_in_compiler
84

95
@_unavailableInEmbedded
10-
public func embedded() { }
11-
public func regular() {
12-
embedded() // expected-error {{'embedded()' is unavailable: unavailable in embedded Swift}}
13-
// expected-note@-3 {{'embedded()' has been explicitly marked unavailable here}}
14-
}
6+
public struct UnavailableInEmbedded {}
7+
// expected-note@-1 {{'UnavailableInEmbedded' has been explicitly marked unavailable here}}
8+
9+
@available(*, unavailable, message: "always unavailable")
10+
public struct UniverallyUnavailable {}
11+
// expected-note@-1 {{'UniverallyUnavailable' has been explicitly marked unavailable here}}
12+
13+
@_unavailableInEmbedded
14+
public func unavailable_in_embedded() { }
15+
// expected-note@-1 2 {{'unavailable_in_embedded()' has been explicitly marked unavailable here}}
16+
17+
@available(*, unavailable, message: "always unavailable")
18+
public func universally_unavailable() { }
19+
// expected-note@-1 3 {{'universally_unavailable()' has been explicitly marked unavailable here}}
1520

1621
@_unavailableInEmbedded
1722
public func unused() { } // no error
1823

24+
public struct S1 {}
25+
public struct S2 {}
26+
1927
@_unavailableInEmbedded
20-
public func called_from_unavailable() { }
28+
public func has_unavailable_in_embedded_overload(_ s1: S1) { }
29+
30+
public func has_unavailable_in_embedded_overload(_ s2: S2) { }
31+
32+
@available(*, unavailable)
33+
public func has_universally_unavailable_overload(_ s1: S1) { }
34+
35+
public func has_universally_unavailable_overload(_ s2: S2) { }
36+
37+
public func available(
38+
_ uie: UnavailableInEmbedded, // expected-error {{'UnavailableInEmbedded' is unavailable: unavailable in embedded Swift}}
39+
_ uu: UniverallyUnavailable // expected-error {{'UniverallyUnavailable' is unavailable: always unavailable}}
40+
) {
41+
unavailable_in_embedded() // expected-error {{'unavailable_in_embedded()' is unavailable: unavailable in embedded Swift}}
42+
universally_unavailable() // expected-error {{'universally_unavailable()' is unavailable: always unavailable}}
43+
has_unavailable_in_embedded_overload(.init())
44+
has_universally_unavailable_overload(.init()) // not ambiguous, selects available overload
45+
}
46+
2147
@_unavailableInEmbedded
22-
public func also_embedded() {
23-
called_from_unavailable() // no error
48+
public func also_unavailable_in_embedded(
49+
_ uie: UnavailableInEmbedded, // OK
50+
_ uu: UniverallyUnavailable // FIXME: should be an error
51+
) {
52+
unavailable_in_embedded() // OK
53+
universally_unavailable() // expected-error {{'universally_unavailable()' is unavailable: always unavailable}}
54+
has_unavailable_in_embedded_overload(.init()) // FIXME: should be ambiguous
55+
has_universally_unavailable_overload(.init()) // not ambiguous, selects available overload
56+
}
57+
58+
@available(*, unavailable)
59+
public func also_universally_unavailable(
60+
_ uie: UnavailableInEmbedded, // OK
61+
_ uu: UniverallyUnavailable // OK
62+
) {
63+
unavailable_in_embedded() // expected-error {{'unavailable_in_embedded()' is unavailable: unavailable in embedded Swift}}
64+
universally_unavailable() // expected-error {{'universally_unavailable()' is unavailable: always unavailable}}
65+
has_unavailable_in_embedded_overload(.init()) // not ambiguous, selects available overload
66+
has_universally_unavailable_overload(.init()) // not ambiguous, selects available overload
2467
}

0 commit comments

Comments
 (0)