Skip to content

Fix #2960: Only allow one inserted apply per tree #2962

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 16, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/transform/Erasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/transform/TreeChecker.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 &&
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
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 @@ -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`.
Expand Down
37 changes: 23 additions & 14 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -1706,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)
Expand Down Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is an attachment needed instead of checking if the tree selects nme.apply ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. It depends how we want to specity it:

1st choice: An apply is inserted on any expression in function position unless that expression is already an inserted apply.

2nd choice: An apply is inserted on any expression in function position unless that expression is itself an apply selection or call.

Which way should we go? Here's where it makes a difference. Consider the case where we want to expand to

f.apply(...).apply(...)

If we give only one apply, which one should be the inserted one? According to the PR, it must be the second apply. If we followed 2nd choice, this would be rejected instead.

Copy link
Contributor Author

@odersky odersky Aug 9, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One reason to keep the current PR is that the first apply could be macro generated where the macro returns a map, say. Then it would seem sensical to insert the 2nd apply automatically, as for any other map.

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 =
Expand All @@ -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)
}
}
Expand All @@ -1845,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
Expand All @@ -1854,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)
}
}

Expand Down Expand Up @@ -1901,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)

Expand All @@ -1919,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,
Expand Down Expand Up @@ -1956,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) {
Expand Down Expand Up @@ -1992,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))
Expand Down Expand Up @@ -2108,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
Expand Down Expand Up @@ -2212,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)
}
}
Expand Down Expand Up @@ -2249,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)
}
Expand All @@ -2262,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)
Expand Down
67 changes: 67 additions & 0 deletions tests/neg/i2960.scala
Original file line number Diff line number Diff line change
@@ -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"</$name>"
}

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
}