Eliminate TypeVars and Wildcards early in glb/lub#6257
Eliminate TypeVars and Wildcards early in glb/lub#6257adriaanm merged 2 commits intoscala:2.13.xfrom
Conversation
|
I'm not sure how this would affect performance |
|
Is it possible that there is a deadlock somewhere in |
|
/rebuild |
5f6d4e1 to
d6f3d9f
Compare
|
@retronym how does this look? |
|
To help me understand your patch, I've attempted to split it into a refactoring and a behavior change. Does that split look faithful to your intent? I still need a bit more time to understand the change. |
|
Your split is almost correct. Apologies for not doing this myself. The semantic change is just the addition of this switch of the current type from if (lteq(t, t1) && t.exists(_.isWildcard)) t1 else tNote that if we're not going to check for wildcards the switch is pointless and there would be no need to compare The (tail)-recursive version of the "find all maximal elements in a list given a partial order" (here |
|
Hold on, it looks to me that |
44df6c6 to
5730ed1
Compare
|
Should be good now |
| @tailrec def inner(work: List[Type], done: List[Type]): List[Type] = work match { | ||
| case t2 :: ts2 if po(t1, t2) => // found a bigger type, we're done here | ||
| if (!t2.exists(_.isWildcard) || !po(t2, t1)) max | ||
| else t1 :: (ts2 reverse_::: done) |
There was a problem hiding this comment.
Could also do ts2 reverse_::: t1 :: done here, but it doesn't seem to matter.
There was a problem hiding this comment.
Huh, I guess it does matter after all - so frustrating that with is not symmetric.
0ecd58f to
bd71ab8
Compare
|
@retronym It looks like the list passed to |
bd71ab8 to
86a16d3
Compare
|
/sync |
|
Updated to fix scala/bug#10740 An alternative would be to use a Note that this also compiles in Dotty, but neither on Scala 2.13.x nor with this PR: def foo[A, B](implicit ev: (Int, A) =:= (B, String)) = 42
fooThis would require a redesign of how type variables and constraints are handled. Edit: Should I add it as a negative test case? |
86a16d3 to
3cd2525
Compare
|
/sync |
2fd6ffd to
f56e2e5
Compare
|
/sync |
Type variables and wildcards can act both as subtypes and supertypes depending on the direction of the test. This means types with variables and/or wildcards can shadow other types in `glb/lub`. Consider the following examples: ```scala lub((Int, ? ) :: (Int, String) :: Nil) glb((Int, ?A) :: (Int, String) :: Nil) ``` Intuitively both should return `(Int, String)`, but this depends on the order of types in the list passed to `glb/lub`. This commit incorporates the simple heuristic of moving types that contain variables and/or wildcards to the end of the list. Fixes scala/bug#10686 and fixes scala/bug#10740
|
|
||
| /** From a list of types, retain only maximal types as determined by the partial order `po`. */ | ||
| private def maxTypes(ts: List[Type])(po: (Type, Type) => Boolean): List[Type] = { | ||
| def loop(ts: List[Type]): List[Type] = ts match { |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| case t :: ts1 => | ||
| val ts2 = loop(ts1.filterNot(po(_, t))) | ||
| if (ts2.exists(po(t, _))) ts2 else t :: ts2 | ||
| case Nil => Nil |
There was a problem hiding this comment.
I assume we special-cased Nil and the singleton list based on profiling before? Before we drop these cases, let's gather some statistics on the frequency with which they occur (from casual inspection, not all callers ensure the list's length is >= 2).
There was a problem hiding this comment.
It would be a good exercise for me - any pointers how to do this?
There was a problem hiding this comment.
Check out Statistics.incCounter
| loop(ts) | ||
| // The order here matters because type variables and | ||
| // wildcards can act both as subtypes and supertypes. | ||
| val (ts2, ts1) = ts.partition(_ exists { |
There was a problem hiding this comment.
I don't think this is the right solution. We shouldn't be calling lub/glb with a list of types that includes type variables or wildcards. These test cases are a great help in debugging this.
There was a problem hiding this comment.
Any idea how we could avoid that?
There was a problem hiding this comment.
Recalling my experiments here, the current tests exercise this code path, i.e. type variables being inferred by virtue of participating in the lub/glb.
The complete solution would probably come with Dotty but in the mean time it's unsatisfactory explaining to people that they have to swap the arguments to =:= to make their code compile 😄 (due to scala/bug#10686). How about approximating just the wildcards?
There was a problem hiding this comment.
I agree we should improve, but I'd like to take a look at the actual code paths that lead here, and how to remove those non-sensical types from the beginning. The type checker already approximates the expected type using wildcards (as wildPt), which shares a similar motiviation.
There was a problem hiding this comment.
One code path is via ExistentialType.withTypeVars in subtyping checks when the other type also has type vars. Try this and see a few of the pos partests fail with "trying to do lub/glb of typevar":
--- a/src/reflect/scala/reflect/internal/tpe/TypeComparers.scala
+++ b/src/reflect/scala/reflect/internal/tpe/TypeComparers.scala
@@ -529,7 +529,7 @@ trait TypeComparers {
(rt2.parents forall (isSubType(tp1, _, depth))) &&
(rt2.decls forall (specializesSym(tp1, _, depth)))
case et2: ExistentialType =>
- et2.withTypeVars(isSubType(tp1, _, depth), depth) || fourthTry
+ suspendingTypeVars(typeVarsInType(tp1))(et2.withTypeVars(isSubType(tp1, _, depth), depth)) || fourthTry
case mt2: MethodType =>
tp1 match {
case mt1 @ MethodType(params1, res1) =>I'm not sure what is the correct thing to do here. Since we only care if there is any solution to the existential skolems but not what that solution is, if feels unexpected that this would affect the inference of some unrelated type variables.
There was a problem hiding this comment.
Agreed that those unrelated type vars should not be constrained -- they should act like skolems, I guess. I'm reluctant to change this until I fully understand how this works, which is not likely to happen anymore for 2.13.0, sadly.
There was a problem hiding this comment.
My initial comment was wrong -- I see now from your ltr/rtl test case that we get into the situation with wildcards in types. I can't think of a better way to make this more consistent, so, let's go with yours.
|
Also, I was poking around here and was reminded of |
|
In its current form it doesn't help, because it handles only top-level type variables and it just bails out, but I will try out something similar with a |
|
Yes, I meant this PR could "target" that method so that it also handles the general case. Maybe it was a mistake that it only handles top-level ones? |
|
Since we have a lot going on, optimistically merging -- if performance is affected, we can fix before RC1. |
Type variables and wildcards can act both as subtypes and
supertypes depending on the direction of the test. This means
types with variables and/or wildcards can shadow other types in
glb/lub.Consider the following examples:
Intuitively both should return
(Int, String), but this dependson the order of types in the list passed to
glb/lub.This commit incorporates the simple heuristic of moving types that
contain variables and/or wildcards to the end of the list.
Fixes scala/bug#10686 and fixes scala/bug#10740