Description
Description
When dealing with imported hierarchy of SWIFT_UNSAFE_REFERENCE or SWIFT_IMMORTAL_REFERENCE types, pointers-to-derived cannot be (implicitly or explicitly) converted to pointer-to-base within the bounds of the type system.
Reproduction
Test.h
#pragma once
#include <swift/bridging>
class Base {
public:
virtual void doSomething() = 0;
protected:
virtual ~Base() = default;
} SWIFT_UNSAFE_REFERENCE;
class Derived : public Base {
public:
void doSomething() override {}
// stand-in for "object of this type becomes visible to swift code somehow"
static Derived* make() { return new Derived; }
static void unmake(Derived* d) { delete d; }
} SWIFT_UNSAFE_REFERENCE;
// Desired behavior: Any type derived from Base can be passed to this method from swift
void inspectBase(Base*) {}
main.swift
import MyCxx
func inspectDerived() {
let d = Derived.make()
inspectBase(d) // error: cannot convert value of type 'Derived?' to expected argument type 'Base?'
inspectBase(d!) // error: cannot convert value of type 'Derived' to expected argument type 'Base'
inspectBase(d as Base?) // error: cannot convert value of type 'Derived?' to type 'Base?' in coercion
inspectBase(d! as Base) // error: cannot convert value of type 'Derived' to type 'Base' in coercion
inspectBase(d! as! Base) // warning: cast from 'Derived' to unrelated type 'Base' always fails
Derived.unmake(d)
}
module.modulemap
module MyCxx {
header "Test.h"
requires cplusplus
export *
}
Compile with:
swiftc -I. \
-cxx-interoperability-mode=default \
-I$(swiftc -print-target-info | jq -r '.paths.runtimeResourcePath + "/../../include"')
main.swift
Expected behavior
Each of the calls to inspectBase() in main.swift should compile without warnings or errors.
Environment
Swift version 6.2-dev (LLVM 162ee50b401fff2, Swift 57288d1)
Target: x86_64-unknown-linux-gnu
Build config: +assertions
aka Swiftly main-snapshot-2025-03-14
Additional information
This pattern is used in Ladybird's garbage collector, for a visitor pattern on GC cells. GC-allocated types are expected to "visit_edges" of all member variables that are also GC-allocated. A GC::Visitor is passed to each object derived from GC::Cell, and callers are expected to call visitor.visit(m_my_member);
.
I suspect this can be worked around by dodging the type system altogether.
Something like
public extension GC.Cell.Visitor {
func visit<T>(_ hopefullyACell: T) {
// convert `hopefullyACell` to OpaquePointer/UnsafeRawMutablePointer
// visit(p.assumingMemoryBound(to: GC.Cell).pointee)
// pray the caller was correct that this is a Cell, otherwise we're in trouble
}
}
https://forums.swift.org/t/ladybird-gc-and-imported-class-hierarchies/78790