crystal
crystal copied to clipboard
`is_a?` fails when using `self.class`
Bug Report
I know this type check isn't necessary because of the type annotation in the method signature, but it is a simplification of the original code. I've seen a few issues about is_a?, but nothing with this specific scenario.
class MyArray
getter elements
def initialize(elements = [] of Bool)
@elements = elements
end
def ==(other : MyArray) : Bool
p! self.class
p! other.class
p! other.is_a?(self.class)
p! other.is_a?(MyArray)
if other.is_a?(self.class)
return @elements == other.elements
end
false
end
end
p MyArray.new([true]) == MyArray.new([true])
this outputs
self.class # => MyArray
other.class # => MyArray
other.is_a?(self.class) # => false
other.is_a?(MyArray) # => true
false
Crystal 1.16.3 [3f369d2c7] (2025-05-12) LLVM: 15.0.7 Default target: x86_64-apple-macosx11.0
I'm somewhat surprised it compiles at all given normally you can't pass a method call to is_a?. Could just be that self.class is "special" so it's evaluating as like .is_a?(typeof(self.class)) which would explain why it returns false.
Some more investigation. If I change it to
if other.is_a?(self) # returns true!
return @elements == other.elements
end
Shouldn't is_a? only receive classes?
I tried calling random methods on self but there seem to be an explicit check for self.class:
if other.is_a?(self.test)
return @elements == other.elements
end
Error: expecting identifier 'class', not 'test'
AFAIK: there are a few things at play:
#is_a?is a pseudo method that the parser handles specifically and it expects the value to be a type;selfcan be a valid type, since the parser expects a type,selfthen refers to the current type (e.g.MyArray) not the variable;
My guess is that self.class is parsed as the MyArray.class type. For example in x = MyArray the x variable has the MyArray.class type (not an instance of).
So other.is_a?(self.class) doesn't work, but other.is_a?(self) does.
@ysbaddaden that makes sense. It sounds like a bug to me, but now I'm confused whether this is the expected behavior
I'm pretty sure it is as @ysbaddaden described. self inside is_a? references the class, not the instance. So it's not the same self as in p! self.class.
This is entirely intended behaviour, but at the same time it's super unexpected, of course.
I don't think there's an easy way to improve this. Perhaps we should consider introducing a different identifier for the self type (for lack of any better idea: Self). But this requires some effort to implement and find a good name in the first place. And it might still cause some friction.
So probably not a very high priority.
@straight-shoota I'm happy to provide a PR, if you can give me any pointers.
This requires a lot of careful thinking before we can implement anything.
First step would be to analyze the problem (ambiguity of self between regular code and type grammar) deeper. E.g. find out where this matters, what other issues this can cause, how do other languages handle this.
Then try to come up with ideas for how we can improve this. That could be language changes, but also little workarounds to improve the ergonomics.
Maybe we can improve the docs of pseudo methods to explain that self can be a type?