diff --git a/compiler/src/dotty/tools/dotc/core/GadtConstraint.scala b/compiler/src/dotty/tools/dotc/core/GadtConstraint.scala index 2faf721a9da8..fbb5b293a9ea 100644 --- a/compiler/src/dotty/tools/dotc/core/GadtConstraint.scala +++ b/compiler/src/dotty/tools/dotc/core/GadtConstraint.scala @@ -116,7 +116,7 @@ final class ProperGadtConstraint private( ) val tvars = params.lazyZip(poly1.paramRefs).map { (sym, paramRef) => - val tv = new TypeVar(paramRef, creatorState = null) + val tv = new TypeVar(paramRef, creatorState = null, ctx.owner.ownersIterator.length) mapping = mapping.updated(sym, tv) reverseMapping = reverseMapping.updated(tv.origin, sym) tv diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index b3629dc2386b..8ce110b5539c 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4110,11 +4110,8 @@ object Types { * * @param origin The parameter that's tracked by the type variable. * @param creatorState The typer state in which the variable was created. - * - * `owningTree` and `owner` are used to determine whether a type-variable can be instantiated - * at some given point. See `Inferencing#interpolateUndetVars`. */ - final class TypeVar(private var _origin: TypeParamRef, creatorState: TyperState) extends CachedProxyType with ValueType { + final class TypeVar(private var _origin: TypeParamRef, creatorState: TyperState, level: Int) extends CachedProxyType with ValueType { def origin: TypeParamRef = _origin @@ -4150,14 +4147,34 @@ object Types { /** Is the variable already instantiated? */ def isInstantiated(implicit ctx: Context): Boolean = instanceOpt.exists + def hygienic(tp: Type)(using Context): Type = + val problemSyms = new TypeAccumulator[Set[Symbol]]: + def apply(syms: Set[Symbol], t: Type): Set[Symbol] = t match + case ref @ TermRef(NoPrefix, _) + if ref.symbol.maybeOwner.ownersIterator.length > level => + syms + ref.symbol + case _ => + foldOver(syms, t) + val problems = problemSyms(Set.empty, tp) + if problems.isEmpty then tp + else + val htp = ctx.typer.avoid(tp, problems.toList) + val msg = i"Inaccessible variables captured by instance for $this.\n$tp was fixed to $htp" + typr.println(msg) + val bound = ctx.typeComparer.fullUpperBound(origin) + if !(htp <:< bound) then + throw new TypeError(s"$msg,\nbut this does not conform to upper bound $bound") + htp + /** Instantiate variable with given type */ def instantiateWith(tp: Type)(implicit ctx: Context): Type = { assert(tp ne this, s"self instantiation of ${tp.show}, constraint = ${ctx.typerState.constraint.show}") - typr.println(s"instantiating ${this.show} with ${tp.show}") + val htp = hygienic(tp) + typr.println(s"instantiating ${this.show} with ${htp.show}") if ((ctx.typerState eq owningState.get) && !ctx.typeComparer.subtypeCheckInProgress) - inst = tp - ctx.typerState.constraint = ctx.typerState.constraint.replace(origin, tp) - tp + inst = htp + ctx.typerState.constraint = ctx.typerState.constraint.replace(origin, htp) + htp } /** Instantiate variable from the constraints over its `origin`. diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index b62e1bc7f0e7..571fa9ffc260 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -502,7 +502,7 @@ object ProtoTypes { for (paramRef <- tl.paramRefs) yield { val tt = new TypeVarBinder().withSpan(owningTree.span) - val tvar = new TypeVar(paramRef, state) + val tvar = new TypeVar(paramRef, state, ctx.owner.ownersIterator.length) state.ownedVars += tvar tt.withType(tvar) } diff --git a/tests/neg/i8861.scala b/tests/neg/i8861.scala new file mode 100644 index 000000000000..87f1884f6155 --- /dev/null +++ b/tests/neg/i8861.scala @@ -0,0 +1,31 @@ +object Test { + sealed trait Container { s => + type A + def visit[R](int: IntV & s.type => R, str: StrV & s.type => R): R + } + final class IntV extends Container { s => + type A = Int + val i: Int = 42 + def visit[R](int: IntV & s.type => R, str: StrV & s.type => R): R = int(this) + } + final class StrV extends Container { s => + type A = String + val t: String = "hello" + def visit[R](int: IntV & s.type => R, str: StrV & s.type => R): R = str(this) + } + + def minimalOk[R](c: Container { type A = R }): R = c.visit[R]( + int = vi => vi.i : vi.A, + str = vs => vs.t : vs.A + ) + def minimalFail[M](c: Container { type A = M }): M = c.visit( + int = vi => vi.i : vi.A, + str = vs => vs.t : vs.A // error + ) + + def main(args: Array[String]): Unit = { + val e: Container { type A = String } = new StrV + println(minimalOk(e)) // this one prints "hello" + println(minimalFail(e)) // this one fails with ClassCastException: class java.lang.String cannot be cast to class java.lang.Integer + } +} \ No newline at end of file