Skip to content

Fill in extended existential support for type(of:) and mirror #72943

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions stdlib/public/runtime/Casting.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,40 @@ findDynamicValueAndType(OpaqueValue *value, const Metadata *type,
}
}
}

case MetadataKind::ExtendedExistential: {
auto *existentialType = cast<ExtendedExistentialTypeMetadata>(type);

switch (existentialType->Shape->Flags.getSpecialKind()) {
case ExtendedExistentialTypeShape::SpecialKind::None: {
auto opaqueContainer =
reinterpret_cast<OpaqueExistentialContainer *>(value);
auto innerValue = const_cast<OpaqueValue *>(opaqueContainer->projectValue());
auto innerType = opaqueContainer->Type;
return findDynamicValueAndType(innerValue, innerType,
outValue, outType, inoutCanTake, false,
isTargetExistentialMetatype);
}
case ExtendedExistentialTypeShape::SpecialKind::Class: {
auto classContainer =
reinterpret_cast<ClassExistentialContainer *>(value);
outType = swift_getObjectType((HeapObject *)classContainer->Value);
outValue = reinterpret_cast<OpaqueValue *>(&classContainer->Value);
return;
}
case ExtendedExistentialTypeShape::SpecialKind::Metatype: {
auto srcExistentialContainer =
reinterpret_cast<ExistentialMetatypeContainer *>(value);
outType = swift_getMetatypeMetadata(srcExistentialContainer->Value);
outValue = reinterpret_cast<OpaqueValue *>(&srcExistentialContainer->Value);
return;
}
case ExtendedExistentialTypeShape::SpecialKind::ExplicitLayout: {
swift_unreachable("Extended Existential with explicit layout not yet implemented");
return;
}
}
}

case MetadataKind::Metatype:
case MetadataKind::ExistentialMetatype: {
Expand Down
55 changes: 44 additions & 11 deletions stdlib/public/runtime/ReflectionMirror.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,18 +90,51 @@ unwrapExistential(const Metadata *T, OpaqueValue *Value) {
// TODO: Should look through existential metatypes too, but it doesn't
// really matter yet since we don't have any special mirror behavior for
// concrete metatypes yet.
while (T->getKind() == MetadataKind::Existential) {
auto *existential
= static_cast<const ExistentialTypeMetadata *>(T);

// Unwrap the existential container.
T = existential->getDynamicType(Value);
Value = existential->projectValue(Value);

// Existential containers can end up nested in some cases due to generic
// abstraction barriers. Repeat in case we have a nested existential.
for (;;) {
switch (T->getKind()) {
case MetadataKind::Existential: {
auto *existential
= static_cast<const ExistentialTypeMetadata *>(T);
T = existential->getDynamicType(Value);
Value = existential->projectValue(Value);
break;
}
case MetadataKind::ExtendedExistential: {
auto *existential
= static_cast<const ExtendedExistentialTypeMetadata *>(T);
switch (existential->Shape->Flags.getSpecialKind()) {
case ExtendedExistentialTypeShape::SpecialKind::None: {
auto opaqueContainer =
reinterpret_cast<OpaqueExistentialContainer *>(Value);
T = opaqueContainer->Type;
Value = const_cast<OpaqueValue *>(opaqueContainer->projectValue());
break;
}
case ExtendedExistentialTypeShape::SpecialKind::Class: {
auto classContainer =
reinterpret_cast<ClassExistentialContainer *>(Value);
T = swift_getObjectType((HeapObject *)classContainer->Value);
Value = reinterpret_cast<OpaqueValue *>(&classContainer->Value);
break;
}
case ExtendedExistentialTypeShape::SpecialKind::Metatype: {
auto srcExistentialContainer =
reinterpret_cast<ExistentialMetatypeContainer *>(Value);
T = swift_getMetatypeMetadata(srcExistentialContainer->Value);
Value = reinterpret_cast<OpaqueValue *>(&srcExistentialContainer->Value);
break;
}
case ExtendedExistentialTypeShape::SpecialKind::ExplicitLayout: {
swift_unreachable("Extended Existential with explicit layout not supported");
break;
}
}
break;
}
default:
return std::make_tuple(T, Value);
}
}
return std::make_tuple(T, Value);
}

static void copyWeakFieldContents(OpaqueValue *destContainer, const Metadata *type, OpaqueValue *fieldData) {
Expand Down
78 changes: 78 additions & 0 deletions test/Casting/TypeOf_ExtendedExistential.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// RUN: %empty-directory(%t)
//
// RUN: %target-build-swift -swift-version 5 -g -Onone -module-name a %s -o %t/a.swift5.Onone.out
// RUN: %target-codesign %t/a.swift5.Onone.out
// RUN: %target-run %t/a.swift5.Onone.out
//
// RUN: %target-build-swift -swift-version 6 -g -Onone -module-name a %s -o %t/a.swift6.Onone.out
// RUN: %target-codesign %t/a.swift6.Onone.out
// RUN: %target-run %t/a.swift6.Onone.out
//
// RUN: %target-build-swift -swift-version 5 -g -O -module-name a %s -o %t/a.swift5.O.out
// RUN: %target-codesign %t/a.swift5.O.out
// RUN: %target-run %t/a.swift5.O.out
//
// RUN: %target-build-swift -swift-version 6 -g -O -module-name a %s -o %t/a.swift6.O.out
// RUN: %target-codesign %t/a.swift6.O.out
// RUN: %target-run %t/a.swift6.O.out
//
// REQUIRES: executable_test
// REQUIRES: concurrency
// UNSUPPORTED: use_os_stdlib
// UNSUPPORTED: back_deployment_runtime

import StdlibUnittest

func blackhole<T>(_ t: T) { }

private func runtimeCast<T,U>(_ from: T, to: U.Type) -> U? {
return from as? U
}

let CastsTests = TestSuite("Casts")

protocol Box<T> {
associatedtype T
var t: T { get }
}

CastsTests.test("type(of:) should look through extended existentials (none)") {
struct C<T>: Box { var t: T }
func genericErase<T>(_ value: T) -> Any { value }
let c: any Box<Int> = C(t: 42)
if #available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) {
let x = genericErase(c)
expectEqual("C<Int>", "\(type(of:x))")
}
}

protocol OBox<T> : AnyObject {
associatedtype T
var t: T { get }
}

CastsTests.test("type(of:) should look through extended existentials (class)") {
class C<T>: OBox {
var t: T
init(t: T) { self.t = t }
}
func genericErase<T>(_ value: T) -> Any { value }
let c: any OBox<Int> = C(t: 42)
if #available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) {
let x = genericErase(c)
expectEqual("C<Int>", "\(type(of:x))")
}
}


CastsTests.test("type(of:) should look through extended existentials (metatype)") {
struct C<T>: Box { var t: T }
func genericErase<T>(_ value: T) -> Any { value }
let t: any Box<Int>.Type = C<Int>.self
if #available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) {
let x = genericErase(t)
expectEqual("C<Int>.Type", "\(type(of:x))")
}
}

runAllTests()
66 changes: 66 additions & 0 deletions test/stdlib/Mirror.swift
Original file line number Diff line number Diff line change
Expand Up @@ -921,6 +921,72 @@ mirrors.test("class/Cluster") {
//===--- Miscellaneous ----------------------------------------------------===//
//===----------------------------------------------------------------------===//

protocol Box<Value> {
associatedtype Value
var value: Value {get}
}

mirrors.test("Extended Existential (struct)") {
struct Container<Value>: Box {
var value: Value
}
func genericErase<T>(_ value: T) -> Any {
value
}
let container: any Box<Int> = Container(value: 42)
if #available(iOS 16, macOS 13, tvOS 16, watchOS 9, *) {
let subject = genericErase(container)
let mirror = Mirror(reflecting: subject)
let children = mirror.children
expectEqual(1, children.count)
let first = children.first!
expectEqual("value", first.label)
expectEqual(42, first.value as! Int)
}
}

protocol OBox<Value>: AnyObject {
associatedtype Value
var value: Value {get}
}

mirrors.test("Extended Existential (class)") {
class Container<Value>: OBox {
var value: Value
init(value: Value) { self.value = value }
}
func genericErase<T>(_ value: T) -> Any {
value
}
let container: any OBox<Int> = Container(value: 42)
if #available(iOS 16, macOS 13, tvOS 16, watchOS 9, *) {
let subject = genericErase(container)
let mirror = Mirror(reflecting: subject)
let children = mirror.children
expectEqual(1, children.count)
let first = children.first!
expectEqual("value", first.label)
expectEqual(42, first.value as! Int)
}
}

mirrors.test("Extended Existential (metatype)") {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@slavapestov Can you think of a better way to verify the mirror support here? I have checked in a debugger that it does actually hit the expected case inside the Mirror implementation.

class Container<Value>: Box {
var value: Value
init(value: Value) { self.value = value }
}
func genericErase<T>(_ value: T) -> Any {
value
}
let t: any Box<Int>.Type = Container<Int>.self
if #available(iOS 16, macOS 13, tvOS 16, watchOS 9, *) {
let subject = genericErase(t)
let mirror = Mirror(reflecting: subject)
let children = mirror.children
expectEqual(0, children.count)
}
}

mirrors.test("Addressing") {
let m0 = Mirror(reflecting: [1, 2, 3])
expectEqual(1, m0.descendant(0) as? Int)
Expand Down