mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
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
This commit is contained in:
@@ -6737,6 +6737,16 @@ ERROR(availability_isolated_any_only_version_newer, none,
|
||||
"%0 %1 or newer",
|
||||
(StringRef, llvm::VersionTuple))
|
||||
|
||||
ERROR(availability_copyable_generics_casting_only_version_newer, none,
|
||||
"runtime support for casting types with noncopyable generic arguments "
|
||||
"is only available in %0 %1 or newer",
|
||||
(StringRef, llvm::VersionTuple))
|
||||
|
||||
ERROR(availability_escapable_generics_casting_only_version_newer, none,
|
||||
"runtime support for casting types with nonescapable generic arguments "
|
||||
"is only available in %0 %1 or newer",
|
||||
(StringRef, llvm::VersionTuple))
|
||||
|
||||
ERROR(availability_typed_throws_only_version_newer, none,
|
||||
"runtime support for typed throws function types is only available in "
|
||||
"%0 %1 or newer",
|
||||
|
||||
@@ -71,6 +71,8 @@ FEATURE(SwiftExceptionPersonality, (6, 0))
|
||||
// Metadata support for @isolated(any) function types
|
||||
FEATURE(IsolatedAny, (6, 0))
|
||||
|
||||
FEATURE(NoncopyableGenerics, (6, 0))
|
||||
|
||||
FEATURE(TaskExecutor, FUTURE)
|
||||
FEATURE(Differentiation, FUTURE)
|
||||
FEATURE(InitRawStructMetadata, FUTURE)
|
||||
|
||||
@@ -3032,6 +3032,83 @@ static bool diagnoseTypedThrowsAvailability(
|
||||
ReferenceDC);
|
||||
}
|
||||
|
||||
/// Make sure the generic arguments conform to all known invertible protocols.
|
||||
/// Runtimes prior to NoncopyableGenerics do not check if any of the
|
||||
/// generic arguments conform to Copyable/Escapable during dynamic casts.
|
||||
/// But a dynamic cast *needs* to check if the generic arguments conform,
|
||||
/// to determine if the cast should be permitted at all. For example:
|
||||
///
|
||||
/// struct X<T> {}
|
||||
/// extension X: P where T: Y {}
|
||||
///
|
||||
/// func f<Y: ~Copyable>(...) {
|
||||
/// let x: X<Y> = ...
|
||||
/// _ = x as? any P // <- cast should fail
|
||||
/// }
|
||||
///
|
||||
/// The dynamic cast here must fail because Y does not conform to Copyable,
|
||||
/// thus X<Y> doesn't conform to P!
|
||||
///
|
||||
/// \param boundTy The generic type with its generic arguments.
|
||||
/// \returns the invertible protocol for which a conformance is missing in
|
||||
/// one of the generic arguments, or none if all are present for
|
||||
/// every generic argument.
|
||||
static std::optional<InvertibleProtocolKind> checkGenericArgsForInvertibleReqs(
|
||||
BoundGenericType *boundTy) {
|
||||
for (auto arg : boundTy->getGenericArgs()) {
|
||||
for (auto ip : InvertibleProtocolSet::allKnown()) {
|
||||
switch (ip) {
|
||||
case InvertibleProtocolKind::Copyable:
|
||||
if (arg->isNoncopyable())
|
||||
return ip;
|
||||
break;
|
||||
case InvertibleProtocolKind::Escapable:
|
||||
if (!arg->isEscapable())
|
||||
return ip;
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/// Older runtimes won't check for required invertible protocol conformances
|
||||
/// at runtime during a cast.
|
||||
///
|
||||
/// \param srcType the source or initial type of the cast
|
||||
/// \param refLoc source location of the cast
|
||||
/// \param refDC decl context in which the cast occurs
|
||||
/// \return true if diagnosed
|
||||
static bool checkInverseGenericsCastingAvailability(Type srcType,
|
||||
SourceRange refLoc,
|
||||
const DeclContext *refDC) {
|
||||
if (!srcType) return false;
|
||||
|
||||
auto type = srcType->getCanonicalType();
|
||||
|
||||
if (auto boundTy = dyn_cast<BoundGenericType>(type)) {
|
||||
if (auto missing = checkGenericArgsForInvertibleReqs(boundTy)) {
|
||||
std::optional<Diag<StringRef, llvm::VersionTuple>> diag;
|
||||
switch (*missing) {
|
||||
case InvertibleProtocolKind::Copyable:
|
||||
diag =
|
||||
diag::availability_copyable_generics_casting_only_version_newer;
|
||||
break;
|
||||
case InvertibleProtocolKind::Escapable:
|
||||
diag =
|
||||
diag::availability_escapable_generics_casting_only_version_newer;
|
||||
break;
|
||||
}
|
||||
|
||||
// Enforce the availability restriction.
|
||||
return TypeChecker::checkAvailability(
|
||||
refLoc,
|
||||
refDC->getASTContext().getNoncopyableGenericsAvailability(),
|
||||
*diag,
|
||||
refDC);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool checkTypeMetadataAvailabilityInternal(CanType type,
|
||||
SourceRange refLoc,
|
||||
const DeclContext *refDC) {
|
||||
@@ -3075,7 +3152,13 @@ static bool checkTypeMetadataAvailabilityForConverted(Type refType,
|
||||
// there.
|
||||
if (type.isAnyExistentialType()) return false;
|
||||
|
||||
return checkTypeMetadataAvailabilityInternal(type, refLoc, refDC);
|
||||
if (checkTypeMetadataAvailabilityInternal(type, refLoc, refDC))
|
||||
return true;
|
||||
|
||||
if (checkInverseGenericsCastingAvailability(type, refLoc, refDC))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
namespace {
|
||||
@@ -3476,6 +3559,9 @@ public:
|
||||
if (auto EE = dyn_cast<ErasureExpr>(E)) {
|
||||
checkTypeMetadataAvailability(EE->getSubExpr()->getType(),
|
||||
EE->getLoc(), Where.getDeclContext());
|
||||
checkInverseGenericsCastingAvailability(EE->getSubExpr()->getType(),
|
||||
EE->getLoc(),
|
||||
Where.getDeclContext());
|
||||
|
||||
for (ProtocolConformanceRef C : EE->getConformances()) {
|
||||
diagnoseConformanceAvailability(E->getLoc(), C, Where, Type(), Type(),
|
||||
|
||||
129
test/Generics/inverse_casting_availability.swift
Normal file
129
test/Generics/inverse_casting_availability.swift
Normal file
@@ -0,0 +1,129 @@
|
||||
// RUN: %target-typecheck-verify-swift \
|
||||
// RUN: -debug-diagnostic-names -target arm64-apple-macos14.4 \
|
||||
// RUN: -enable-experimental-feature NonescapableTypes
|
||||
|
||||
// REQUIRES: OS=macosx || OS=ios || OS=tvos || OS=watchOS || OS=xros
|
||||
|
||||
protocol P {}
|
||||
struct NCG<T: ~Copyable> {}
|
||||
extension NCG: P where T: Copyable {} // expected-note 2{{requirement_implied_by_conditional_conformance}}
|
||||
|
||||
struct NEG<T: ~Escapable> {}
|
||||
extension NEG: P {} // expected-note {{requirement_implied_by_conditional_conformance}}
|
||||
|
||||
struct All {}
|
||||
struct NoCopy: ~Copyable {}
|
||||
struct NoEscape: ~Escapable {}
|
||||
|
||||
|
||||
|
||||
/// MARK: dynamic casts are gated by availability. Older runtimes don't check
|
||||
/// for conformance to Copyable so they'll give bogus results.
|
||||
|
||||
// expected-note@+1 8{{availability_add_attribute}}
|
||||
func dyn_cast_errors<T: ~Copyable, V: ~Escapable>(
|
||||
_ generic: NCG<T>, _ concrete: NCG<NoCopy>,
|
||||
_ genericEsc: NEG<V>, _ concreteEsc: NEG<NoEscape>) {
|
||||
_ = concrete as? any P // expected-error {{availability_copyable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
|
||||
_ = generic as? any P // expected-error {{availability_copyable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
|
||||
|
||||
_ = concrete is any P // expected-error {{availability_copyable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
|
||||
_ = generic is any P // expected-error {{availability_copyable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
|
||||
|
||||
_ = concrete as! any P // expected-error {{availability_copyable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
|
||||
_ = generic as! any P // expected-error {{availability_copyable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
|
||||
|
||||
_ = genericEsc as? any P // expected-error {{availability_escapable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
|
||||
_ = concreteEsc is any P // expected-error {{availability_escapable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
|
||||
}
|
||||
|
||||
@available(SwiftStdlib 6.0, *)
|
||||
func FIXED_dyn_cast_errors<T: ~Copyable, V: ~Escapable>(
|
||||
_ generic: NCG<T>, _ concrete: NCG<NoCopy>,
|
||||
_ genericEsc: NEG<V>, _ concreteEsc: NEG<NoEscape>) {
|
||||
_ = concrete as? any P
|
||||
_ = generic as? any P
|
||||
|
||||
_ = concrete is any P
|
||||
_ = generic is any P
|
||||
|
||||
_ = concrete as! any P
|
||||
_ = generic as! any P
|
||||
|
||||
_ = genericEsc as? any P
|
||||
_ = concreteEsc is any P
|
||||
}
|
||||
|
||||
func noAvailabilityNeeded<T>(_ generic: NCG<T>, _ concrete: NCG<All>) {
|
||||
_ = concrete as? any P // expected-warning {{conditional_downcast_coercion}}
|
||||
_ = generic as? any P // expected-warning {{conditional_downcast_coercion}}
|
||||
|
||||
_ = concrete is any P // expected-warning {{isa_is_always_true}}
|
||||
_ = generic is any P // expected-warning {{isa_is_always_true}}
|
||||
|
||||
_ = concrete as! any P // expected-warning {{forced_downcast_coercion}}
|
||||
_ = generic as! any P // expected-warning {{forced_downcast_coercion}}
|
||||
|
||||
_ = concrete as any P
|
||||
_ = generic as any P
|
||||
|
||||
_ = concrete as Any
|
||||
_ = generic as Any
|
||||
}
|
||||
|
||||
func expected_checked_cast_errors<T: ~Copyable>(_ generic: NCG<T>, _ concrete: NCG<NoCopy>,
|
||||
_ concreteEsc: NEG<NoEscape>) {
|
||||
_ = concrete as any P // expected-error {{type_does_not_conform_decl_owner}}
|
||||
_ = generic as any P // expected-error {{type_does_not_conform_decl_owner}}
|
||||
_ = concreteEsc as any P // expected-error {{type_does_not_conform_decl_owner}}
|
||||
}
|
||||
|
||||
/// MARK: existential erasure requires availability, because later dynamic casts
|
||||
/// of that erased type will not correctly check for Copyable generic args.
|
||||
|
||||
func eraseImplicit(_ a: Any) {}
|
||||
|
||||
// expected-note@+1 9{{availability_add_attribute}}
|
||||
func erasure_cast_disallowed<T: ~Copyable>(_ generic: NCG<T>, _ concrete: NCG<NoCopy>, _ concreteEsc: NEG<NoEscape>) -> Any {
|
||||
_ = concrete as Any // expected-error {{availability_copyable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
|
||||
_ = concreteEsc as Any // expected-error {{availability_escapable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
|
||||
_ = generic as Any // expected-error {{availability_copyable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
|
||||
|
||||
let _: Any = concrete // expected-error {{availability_copyable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
|
||||
let _: Any = generic // expected-error {{availability_copyable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
|
||||
|
||||
let _: Any = { concreteEsc }() // expected-error {{availability_escapable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
|
||||
|
||||
eraseImplicit(concrete) // expected-error {{availability_copyable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
|
||||
eraseImplicit(generic) // expected-error {{availability_copyable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
|
||||
|
||||
return concrete // expected-error {{availability_copyable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
|
||||
}
|
||||
|
||||
struct Box<Wrapped: ~Copyable>: ~Copyable { // expected-note {{availability_add_attribute}}
|
||||
private let _pointer: UnsafeMutablePointer<Wrapped>
|
||||
|
||||
init(_ element: consuming Wrapped) { // expected-note {{availability_add_attribute}}
|
||||
_pointer = .allocate(capacity: 1)
|
||||
print("allocating",_pointer) // expected-error {{availability_copyable_generics_casting_only_version_newer}} // expected-note {{availability_guard_with_version_check}}
|
||||
_pointer.initialize(to: element)
|
||||
}
|
||||
}
|
||||
|
||||
/// MARK: misc. operations that are permitted
|
||||
|
||||
public protocol Debatable: ~Copyable {}
|
||||
|
||||
public func asExistential(_ t: consuming any Debatable & ~Copyable) {}
|
||||
|
||||
public func hello<T: Debatable & ~Copyable>(_ t: consuming T) {
|
||||
asExistential(t)
|
||||
}
|
||||
|
||||
extension UnsafeMutableRawPointer {
|
||||
public func blahInitializeMemory<T: ~Copyable>(
|
||||
as type: T.Type, from source: UnsafeMutablePointer<T>, count: Int
|
||||
) {
|
||||
_ = UnsafeMutableRawPointer(source + count)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// RUN: %target-run-simple-swift(-Xfrontend -sil-verify-all) | %FileCheck %s
|
||||
// RUN: %target-run-simple-swift(-O -Xfrontend -sil-verify-all) | %FileCheck %s
|
||||
// RUN: %target-run-simple-swift(-Xfrontend -sil-verify-all -Xfrontend -disable-availability-checking) | %FileCheck %s
|
||||
// RUN: %target-run-simple-swift(-O -Xfrontend -sil-verify-all -Xfrontend -disable-availability-checking) | %FileCheck %s
|
||||
|
||||
// REQUIRES: executable_test
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// RUN: %target-swift-frontend -parse-as-library -O -emit-sil -verify %s
|
||||
// RUN: %target-swift-frontend -parse-as-library -O -emit-sil -verify %s -disable-availability-checking
|
||||
|
||||
extension List {
|
||||
var peek: Element {
|
||||
@@ -13,30 +13,8 @@ extension List {
|
||||
}
|
||||
}
|
||||
|
||||
struct MyPointer<Wrapped: ~Copyable>: Copyable {
|
||||
var v: UnsafeMutablePointer<Int>
|
||||
|
||||
static func allocate(capacity: Int) -> Self {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func initialize(to: consuming Wrapped) {
|
||||
}
|
||||
func deinitialize(count: Int) {
|
||||
}
|
||||
func deallocate() {
|
||||
}
|
||||
func move() -> Wrapped {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
var pointee: Wrapped {
|
||||
_read { fatalError() }
|
||||
}
|
||||
}
|
||||
|
||||
struct Box<Wrapped: ~Copyable>: ~Copyable {
|
||||
private let _pointer: MyPointer<Wrapped>
|
||||
private let _pointer: UnsafeMutablePointer<Wrapped>
|
||||
|
||||
init(_ element: consuming Wrapped) {
|
||||
_pointer = .allocate(capacity: 1)
|
||||
|
||||
@@ -1,27 +1,7 @@
|
||||
// RUN: %target-swift-frontend -emit-sil -verify %s
|
||||
|
||||
// TODO: Remove this and just use the real `UnsafeMutablePointer` when
|
||||
// noncopyable type support has been upstreamed.
|
||||
struct MyPointer<Wrapped: ~Copyable>: Copyable {
|
||||
var v: UnsafeMutablePointer<Int>
|
||||
|
||||
static func allocate(capacity: Int) -> Self {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func initialize(to: consuming Wrapped) {
|
||||
}
|
||||
func deinitialize(count: Int) {
|
||||
}
|
||||
func deallocate() {
|
||||
}
|
||||
func move() -> Wrapped {
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
// RUN: %target-swift-frontend -emit-sil -verify %s -disable-availability-checking
|
||||
|
||||
struct Box<Wrapped: ~Copyable>: ~Copyable {
|
||||
private let _pointer: MyPointer<Wrapped>
|
||||
private let _pointer: UnsafeMutablePointer<Wrapped>
|
||||
|
||||
init(_ element: consuming Wrapped) {
|
||||
_pointer = .allocate(capacity: 1)
|
||||
|
||||
Reference in New Issue
Block a user