Skip to content

Commit 9dd3d5c

Browse files
committed
Add more explanations, and another test case
1 parent 4ad6460 commit 9dd3d5c

File tree

3 files changed

+57
-8
lines changed

3 files changed

+57
-8
lines changed

compiler/src/dotty/tools/dotc/core/Types.scala

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4149,10 +4149,15 @@ object Types {
41494149
/** Is the variable already instantiated? */
41504150
def isInstantiated(implicit ctx: Context): Boolean = instanceOpt.exists
41514151

4152-
def avoidCaptures(tp: Type)(using Context): Type =
4152+
/** Avoid term references in `tp` to parameters or local variables that
4153+
* are nested more deeply than the type variable itself.
4154+
*/
4155+
private def avoidCaptures(tp: Type)(using Context): Type =
41534156
val problemSyms = new TypeAccumulator[Set[Symbol]]:
41544157
def apply(syms: Set[Symbol], t: Type): Set[Symbol] = t match
41554158
case ref @ TermRef(NoPrefix, _)
4159+
// AVOIDANCE TODO: Are there other problematic kinds of references?
4160+
// Our current tests only give us these, but we might need to generalize this.
41564161
if ref.symbol.maybeOwner.nestingLevel > nestingLevel =>
41574162
syms + ref.symbol
41584163
case _ =>
@@ -4162,21 +4167,26 @@ object Types {
41624167
else
41634168
val atp = ctx.typer.avoid(tp, problems.toList)
41644169
val msg = i"Inaccessible variables captured in instantation of type variable $this.\n$tp was fixed to $atp"
4165-
typr.println(msg)
4170+
println(msg)
41664171
val bound = ctx.typeComparer.fullUpperBound(origin)
41674172
if !(atp <:< bound) then
41684173
throw new TypeError(s"$msg,\nbut the latter type does not conform to the upper bound $bound")
41694174
atp
4175+
// AVOIDANCE TODO: This really works well only if variables are instantiated from below
4176+
// If we hit a problematic symbol while instantiating from above, then avoidance
4177+
// will widen the instance type further. This could yield an alias, which would be OK.
4178+
// But it also could yield a true super type which would then fail the bounds check
4179+
// and throw a TypeError. The right thing to do instead would be to avoid "downwards".
4180+
// To do this, we need first test cases for that situation.
41704181

41714182
/** Instantiate variable with given type */
41724183
def instantiateWith(tp: Type)(implicit ctx: Context): Type = {
41734184
assert(tp ne this, s"self instantiation of ${tp.show}, constraint = ${ctx.typerState.constraint.show}")
4174-
val atp = avoidCaptures(tp)
4175-
typr.println(s"instantiating ${this.show} with ${atp.show}")
4185+
typr.println(s"instantiating ${this.show} with ${tp.show}")
41764186
if ((ctx.typerState eq owningState.get) && !ctx.typeComparer.subtypeCheckInProgress)
4177-
inst = atp
4178-
ctx.typerState.constraint = ctx.typerState.constraint.replace(origin, atp)
4179-
atp
4187+
inst = tp
4188+
ctx.typerState.constraint = ctx.typerState.constraint.replace(origin, tp)
4189+
tp
41804190
}
41814191

41824192
/** Instantiate variable from the constraints over its `origin`.
@@ -4187,7 +4197,7 @@ object Types {
41874197
* is also a singleton type.
41884198
*/
41894199
def instantiate(fromBelow: Boolean)(implicit ctx: Context): Type =
4190-
instantiateWith(ctx.typeComparer.instanceType(origin, fromBelow))
4200+
instantiateWith(avoidCaptures(ctx.typeComparer.instanceType(origin, fromBelow)))
41914201

41924202
/** For uninstantiated type variables: Is the lower bound different from Nothing? */
41934203
def hasLowerBound(implicit ctx: Context): Boolean =

compiler/src/dotty/tools/dotc/typer/Namer.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1496,7 +1496,19 @@ class Namer { typer: Typer =>
14961496
case TypedSplice(tpt: TypeTree) if !isFullyDefined(tpt.tpe, ForceDegree.none) =>
14971497
mdef match {
14981498
case mdef: DefDef if mdef.name == nme.ANON_FUN =>
1499+
// This case applies if the closure result type contains uninstantiated
1500+
// type variables. In this case, constrain the closure result from below
1501+
// by the parameter-capture-avoiding type of the body.
14991502
val rhsType = typedAheadExpr(mdef.rhs, tpt.tpe).tpe
1503+
1504+
// The following part is important since otherwise we might instantiate
1505+
// the closure result type with a plain functon type that refers
1506+
// to local parameters. An example where this happens in `dependent-closures.scala`
1507+
// If the code after `val rhsType` is commented out, this file fails pickling tests.
1508+
// AVOIDANCE TODO: Follow up why this happens, and whether there
1509+
// are better ways to achieve this. It would be good if we could get rid of this code.
1510+
// It seems at least partially redundant with the nesting level checking on TypeVar
1511+
// instantiation.
15001512
val hygienicType = avoid(rhsType, paramss.flatten)
15011513
if (!hygienicType.isValueType || !(hygienicType <:< tpt.tpe))
15021514
ctx.error(i"return type ${tpt.tpe} of lambda cannot be made hygienic;\n" +

tests/pos/dependent-closures.scala

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
trait S { type N; def n: N }
2+
3+
def newS[X](n: X): S { type N = X } = ???
4+
5+
def test =
6+
val ss: List[S] = ???
7+
val cl1 = (s: S) => newS(s.n)
8+
val cl2: (s: S) => S { type N = s.N } = cl1
9+
def f[R](cl: (s: S) => R) = cl
10+
val x = f(s => newS(s.n))
11+
val x1: (s: S) => S = x
12+
// If the code in `tptProto` of Namer that refers to this
13+
// file is commented out, we see:
14+
// pickling difference for the result type of the closure argument
15+
// before pickling: S => S { type N = s.N }
16+
// after pickling : (s: S) => S { type N = s.N }
17+
18+
ss.map(s => newS(s.n))
19+
// If the code in `tptProto` of Namer that refers to this
20+
// file is commented out, we see a pickling difference like the one above.
21+
22+
def g[R](cl: (s: S) => (S { type N = s.N }, R)) = ???
23+
g(s => (newS(s.n), identity(1)))
24+
25+
def h(cl: (s: S) => S { type N = s.N }) = ???
26+
h(s => newS(s.n))
27+

0 commit comments

Comments
 (0)