Skip to content

Class#cast doesn't work correctly on virtual receivers #13706

Open
@HertzDevil

Description

@HertzDevil

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    kind:bugA bug in the code. Does not apply to documentation, specs, etc.topic:stdlib

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions