Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
HertzDevil opened this issue Jul 25, 2023 · 1 comment
Open

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

HertzDevil opened this issue Jul 25, 2023 · 1 comment

Comments

@HertzDevil
Copy link
Contributor

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

@beta-ziliani
Copy link
Member

What does {% @type %} do in this case? And what's the relation to reopening classes?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants