SI-7289 Less strict type application for TypeVar. #2354

Merged
merged 1 commit into from Apr 9, 2013

2 participants

@adriaanm
The Scala Programming Language member

(Proposed alternative for #2294)

When a type constructor variable is applied to the wrong number of arguments,
return a new type variable whose instance is ErrorType.

Let's dissect the reported test case.

Define the first implicit:

scala> trait Schtroumpf[T]
defined trait Schtroumpf

scala> implicit def schtroumpf[T, U <: Coll[T], Coll[X] <: Traversable[X]]
     |     (implicit minorSchtroumpf: Schtroumpf[T]): Schtroumpf[U] = ???
schtroumpf: [T, U <: Coll[T], Coll[X] <: Traversable[X]](implicit minorSchtroumpf: Schtroumpf[T])Schtroumpf[U]

Call it explicitly => kind error during type inference reported.

scala> schtroumpf(null): Schtroumpf[Int]
<console>:10: error: inferred kinds of the type arguments (Nothing,Int,Int) do not conform to the expected kinds of the type parameters (type T,type U,type Coll).
Int's type parameters do not match type Coll's expected parameters:
class Int has no type parameters, but type Coll has one
              schtroumpf(null): Schtroumpf[Int]
              ^
<console>:10: error: type mismatch;
 found   : Schtroumpf[U]
 required: Schtroumpf[Int]
              schtroumpf(null): Schtroumpf[Int]
                        ^

Add another implicit, and let implicit search weigh them up.

scala> implicitly[Schtroumpf[Int]]
<console>:10: error: diverging implicit expansion for type Schtroumpf[Int]
starting with method schtroumpf
              implicitly[Schtroumpf[Int]]
                        ^

scala> implicit val qoo = new Schtroumpf[Int]{}
qoo: Schtroumpf[Int] = $anon$1@c1b9b03

scala> implicitly[Schtroumpf[Int]]
<crash>

Implicit search compares the two in-scope implicits in isStrictlyMoreSpecific,
which constructs an existential type:

type ET = Schtroumpf[U] forSome { type T; type U <: Coll[T]; type Coll[] <: Traversable[] }

A subsequent subtype check ET <:< Schtroumpf[Int] gets to withTypeVars, which
replaces the quantified types with type variables, checks conformance of that
substitued underlying type against Schtroumpf[Int], and then tries to solve
the collected type constraints. The type var trace looks like:

[    create] ?T                       ( In Test#schtroumpf[T,U <: Coll[T],Coll[_] <: Traversable[_]] )
[    create] ?U                       ( In Test#schtroumpf[T,U <: Coll[T],Coll[_] <: Traversable[_]] )
[    create] ?Coll                    ( In Test#schtroumpf[T,U <: Coll[T],Coll[_] <: Traversable[_]] )
[   setInst] Nothing                  ( In Test#schtroumpf[T,U <: Coll[T],Coll[_] <: Traversable[_]], T=Nothing )
[   setInst] scala.collection.immutable.Nil.type( In Test#schtroumpf[T,U <: Coll[T],Coll[_] <: Traversable[_]], U=scala.collection.immutable.Nil.type )
[   setInst] =?scala.collection.immutable.Nil.type( In Test#schtroumpf[T,U <: Coll[T],Coll[_] <: Traversable[_]], Coll==?scala.collection.immutable.Nil.type )
[    create] ?T                       ( In Test#schtroumpf[T,U <: Coll[T],Coll[_] <: Traversable[_]] )
[   setInst] Int                      ( In Test#schtroumpf[T,U <: Coll[T],Coll[_] <: Traversable[_]], T=Int )
[    create] ?T                       ( In Test#schtroumpf[T,U <: Coll[T],Coll[_] <: Traversable[_]] )
[    create] ?U                       ( In Test#schtroumpf[T,U <: Coll[T],Coll[_] <: Traversable[_]] )
[    create] ?Coll                    ( In Test#schtroumpf[T,U <: Coll[T],Coll[_] <: Traversable[_]] )
[   setInst] Nothing                  ( In Test#schtroumpf[T,U <: Coll[T],Coll[_] <: Traversable[_]], T=Nothing )
[   setInst] Int                      ( In Test#schtroumpf[T,U <: Coll[T],Coll[_] <: Traversable[_]], U=Int )
[   setInst] =?Int                    ( In Test#schtroumpf[T,U <: Coll[T],Coll[_] <: Traversable[_]], Coll==?Int )

The problematic part is when ?Int (the type var originated from U) is registered
as a lower bound for Coll. That happens in solveOne:

for (tparam2 <- tparams)
  tparam2.info.bounds.hi.dealias match {
    case TypeRef(_, `tparam`, _) =>
      log(s"$tvar addLoBound $tparam2.tpeHK.instantiateTypeParams($tparams, $tvars)")
      tvar addLoBound tparam2.tpeHK.instantiateTypeParams(tparams, tvars)
    case _ =>
  }
@adriaanm
The Scala Programming Language member

review by @retronym

@retronym retronym commented on an outdated diff Apr 5, 2013
src/reflect/scala/reflect/internal/Types.scala
@@ -3090,8 +3092,11 @@ trait Types extends api.Types { self: SymbolTable =>
val tv = TypeVar(origin, constr, newArgs, params)
TypeVar.trace("applyArgs", "In " + originLocation + ", apply args " + newArgs.mkString(", ") + " to " + originName)(tv)
}
- else
- throw new Error("Invalid type application in TypeVar: " + params + ", " + newArgs)
+ else {
+ val tv = TypeVar(typeSymbol)
+ tv.setInst(ErrorType)
+ tv
@retronym
The Scala Programming Language member
retronym added a note Apr 5, 2013

For consistency with other setXxx methods, we could change setInst to return this.type and shed a few lines here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@retronym
The Scala Programming Language member

Looks like like fixing it here is cleaner and more comprehensive than my proposal.

Could you add Paul's test case from the comments the old PR, and opine its desired results?

@adriaanm
The Scala Programming Language member

reviewer comments addressed

@adriaanm adriaanm SI-7289 Less strict type application for TypeVar.
When a type constructor variable is applied to the wrong number of arguments,
return a new type variable whose instance is `ErrorType`.

Dissection of the reported test case by @retronym:

Define the first implicit:

    scala> trait Schtroumpf[T]
    defined trait Schtroumpf

    scala> implicit def schtroumpf[T, U <: Coll[T], Coll[X] <: Traversable[X]]
         |     (implicit minorSchtroumpf: Schtroumpf[T]): Schtroumpf[U] = ???
    schtroumpf: [T, U <: Coll[T], Coll[X] <: Traversable[X]](implicit minorSchtroumpf: Schtroumpf[T])Schtroumpf[U]

Call it explicitly => kind error during type inference reported.

    scala> schtroumpf(null): Schtroumpf[Int]
    <console>:10: error: inferred kinds of the type arguments (Nothing,Int,Int) do not conform to the expected kinds of the type parameters (type T,type U,type Coll).
    Int's type parameters do not match type Coll's expected parameters:
    class Int has no type parameters, but type Coll has one
                  schtroumpf(null): Schtroumpf[Int]
                  ^
    <console>:10: error: type mismatch;
     found   : Schtroumpf[U]
     required: Schtroumpf[Int]
                  schtroumpf(null): Schtroumpf[Int]
                            ^

Add another implicit, and let implicit search weigh them up.

    scala> implicitly[Schtroumpf[Int]]
    <console>:10: error: diverging implicit expansion for type Schtroumpf[Int]
    starting with method schtroumpf
                  implicitly[Schtroumpf[Int]]
                            ^

    scala> implicit val qoo = new Schtroumpf[Int]{}
    qoo: Schtroumpf[Int] = $anon$1@c1b9b03

    scala> implicitly[Schtroumpf[Int]]
    <crash>

Implicit search compares the two in-scope implicits in `isStrictlyMoreSpecific`,
which constructs an existential type:

   type ET = Schtroumpf[U] forSome { type T; type U <: Coll[T]; type Coll[_] <: Traversable[_] }

A subsequent subtype check `ET <:< Schtroumpf[Int]` gets to `withTypeVars`, which
replaces the quantified types with type variables, checks conformance of that
substitued underlying type against `Schtroumpf[Int]`, and then tries to solve
the collected type constraints. The type var trace looks like:

    [    create] ?T                       ( In Test#schtroumpf[T,U <: Coll[T],Coll[_] <: Traversable[_]] )
    [    create] ?U                       ( In Test#schtroumpf[T,U <: Coll[T],Coll[_] <: Traversable[_]] )
    [    create] ?Coll                    ( In Test#schtroumpf[T,U <: Coll[T],Coll[_] <: Traversable[_]] )
    [   setInst] Nothing                  ( In Test#schtroumpf[T,U <: Coll[T],Coll[_] <: Traversable[_]], T=Nothing )
    [   setInst] scala.collection.immutable.Nil.type( In Test#schtroumpf[T,U <: Coll[T],Coll[_] <: Traversable[_]], U=scala.collection.immutable.Nil.type )
    [   setInst] =?scala.collection.immutable.Nil.type( In Test#schtroumpf[T,U <: Coll[T],Coll[_] <: Traversable[_]], Coll==?scala.collection.immutable.Nil.type )
    [    create] ?T                       ( In Test#schtroumpf[T,U <: Coll[T],Coll[_] <: Traversable[_]] )
    [   setInst] Int                      ( In Test#schtroumpf[T,U <: Coll[T],Coll[_] <: Traversable[_]], T=Int )
    [    create] ?T                       ( In Test#schtroumpf[T,U <: Coll[T],Coll[_] <: Traversable[_]] )
    [    create] ?U                       ( In Test#schtroumpf[T,U <: Coll[T],Coll[_] <: Traversable[_]] )
    [    create] ?Coll                    ( In Test#schtroumpf[T,U <: Coll[T],Coll[_] <: Traversable[_]] )
    [   setInst] Nothing                  ( In Test#schtroumpf[T,U <: Coll[T],Coll[_] <: Traversable[_]], T=Nothing )
    [   setInst] Int                      ( In Test#schtroumpf[T,U <: Coll[T],Coll[_] <: Traversable[_]], U=Int )
    [   setInst] =?Int                    ( In Test#schtroumpf[T,U <: Coll[T],Coll[_] <: Traversable[_]], Coll==?Int )

The problematic part is when `?Int` (the type var originated from `U`) is registered
as a lower bound for `Coll`. That happens in `solveOne`:

    for (tparam2 <- tparams)
      tparam2.info.bounds.hi.dealias match {
        case TypeRef(_, `tparam`, _) =>
          log(s"$tvar addLoBound $tparam2.tpeHK.instantiateTypeParams($tparams, $tvars)")
          tvar addLoBound tparam2.tpeHK.instantiateTypeParams(tparams, tvars)
        case _ =>
      }
6a61e17
@retronym
The Scala Programming Language member

LGTM

@adriaanm adriaanm merged commit 2a22b86 into scala:2.10.x Apr 9, 2013

1 check passed

Details default pr-checkin-per-commit Took 43 min.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment