Skip to content

Commit

Permalink
Check ownerchain lengths when instantiating type variables
Browse files Browse the repository at this point in the history
  • Loading branch information
odersky committed May 6, 2020
1 parent c4c1f65 commit 57c4982
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 10 deletions.
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/GadtConstraint.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
33 changes: 25 additions & 8 deletions compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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`.
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
31 changes: 31 additions & 0 deletions tests/neg/i8861.scala
Original file line number Diff line number Diff line change
@@ -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
}
}

0 comments on commit 57c4982

Please sign in to comment.