Skip to content

Commit 0445405

Browse files
committed
NCGenerics: add availability checking
Not all runtimes can correctly operate with types that use noncopyable generics. When the generic argument of a type is noncopyable, old runtimes can't recognize that to correctly check conformances that may be conditional on those arguments being Copyable, etc. resolves rdar://126239335
1 parent 5c57745 commit 0445405

File tree

7 files changed

+234
-49
lines changed

7 files changed

+234
-49
lines changed

include/swift/AST/DiagnosticsSema.def

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

6740+
ERROR(availability_copyable_generics_casting_only_version_newer, none,
6741+
"runtime support for casting types with noncopyable generic arguments "
6742+
"is only available in %0 %1 or newer",
6743+
(StringRef, llvm::VersionTuple))
6744+
6745+
ERROR(availability_escapable_generics_casting_only_version_newer, none,
6746+
"runtime support for casting types with nonescapable generic arguments "
6747+
"is only available in %0 %1 or newer",
6748+
(StringRef, llvm::VersionTuple))
6749+
67406750
ERROR(availability_typed_throws_only_version_newer, none,
67416751
"runtime support for typed throws function types is only available in "
67426752
"%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: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3032,6 +3032,83 @@ static bool diagnoseTypedThrowsAvailability(
30323032
ReferenceDC);
30333033
}
30343034

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

3078-
return checkTypeMetadataAvailabilityInternal(type, refLoc, refDC);
3155+
if (checkTypeMetadataAvailabilityInternal(type, refLoc, refDC))
3156+
return true;
3157+
3158+
if (checkInverseGenericsCastingAvailability(type, refLoc, refDC))
3159+
return true;
3160+
3161+
return false;
30793162
}
30803163

30813164
namespace {
@@ -3476,6 +3559,9 @@ class ExprAvailabilityWalker : public ASTWalker {
34763559
if (auto EE = dyn_cast<ErasureExpr>(E)) {
34773560
checkTypeMetadataAvailability(EE->getSubExpr()->getType(),
34783561
EE->getLoc(), Where.getDeclContext());
3562+
checkInverseGenericsCastingAvailability(EE->getSubExpr()->getType(),
3563+
EE->getLoc(),
3564+
Where.getDeclContext());
34793565

34803566
for (ProtocolConformanceRef C : EE->getConformances()) {
34813567
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)