-
Notifications
You must be signed in to change notification settings - Fork 1.1k
[Experiment] Introduce hard ConstantTypes #14360
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
Conversation
03c36b3
to
e9a73f9
Compare
@@ -3282,7 +3290,12 @@ object Types { | |||
else tp1.atoms | tp2.atoms | |||
val tp1w = tp1.widenSingletons | |||
val tp2w = tp2.widenSingletons | |||
myWidened = if ((tp1 eq tp1w) && (tp2 eq tp2w)) this else tp1w | tp2w | |||
myWidened = | |||
if isSoft then |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With this change, "a" |<hard> String
would not be widened to String
anymore. I could add something like || tp1w <:< tp2w || tp2w <:< tp1w
here if we want that.
@@ -2074,6 +2078,10 @@ object Types { | |||
} | |||
} | |||
|
|||
trait Softenable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need a trait for that? Or could isSoft
be a method of Type
directly, returning true
by default?
To do:
- Find a better name
- Documentation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's never consumed as its upper type, and there's no "shared implementation". So I'd just define/redefine it on SingletonType/ConstantType, and maybe link the docs if they're extensive. Just IMO 😄
Oh, otherwise I'd call it Softness
or just IsSoft
.
test performance please |
performance test scheduled: 4 job(s) in queue, 1 running. |
Performance test finished successfully: Visit https://dotty-bench.epfl.ch/14360/ to see the changes. Benchmarks is based on merging with master (8ae2962) |
Could you please describe a little more what this PR is about? |
Description updated 😄 Note: my PRs in draft state are generally just experiments or work in progress and not ready to be reviewed. There are still some tests not passing on this one, but I'd be happy to have your feedback on the idea already! |
OK, now that I know what this PR is about, here is a feedback regarding the idea (not a review). I think there are various ways we've come to live with widening and rely on it without realizing them, so changing this behavior without things breaking is risky business (sadly, because my use-cases suffer from this). Questions you should be able to answer before proceeding:
val x = (2, 2) //is the type (2,2) or (Int, Int)
val y = (1 | 2, 4) //what is the type here?
class Foo
val foo = new Foo
val z = (1, foo) //is the type (Int, Foo) or (1, foo.type)
val x = if (cond) 1 else 1 //is the type Int or 1
val y = if (cond) 1 else 2 //is the type Int or (1 | 2)
def foo[X <: Int](x : X) : X = x
val x = foo(5) //is the type Int or 5? No need for `& Singleton` ? |
@soronpo thanks for the pointers! I am starting to see how much various parts of the compiler depend on widening and how tricky it is to change it. This PR tries to address a narrower problem than the general problem of precise types, which is only to preserve constant types explicitly written by the user. For example, we currently have: val x: String | Int = ???
val y /* : String | Int */ = x But: val x2: "a" | 1 = ???
val y2 /*: Matchable */ = x2 Said otherwise, a more precise type is widened to a more general type, which is counter-intuitive. It seems to me that this could be solved independently of the general problem of precise types. Note that singleton types in type applications and tuples such as in your first example are preserved: val x3: (2, 2) = ???
val y3 /* : (2, 2) */ = x3 |
@soronpo for your examples 2 and 3, it seems like doing less widening in these situations in general would break too much existing code. However, I started experiencing with a precise def f() =
val b: Boolean
val x /* : 1 | 2 */ = if b then 1 else 2 This could be an opt-in way to get more precise types in general and inference in particular. What would you think about something like that? I'll post my early-stage experiment soon so that we can discuss with more material. |
In the current state, there at least two problems with this PR:
|
I think |
|
As other precise types, constant types are currently often widened away. For example:
For union types, there exists a concept of softeness: hard unions are union types explicitly written by the user, while soft unions are created by the compiler (for example as the result of a condition or a match). The former are not widened, while the later are:
This PR adds the same softeness concept to constant types, such that constant types explicitly typed by the user are not widened:
Previous attempt: #14347, which was only handling singletons inside hard unions.