Skip to content

Change distribute or #1001

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

Merged
merged 12 commits into from
Dec 21, 2015
Merged

Change distribute or #1001

merged 12 commits into from
Dec 21, 2015

Conversation

odersky
Copy link
Contributor

@odersky odersky commented Dec 15, 2015

More changes to alias tests and rewriting of lubs.

Removes known unsoundness and makes previously seen deep subtypes go away. Review by @smarter.

@smarter
Copy link
Member

smarter commented Dec 15, 2015

This looks better, but we are still making the assumption that type aliases and type bounds are invariant.There are two cases where we can have covariant aliases:

  • In type parameters, but because they're private it doesn't open a soundness hole I think.
  • In higher-kinded types with #Apply and here we have a soundness issue. To demonstrate this I started from a minimized Iter2.scala and got:
val c: Iterable[A] with FromIterator[C] = ???
val d: Iterable[A] with FromIterator[D] = ???
val cget = c.get()
var dget = d.get()
dget = cget

where the inferred type of cget is (([+X0] -> Test.Iterable[X0]) & C)[Int] and the inferred type of dget is (([+X0] -> Test.Iterable[X0]) & D)[Int], so they should not be compatible and yet I can assign dget = cget, because we determined that (([+X0] -> Test.Iterable[X0]) & C)#Apply <:< (([+X0] -> Test.Iterable[X0]) & D)#Apply because we followed the alias on the right. The full code is at: https://gist.github.com/anonymous/ff0b523503b988ee161d

Unfortunately, if we avoid following aliases when variance != 0, we end up with the same deep subtyping issues we had before.

Note that the soundness bug is also present in master, because we assume that we can follow the lower bound of a TypeBounds (https://github.com/lampepfl/dotty/blob/master/src/dotty/tools/dotc/core/TypeComparer.scala#L328), but that's incorrect when the TypeBounds is a TypeAlias with variance = 1

@smarter
Copy link
Member

smarter commented Dec 15, 2015

I think we can fix this by:

  1. Always distributing #Apply inside intersections and unions, this should be safe because it does not make sense to intersect a lambda with something that is not a lambda
  2. Disallowing A#Apply <:< B#Apply if A <:< B

Then we can safely follow type aliases.

@odersky
Copy link
Contributor Author

odersky commented Dec 16, 2015

Can you complete the example to give an actual classcast exception? Would
be good to have this as a test case for future versions of the type system.

On Tue, Dec 15, 2015 at 9:05 PM, Guillaume Martres <notifications@github.com

wrote:

I think we can fix this by:

  1. Always distributing #Apply inside intersections and unions, this
    should be safe because it does not make sense to intersect a lambda with
    something that is not a lambda
  2. Disallowing A#Apply <:< B#Apply if A <:< B

Then we can safely follow type aliases.


Reply to this email directly or view it on GitHub
#1001 (comment).

Martin Odersky
EPFL

@smarter
Copy link
Member

smarter commented Dec 16, 2015

@smarter
Copy link
Member

smarter commented Dec 17, 2015

So the reason we end up with these types is that at some point we call findMember(C, ...) on Test.Iterable[A] & Test.FromIterator[C], so we get [X] => Iterable[X] from the lhs and C from the rhs and we merge them with &, I think that if we teach glb (and lub) to deal with higher-kinded types, we're good.

Since And/Or type themselves are parameterless, their
the union and intersection of hgiher-kinded types has to be treated
specially: The types have to be pulled under a common lambda.
If splitProjections is set, it is more efficient that way.
@odersky odersky force-pushed the change-distribute-or branch from 4e3620e to 8e257a6 Compare December 19, 2015 11:06
@odersky
Copy link
Contributor Author

odersky commented Dec 19, 2015

Rebased to master. Review by @smarter.

case prefix: AndType =>
def isMissing(tp: Type) = tp match {
case tp: TypeRef => !tp.info.exists
case _ => false
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't isMissing too restrictive? If we have (S & T)#A, then S#A might be an alias in S, so derived1 may not return a TypeRef.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't understand. isMissing is supposed to detect when a branch of an |/& does not have a member with the given name. If it is an alias then it does have a member, so it won't be missing anyway.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nevermind, I misunderstood how this function worked.

* or `tp2`, or are 0 of the latter disagree.
* - bounds `Bi` are the intersection of the corresponding type parameter bounds
* of `tp1` and `tp2`.
*/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add @pre tparams1.length == tparams2.length or add an assert.

@smarter
Copy link
Member

smarter commented Dec 20, 2015

Otherwise LGTM, I can't break it anymore :).

@odersky odersky mentioned this pull request Dec 20, 2015
odersky added a commit that referenced this pull request Dec 21, 2015
@odersky odersky merged commit 4163b24 into scala:master Dec 21, 2015
@allanrenucci allanrenucci deleted the change-distribute-or branch December 14, 2017 16:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants