Skip to content

Commit

Permalink
Try to instantiate type variables in tryInsertImplicitOnQualifier
Browse files Browse the repository at this point in the history
Fixes #13842

What happened in #13842 was that we were not hitting this case in
`tryWiden`, which is a method in ApproximatingTypeMap that tries
to dealias or widen before propagating a Range outwards:

```scala
          case info: SingletonType =>
            // if H#x: y.type, then for any x in L..H, x.type =:= y.type,
            // hence we can replace with y.type under all variances
            reapply(info)
```
The actual info here was a TypeVar with a singleton type as lower bound. Instantiating
the TypeVar produces a SingletonType and case applies.

We cannot really instantiate TypeVars here since this is very low-level code. For instance
we might be in a state where the instantiation is provisional and the TyperState is thrown
away. But AsSeenFrom results are cached so we'd have the wrong type in the caches.

What we do instead is add an additional case in `tryInsertImplicitOnQualifier` which is the
last resort when an application fails. Here, if we can instantiate some type variables in the
qualifier, we try again.
  • Loading branch information
odersky committed Nov 5, 2021
1 parent 483bf2c commit 3183820
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 16 deletions.
16 changes: 10 additions & 6 deletions compiler/src/dotty/tools/dotc/typer/Inferencing.scala
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,12 @@ object Inferencing {
* their instantiation could uncover new type members. However that search is best
* effort only. It might miss type variables that appear in structures involving
* alias types and type projections.
* @param applied Test is done in a `tryInsertImplicitOnQualifier` application.
* In this case, we always try to instantiate TypeVars in type arguments.
* If `applied` is false, we only try that in arguments that may affect
* the result type.
*/
def couldInstantiateTypeVar(tp: Type)(using Context): Boolean = tp.dealias match
def couldInstantiateTypeVar(tp: Type, applied: Boolean = false)(using Context): Boolean = tp.dealias match
case tvar: TypeVar
if !tvar.isInstantiated
&& ctx.typerState.constraint.contains(tvar)
Expand All @@ -120,14 +124,14 @@ object Inferencing {
case _ => Nil
case _ => Nil
case _ => Nil
couldInstantiateTypeVar(tycon)
|| argsInResult.exists(couldInstantiateTypeVar)
couldInstantiateTypeVar(tycon, applied)
|| (if applied then args else argsInResult).exists(couldInstantiateTypeVar(_, applied))
case RefinedType(parent, _, _) =>
couldInstantiateTypeVar(parent)
couldInstantiateTypeVar(parent, applied)
case tp: AndOrType =>
couldInstantiateTypeVar(tp.tp1) || couldInstantiateTypeVar(tp.tp2)
couldInstantiateTypeVar(tp.tp1, applied) || couldInstantiateTypeVar(tp.tp2, applied)
case AnnotatedType(tp, _) =>
couldInstantiateTypeVar(tp)
couldInstantiateTypeVar(tp, applied)
case _ =>
false

Expand Down
35 changes: 25 additions & 10 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ object Typer {
*/
private val InsertedApply = new Property.Key[Unit]

/** An attachment on a result of an implicit conversion or extension method
* that was added by tryInsertImplicitOnQualifier. Needed to prevent infinite
* expansions in error cases (e.g. in fuzzy/i9293.scala).
*/
private val InsertedImplicitOnQualifier = new Property.Key[Unit]

/** An attachment on a tree `t` occurring as part of a `t()` where
* the `()` was dropped by the Typer.
*/
Expand Down Expand Up @@ -3130,21 +3136,30 @@ class Typer extends Namer
}

/** If this tree is a select node `qual.name` (possibly applied to type variables)
* that does not conform to `pt`, try to insert an implicit conversion `c` around
* `qual` so that `c(qual).name` conforms to `pt`.
* that does not conform to `pt`, try two mitigations:
* 1. Instantiate any TypeVars in the widened type of `tree` with their lower bounds.
* 2. Try to insert an implicit conversion `c` around `qual` so that
* `c(qual).name` conforms to `pt`.
*/
def tryInsertImplicitOnQualifier(tree: Tree, pt: Type, locked: TypeVars)(using Context): Option[Tree] = trace(i"try insert impl on qualifier $tree $pt") {
tree match
case tree @ Select(qual, name) if name != nme.CONSTRUCTOR =>
val selProto = SelectionProto(name, pt, NoViewsAllowed, privateOK = false)
if selProto.isMatchedBy(qual.tpe) then None
if couldInstantiateTypeVar(qual.tpe.widen, applied = true)
then
Some(adapt(tree, pt, locked))
else
tryEither {
val tree1 = tryExtensionOrConversion(tree, pt, pt, qual, locked, NoViewsAllowed, inSelect = false)
if tree1.isEmpty then None
else Some(adapt(tree1, pt, locked))
} { (_, _) => None
}
val selProto = SelectionProto(name, pt, NoViewsAllowed, privateOK = false)
if selProto.isMatchedBy(qual.tpe) || tree.hasAttachment(InsertedImplicitOnQualifier) then
None
else
tryEither {
val tree1 = tryExtensionOrConversion(tree, pt, pt, qual, locked, NoViewsAllowed, inSelect = false)
if tree1.isEmpty then None
else
tree1.putAttachment(InsertedImplicitOnQualifier, ())
Some(adapt(tree1, pt, locked))
} { (_, _) => None
}
case TypeApply(fn, args) if args.forall(_.isInstanceOf[untpd.InferredTypeTree]) =>
tryInsertImplicitOnQualifier(fn, pt, locked)
case _ => None
Expand Down
4 changes: 4 additions & 0 deletions compiler/test/dotc/pos-test-pickling.blacklist
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ i8182.scala
# local lifted value in annotation argument has different position after pickling
i2797a

# Late instantiation of type variable in tryInsertImplicitOnQualifier
# allows to simplify a type that was already computed
i13842.scala

# GADT cast applied to singleton type difference
i4176-gadt.scala

Expand Down
14 changes: 14 additions & 0 deletions tests/pos/i13842.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
class Parent { class E }

object ChildA extends Parent

object ChildB extends Parent

class Printer[C <: Parent](val child: C):
def print22(e: child.E): String = ""

def test =
Printer(ChildA).print22(new ChildA.E) // does not work

//Printer[ChildA.type](ChildA).print22(new ChildA.E) // works
//val p = Printer(ChildA); p.print22(new ChildA.E) // works

0 comments on commit 3183820

Please sign in to comment.