You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Previously we discussed the possibility of recovering Crystal wrapper instances/types from their @unwrap pointers, but it turns out that only solves part of the marshalling problem; if there wasn't a wrapper instance in the first place, we are left with a C pointer in Crystal with no access to C++'s RTTI. This issue attempts to address this other part. Consider:
structA {
virtual~A() { }
A *create();
};
structB : A { };
structC : A { };
A *A::create() {
returnnew B;
}
moduleTestclassAdefcreate : AA.new(unwrap:Binding.bg_A_create_(self))
endendclassB < AendclassC < Aendend
x =Test::A.new.create
x.as?(Test::B) # returns `nil`, runtime type of `x` is `Test::A` (or even `Test::AImpl`)
x.unsafe_as(Test::C) # bad idea
To that end I suggest wrapping dynamic_cast for every possible downcast from one wrapped polymorphic type to another. For example, the cast from A to B would look like:
extern"C" B * bg_A__CAST_B_(A * _self_) {
returndynamic_cast<B *>(_self_);
}
moduleTestlibBindingaliasA=VoidaliasB=Voidfunbg_A__CAST_B_(_self_ : A*) : B*endclassAdefto?(_type_ : B.class) : B?
ptr =Binding.bg_A__CAST_B_(self)
B.new(unwrap: ptr) unless ptr.null?
endendclassB < A# cast is not needed from here, because `Test::B` and# its subclasses always wrap a `B` instance from C++# the return type is also no longer nilabledefto?(_type_ : B.class) : Bselfendendend
x.to?(Test::B) # => #<Test::B:...>
x.to?(Test::C) # => nil
Taking inspiration from block overloads, these cast methods take the target wrapper class itself as an argument. The C++ wrappers always perform casts using pointers; bad casts can be reported on the Crystal side with #not_nil!.
For a linear hierarchy of n classes (Tn < Tn-1 < ... < T2 < T1), a naive approach would generate a total of O(n²) #to? methods. This can be reduced to O(n) by noting that pointers to base types are also pointers to derived types, so that e.g. T1#to?(T4.class) can be reused in T2 and T3 and therefore does not have to be redefined in those subclasses. (The case with multiple inheritance will be more complicated.)
No special handling is needed for abstract wrappers; the Impl classes will pick up the #to? methods from the abstract classes they inherit from. Code like this will finally be possible:
defall_groups(scene : Qt::GraphicsScene)
scene.items.compact_map &.to?(Qt::GraphicsItemGroup)
end
As part of the changes I'd suggest that the existing upcast methods for classes with multiple bases (the #as_X methods generated by the Inheritance processor) also take the #to? form. Note that they already share a similar C++ body, using static_cast instead of dynamic_cast.
The text was updated successfully, but these errors were encountered:
Previously we discussed the possibility of recovering Crystal wrapper instances/types from their
@unwrap
pointers, but it turns out that only solves part of the marshalling problem; if there wasn't a wrapper instance in the first place, we are left with a C pointer in Crystal with no access to C++'s RTTI. This issue attempts to address this other part. Consider:To that end I suggest wrapping
dynamic_cast
for every possible downcast from one wrapped polymorphic type to another. For example, the cast fromA
toB
would look like:Taking inspiration from block overloads, these cast methods take the target wrapper class itself as an argument. The C++ wrappers always perform casts using pointers; bad casts can be reported on the Crystal side with
#not_nil!
.For a linear hierarchy of n classes (
Tn < Tn-1 < ... < T2 < T1
), a naive approach would generate a total of O(n²)#to?
methods. This can be reduced to O(n) by noting that pointers to base types are also pointers to derived types, so that e.g.T1#to?(T4.class)
can be reused inT2
andT3
and therefore does not have to be redefined in those subclasses. (The case with multiple inheritance will be more complicated.)No special handling is needed for abstract wrappers; the
Impl
classes will pick up the#to?
methods from the abstract classes they inherit from. Code like this will finally be possible:As part of the changes I'd suggest that the existing upcast methods for classes with multiple bases (the
#as_X
methods generated by the Inheritance processor) also take the#to?
form. Note that they already share a similar C++ body, usingstatic_cast
instead ofdynamic_cast
.The text was updated successfully, but these errors were encountered: