diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 4058e1b5cdc3..d311ac834450 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -75,7 +75,7 @@ object desugar { else { def msg = s"no matching symbol for ${tp.symbol.showLocated} in ${defctx.owner} / ${defctx.effectiveScope}" - if (ctx.reporter.errorsReported) new ErrorType(msg) + if (ctx.reporter.errorsReported) ErrorType(msg) else throw new java.lang.Error(msg) } case _ => diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index 2e7979a98c25..97624aab777a 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -24,6 +24,7 @@ import Implicits.ContextualImplicits import config.Settings._ import config.Config import reporting._ +import reporting.diagnostic.Message import collection.mutable import collection.immutable.BitSet import printing._ @@ -636,6 +637,12 @@ object Contexts { */ private[dotty] var unsafeNonvariant: RunId = NoRunId + /** A map from ErrorType to associated message computation. We use this map + * instead of storing message computations directly in ErrorTypes in order + * to avoid space leaks - the message computation usually captures a context. + */ + private[core] val errorTypeMsg = mutable.Map[ErrorType, () => Message]() + // Phases state private[core] var phasesPlan: List[List[Phase]] = _ @@ -662,6 +669,7 @@ object Contexts { def reset() = { for ((_, set) <- uniqueSets) set.clear() + errorTypeMsg.clear() } // Test that access is single threaded diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 3a6eaef14a4b..bfaa12729090 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -1904,7 +1904,7 @@ object SymDenotations { case denot: ClassDenotation => ClassInfo(denot.owner.thisType, denot.classSymbol, Nil, EmptyScope) case _ => - new ErrorType(errMsg) + ErrorType(errMsg) } denot.privateWithin = NoSymbol } diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index bf4eec0f1649..b5c3baac2cf1 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -298,7 +298,7 @@ trait Symbols { this: Context => def newSkolem(tp: Type) = newSymbol(defn.RootClass, nme.SKOLEM, SyntheticArtifact | Permanent, tp) def newErrorSymbol(owner: Symbol, name: Name, msg: => Message) = { - val errType = new ErrorType(msg) + val errType = ErrorType(msg) newSymbol(owner, name, SyntheticArtifact, if (name.isTypeName) TypeAlias(errType) else errType) } @@ -348,9 +348,9 @@ trait Symbols { this: Context => info = completer, privateWithin = ttmap1.mapOwner(odenot.privateWithin), // since this refers to outer symbols, need not include copies (from->to) in ownermap here. annotations = odenot.annotations) - } + copies.foreach(_.ensureCompleted()) // avoid memory leak copies } diff --git a/compiler/src/dotty/tools/dotc/core/TyperState.scala b/compiler/src/dotty/tools/dotc/core/TyperState.scala index 8735a1fe8130..051fb20ee02c 100644 --- a/compiler/src/dotty/tools/dotc/core/TyperState.scala +++ b/compiler/src/dotty/tools/dotc/core/TyperState.scala @@ -11,6 +11,7 @@ import printing.{Showable, Printer} import printing.Texts._ import config.Config import collection.mutable +import java.lang.ref.WeakReference class TyperState(r: Reporter) extends DotClass with Showable { @@ -143,8 +144,7 @@ extends TyperState(r) { if (targetState.constraint eq previousConstraint) constraint else targetState.constraint & constraint constraint foreachTypeVar { tvar => - if (tvar.owningState eq this) - tvar.owningState = targetState + if (tvar.owningState.get eq this) tvar.owningState = new WeakReference(targetState) } targetState.ephemeral |= ephemeral targetState.gc() @@ -157,7 +157,7 @@ extends TyperState(r) { constraint foreachTypeVar { tvar => if (!tvar.inst.exists) { val inst = instType(tvar) - if (inst.exists && (tvar.owningState eq this)) { + if (inst.exists && (tvar.owningState.get eq this)) { tvar.inst = inst val lam = tvar.origin.binder if (constraint.isRemovable(lam)) toCollect += lam diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 8c6474ae0e55..ba508a468f19 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -36,6 +36,7 @@ import Flags.FlagSet import language.implicitConversions import scala.util.hashing.{ MurmurHash3 => hashing } import config.Printers.{core, typr, cyclicErrors} +import java.lang.ref.WeakReference object Types { @@ -3175,12 +3176,18 @@ object Types { final class TypeVar(val origin: TypeParamRef, creatorState: TyperState, val bindingTree: untpd.Tree, val owner: Symbol) extends CachedProxyType with ValueType { /** The permanent instance type of the variable, or NoType is none is given yet */ - private[core] var inst: Type = NoType + private[this] var myInst: Type = NoType + + private[core] def inst = myInst + private[core] def inst_=(tp: Type) = { + myInst = tp + if (tp.exists) owningState = null // no longer needed; null out to avoid a memory leak + } /** The state owning the variable. This is at first `creatorState`, but it can * be changed to an enclosing state on a commit. */ - private[core] var owningState = creatorState + private[core] var owningState = new WeakReference(creatorState) /** The instance type of this variable, or NoType if the variable is currently * uninstantiated @@ -3198,7 +3205,7 @@ object Types { private 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}") - if ((ctx.typerState eq owningState) && !ctx.typeComparer.subtypeCheckInProgress) + if ((ctx.typerState eq owningState.get) && !ctx.typeComparer.subtypeCheckInProgress) inst = tp ctx.typerState.constraint = ctx.typerState.constraint.replace(origin, tp) tp @@ -3546,11 +3553,24 @@ object Types { */ abstract class FlexType extends UncachedGroundType with ValueType - class ErrorType(_msg: => Message) extends FlexType { - def msg = _msg + class ErrorType private[Types] () extends FlexType { + def msg(implicit ctx: Context): Message = + ctx.errorTypeMsg.get(this) match { + case Some(msgFun) => msgFun() + case None => "error message from previous run no longer available" + } + } + object ErrorType { + def apply(msg: => Message)(implicit ctx: Context): ErrorType = { + val et = new ErrorType + ctx.base.errorTypeMsg(et) = () => msg + et + } } - object UnspecifiedErrorType extends ErrorType("unspecified error") + object UnspecifiedErrorType extends ErrorType() { + override def msg(implicit ctx: Context): Message = "unspecified error" + } /* Type used to track Select nodes that could not resolve a member and their qualifier is a scala.Dynamic. */ object TryDynamicCallType extends FlexType diff --git a/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala b/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala index 0cb4f3e208f2..c5f5d879178c 100644 --- a/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala +++ b/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala @@ -202,6 +202,7 @@ class LambdaLift extends MiniPhase with IdentityDenotTransformer { thisTransform ctx.debuglog(i"mark free: ${sym.showLocated} with owner ${sym.maybeOwner} marked free in $enclosure") val intermediate = if (enclosure.is(PackageClass)) enclosure + else if (enclosure.isConstructor) markFree(sym, enclosure.owner.enclosure) else markFree(sym, enclosure.enclosure) narrowLiftedOwner(enclosure, intermediate orElse sym.enclosingClass) if (!intermediate.isRealClass || enclosure.isConstructor) { diff --git a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala index d0ab39a988c4..fda4f05753e7 100644 --- a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -23,7 +23,7 @@ object ErrorReporting { def errorType(msg: => Message, pos: Position)(implicit ctx: Context): ErrorType = { ctx.error(msg, pos) - new ErrorType(msg) + ErrorType(msg) } def cyclicErrorMsg(ex: CyclicReference)(implicit ctx: Context) = { diff --git a/tests/run/i2883.check b/tests/run/i2883.check new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/tests/run/i2883.check @@ -0,0 +1 @@ + diff --git a/tests/run/i2883.scala b/tests/run/i2883.scala new file mode 100644 index 000000000000..d42f715d7f0a --- /dev/null +++ b/tests/run/i2883.scala @@ -0,0 +1,16 @@ +class Wrapper(val value: Int) + +abstract class Foo(val x: Int) + +class Test { + def foo(wrapper: Wrapper): Unit = { + new Foo(wrapper.value) {} + } +} +object Test extends App { + def foo(wrapper: Wrapper): Foo = + new Foo(wrapper.value) {} + def printFields(obj: Any) = + println(obj.getClass.getDeclaredFields.map(_.toString).sorted.deep.mkString("\n")) + printFields(foo(new Wrapper(1))) +}