-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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: don't say "undefined method" on abstract leaf types #8870
Conversation
What I don't understand about the explanation is why |
It's not allocating an abstract class. It's allocating space to put an abstract class. |
Better said: space to put anything that inherits from that abstract class. |
Ah ok, and it only works because it references have a fix size. Got it. |
I hoped it fixes #8853 too, but actually no. |
Co-Authored-By: Brian J. Cardiff <bcardiff@manas.tech>
In my opinion, the first code should NOT work, unless you declare an abstract def for the method, so: abstract class Foo
# Without this is should fail to compile
abstract def valid? : Bool
end
foos = [] of Foo
foos.all?(&.valid?) # => true no? |
@bew Ideally, maybe yes. Abstract methods are not required at all, they are just there to better document the code. The compiler doesn't use abstract methods at all except for checking that subclasses implement them. But it's a different topic. |
But then, how |
The compiler says: there are no concrete instances. It doesn't matter what this method is or how is this called, I'm going to type it as NoReturn. |
@asterite I see thanks! |
Fixes #6996
Consider this code:
The code above compiles fine in Crystal. The reason is that, because
Foo
is abstract, only its subclasses can actually be instantiated. But there are no subclasses! So there's no way to actually instantiate aFoo
.The compiler will then consider any call on such abstract types to be valid, but produce
NoReturn
... which makes sense: there's no way to reach that (but see the "Reaching that NoReturn" at the end).The reason this also compiles is because Crystal tries to mimic Ruby. Let's consider this Ruby code:
It works! Because we didn't pass any Foo, it's trivially true. Ruby doesn't even need to check that
valid?
method, and Crystal does the same thing.Crystal is always "if you don't call it, we don't know". Which makes sense according to its prototypey and duck-typey nature.
But it didn't always work
If
Foo
had an abstract subclasses, the compiler used to complain:The bug is that the compiler just checked whether
Foo
was abstract without subclasses. But the condition should also apply ifFoo
is abstract and all of its subclasses are in the same condition. The compiler calls this "abstract leaf".It can also happen with generic types, even if they are concrete:
The above snippet didn't work, but with this PR it works. It works because
Bar(T)
has no generic instances.Foo
was never actually instantiated.And if you do instantiate
Bar
you'll get an error saying "undefined method 'valid?' for Bar(Int32) (compile-time type is Foo+)".Why is this useful?
This is useful because one could define a hierarchy of abstract types, or generic types, and write code to work with that. Maybe a test creates instances. Maybe other tests don't because they expect third party shards to defined subclasses. All of these cases should work.
Reaching that NoReturn
Actually, there is:
The above compiles fine and raises an error on runtime:
But that's unsafe and it usually never happens in real code: the pointer is inside an array, there are bound checks, etc.