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

Don't automatically virtualize two types in the same hierarchy, unless one is deeper than the other #6024

Merged
merged 1 commit into from Apr 30, 2018

Conversation

asterite
Copy link
Member

@asterite asterite commented Apr 28, 2018

Fixes #2661
Fixes #4303

What this means is that with this PR, the language will now behave like this:

class Foo
end

class Bar < Foo
end

class Baz < Foo
end

var = rand < 0.5 ? Bar.new : Baz.new
typeof(var) #=> Bar | Baz

Previously the type would go up to Foo.

However:

var = rand < 0.5 ? Bar.new : Foo.new
typeof(var) #=> Foo

because Bar < Foo, so that gets "upcasted" to Foo (or Foo+, if you know what I mean).

This makes it possible to have more accurate information about the type of a variable. For example in the compiler's source code we have a TypeDeclaration node whose var is of type ASTNode, where in reality it can only be Var | InstanceVar | ClassVar | Global. So the compiler has some "otherwise, raise bug" that could be removed.

Somehow, compilation times for the compiler don't get much more slower. Memory consumption goes a bit higher (most probably because now there are some union types that previously would go up to ASTNode but not now, so a bit more duplicated code is generated), but I don't think we should care anymore about compile times or memory consumption of the compiler (Slack alone, or Chrome, already consume 1GB from your computer and nobody seems to complain... or maybe yes, but well, "if they do it..."). Or, as usual, we can worry about such things later, or maybe a superhero will come with a compiler rewrite or some patches to improve performance 😛

PLEASE, if you can, checkout this branch, compiler the compiler, and try it in your projects. Many things can happen:

  1. Your code won't compile for some reason. This usually happens when you have [foo, bar], you'll probably now have to write [foo, bar] of T for some T because (taking the above example), Array(Foo) is not compatible with Array(Bar | Baz).
  2. Your code will explode (after fixing possible errors in 1). This is what I'd like to know, as it probably means this PR is buggy.

Once and if we can go ahead with this change, I'd like to revisit #4837, as it can be another great addition for more type safey and accuracy in the language, avoiding useless else raise "Impossible" and generally making the code more robust to changes.

@asterite asterite self-assigned this Apr 28, 2018
@RX14
Copy link
Contributor

RX14 commented Apr 28, 2018

This was a PR I wasn't expecting to see (I thought the issue was unfixable), but you've worked out a simple, easy to understand semantic to work around it, and this PR has made my day. Thank you so much.

@asterite
Copy link
Member Author

Yeah, I was cleaning up branches and I accidentally deleted my old attempt at it. Then I said "maybe I can try it once more and see how bad it was". I probably did something different this time, because it didn't become that much worse.

I think at least in the parser it would be worth it to add explicit casts to ASTNode in a few places to avoid the many different union types. The main issue here is that this is a guess game, unless we somehow provide a tool to list methods or places where big (or not so big) union types are formed. I don't feel comfortable generating redundant code under the hood, but that's how the language worked all the time (in some code spots, usually when you have a big hierarchy like ASTNode has, so I believe it's more relevant in parsers and compilers), and so far it didn't seem to bother anyone other than me, so ai guess it's fine.

@asterite
Copy link
Member Author

I amended the commit to add a few explanatory comments.

@asterite
Copy link
Member Author

I tested this in a few projects (the one that compile with master) and it seems to be working well. We can always revert it if we find out there's a huge problem with this.

@asterite
Copy link
Member Author

Docs about the old behaviour (virtual types) will also have to be updated.

@RX14 RX14 added this to the Next milestone Apr 30, 2018
@Valfather
Copy link

Hey, since this happened, my project (https://gitlab.com/WitchKnight/ParadoxPyramid) can't compile anymore...
First I generated a lot of subtypes for a class using a macro, and then I used the parent type as an umbrella type for all the subtypes and passed an array of the parent type , with possibly all the different subtypes inside. Then I could just pattern match as needed if I needed to do subclass-specific operations.
Here's the error : https://play.crystal-lang.org/#/r/4dpl
I'd like to know a workaround.. tried to include subclasses of a subclass in a type alias and use array of that instead (because then it wouldn't be on the same level) but I get the same error.

@ysbaddaden ysbaddaden deleted the feature/dont_virtualize branch June 25, 2018 08:30
@Valfather
Copy link

I'll just use a macro to generate the union type of all the subclasses but it feels really dirty

@ysbaddaden
Copy link
Contributor

Crystal won't merge subtypes as their parent type anymore, you must specify the parent type explicitly. Using the issue example, you can cast like so, for example:

var = (rand < 0.5 ? Bar.new : Baz.new).as(Foo)

See https://play.crystal-lang.org/#/r/4dqh

@RX14
Copy link
Contributor

RX14 commented Jun 25, 2018

You probably want to cast .as(Label) in your to_label method. Should be the minimal code change.

@Valfather
Copy link

wow thanks !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
4 participants