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

Compiler: make is_a?(union) work correctly for virtual types #11176

Merged
merged 1 commit into from
Sep 6, 2021

Conversation

asterite
Copy link
Member

@asterite asterite commented Sep 6, 2021

Fixes #10244

Now the snippet in that issue gives this:

class Foo; end

class BarA < Foo; end
class BarB < Foo; end
class BarC < Foo; end

Union(BarB, BarC)              # => Foo
BarB | BarC                    # => Foo

x = BarA.new || BarB.new || BarC.new
typeof(x)                      # => Foo
x.is_a?(BarB) || x.is_a?(BarC) # => false
x.is_a?(BarB | BarC)           # => false
x.is_a?(Union(BarB, BarC))     # => false
x.is_a?(Foo)                   # => true

y = BarA.new
typeof(y)                      # => BarA
y.is_a?(BarB) || x.is_a?(BarC) # => false
y.is_a?(BarB | BarC)           # => false
y.is_a?(Union(BarB, BarC))     # => false
y.is_a?(Foo)                   # => true

which I believe is more correct than the previous behavior.

Please note that this change only applies to is_a?: as you can see, Union(BarB, BarC) unifies to the parent type. In theory we could also make that change, but it's much more complex (a quick change to this already makes the compiler stop compiling itself, or the std.) But, I think checking x.is_a?(X | Y) is much more common than using union types outside of is_a?. So for now I'd like to defer this.

Also, I need this change to keep working on the interpreter. The way I implemented multidispatch there, I end up generating code like exp.is_a?(B | C) and if that resolves to the parent type then it's not the same condition I'm looking for. I could do some hacks around that, or try to implement multidispatch in a different, but much harder, way, but I think it's better if we also fix these small edges in the language.

It seems this change also fixes this bug:

class A
end

class B < A
end

class C < B
end

x = C.new || B.new
if x.is_a?(B | C)
  # This would print "B" before this PR, not it prints "B | C"
  puts typeof(x)
end

@asterite
Copy link
Member Author

asterite commented Sep 6, 2021

Technically this is a breaking change, because a code that did exp.is_a?(B | C) would go from returning true to returning false. However, if you did that and B and C were both under the same type hierarchy, the result you got was probably unexpected, or you hit the other bug I mentioned, so you would not do that. Instead, you probably would do exp.is_a?(B) || exp.is_a?(C). So this breaking change is very unlikely to actually break things.

@straight-shoota straight-shoota added kind:bug A bug in the code. Does not apply to documentation, specs, etc. topic:lang:type-system labels Sep 6, 2021
@j8r
Copy link
Contributor

j8r commented Sep 6, 2021

Yes, people likely also used

case x
when B, C then do_something
else do_other_things
end

@asterite asterite added this to the 1.2.0 milestone Sep 6, 2021
@asterite asterite merged commit 0f296dc into master Sep 6, 2021
@asterite asterite deleted the is-a-union branch September 6, 2021 23:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind:bug A bug in the code. Does not apply to documentation, specs, etc. topic:lang:type-system
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Explicit Union does not agree with Class#| inside is_a? on virtual type hierarchy
3 participants