Skip to content

Commit c4ea1e7

Browse files
committed
[PROOF-OF-CONCEPT] Avoid forcing type aliases to get their typeParams
This is a strawman proposal for fixing #970, before this commit, the following fails with a cyclic reference error: dotc B.scala\ ./scala-scala/src/library/scala/collection/immutable/Seq.scala\ ./scala-scala/src/library/scala/package.scala\ ./scala-scala/src/library/scala/collection/GenSeqLike.scala\ ./scala-scala/src/library/scala/collection/SeqLike.scala\ ./scala-scala/src/library/scala/collection/generic/GenSeqFactory.scala where B.scala is defined as: object B{ def main(args: Array[String]): Unit = { val s = List(1,2,3) () } } Here's what I think is happening: 1. Unpickling a Scala2 TypeRef requires us to perform type application. (`Scala2Unpickler#readType`) 2. To apply a TypeAlias to some arguments, we first need to determine its type parameters. (`TypeApplications#appliedTo` and `TypeApplications#typeParams`) 3. To do this, we look at the type parameters of the underlying type of the TypeAlias, this requires completing the TypeAlias. 4. If the TypeAlias is defined in a source tree and not unpickled, this forces us to typecheck its right-hand side. (`Namer#Completer#typeSig`) 5. In turns, this forces various classes to be completed, which might themselves refer indirectly to type aliases, forcing even more stuff. This commit is a hacky way to avoid 3. by only completing the type parameters of a type alias instead of completing the whole type alias when we don't need it. Let me know if you think the basic idea is sound or not and if you can think of a nicer way to implement it!
1 parent 2bb73c3 commit c4ea1e7

File tree

2 files changed

+42
-6
lines changed

2 files changed

+42
-6
lines changed

src/dotty/tools/dotc/core/TypeApplications.scala

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import util.Positions.Position
1616
import config.Printers._
1717
import collection.mutable
1818
import java.util.NoSuchElementException
19+
import ast.untpd.TypeDef
20+
import typer.Namer
1921

2022
object TypeApplications {
2123

@@ -63,7 +65,14 @@ class TypeApplications(val self: Type) extends AnyVal {
6365
case self: TypeRef =>
6466
val tsym = self.typeSymbol
6567
if (tsym.isClass) tsym.typeParams
66-
else if (tsym.isAliasType) self.underlying.typeParams
68+
else if (tsym.isAliasType) {
69+
tsym.infoOrCompleter match {
70+
case c: Namer#Completer =>
71+
c.typeParams(tsym)
72+
case _ =>
73+
self.underlying.typeParams
74+
}
75+
}
6776
else {
6877
val lam = LambdaClass(forcing = false)
6978
if (lam.exists) lam.typeParams else Nil
@@ -200,6 +209,13 @@ class TypeApplications(val self: Type) extends AnyVal {
200209
case nil => tp
201210
}
202211

212+
def canForce(sym: Symbol) = sym.infoOrCompleter match {
213+
case c: Namer#Completer =>
214+
!sym.isAliasType
215+
case _ =>
216+
true
217+
}
218+
203219
/** Instantiate type `tp` with `args`.
204220
* @param original The original type for which we compute the type parameters
205221
* This makes a difference for refinement types, because
@@ -209,7 +225,7 @@ class TypeApplications(val self: Type) extends AnyVal {
209225
def instantiate(tp: Type, original: Type): Type = tp match {
210226
case tp: TypeRef =>
211227
val tsym = tp.symbol
212-
if (tsym.isAliasType) tp.underlying.appliedTo(args)
228+
if (tsym.isAliasType && canForce(tsym)) tp.underlying.appliedTo(args)
213229
else {
214230
val safeTypeParams =
215231
if (tsym.isClass || !tp.typeSymbol.isCompleting) original.typeParams
@@ -242,7 +258,7 @@ class TypeApplications(val self: Type) extends AnyVal {
242258
case tp: TypeRef =>
243259
val sym = tp.symbol
244260
if (sym.isClass) sym.isLambdaTrait
245-
else !sym.isAliasType || isKnownHK(tp.info)
261+
else !sym.isAliasType || !canForce(sym) || isKnownHK(tp.info)
246262
case tp: TypeProxy => isKnownHK(tp.underlying)
247263
case _ => false
248264
}

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

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,8 @@ class Namer { typer: Typer =>
478478

479479
/** The completer of a symbol defined by a member def or import (except ClassSymbols) */
480480
class Completer(val original: Tree)(implicit ctx: Context) extends LazyType {
481+
var completedParams: Boolean = false
482+
var nestedCtx: Context = null
481483

482484
protected def localContext(owner: Symbol) = ctx.fresh.setOwner(owner).setTree(original)
483485

@@ -491,7 +493,9 @@ class Namer { typer: Typer =>
491493
typer1.defDefSig(original, sym)(localContext(sym).setTyper(typer1))
492494
case original: TypeDef =>
493495
assert(!original.isClassDef)
494-
typeDefSig(original, sym)(localContext(sym).setNewScope)
496+
if (nestedCtx == null)
497+
nestedCtx = localContext(sym).setNewScope
498+
typeDefSig(original, sym, completedParams)(nestedCtx)
495499
case imp: Import =>
496500
try {
497501
val expr1 = typedAheadExpr(imp.expr, AnySelectionProto)
@@ -503,6 +507,21 @@ class Namer { typer: Typer =>
503507
}
504508
}
505509

510+
def typeParams(sym: Symbol) = original match {
511+
case tdef: TypeDef =>
512+
if (nestedCtx == null)
513+
nestedCtx = localContext(sym).setNewScope
514+
515+
{
516+
implicit val ctx: Context = nestedCtx
517+
if (!completedParams) {
518+
completeParams(tdef.tparams)
519+
completedParams = true
520+
}
521+
tdef.tparams map (symbolOfTree(_).asType)
522+
}
523+
}
524+
506525
final override def complete(denot: SymDenotation)(implicit ctx: Context) = {
507526
if (completions != noPrinter && ctx.typerState != this.ctx.typerState) {
508527
completions.println(completions.getClass.toString)
@@ -807,8 +826,9 @@ class Namer { typer: Typer =>
807826
else valOrDefDefSig(ddef, sym, typeParams, paramSymss, wrapMethType)
808827
}
809828

810-
def typeDefSig(tdef: TypeDef, sym: Symbol)(implicit ctx: Context): Type = {
811-
completeParams(tdef.tparams)
829+
def typeDefSig(tdef: TypeDef, sym: Symbol, completedParams: Boolean)(implicit ctx: Context): Type = {
830+
if (!completedParams)
831+
completeParams(tdef.tparams)
812832
val tparamSyms = tdef.tparams map symbolOfTree
813833
val isDerived = tdef.rhs.isInstanceOf[untpd.DerivedTypeTree]
814834
//val toParameterize = tparamSyms.nonEmpty && !isDerived

0 commit comments

Comments
 (0)