Skip to content

Commit 57c4982

Browse files
committed
Check ownerchain lengths when instantiating type variables
1 parent c4c1f65 commit 57c4982

File tree

4 files changed

+58
-10
lines changed

4 files changed

+58
-10
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ final class ProperGadtConstraint private(
116116
)
117117

118118
val tvars = params.lazyZip(poly1.paramRefs).map { (sym, paramRef) =>
119-
val tv = new TypeVar(paramRef, creatorState = null)
119+
val tv = new TypeVar(paramRef, creatorState = null, ctx.owner.ownersIterator.length)
120120
mapping = mapping.updated(sym, tv)
121121
reverseMapping = reverseMapping.updated(tv.origin, sym)
122122
tv

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

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4110,11 +4110,8 @@ object Types {
41104110
*
41114111
* @param origin The parameter that's tracked by the type variable.
41124112
* @param creatorState The typer state in which the variable was created.
4113-
*
4114-
* `owningTree` and `owner` are used to determine whether a type-variable can be instantiated
4115-
* at some given point. See `Inferencing#interpolateUndetVars`.
41164113
*/
4117-
final class TypeVar(private var _origin: TypeParamRef, creatorState: TyperState) extends CachedProxyType with ValueType {
4114+
final class TypeVar(private var _origin: TypeParamRef, creatorState: TyperState, level: Int) extends CachedProxyType with ValueType {
41184115

41194116
def origin: TypeParamRef = _origin
41204117

@@ -4150,14 +4147,34 @@ object Types {
41504147
/** Is the variable already instantiated? */
41514148
def isInstantiated(implicit ctx: Context): Boolean = instanceOpt.exists
41524149

4150+
def hygienic(tp: Type)(using Context): Type =
4151+
val problemSyms = new TypeAccumulator[Set[Symbol]]:
4152+
def apply(syms: Set[Symbol], t: Type): Set[Symbol] = t match
4153+
case ref @ TermRef(NoPrefix, _)
4154+
if ref.symbol.maybeOwner.ownersIterator.length > level =>
4155+
syms + ref.symbol
4156+
case _ =>
4157+
foldOver(syms, t)
4158+
val problems = problemSyms(Set.empty, tp)
4159+
if problems.isEmpty then tp
4160+
else
4161+
val htp = ctx.typer.avoid(tp, problems.toList)
4162+
val msg = i"Inaccessible variables captured by instance for $this.\n$tp was fixed to $htp"
4163+
typr.println(msg)
4164+
val bound = ctx.typeComparer.fullUpperBound(origin)
4165+
if !(htp <:< bound) then
4166+
throw new TypeError(s"$msg,\nbut this does not conform to upper bound $bound")
4167+
htp
4168+
41534169
/** Instantiate variable with given type */
41544170
def instantiateWith(tp: Type)(implicit ctx: Context): Type = {
41554171
assert(tp ne this, s"self instantiation of ${tp.show}, constraint = ${ctx.typerState.constraint.show}")
4156-
typr.println(s"instantiating ${this.show} with ${tp.show}")
4172+
val htp = hygienic(tp)
4173+
typr.println(s"instantiating ${this.show} with ${htp.show}")
41574174
if ((ctx.typerState eq owningState.get) && !ctx.typeComparer.subtypeCheckInProgress)
4158-
inst = tp
4159-
ctx.typerState.constraint = ctx.typerState.constraint.replace(origin, tp)
4160-
tp
4175+
inst = htp
4176+
ctx.typerState.constraint = ctx.typerState.constraint.replace(origin, htp)
4177+
htp
41614178
}
41624179

41634180
/** Instantiate variable from the constraints over its `origin`.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -502,7 +502,7 @@ object ProtoTypes {
502502
for (paramRef <- tl.paramRefs)
503503
yield {
504504
val tt = new TypeVarBinder().withSpan(owningTree.span)
505-
val tvar = new TypeVar(paramRef, state)
505+
val tvar = new TypeVar(paramRef, state, ctx.owner.ownersIterator.length)
506506
state.ownedVars += tvar
507507
tt.withType(tvar)
508508
}

tests/neg/i8861.scala

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
object Test {
2+
sealed trait Container { s =>
3+
type A
4+
def visit[R](int: IntV & s.type => R, str: StrV & s.type => R): R
5+
}
6+
final class IntV extends Container { s =>
7+
type A = Int
8+
val i: Int = 42
9+
def visit[R](int: IntV & s.type => R, str: StrV & s.type => R): R = int(this)
10+
}
11+
final class StrV extends Container { s =>
12+
type A = String
13+
val t: String = "hello"
14+
def visit[R](int: IntV & s.type => R, str: StrV & s.type => R): R = str(this)
15+
}
16+
17+
def minimalOk[R](c: Container { type A = R }): R = c.visit[R](
18+
int = vi => vi.i : vi.A,
19+
str = vs => vs.t : vs.A
20+
)
21+
def minimalFail[M](c: Container { type A = M }): M = c.visit(
22+
int = vi => vi.i : vi.A,
23+
str = vs => vs.t : vs.A // error
24+
)
25+
26+
def main(args: Array[String]): Unit = {
27+
val e: Container { type A = String } = new StrV
28+
println(minimalOk(e)) // this one prints "hello"
29+
println(minimalFail(e)) // this one fails with ClassCastException: class java.lang.String cannot be cast to class java.lang.Integer
30+
}
31+
}

0 commit comments

Comments
 (0)