From 426f25aa689c93e34410e0cf87a28cfe5d352863 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 8 Aug 2017 13:16:21 +0200 Subject: [PATCH 1/3] Fix #2960: Only allow one inserted apply per tree This fix prevents infinite chains of inserted apply's. Fixes #2960. --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index a389cf77e98c..86973ac3ad22 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -60,6 +60,7 @@ object Typer { assert(tree.pos.exists, s"position not set for $tree # ${tree.uniqueId}") private val ExprOwner = new Property.Key[Symbol] + private val InsertedApply = new Property.Key[Unit] } class Typer extends Namer with TypeAssigner with Applications with Implicits with Dynamic with Checking with Docstrings { @@ -1818,9 +1819,17 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit */ def tryInsertApplyOrImplicit(tree: Tree, pt: ProtoType)(fallBack: => Tree)(implicit ctx: Context): Tree = { + def isSyntheticApply(tree: Tree): Boolean = tree match { + case tree: Select => tree.getAttachment(InsertedApply).isDefined + case Apply(fn, _) => fn.getAttachment(InsertedApply).isDefined + case _ => false + } + def tryApply(implicit ctx: Context) = { val sel = typedSelect(untpd.Select(untpd.TypedSplice(tree), nme.apply), pt) - if (sel.tpe.isError) sel else adapt(sel, pt) + sel.pushAttachment(InsertedApply, ()) + if (sel.tpe.isError) sel + else try adapt(sel, pt) finally sel.removeAttachment(InsertedApply) } def tryImplicit = @@ -1832,7 +1841,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit pt.markAsDropped() tree case _ => - if (isApplyProto(pt)) tryImplicit + if (isApplyProto(pt) || isSyntheticApply(tree)) tryImplicit else tryEither(tryApply(_))((_, _) => tryImplicit) } } From b5ad5028493af358a24f696ccc12b27fc0d08fcf Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 8 Aug 2017 13:34:08 +0200 Subject: [PATCH 2/3] Drop `original` parameter of `adapt`. The parameter is never queried in current code, just passed along. --- .../dotty/tools/dotc/transform/Erasure.scala | 2 +- .../tools/dotc/transform/TreeChecker.scala | 2 +- .../dotty/tools/dotc/typer/Applications.scala | 4 ++-- .../dotty/tools/dotc/typer/ProtoTypes.scala | 2 +- .../src/dotty/tools/dotc/typer/Typer.scala | 24 +++++++++---------- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index b26ea538646f..4e1be3f111ca 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -635,7 +635,7 @@ object Erasure { super.typedStats(stats1, exprOwner).filter(!_.isEmpty) } - override def adapt(tree: Tree, pt: Type, original: untpd.Tree)(implicit ctx: Context): Tree = + override def adapt(tree: Tree, pt: Type)(implicit ctx: Context): Tree = ctx.traceIndented(i"adapting ${tree.showSummary}: ${tree.tpe} to $pt", show = true) { assert(ctx.phase == ctx.erasurePhase.next, ctx.phase) if (tree.isEmpty) tree diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index b05de26a7b04..8fe3df9978bf 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -436,7 +436,7 @@ class TreeChecker extends Phase with SymTransformer { override def ensureNoLocalRefs(tree: Tree, pt: Type, localSyms: => List[Symbol])(implicit ctx: Context): Tree = tree - override def adapt(tree: Tree, pt: Type, original: untpd.Tree = untpd.EmptyTree)(implicit ctx: Context) = { + override def adapt(tree: Tree, pt: Type)(implicit ctx: Context) = { def isPrimaryConstructorReturn = ctx.owner.isPrimaryConstructor && pt.isRef(ctx.owner.owner) && tree.tpe.isRef(defn.UnitClass) if (ctx.mode.isExpr && diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 5e56a9bbea40..1fe1fd7db972 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -546,7 +546,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => init() def addArg(arg: Tree, formal: Type): Unit = - typedArgBuf += adaptInterpolated(arg, formal.widenExpr, EmptyTree) + typedArgBuf += adaptInterpolated(arg, formal.widenExpr) def makeVarArg(n: Int, elemFormal: Type): Unit = { val args = typedArgBuf.takeRight(n).toList @@ -1477,7 +1477,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => def harmonize(trees: List[Tree])(implicit ctx: Context): List[Tree] = { def adapt(tree: Tree, pt: Type): Tree = tree match { case cdef: CaseDef => tpd.cpy.CaseDef(cdef)(body = adapt(cdef.body, pt)) - case _ => adaptInterpolated(tree, pt, tree) + case _ => adaptInterpolated(tree, pt) } if (ctx.isAfterTyper) trees else harmonizeWith(trees)(_.tpe, adapt) } diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 32acd28fb32e..4906a26966a2 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -238,7 +238,7 @@ object ProtoTypes { */ def typedArg(arg: untpd.Tree, formal: Type)(implicit ctx: Context): Tree = { val targ = cacheTypedArg(arg, typer.typedUnadapted(_, formal)) - typer.adapt(targ, formal, arg) + typer.adapt(targ, formal) } /** The type of the argument `arg`. diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 86973ac3ad22..d33c09ee8cf4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1707,7 +1707,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit def typed(tree: untpd.Tree, pt: Type = WildcardType)(implicit ctx: Context): Tree = /*>|>*/ ctx.traceIndented (i"typing $tree", typr, show = true) /*<|<*/ { assertPositioned(tree) - try adapt(typedUnadapted(tree, pt), pt, tree) + try adapt(typedUnadapted(tree, pt), pt) catch { case ex: CyclicReference => errorTree(tree, cyclicErrorMsg(ex)) case ex: TypeError => errorTree(tree, ex.getMessage) @@ -1854,7 +1854,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit case Select(qual, name) => val qualProto = SelectionProto(name, pt, NoViewsAllowed, privateOK = false) tryEither { implicit ctx => - val qual1 = adaptInterpolated(qual, qualProto, EmptyTree) + val qual1 = adaptInterpolated(qual, qualProto) if ((qual eq qual1) || ctx.reporter.hasErrors) None else Some(typed(cpy.Select(tree)(untpd.TypedSplice(qual1), name), pt)) } { (_, _) => None @@ -1863,12 +1863,12 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } } - def adapt(tree: Tree, pt: Type, original: untpd.Tree = untpd.EmptyTree)(implicit ctx: Context): Tree = /*>|>*/ track("adapt") /*<|<*/ { + def adapt(tree: Tree, pt: Type)(implicit ctx: Context): Tree = /*>|>*/ track("adapt") /*<|<*/ { /*>|>*/ ctx.traceIndented(i"adapting $tree of type ${tree.tpe} to $pt", typr, show = true) /*<|<*/ { if (tree.isDef) interpolateUndetVars(tree, tree.symbol) else if (!tree.tpe.widen.isInstanceOf[LambdaType]) interpolateUndetVars(tree, NoSymbol) tree.overwriteType(tree.tpe.simplified) - adaptInterpolated(tree, pt, original) + adaptInterpolated(tree, pt) } } @@ -1910,7 +1910,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit * (14) When in mode EXPRmode, apply a view * If all this fails, error */ - def adaptInterpolated(tree: Tree, pt: Type, original: untpd.Tree)(implicit ctx: Context): Tree = { + def adaptInterpolated(tree: Tree, pt: Type)(implicit ctx: Context): Tree = { assert(pt.exists) @@ -1928,7 +1928,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit TermRef.withSigAndDenot(ref.prefix, ref.name, alt.info.signature, alt)) resolveOverloaded(alts, pt) match { case alt :: Nil => - adapt(tree.withType(alt), pt, original) + adapt(tree.withType(alt), pt) case Nil => def noMatches = errorTree(tree, @@ -1965,7 +1965,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit def adaptToArgs(wtp: Type, pt: FunProto): Tree = wtp match { case _: MethodOrPoly => if (pt.args.lengthCompare(1) > 0 && isUnary(wtp) && ctx.canAutoTuple) - adaptInterpolated(tree, pt.tupled, original) + adaptInterpolated(tree, pt.tupled) else tree case _ => tryInsertApplyOrImplicit(tree, pt) { @@ -2001,7 +2001,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit def adaptNoArgs(wtp: Type): Tree = wtp match { case wtp: ExprType => - adaptInterpolated(tree.withType(wtp.resultType), pt, original) + adaptInterpolated(tree.withType(wtp.resultType), pt) case wtp: ImplicitMethodType if constrainResult(wtp, followAlias(pt)) => val tvarsToInstantiate = tvarsInParams(tree) wtp.paramInfos.foreach(instantiateSelected(_, tvarsToInstantiate)) @@ -2117,7 +2117,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit !(isSyntheticApply(tree) && !isExpandableApply)) typed(etaExpand(tree, wtp, arity), pt) else if (wtp.paramInfos.isEmpty && isAutoApplied(tree.symbol)) - adaptInterpolated(tpd.Apply(tree, Nil), pt, EmptyTree) + adaptInterpolated(tpd.Apply(tree, Nil), pt) else if (wtp.isImplicit) err.typeMismatch(tree, pt) else @@ -2221,7 +2221,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit val prevConstraint = ctx.typerState.constraint if (pt.isInstanceOf[ProtoType] && !failure.isInstanceOf[AmbiguousImplicits]) tree else if (isFullyDefined(wtp, force = ForceDegree.all) && - ctx.typerState.constraint.ne(prevConstraint)) adapt(tree, pt, original) + ctx.typerState.constraint.ne(prevConstraint)) adapt(tree, pt) else err.typeMismatch(tree, pt, failure) } } @@ -2258,7 +2258,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit pt match { case pt: FunProto if pt.args.lengthCompare(1) > 0 && isUnary(ref) && ctx.canAutoTuple => - adaptInterpolated(tree, pt.tupled, original) + adaptInterpolated(tree, pt.tupled) case _ => adaptOverloaded(ref) } @@ -2271,7 +2271,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } if (typeArgs.isEmpty) typeArgs = constrained(poly, tree)._2 convertNewGenericArray( - adaptInterpolated(tree.appliedToTypeTrees(typeArgs), pt, original)) + adaptInterpolated(tree.appliedToTypeTrees(typeArgs), pt)) } case wtp => if (isStructuralTermSelect(tree)) adapt(handleStructural(tree), pt) From 0b0cbb36ccbe72e948e1a4bf0d9a8dbadd046ac1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 8 Aug 2017 14:06:57 +0200 Subject: [PATCH 3/3] Add test file --- tests/neg/i2960.scala | 67 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 tests/neg/i2960.scala diff --git a/tests/neg/i2960.scala b/tests/neg/i2960.scala new file mode 100644 index 000000000000..3a423612cb19 --- /dev/null +++ b/tests/neg/i2960.scala @@ -0,0 +1,67 @@ +package org.glavo.dotty { + +import scala.collection.mutable + +sealed trait Node { + def mkString(n: Int): String +} + +class Tag(val name: String, + val attributes: mutable.LinkedHashMap[Symbol, String] = mutable.LinkedHashMap(), + val children: mutable.Buffer[Node] = mutable.Buffer()) extends Node { + + override def mkString(n: Int): String = { + Tag.spaces(n) + s"<$name ${attributes.map(_.name + "=" + Tag.unescape(_)).mkString(" ")}>" + + (if(children.isEmpty) "\n" + else children.map(_.mkString(n + 4)).mkString("\n", "\n", "\n")) + + Tag.spaces(n) + s"" + } + + def apply(attrs: (Symbol, String)*): this.type = { + attributes ++= attrs + this + } + + def apply[U](f: implicit Tag => U)(implicit t: Tag = null): this.type = { + if(t != null) t.children += this + f(this) + this + } +} + +object Tag { + def spaces(n: Int = 0): String = { + if(n == 0) "" + else { + val cs = new Array[Char](n) + for (i <- 0 until n) + cs(i) = 0 + + new String(cs) + } + } + + def unescape(str: String): String = { + "\"" + str + "\"" + } + + implicit def symbolToTag(symbol: Symbol): Tag = + new Tag(symbol.name) + + implicit class PairMaker(val tag: Symbol) extends AnyVal { + def :=(value: String): (Symbol, String) = (tag, value) + } +} + +class Text(val value: String) extends Node { + override def mkString(n: Int): String = { + Tag.spaces(n) + value + } +} +} + +object Test { +import org.glavo.dotty._ +import org.glavo.dotty.Tag._ +'html{} // error +}