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

Inconsistent inheritance in generics #9748

Closed
wolfgang371 opened this issue Sep 14, 2020 · 7 comments
Closed

Inconsistent inheritance in generics #9748

wolfgang371 opened this issue Sep 14, 2020 · 7 comments
Labels
kind:bug A bug in the code. Does not apply to documentation, specs, etc. topic:compiler:generics

Comments

@wolfgang371
Copy link

wolfgang371 commented Sep 14, 2020

The following code shows three different (incompatible) flavors of generic inheritance:

class Arg
end

class Arg2 < Arg
end

class Arg3
end

class A(X)
	def initialize
		@x = X.new
		p true
	end
end

class B(X) < A(X)
end

#########################################################
# first test
# compiles, but yields wrong result (known bug):
p B(Arg).is_a?(A(Arg)) # this yields "false"

#########################################################
# second test
# dispite the is_a?-bug all the following compiles:
def mytest(a : A(Arg))
end
mytest(A(Arg).new)
mytest(B(Arg).new)
mytest(A(Arg2).new)
mytest(B(Arg2).new)
A(Arg).new
B(Arg).new
A(Arg2).new
B(Arg2).new
#~ mytest(B(Arg3).new) # does not compile since Arg3 is not in hierarchy of Arg
# -> so all four variants seem to be subclasses of A(Arg)

#########################################################
# third test
# but then again here only 50% compiles:
class MyTest
	def initialize(@x : A(Arg))
	end
end
MyTest.new(A(Arg).new)
MyTest.new(B(Arg).new)
#~ MyTest.new(A(Arg2).new) # doesn't compile: instance variable '@x' of MyTest must be A(Arg), not A(Arg2)
#~ MyTest.new(B(Arg2).new) # doesn't compile: instance variable '@x' of MyTest must be A(Arg), not B(Arg2)
# -> so only the first two seem to be subclasses of A(Arg); or at least can be used as instance variables

Looks like a bug to me and I couldn't find it so far.

I'm using crystal 0.35.1 on Ubuntu.

@asterite
Copy link
Member

The fist is_a? is a bug. Then, type restrictions (the type you put in method arguments) work differently than type checks (is_a?) and assigning to instance vars is another case that works a bit differently. Those are essentially a duplicate of #3803

And the first one might be related to #9660

For now, I would advice avoiding generic inheritance if possible.

@wolfgang371
Copy link
Author

@asterite: thanks for the quick answer; I checked those two issues you mention beforehand and did not find a reference to instance variables - so I posted here... I still don't see it as duplicate.
But I agree, generic inheritance doesn't look so good as of now, unfortunately. I wanted to implement a generic graph with some graph nodes and derive a specialized tree with some specialized tree nodes...

@HertzDevil
Copy link
Contributor

B(Arg).is_a?(A(Arg)) is always false because B(Arg)'s type is a metaclass and A(Arg) isn't. B(Arg) <= A(Arg) and B(Arg).new.is_a?(A(Arg)) both return true.

However B(Arg).is_a?(A(Arg).class) is also false. That I believe is an actual bug since metaclasses should mirror the same hierarchy as normal classes.

@wolfgang371
Copy link
Author

I cannot follow you. A(X) is a base class (not a metaclass), B(X) its subclass, isn't it?
A(X).class (and B(X).class) are probably metaclasses to my understanding.
Both the concept of metaclass and operator <= on inheritance I cannot find in the manual (but the latter one is in the API docs).

In the API docs it reads:

def self.<=(other : T.class) : Bool forall T # Returns whether this class inherits or includes other, or is equal to other.
is_a?(type : Class) : Bool # Returns true if self inherits or includes type. type must be a constant or typeof() expression. It cannot be evaluated at runtime.

To me they sound almost alike, unless is_a? is compile-time only, which would also match my initial code where everything needs to be decided in compile-time.

@HertzDevil
Copy link
Contributor

The receiver of is_a? is an instance, not the class itself, so if you do B(Arg).is_a?(...), you're referring to B(Arg) as an instance of B(Arg).class, imstead of some instance of B(Arg) (unless B(Arg) extends itself). That's why B(Arg).is_a?(A(Arg)) returning false is not a bug.

@wolfgang371
Copy link
Author

got it, thanks; so I should have called B(Arg).new.is_a?(A(Arg)), which works fine.
So I'm only stuck with my "third test" failing.

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

Closing as duplicate of #2665

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:generics
Projects
None yet
Development

No branches or pull requests

4 participants