Skip to content

Commit 5f191e4

Browse files
authored
Merge pull request swiftlang#75064 from kavon/6.0-availability-ncgenerics
[6.0🍒] NCGenerics: add availability checking
2 parents 0d376f8 + 467de90 commit 5f191e4

File tree

7 files changed

+235
-49
lines changed

7 files changed

+235
-49
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6688,6 +6688,16 @@ ERROR(availability_isolated_any_only_version_newer, none,
66886688
"%0 %1 or newer",
66896689
(StringRef, llvm::VersionTuple))
66906690

6691+
ERROR(availability_copyable_generics_casting_only_version_newer, none,
6692+
"runtime support for casting types with noncopyable generic arguments "
6693+
"is only available in %0 %1 or newer",
6694+
(StringRef, llvm::VersionTuple))
6695+
6696+
ERROR(availability_escapable_generics_casting_only_version_newer, none,
6697+
"runtime support for casting types with nonescapable generic arguments "
6698+
"is only available in %0 %1 or newer",
6699+
(StringRef, llvm::VersionTuple))
6700+
66916701
ERROR(availability_typed_throws_only_version_newer, none,
66926702
"runtime support for typed throws function types is only available in "
66936703
"%0 %1 or newer",

include/swift/AST/FeatureAvailability.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ FEATURE(SwiftExceptionPersonality, (6, 0))
7171
// Metadata support for @isolated(any) function types
7272
FEATURE(IsolatedAny, (6, 0))
7373

74+
FEATURE(NoncopyableGenerics, (6, 0))
75+
7476
FEATURE(TaskExecutor, FUTURE)
7577
FEATURE(Differentiation, FUTURE)
7678
FEATURE(InitRawStructMetadata, FUTURE)

lib/Sema/TypeCheckAvailability.cpp

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3040,6 +3040,84 @@ static bool diagnoseTypedThrowsAvailability(
30403040
ReferenceDC);
30413041
}
30423042

3043+
/// Make sure the generic arguments conform to all known invertible protocols.
3044+
/// Runtimes prior to NoncopyableGenerics do not check if any of the
3045+
/// generic arguments conform to Copyable/Escapable during dynamic casts.
3046+
/// But a dynamic cast *needs* to check if the generic arguments conform,
3047+
/// to determine if the cast should be permitted at all. For example:
3048+
///
3049+
/// struct X<T> {}
3050+
/// extension X: P where T: Y {}
3051+
///
3052+
/// func f<Y: ~Copyable>(...) {
3053+
/// let x: X<Y> = ...
3054+
/// _ = x as? any P // <- cast should fail
3055+
/// }
3056+
///
3057+
/// The dynamic cast here must fail because Y does not conform to Copyable,
3058+
/// thus X<Y> doesn't conform to P!
3059+
///
3060+
/// \param boundTy The generic type with its generic arguments.
3061+
/// \returns the invertible protocol for which a conformance is missing in
3062+
/// one of the generic arguments, or none if all are present for
3063+
/// every generic argument.
3064+
static std::optional<InvertibleProtocolKind> checkGenericArgsForInvertibleReqs(
3065+
BoundGenericType *boundTy) {
3066+
for (auto arg : boundTy->getGenericArgs()) {
3067+
for (auto ip : InvertibleProtocolSet::allKnown()) {
3068+
switch (ip) {
3069+
case InvertibleProtocolKind::Copyable:
3070+
if (arg->isNoncopyable())
3071+
return ip;
3072+
break;
3073+
case InvertibleProtocolKind::Escapable:
3074+
if (!arg->isEscapable())
3075+
return ip;
3076+
}
3077+
}
3078+
}
3079+
return std::nullopt;
3080+
}
3081+
3082+
/// Older runtimes won't check for required invertible protocol conformances
3083+
/// at runtime during a cast.
3084+
///
3085+
/// \param srcType the source or initial type of the cast
3086+
/// \param refLoc source location of the cast
3087+
/// \param refDC decl context in which the cast occurs
3088+
/// \return true if diagnosed
3089+
static bool checkInverseGenericsCastingAvailability(Type srcType,
3090+
SourceRange refLoc,
3091+
const DeclContext *refDC) {
3092+
if (!srcType) return false;
3093+
3094+
auto type = srcType->getCanonicalType();
3095+
3096+
if (auto boundTy = dyn_cast<BoundGenericType>(type)) {
3097+
if (auto missing = checkGenericArgsForInvertibleReqs(boundTy)) {
3098+
std::optional<Diag<StringRef, llvm::VersionTuple>> diag;
3099+
switch (*missing) {
3100+
case InvertibleProtocolKind::Copyable:
3101+
diag =
3102+
diag::availability_copyable_generics_casting_only_version_newer;
3103+
break;
3104+
case InvertibleProtocolKind::Escapable:
3105+
diag =
3106+
diag::availability_escapable_generics_casting_only_version_newer;
3107+
break;
3108+
}
3109+
3110+
// Enforce the availability restriction.
3111+
return TypeChecker::checkAvailability(
3112+
refLoc,
3113+
refDC->getASTContext().getNoncopyableGenericsAvailability(),
3114+
*diag,
3115+
refDC);
3116+
}
3117+
}
3118+
return false;
3119+
}
3120+
30433121
static bool checkTypeMetadataAvailabilityInternal(CanType type,
30443122
SourceRange refLoc,
30453123
const DeclContext *refDC) {
@@ -3083,7 +3161,13 @@ static bool checkTypeMetadataAvailabilityForConverted(Type refType,
30833161
// there.
30843162
if (type.isAnyExistentialType()) return false;
30853163

3086-
return checkTypeMetadataAvailabilityInternal(type, refLoc, refDC);
3164+
if (checkTypeMetadataAvailabilityInternal(type, refLoc, refDC))
3165+
return true;
3166+
3167+
if (checkInverseGenericsCastingAvailability(type, refLoc, refDC))
3168+
return true;
3169+
3170+
return false;
30873171
}
30883172

30893173
namespace {
@@ -3484,6 +3568,9 @@ class ExprAvailabilityWalker : public ASTWalker {
34843568
if (auto EE = dyn_cast<ErasureExpr>(E)) {
34853569
checkTypeMetadataAvailability(EE->getSubExpr()->getType(),
34863570
EE->getLoc(), Where.getDeclContext());
3571+
checkInverseGenericsCastingAvailability(EE->getSubExpr()->getType(),
3572+
EE->getLoc(),
3573+
Where.getDeclContext());
34873574

34883575
for (ProtocolConformanceRef C : EE->getConformances()) {
34893576
diagnoseConformanceAvailability(E->getLoc(), C, Where, Type(), Type(),
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
// RUN: %target-typecheck-verify-swift \
2+
// RUN: -debug-diagnostic-names -target arm64-apple-macos14.4 \
3+
// RUN: -enable-experimental-feature NonescapableTypes
4+
5+
// REQUIRES: OS=macosx || OS=ios || OS=tvos || OS=watchOS || OS=xros
6+
7+
protocol P {}
8+
struct NCG<T: ~Copyable> {}
9+
extension NCG: P where T: Copyable {} // expected-note 2{{requirement_implied_by_conditional_conformance}}
10+
11+
struct NEG<T: ~Escapable> {}
12+
extension NEG: P {} // expected-note {{requirement_implied_by_conditional_conformance}}
13+
14+
struct All {}
15+
struct NoCopy: ~Copyable {}
16+
struct NoEscape: ~Escapable {}
17+
18+
19+
20+
/// MARK: dynamic casts are gated by availability. Older runtimes don't check
21+
/// for conformance to Copyable so they'll give bogus results.
22+
23+
// expected-note@+1 8{{availability_add_attribute}}
24+
func dyn_cast_errors<T: ~Copyable, V: ~Escapable>(
25+
_ generic: NCG<T>, _ concrete: NCG<NoCopy>,
26+
_ genericEsc: NEG<V>, _ concreteEsc: NEG<NoEscape>) {
27+
_ = concrete as? any P // expected-error {{availability_copyable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
28+
_ = generic as? any P // expected-error {{availability_copyable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
29+
30+
_ = concrete is any P // expected-error {{availability_copyable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
31+
_ = generic is any P // expected-error {{availability_copyable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
32+
33+
_ = concrete as! any P // expected-error {{availability_copyable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
34+
_ = generic as! any P // expected-error {{availability_copyable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
35+
36+
_ = genericEsc as? any P // expected-error {{availability_escapable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
37+
_ = concreteEsc is any P // expected-error {{availability_escapable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
38+
}
39+
40+
@available(SwiftStdlib 6.0, *)
41+
func FIXED_dyn_cast_errors<T: ~Copyable, V: ~Escapable>(
42+
_ generic: NCG<T>, _ concrete: NCG<NoCopy>,
43+
_ genericEsc: NEG<V>, _ concreteEsc: NEG<NoEscape>) {
44+
_ = concrete as? any P
45+
_ = generic as? any P
46+
47+
_ = concrete is any P
48+
_ = generic is any P
49+
50+
_ = concrete as! any P
51+
_ = generic as! any P
52+
53+
_ = genericEsc as? any P
54+
_ = concreteEsc is any P
55+
}
56+
57+
func noAvailabilityNeeded<T>(_ generic: NCG<T>, _ concrete: NCG<All>) {
58+
_ = concrete as? any P // expected-warning {{conditional_downcast_coercion}}
59+
_ = generic as? any P // expected-warning {{conditional_downcast_coercion}}
60+
61+
_ = concrete is any P // expected-warning {{isa_is_always_true}}
62+
_ = generic is any P // expected-warning {{isa_is_always_true}}
63+
64+
_ = concrete as! any P // expected-warning {{forced_downcast_coercion}}
65+
_ = generic as! any P // expected-warning {{forced_downcast_coercion}}
66+
67+
_ = concrete as any P
68+
_ = generic as any P
69+
70+
_ = concrete as Any
71+
_ = generic as Any
72+
}
73+
74+
func expected_checked_cast_errors<T: ~Copyable>(_ generic: NCG<T>, _ concrete: NCG<NoCopy>,
75+
_ concreteEsc: NEG<NoEscape>) {
76+
_ = concrete as any P // expected-error {{type_does_not_conform_decl_owner}}
77+
_ = generic as any P // expected-error {{type_does_not_conform_decl_owner}}
78+
_ = concreteEsc as any P // expected-error {{type_does_not_conform_decl_owner}}
79+
}
80+
81+
/// MARK: existential erasure requires availability, because later dynamic casts
82+
/// of that erased type will not correctly check for Copyable generic args.
83+
84+
func eraseImplicit(_ a: Any) {}
85+
86+
// expected-note@+1 9{{availability_add_attribute}}
87+
func erasure_cast_disallowed<T: ~Copyable>(_ generic: NCG<T>, _ concrete: NCG<NoCopy>, _ concreteEsc: NEG<NoEscape>) -> Any {
88+
_ = concrete as Any // expected-error {{availability_copyable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
89+
_ = concreteEsc as Any // expected-error {{availability_escapable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
90+
_ = generic as Any // expected-error {{availability_copyable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
91+
92+
let _: Any = concrete // expected-error {{availability_copyable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
93+
let _: Any = generic // expected-error {{availability_copyable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
94+
95+
let _: Any = { concreteEsc }() // expected-error {{availability_escapable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
96+
97+
eraseImplicit(concrete) // expected-error {{availability_copyable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
98+
eraseImplicit(generic) // expected-error {{availability_copyable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
99+
100+
return concrete // expected-error {{availability_copyable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
101+
}
102+
103+
struct Box<Wrapped: ~Copyable>: ~Copyable { // expected-note {{availability_add_attribute}}
104+
private let _pointer: UnsafeMutablePointer<Wrapped>
105+
106+
init(_ element: consuming Wrapped) { // expected-note {{availability_add_attribute}}
107+
_pointer = .allocate(capacity: 1)
108+
print("allocating",_pointer) // expected-error {{availability_copyable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
109+
_pointer.initialize(to: element)
110+
}
111+
}
112+
113+
/// MARK: misc. operations that are permitted
114+
115+
public protocol Debatable: ~Copyable {}
116+
117+
public func asExistential(_ t: consuming any Debatable & ~Copyable) {}
118+
119+
public func hello<T: Debatable & ~Copyable>(_ t: consuming T) {
120+
asExistential(t)
121+
}
122+
123+
extension UnsafeMutableRawPointer {
124+
public func blahInitializeMemory<T: ~Copyable>(
125+
as type: T.Type, from source: UnsafeMutablePointer<T>, count: Int
126+
) {
127+
_ = UnsafeMutableRawPointer(source + count)
128+
}
129+
}

test/Interpreter/moveonly_generics_casting.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
// RUN: %target-run-simple-swift(-Xfrontend -sil-verify-all) | %FileCheck %s
2-
// RUN: %target-run-simple-swift(-O -Xfrontend -sil-verify-all) | %FileCheck %s
1+
// RUN: %target-run-simple-swift(-Xfrontend -sil-verify-all -Xfrontend -disable-availability-checking) | %FileCheck %s
2+
// RUN: %target-run-simple-swift(-O -Xfrontend -sil-verify-all -Xfrontend -disable-availability-checking) | %FileCheck %s
33

44
// REQUIRES: executable_test
55

test/SILOptimizer/moveonly_borrowing_switch_yield.swift

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %target-swift-frontend -parse-as-library -O -emit-sil -verify %s
1+
// RUN: %target-swift-frontend -parse-as-library -O -emit-sil -verify %s -disable-availability-checking
22

33
extension List {
44
var peek: Element {
@@ -13,30 +13,8 @@ extension List {
1313
}
1414
}
1515

16-
struct MyPointer<Wrapped: ~Copyable>: Copyable {
17-
var v: UnsafeMutablePointer<Int>
18-
19-
static func allocate(capacity: Int) -> Self {
20-
fatalError()
21-
}
22-
23-
func initialize(to: consuming Wrapped) {
24-
}
25-
func deinitialize(count: Int) {
26-
}
27-
func deallocate() {
28-
}
29-
func move() -> Wrapped {
30-
fatalError()
31-
}
32-
33-
var pointee: Wrapped {
34-
_read { fatalError() }
35-
}
36-
}
37-
3816
struct Box<Wrapped: ~Copyable>: ~Copyable {
39-
private let _pointer: MyPointer<Wrapped>
17+
private let _pointer: UnsafeMutablePointer<Wrapped>
4018

4119
init(_ element: consuming Wrapped) {
4220
_pointer = .allocate(capacity: 1)

test/SILOptimizer/moveonly_consuming_switch.swift

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,7 @@
1-
// RUN: %target-swift-frontend -emit-sil -verify %s
2-
3-
// TODO: Remove this and just use the real `UnsafeMutablePointer` when
4-
// noncopyable type support has been upstreamed.
5-
struct MyPointer<Wrapped: ~Copyable>: Copyable {
6-
var v: UnsafeMutablePointer<Int>
7-
8-
static func allocate(capacity: Int) -> Self {
9-
fatalError()
10-
}
11-
12-
func initialize(to: consuming Wrapped) {
13-
}
14-
func deinitialize(count: Int) {
15-
}
16-
func deallocate() {
17-
}
18-
func move() -> Wrapped {
19-
fatalError()
20-
}
21-
}
1+
// RUN: %target-swift-frontend -emit-sil -verify %s -disable-availability-checking
222

233
struct Box<Wrapped: ~Copyable>: ~Copyable {
24-
private let _pointer: MyPointer<Wrapped>
4+
private let _pointer: UnsafeMutablePointer<Wrapped>
255

266
init(_ element: consuming Wrapped) {
277
_pointer = .allocate(capacity: 1)

0 commit comments

Comments
 (0)