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

Fix logic for responds_to? of generic module instances #10760

Conversation

HertzDevil
Copy link
Contributor

@HertzDevil HertzDevil commented May 29, 2021

Fixes #8334 (more specifically snippet 4 there). This resembles #10519 but the mechanisms are vastly different.

There are essentially two kinds of logic used in responds_to?(:foo) filters:

  • Non-virtual: If T or its parents define any method called foo, the filtered type is T.
  • Virtual: The filtered type is the union of all subtypes of T, through either including or inheriting T, that respond to foo.

Virtual classes and modules are supposed to use the latter, but generic module instances use the former for some reason. This PR makes them behave like non-generic modules:

module A(T)
end

class B(T)
  include A(T)

  def f; end
end

typeof(begin
  x = B(Int32).new.as(A(Int32))
  x.responds_to?(:f) ? x : raise ""
end)      # => B(Int32)
# before: # => NoReturn

One consequence is that even if the generic module itself defines that method, responds_to? will filter the variable to all including types of the module:

module A(T)
  def f; end
end

class B(T)
  include A(T)
end

class C
  include A(Int32)
end

typeof(begin
  x = B(Int32).new.as(A(Int32))
  x.responds_to?(:f) ? x : raise ""
end)      # => B(Int32) | C
# before: # => A(Int32)

This behaviour is currently consistent with non-generic modules. There might be a follow-up PR to make modules exhibit both non-virtual logic and fall back to virtual logic, which can eliminate very large unions and/or NoReturn in some places. (In fact I said the exact same thing on #10519.)

Type filtering and codegen are both backed by Crystal::Type#filter_by_responds_to so this PR fixes both at once.

Copy link
Member

@asterite asterite left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

@straight-shoota straight-shoota added kind:bug A bug in the code. Does not apply to documentation, specs, etc. topic:compiler:semantic labels May 29, 2021
Copy link
Member

@sdogruyol sdogruyol left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @HertzDevil 🙏

@asterite asterite added this to the 1.1.0 milestone Jun 6, 2021
@asterite asterite merged commit 5183e02 into crystal-lang:master Jun 6, 2021
@HertzDevil HertzDevil deleted the bug/generic-module-instance-responds-to branch June 6, 2021 18:32
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:compiler:semantic
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Inconsistent behaviour for .as(A), where A is either a class or a module
4 participants