-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Subtype checking for path-dependent types ignores singletons (without eta-expansion) #6502
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
Comments
Is this a special case of #6357 ? |
Well, it seems not, but this problem is probably blocked on that one. However, here subtyping checking fails much earlier, because of error] -- [E007] Type Mismatch Error: /Users/pgiarrusso/git/dotty-example-project/src/main/scala/foo.scala:27:28
[error] 27 | val Result1: Int = Client(Server0) // rejected
[error] | ^^^^^^^
[error] |Found: (mImpl: MapImpl) => (mImpl.Map => mImpl.Key => mImpl.Value)(Foo.Server0)
[error] |Required: (mImpl: MapImpl{Key = String; Value = Int}) => mImpl.Map => String => Int
[error] |Constraint(
[error] | uninstVars = ;
[error] | constrained types =
[error] | bounds =
[error] | ordering =
[error] |)
[error] |Subtype trace:
[error] | ==> (mImpl: MapImpl) => (mImpl.Map => mImpl.Key => mImpl.Value)(Foo.Server0) <:< (mImpl: MapImpl{Key = String; Value = Int}) => mImpl.Map => String => Int
[error] | ==> MapImpl => Nothing => Nothing => MapImpl#Value <:< MapImpl{Key = String; Value = Int} => Nothing => String => Int LoApprox
[error] | ==> scala.type(scala) <:< scala.type(scala)
[error] | <== scala.type(scala) <:< scala.type(scala) = true
[error] | ==> MapImpl{Key = String; Value = Int} <:< MapImpl
[error] | ==> MapImpl <:< MapImpl
[error] | <== MapImpl <:< MapImpl = true
[error] | <== MapImpl{Key = String; Value = Int} <:< MapImpl = true
[error] | ==> Nothing => Nothing => MapImpl#Value <:< Nothing => String => Int
[error] | ==> scala.type(scala) <:< scala.type(scala)
[error] | <== scala.type(scala) <:< scala.type(scala) = true
[error] | ==> Nothing <:< Nothing
[error] | <== Nothing <:< Nothing = true
[error] | ==> Nothing => MapImpl#Value <:< String => Int
[error] | ==> scala.type(scala) <:< scala.type(scala)
[error] | <== scala.type(scala) <:< scala.type(scala) = true
[error] | ==> String <:< Nothing
[error] | <== String <:< Nothing = false
[error] | <== Nothing => MapImpl#Value <:< String => Int = false
[error] | <== Nothing => Nothing => MapImpl#Value <:< Nothing => String => Int = false
[error] | <== MapImpl => Nothing => Nothing => MapImpl#Value <:< MapImpl{Key = String; Value = Int} => Nothing => String => Int LoApprox = false
[error] | <== (mImpl: MapImpl) => (mImpl.Map => mImpl.Key => mImpl.Value)(Foo.Server0) <:< (mImpl: MapImpl{Key = String; Value = Int}) => mImpl.Map => String => Int = false
[error] one error found
[error] (Compile / compileIncremental) Compilation failed
[error] Total time: 4 s, completed May 13, 2019 9:50:49 PM Indeed,
Are refinements invariant? I'm not sure why... |
Because they're also used for structural typing which can be translated to reflective calls on the JVM, and there's no built-in variance in the JVM, instead different parameter types correspond to different overloads. I'm exploring using a magic marker trait to work around that limitation for the purpose of encoding functions (all of them: regular, implicit, dependent, polymorphic, ..) |
To be sure, this issue was born out of our other project with you and @sstucki. For extra fun, this still fails before running into #6357: val ServerCast: ((mImpl: MapImpl {type Key = String; type Value = Int}) => mImpl.Map => mImpl.Key => mImpl.Value) = Server0 //fails Here we get subtype checking to say that Take DOT's subtyping rule for function types:
Here, we check
where Hence, implementing this rule correctly might require some additional substitution in Dotty, unlike on paper? That might be why eta-expansion is an effective workaround. -- [E007] Type Mismatch Error: src/main/scala/foo.scala:35:118 -----------------
35 | val ServerCast: ((mImpl: MapImpl {type Key = String; type Value = Int}) => mImpl.Map => mImpl.Key => mImpl.Value) = Server0 //fails
| ^^^^^^^
|Found: (mImpl: MapImpl) => (mImpl.Map => mImpl.Key => mImpl.Value)(Foo.Server0)
|Required: (mImpl: MapImpl{Key = String; Value = Int}) => mImpl.Map => mImpl.Key =>
| mImpl.Value
|Constraint(
| uninstVars = ;
| constrained types =
| bounds =
| ordering =
|)
|Subtype trace:
| ==> (mImpl: MapImpl) => (mImpl.Map => mImpl.Key => mImpl.Value)(Foo.Server0) <:< (mImpl: MapImpl{Key = String; Value = Int}) => mImpl.Map => mImpl.Key =>
| mImpl.Value
| ==> MapImpl => Nothing => Nothing => MapImpl#Value <:< MapImpl{Key = String; Value = Int} => Nothing => String => Int LoApprox
| ==> scala.type(scala) <:< scala.type(scala)
| <== scala.type(scala) <:< scala.type(scala) = true
| ==> MapImpl{Key = String; Value = Int} <:< MapImpl
| ==> MapImpl <:< MapImpl
| <== MapImpl <:< MapImpl = true
| <== MapImpl{Key = String; Value = Int} <:< MapImpl = true
| ==> Nothing => Nothing => MapImpl#Value <:< Nothing => String => Int
| ==> scala.type(scala) <:< scala.type(scala)
| <== scala.type(scala) <:< scala.type(scala) = true
| ==> Nothing <:< Nothing
| <== Nothing <:< Nothing = true
| ==> Nothing => MapImpl#Value <:< String => Int
| ==> scala.type(scala) <:< scala.type(scala)
| <== scala.type(scala) <:< scala.type(scala) = true
| ==> String <:< Nothing
| <== String <:< Nothing = false
| <== Nothing => MapImpl#Value <:< String => Int = false
| <== Nothing => Nothing => MapImpl#Value <:< Nothing => String => Int = false
| <== MapImpl => Nothing => Nothing => MapImpl#Value <:< MapImpl{Key = String; Value = Int} => Nothing => String => Int LoApprox = false
| <== (mImpl: MapImpl) => (mImpl.Map => mImpl.Key => mImpl.Value)(Foo.Server0) <:< (mImpl: MapImpl{Key = String; Value = Int}) => mImpl.Map => mImpl.Key =>
| mImpl.Value = false
one error found To get #6357, one must workaround all those bugs by using fewer dependent types, and then we get it with:
|
In the code below, I'd hope for
Client(Server0)
to be accepted, without the need for eta-expansion. Achieving this appears important in the literature on path-dependent types.gives:
BTW, inspecting source code suggests a potential similar problem for methods:
matchingMethodParams(tp1, tp2)
only performs dependent substitution ontp2
, but here we need "dependent substitution" ontp1
as well.The text was updated successfully, but these errors were encountered: