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鈥檒l 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

Projects
None yet
4 participants
@asterite
Contributor

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

@asterite asterite force-pushed the feature/dont_virtualize branch from 6fdb2e5 to 7067190 Apr 28, 2018

@RX14

This comment has been minimized.

Member

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.

@RX14

RX14 approved these changes Apr 28, 2018

@asterite

This comment has been minimized.

Contributor

asterite commented Apr 28, 2018

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 asterite force-pushed the feature/dont_virtualize branch from 7067190 to 26635f1 Apr 29, 2018

@asterite

This comment has been minimized.

Contributor

asterite commented Apr 29, 2018

I amended the commit to add a few explanatory comments.

@RX14

RX14 approved these changes Apr 30, 2018

@asterite

This comment has been minimized.

Contributor

asterite commented Apr 30, 2018

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 asterite merged commit d02c267 into master Apr 30, 2018

3 of 4 checks passed

continuous-integration/travis-ci/pr The Travis CI build failed
Details
ci/circleci: test_darwin Your tests passed on CircleCI!
Details
ci/circleci: test_linux Your tests passed on CircleCI!
Details
ci/circleci: test_linux32 Your tests passed on CircleCI!
Details
@asterite

This comment has been minimized.

Contributor

asterite commented Apr 30, 2018

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

This comment has been minimized.

Valfather commented Jun 25, 2018

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 Jun 25, 2018

@Valfather

This comment has been minimized.

Valfather commented Jun 25, 2018

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

@ysbaddaden

This comment has been minimized.

Member

ysbaddaden commented Jun 25, 2018

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

This comment has been minimized.

Member

RX14 commented Jun 25, 2018

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

@Valfather

This comment has been minimized.

Valfather commented Jun 25, 2018

wow thanks !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment