Files
swift-mirror/test/SILOptimizer/cast_folding_invertible_existential.swift
Tim Kientzle ad688b6a0c Reject ~Copyable/~Escapable sources for plain existentials
This PR fixes prevents `~Copyable` or `~Escapable` values from being
put into plain (non-extended) existential containers: `Any`, `AnyObject`,
`any P`, `Error`.  These cannot record a suppressed requirement, so the
payload must be both `Copyable` and `Escapable`.

Without these checks, a `~Escapable` value can escape through an existential:
```
    func asAny(_ s: Span<Int>) -> Any? { s as? Any }   // .some(Span<Int>)
```

There are four fixes:

- SIL (`DynamicCasts.cpp`): Fix `classifyDynamicCastToProtocol` to
  return `WillFail` on any attempt to put a `~Copyable` source into
  `Copyable` existential or a `~Escapable` source into an `Escapable`
  existential. This fixes an incorrect cast optimization.

- IRGen (`MetadataRequest.cpp`): Use extended shapes whenever there is an
  inverse requirement. This ensures that runtime casts of `~Copyable`
  or `~Escapable` types receive the correct information.

- Sema (`TypeCheckConstraints.cpp`): `typeCheckCheckedCast` didn't handle
  `~Escapable` types being cast to a plain (non-extended) existential.  Attempts
  fell through to an assert (in an asserts compiler) or were silently accepted
  (in a release compiler).  Fix this to report "always fail."

- Sema (`TypeCheckConstraints.cpp`): `typeCheckCheckedCast` also believed
  that any value can bridge to `AnyObject` but this is no longer true.
  For example, `~Copyable` values can NOT be boxed into a `__SwiftValue`.

And of course some new tests to verify the above.

This complements my ongoing series of PRs to correctly handle noncopyable
existentials, including casting, `is`, and `type(of:)` tests.
2026-09-29 09:11:25 -07:00

83 lines
3.9 KiB
Swift

// A plain (non-extended) existential requires its payload to be `Copyable` and
// `Escapable` -- there is nowhere in an `ExistentialTypeMetadata` container to
// record a suppression. `classifyDynamicCastToProtocol` decided feasibility from
// the conformance alone, so a cast of a `~Escapable` value to `any P` folded to a
// static `true` purely because the conformance exists. The runtime rejects that
// cast, which made the fold a miscompile rather than merely imprecise.
//
// Both optimization levels are checked, because the fold happens in
// classifyDynamicCast and so applies at -Onone too.
// RUN: %target-swift-frontend -Onone -Xllvm -sil-print-types -emit-sil %s -enable-experimental-feature Lifetimes | %FileCheck %s
// RUN: %target-swift-frontend -O -Xllvm -sil-print-types -emit-sil %s -enable-experimental-feature Lifetimes | %FileCheck %s
// REQUIRES: swift_feature_Lifetimes
public protocol P: ~Copyable, ~Escapable {}
/// Nonescapable, and conforming to `P` -- so the conformance check succeeds while
/// the container still cannot hold it.
public struct NE: P, ~Escapable {
var t: Int
@_lifetime(immortal) public init(_ t: Int) { self.t = t }
}
/// Copyable and escapable, so every cast below must keep its old answer.
public struct OK: P { public init() {} }
// MARK: - a nonescapable source cannot inhabit a plain existential
// CHECK-LABEL: sil{{.*}}@$s{{.*}}8neToAnyPySbAA2NEVnF
// CHECK: [[R:%.*]] = integer_literal $Builtin.Int1, 0
// CHECK: struct $Bool ([[R]] : $Builtin.Int1)
// CHECK: } // end sil function '$s{{.*}}8neToAnyPySbAA2NEVnF'
public func neToAnyP(_ x: consuming NE) -> Bool { x is any P }
// CHECK-LABEL: sil{{.*}}@$s{{.*}}7neToAnyySbAA2NEVnF
// CHECK: [[R:%.*]] = integer_literal $Builtin.Int1, 0
// CHECK: } // end sil function '$s{{.*}}7neToAnyySbAA2NEVnF'
public func neToAny(_ x: consuming NE) -> Bool { x is Any }
// CHECK-LABEL: sil{{.*}}@$s{{.*}}13neToAnyObjectySbAA2NEVnF
// CHECK: [[R:%.*]] = integer_literal $Builtin.Int1, 0
// CHECK: } // end sil function '$s{{.*}}13neToAnyObjectySbAA2NEVnF'
public func neToAnyObject(_ x: consuming NE) -> Bool { x is AnyObject }
// MARK: - but it does inhabit one that suppresses the requirement
// An extended existential carries the inverse in its requirement signature, so
// this must still fold to `true` rather than being caught by the new rule.
// CHECK-LABEL: sil{{.*}}@$s{{.*}}16neToSuppressingPySbAA2NEVnF
// CHECK: [[R:%.*]] = integer_literal $Builtin.Int1, -1
// CHECK: } // end sil function '$s{{.*}}16neToSuppressingPySbAA2NEVnF'
public func neToSuppressingP(_ x: consuming NE) -> Bool { x is any P & ~Escapable }
// MARK: - a copyable, escapable source is unaffected
// CHECK-LABEL: sil{{.*}}@$s{{.*}}8okToAnyPySbAA2OKVnF
// CHECK: [[R:%.*]] = integer_literal $Builtin.Int1, -1
// CHECK: } // end sil function '$s{{.*}}8okToAnyPySbAA2OKVnF'
public func okToAnyP(_ x: consuming OK) -> Bool { x is any P }
// CHECK-LABEL: sil{{.*}}@$s{{.*}}7okToAnyySbAA2OKVnF
// CHECK: [[R:%.*]] = integer_literal $Builtin.Int1, -1
// CHECK: } // end sil function '$s{{.*}}7okToAnyySbAA2OKVnF'
public func okToAny(_ x: consuming OK) -> Bool { x is Any }
// MARK: - an existential source must not be folded
// The static type says nothing about the value inside: `any P & ~Escapable` can
// hold an `OK`, which does inhabit `Any`. So this has to reach the runtime rather
// than fold either way.
// CHECK-LABEL: sil{{.*}}@$s{{.*}}10boxSubjectySbAA1P_pRi0_s_XPnF
// CHECK-NOT: integer_literal $Builtin.Int1
// CHECK: checked_cast_addr_br
// CHECK: } // end sil function '$s{{.*}}10boxSubjectySbAA1P_pRi0_s_XPnF'
public func boxSubject(_ x: consuming any P & ~Escapable) -> Bool { x is Any }
// An archetype is equally undecidable: `S` may be bound to an escapable type.
// CHECK-LABEL: sil{{.*}}@$s{{.*}}16archetypeSubjectySbxnRi0_zlF
// CHECK-NOT: integer_literal $Builtin.Int1
// CHECK: checked_cast_addr_br
public func archetypeSubject<S: ~Escapable>(_ x: consuming S) -> Bool { x is Any }