Skip to content

Type member inference issue #6199

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

Closed
VladKopanev opened this issue Apr 1, 2019 · 6 comments
Closed

Type member inference issue #6199

VladKopanev opened this issue Apr 1, 2019 · 6 comments

Comments

@VladKopanev
Copy link

VladKopanev commented Apr 1, 2019

The code below compiles with scalac 2.12, but not with dotty

trait Foo {
  type State
  def bar(f: Bar[State] => Bar[State]): Foo = this
}
object Foo {
  def unit: Foo = new Foo {
    type State = Any
  }
  def doBar: Foo = unit.bar(bar => bar)
}
case class Bar[+A]()

Code like this will produce following compilation error:

[error] 17 |    identity.bar(bar => bar)
[error]    |                        ^^^
[error]    |           Found:    scalaz.zio.test.Bar[scalaz.zio.test.Foo#State](bar)
[error]    |           Required: scalaz.zio.test.Bar[Nothing]
[error] one error found

If we change the declaration of Bar to case class Bar[A]() (got rid of covariance) we will get another error:

[error] 16 |  def doBar: Foo = unit.bar(bar => bar)
[error]    |                                   ^^^
[error]    |                                   Found:    scalaz.zio.test.Bar[_](bar)
[error]    |                                   Required: Nothing
[error] one error found

If we change unit to be a val instead of def it compiles, but this is just a distilled, minimum code that produces the same error as we encountered during scalaz-zio compilation with dotty 0.13.0-RC1, see related ticket zio/zio#702

@odersky
Copy link
Contributor

odersky commented Apr 1, 2019

I had a long look at this. The root cause is that we don't support existential types anymore.

To see why, let's try to answer the question: What is the type of unit.bar?

scalac said: Bar[_1.State] => Bar[_1.State] where _1 is a hypothetical value of type Foo.

But if you actually try to give the parameter type of the argument bar => bar explicitly, it does not work, since _1.State is not a legal type you can write. So something fishy is going on here.

Dotty, not having existential types uses an "avoidance"algorithm instead. Since the result type Bar[_1.State]is not expressible, it has to be approximated from below (since it is argument position). That's what gives Nothing.

To fix this, the program should be rewritten so that parameter types could be given explicitly. If you can write them explicitly, there's a good chance they can be inferred as well.

Both of the following would work:

  • Make unit a val instead of a def, or
  • Make unit have to Foo { type State = Any }

As a way to improve the situation we plan to improve support for stable values. I.e. we would provide ways to declare a method like unit stable, which means it will be treated like a val in paths. That would then be a third way to make the program compile.

@nafg
Copy link

nafg commented Apr 1, 2019 via email

@smarter
Copy link
Member

smarter commented Apr 1, 2019

I think the scheme we used for as-seen-from before 91ee02f supported this kind of usecase. It has a complexity cost but if this pattern is prevalent enough it might be worth bringing back a variant of it.

@nafg
Copy link

nafg commented Apr 1, 2019 via email

@neko-kai
Copy link
Contributor

neko-kai commented Apr 3, 2019

So, removal of existentials means that any path-dependent type whose path prefix is not present as a bound val in scope just can't be interacted with meaningfully anymore?

@smarter
Copy link
Member

smarter commented Apr 5, 2019

@nafg @Kaishh Could you post some examples showing how you need this in practice ? I might have a not-too-invasive fix for this and concrete examples would help me fine-tune it.

smarter added a commit to dotty-staging/dotty that referenced this issue May 4, 2019
Prior to
scala@91ee02f,
unstable prefixes appearing in selection types were handled in a
two-pass approach which can be summarized as:

1. Type the selection with the unstable prefix, if this prefix appears
   in a position where it cannot be widened away, mark it with a special
   annotation.
2. If the result of the selection contains this annotation, retype it
   after having wrapped its prefix in a skolem.

This got replaced by the use of an `ApproximatingTypeMap` which can
always construct a valid (but potentially approximated) type for the
selection.

Most of the time this is all we need but there are some cases where
skolemizing the prefix is still useful as witnessed by the tests added
in this commit. They are now handled by unconditionally skolemizing
unstable prefixes.

To avoid any potential performance impact from this, we teach
`asSeenFrom` to always widen these prefixes first and only make use of
the skolem to avoid returning an approximation. Since this almost never
occurs (it happens only once when compiling Dotty) this means we incur
almost no extra cost (the skolem itself still needs to be allocated
unconditionally in advance, this cannot be delegated to `asSeenFrom`
because it's supposed to be an idempotent operation and each skolem is
unique).
smarter added a commit to dotty-staging/dotty that referenced this issue May 4, 2019
Prior to
scala@91ee02f,
unstable prefixes appearing in selection types were handled in a
two-pass approach which can be summarized as:

1. Type the selection with the unstable prefix, if this prefix appears
   in a position where it cannot be widened away, mark it with a special
   annotation.
2. If the result of the selection contains this annotation, retype it
   after having wrapped its prefix in a skolem.

This got replaced by the use of an `ApproximatingTypeMap` which can
always construct a valid (but potentially approximated) type for the
selection.

Most of the time this is all we need but there are some cases where
skolemizing the prefix is still useful as witnessed by the tests added
in this commit. They are now handled by unconditionally skolemizing
unstable prefixes.

To avoid any potential performance impact from this, we teach
`asSeenFrom` to always widen these prefixes first and only make use of
the skolem to avoid returning an approximation. Since this almost never
occurs (it happens only once when compiling Dotty) this means we incur
almost no extra cost (the skolem itself still needs to be allocated
unconditionally in advance, this cannot be delegated to `asSeenFrom`
because it's supposed to be an idempotent operation and each skolem is
unique).
smarter added a commit to dotty-staging/dotty that referenced this issue May 4, 2019
Prior to
scala@91ee02f,
unstable prefixes appearing in selection types were handled in a
two-pass approach which can be summarized as:

1. Type the selection with the unstable prefix, if this prefix appears
   in a position where it cannot be widened away, mark it with a special
   annotation.
2. If the result of the selection contains this annotation, retype it
   after having wrapped its prefix in a skolem.

This got replaced by the use of an `ApproximatingTypeMap` which can
always construct a valid (but potentially approximated) type for the
selection.

Most of the time this is all we need but there are some cases where
skolemizing the prefix is still useful as witnessed by the tests added
in this commit. They are now handled by unconditionally skolemizing
unstable prefixes.

To avoid any potential performance impact from this, we teach
`asSeenFrom` to always widen these prefixes first and only make use of
the skolem to avoid returning an approximation. Since this almost never
occurs (it happens only once when compiling Dotty) this means we incur
almost no extra cost (the skolem itself still needs to be allocated
unconditionally in advance, this cannot be delegated to `asSeenFrom`
because it's supposed to be an idempotent operation and each skolem is
unique).
smarter added a commit to dotty-staging/dotty that referenced this issue May 4, 2019
Prior to
scala@91ee02f,
unstable prefixes appearing in selection types were handled in a
two-pass approach which can be summarized as:

1. Type the selection with the unstable prefix, if this prefix appears
   in a position where it cannot be widened away, mark it with a special
   annotation.
2. If the result of the selection contains this annotation, retype it
   after having wrapped its prefix in a skolem.

This got replaced by the use of an `ApproximatingTypeMap` which can
always construct a valid (but potentially approximated) type for the
selection.

Most of the time this is all we need but there are some cases where
skolemizing the prefix is still useful as witnessed by the tests added
in this commit. They are now handled by unconditionally skolemizing
unstable prefixes.

To avoid any potential performance impact from this, we teach
`asSeenFrom` to always widen these prefixes first and only make use of
the skolem to avoid returning an approximation. Since this almost never
occurs (it happens only once when compiling Dotty) this means we incur
almost no extra cost (the skolem itself still needs to be allocated
unconditionally in advance, this cannot be delegated to `asSeenFrom`
because it's supposed to be an idempotent operation and each skolem is
unique).
@smarter smarter closed this as completed in 502c7c9 May 7, 2019
smarter added a commit that referenced this issue May 7, 2019
Fix #6199: Use a skolemized prefix in asSeenFrom when needed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants