Skip to content

[cxx-interop] Imported class hierarchy of reference types unable to (implicitly) upcast to base #80231

Open
@ADKaster

Description

@ADKaster

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugA deviation from expected or documented behavior. Also: expected but undesirable behavior.c++ interopFeature: Interoperability with C++triage neededThis issue needs more specific labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions