Skip to content

Type parameters are instantiated too early in some cases #738

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
smarter opened this issue Jul 24, 2015 · 5 comments
Closed

Type parameters are instantiated too early in some cases #738

smarter opened this issue Jul 24, 2015 · 5 comments

Comments

@smarter
Copy link
Member

smarter commented Jul 24, 2015

When a type parameter is used in the second parameter list of a method, the inference is not precise enough, unless it also part of the return type:

object Test {
  def one[T](ev: T): Nothing = ???
  def two1[T](ev: T)(): Nothing = ???
  def two2a[T]()(ev: T): Nothing = ???
  def two2b[T]()(ev: T): T = ???
  def two3a[T](ev: T)(ev2: T): Nothing = ???
  def two3b[T](ev: T)(ev2: T): T = ???

  def test = {
    one(42) // Correct: Infer T=Int
    two1(42)() // Correct: Infer T=Int
    two2a()(42) // Wrong: Infer T=Any
    two2b()(42) // Correct: Infer T=Int
    two3a(42)(42) // Wrong: Infer T=Any
    two3b(42)(42) // Correct: Infer T=Int
  }
}

Here's how one(42) is typed:

[log frontend]         ==> typing one(42)?
[log frontend]           ==> typing one?
typed ident one in method test
[log frontend]             ==> adapting Test.one of type ([T](ev: T)Nothing)(Test.one) to FunProto(42):??
[log frontend]             <== adapting Test.one of type ([T](ev: T)Nothing)(Test.one) to FunProto(42):? = Test.one[(T?)]
[log frontend]           <== typing Test.one = Test.one[(T?)]
[log frontend]           ==> adapting 42 of type Int(42) to (T?)?
adding constraint T >: Int(42) to
Constraint(
 uninstVars = (T?);
 constrained types = [T](ev: T)Nothing
 bounds = 
     T
 ordering = 
)
added constraint T >: Int(42) to
Constraint(
 uninstVars = (T? >: Int(42));
 constrained types = [T](ev: T)Nothing
 bounds = 
     T >: Int(42)
 ordering = 
) = true
[log frontend]           <== adapting 42 of type Int(42) to (T? >: Int(42)) = 42
[log frontend]           ==> adapting Test.one[(T? >: Int(42))](42) of type Nothing to ??
interpolate undet vars in Nothing, pos = [263..266..270], mode = Mode(ImplicitsEnabled,InferringReturnType), undets = ArrayBuffer((T? >: Int(42))@[263..266])
qualifying undet vars: ArrayBuffer(TypeVar(PolyParam(T)) / (T? >: Int(42))), constraint: Constraint(
 uninstVars = (T? >: Int(42));
 constrained types = [T](ev: T)Nothing
 bounds = 
     T >: Int(42)
 ordering = 
)
instantiating non-occurring (T? >: Int(42)) in Nothing
approx T, from below = true, bound = Int(42), inst = Int(42)
instantiating (T? >: Int(42)) with Int
[log frontend]           <== adapting Test.one[Int'](42) of type Nothing to ? = Test.one[Int'](42)
[log frontend]         <== typing Test.one(42) = Test.one[Int'](42)

Here's how two2a()(42) is typed:

[log frontend]         ==> typing two2a()(42)?
[log frontend]           ==> typing two2a()?
[log frontend]             ==> typing two2a?
typed ident two2a in method test
[log frontend]               ==> adapting Test.two2a of type ([T]()(ev: T)Nothing)(Test.two2a) to FunProto():??
[log frontend]               <== adapting Test.two2a of type ([T]()(ev: T)Nothing)(Test.two2a) to FunProto():? = Test.two2a[(T?)]
[log frontend]             <== typing Test.two2a = Test.two2a[(T?)]
[log frontend]             ==> adapting Test.two2a[(T?)]() of type (ev: (T?))Nothing to FunProto(42):??
interpolate undet vars in (ev: (T?))Nothing, pos = [334..339..341], mode = Mode(ImplicitsEnabled,InferringReturnType), undets = ArrayBuffer((T?)@[334..339])
qualifying undet vars: ArrayBuffer(TypeVar(PolyParam(T)) / (T?)), constraint: Constraint(
 uninstVars = (T?);
 constrained types = [T]()(ev: T)Nothing
 bounds = 
     T
 ordering = 
)
interpolate contravariant (T?) in (ev: (T?))Nothing
approx T, from below = false, bound = Any, inst = Any
instantiating (T?) with Any
[log frontend]             <== adapting Test.two2a[Any']() of type (ev: Any)Nothing to FunProto(42):? = Test.two2a[Any']()
[log frontend]           <== typing Test.two2a() = Test.two2a[Any']()
[log frontend]           ==> adapting 42 of type Int(42) to Any?
[log frontend]           <== adapting 42 of type Int(42) to Any = 42
[log frontend]           ==> adapting Test.two2a[Any']()(42) of type Nothing to ??
[log frontend]           <== adapting Test.two2a[Any']()(42) of type Nothing to ? = Test.two2a[Any']()(42)
[log frontend]         <== typing Test.two2a()(42) = Test.two2a[Any']()(42)

And here's how two2b()(42) is typed:

[error] [log frontend]         ==> typing two2b()(42)?
[error] [log frontend]           ==> typing two2b()?
[error] [log frontend]             ==> typing two2b?
[info] typed ident two2b in method test
[error] [log frontend]               ==> adapting Test.two2b of type ([T]()(ev: T)T)(Test.two2b) to FunProto():??
[error] [log frontend]               <== adapting Test.two2b of type ([T]()(ev: T)T)(Test.two2b) to FunProto():? = Test.two2b[(T?)]
[error] [log frontend]             <== typing Test.two2b = Test.two2b[(T?)]
[error] [log frontend]             ==> adapting Test.two2b[(T?)]() of type (ev: (T?))(T?) to FunProto(42):??
[info] interpolate undet vars in (ev: (T?))(T?), pos = [370..375..377], mode = Mode(ImplicitsEnabled,InferringReturnType), undets = ArrayBuffer((T?)@[370..375])
[info] qualifying undet vars: ArrayBuffer(TypeVar(PolyParam(T)) / (T?)), constraint: Constraint(
[info]  uninstVars = (T?);
[info]  constrained types = [T]()(ev: T)T
[info]  bounds = 
[info]      T
[info]  ordering = 
[info] )
[error] [log frontend]             <== adapting Test.two2b[(T?)]() of type (ev: (T?))(T?) to FunProto(42):? = Test.two2b[(T?)]()
[error] [log frontend]           <== typing Test.two2b() = Test.two2b[(T?)]()
[error] [log frontend]           ==> adapting 42 of type Int(42) to (T?)?
[info] adding constraint T >: Int(42) to
[info] Constraint(
[info]  uninstVars = (T?);
[info]  constrained types = [T]()(ev: T)T
[info]  bounds = 
[info]      T
[info]  ordering = 
[info] )
[info] added constraint T >: Int(42) to
[info] Constraint(
[info]  uninstVars = (T? >: Int(42));
[info]  constrained types = [T]()(ev: T)T
[info]  bounds = 
[info]      T >: Int(42)
[info]  ordering = 
[info] ) = true
[error] [log frontend]           <== adapting 42 of type Int(42) to (T? >: Int(42)) = 42
[error] [log frontend]           ==> adapting Test.two2b[(T? >: Int(42))]()(42) of type (T? >: Int(42)) to ??
[info] interpolate undet vars in (T? >: Int(42)), pos = [370..377..381], mode = Mode(ImplicitsEnabled,InferringReturnType), undets = ArrayBuffer((T? >: Int(42))@[370..375])
[info] qualifying undet vars: ArrayBuffer(TypeVar(PolyParam(T)) / (T? >: Int(42))), constraint: Constraint(
[info]  uninstVars = (T? >: Int(42));
[info]  constrained types = [T]()(ev: T)T
[info]  bounds = 
[info]      T >: Int(42)
[info]  ordering = 
[info] )
[info] interpolate covariant (T? >: Int(42)) in (T? >: Int(42))
[info] approx T, from below = true, bound = Int(42), inst = Int(42)
[info] instantiating (T? >: Int(42)) with Int
[error] [log frontend]           <== adapting Test.two2b[Int']()(42) of type Int to ? = Test.two2b[Int']()(42)
[error] [log frontend]         <== typing Test.two2b()(42) = Test.two2b[Int']()(42)

When we type one(42):

  • First we adapt Test.one to FunProto(42), this does not add any additional constraint or instantiate any type variable
  • Then we adapt 42 to T, this adds a constraint >: Int on T
  • Then we adapt Test.one[T](42) to ??, this instantiates T to Int, as expected

But when we type two2a()(42):

  • First we adapt Test.two2a to FunProto(), this does not add any additional constraint or instantiate any type variable
  • Then we adapt Test.two2a[T]() to FunProto(42), this interpolates T to Any because T appears contravariantly in the method type, which is not what we wanted!
  • Then we adapt 42 to Any, this does not add any additional constraint or instantiate any type variable
  • Then we adapt Test.two2a[Any]()(42) to ??, this does not add any additional constraint or instantiate any type variable

And when we type two2b()(42):

  • First we adapt Test.two2b to FunProto(), this does not add any additional constraint or instantiate any type variable
  • Then we adapt Test.two2b[T]() to FunProto(42), this does not add any additional constraint or instantiate any type variable because T appears both contravariantly and covariantly in the method type
  • Then we adapt 42 to T, this adds a constraint >: Int on T
  • Then we adapt Test.two2b[T]()(42) to ??, this instantiates T to Int, as expected
@smarter
Copy link
Member Author

smarter commented Jul 24, 2015

This may be related to #718.

@odersky
Copy link
Contributor

odersky commented Aug 10, 2015

What is wrong with inferring T to Any in case two2a? That's how it is intended to work.

@smarter
Copy link
Member Author

smarter commented Aug 10, 2015

Could you explain why it makes sense to infer T differently for two2a compared to one and two2b? I don't see why adding an empty parameter list or changing the return type should influence type inference in this way. It can also lead to confusing issues:

object Test {
  def twoN[T](x: T)(implicit ev: T): Nothing = ???
  def twoT[T](x: T)(implicit ev: T): T = ???

  def test = {
    implicit val i: Int = 1

    twoN(42) // dotty with #738: T=Any, implicit search fails; scalac: T=Int, implicit search succeeds
    twoT(42) // dotty with #738 and scalac: T=Int, implicit search succeeds
  }
}

@smarter
Copy link
Member Author

smarter commented Aug 10, 2015

Also, in #739 (comment) you said:

In dotty, we leave type variables uninstantiated by default, and instantiate in a small number of well-specifed cases.

I thought that this meant that we only instantiate a type variable when we "really need" to, but for two2a, we instantiate T while typing two2a() but before we have typed two2a()(42).

@odersky
Copy link
Contributor

odersky commented Aug 10, 2015

@smarter No, we always interpolate if it is safe to do so. I.e. if the type variable appears co-variantly in an expression's type we interpolate it downwards, if it appears contravariantly, upwards. Only non-variant type variables are kept.

But it's true that this gives an inconsistency for implicits. I'll add to PR #739 a fix that ignores
implicit parameters when computing the variance. That also fixes your 2nd test case.

odersky added a commit to dotty-staging/dotty that referenced this issue Aug 10, 2015
Fixes scala#738.

The previous scheme interpolated type variables according to the
full variance of the result type. This means that the interpolation direction
is downwards if a type varaible occurs like this:

   (implicit x: T)T

but it is upwards if it occurs like this

   (implicit x: T)Unit

For a normal method this makes sense, since the upper bound of T gives
you the most general function. But if the method is implicit, it makes
no sense, since upward instantiation means that we likely get ambiguity
errors.

Interestingly the fix causes a different problem (exemplified by failures
in run/stream-stack-overflow.scala, and also in dotc itself), which will
be fixed in the next commit.
odersky added a commit to dotty-staging/dotty that referenced this issue Aug 10, 2015
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

2 participants