Open
Description
t.cast(x)
effectively returns x.as(t)
. However the following suggests otherwise:
class Parent
end
class Child1 < Parent
end
class Child2 < Parent
end
# Child1.cast(Parent.new) # Error: can't cast Parent to Child1
[Child1, Child2][0].cast(Parent.new) # => #<Parent:0x7f9f6792ae70>
Child1.as(Parent.class).cast(Parent.new) # => #<Parent:0x7f9f6792ae60>
It seems that Class#cast
is not duplicated for each actual metaclass, so the last two lines are calling Parent+.cast
, which always succeeds.
Instead Class#cast
should reference {% @type %}
so that it performs the correct casts:
class Class
def cast2(other)
{% @type %}
other.as(self)
end
end
Parent.as(Parent.class).cast2(Parent.new) # Error: can't cast Parent to Child2
Parent.as(Parent.class).cast2(Child1.new) # Error: can't cast Child1 to Child2
Parent.as(Parent.class).cast2(Child2.new) # Error: can't cast Child2 to Child1
which doesn't work. I believe this is because the compiler tries to instantiate as
calls that always fail and produce compile-time errors. Considering the contexts where Class#cast
is likely to be used (the receiver is not known ahead of time, otherwise a plain as
would have sufficed), I don't think Class#cast
should ever fail to compile. So the implementation might look like this instead:
class Class
def cast2(other)
{% @type %}
other.is_a?(self) ? other : raise "..."
end
end
Parent.as(Parent.class).cast2(Parent.new) # => #<Parent:0x7f6b3df98e70>
Parent.as(Parent.class).cast2(Child1.new) # => #<Child1:0x7f6b3df98e60>
Parent.as(Parent.class).cast2(Child2.new) # => #<Child2:0x7f6b3df98e50>
Child1.as(Parent.class).cast2(Parent.new) # raises
Child1.as(Parent.class).cast2(Child1.new) # => #<Child1:0x7f6b3df98e40>
Child1.as(Parent.class).cast2(Child2.new) # raises
Child2.as(Parent.class).cast2(Parent.new) # raises
Child2.as(Parent.class).cast2(Child1.new) # raises
Child2.as(Parent.class).cast2(Child2.new) # => #<Child2:0x7f6b3df98e30>
Related: #8422