Skip to content

Fix #2808: Modify lifting of infix operations #3841

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 5 commits into from
Feb 3, 2018
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
55 changes: 28 additions & 27 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,31 @@ object desugar {
tree
}

/** Translate infix operation expression
*
* l op r ==> l.op(r) if op is left-associative
* ==> r.op(l) if op is right-associative
*/
def binop(left: Tree, op: Ident, right: Tree)(implicit ctx: Context): Apply = {
def assignToNamedArg(arg: Tree) = arg match {
case Assign(Ident(name), rhs) => cpy.NamedArg(arg)(name, rhs)
case _ => arg
}
def makeOp(fn: Tree, arg: Tree, selectPos: Position) = {
val args: List[Tree] = arg match {
case Parens(arg) => assignToNamedArg(arg) :: Nil
case Tuple(args) => args.mapConserve(assignToNamedArg)
case _ => arg :: Nil
}
Apply(Select(fn, op.name).withPos(selectPos), args)
}

if (isLeftAssoc(op.name))
makeOp(left, right, Position(left.pos.start, op.pos.end, op.pos.start))
else
makeOp(right, left, Position(op.pos.start, right.pos.end))
}

/** Make closure corresponding to function.
* params => body
* ==>
Expand Down Expand Up @@ -832,30 +857,6 @@ object desugar {
Block(ldef, call)
}

/** Translate infix operation expression left op right
*/
def makeBinop(left: Tree, op: Ident, right: Tree): Tree = {
def assignToNamedArg(arg: Tree) = arg match {
case Assign(Ident(name), rhs) => cpy.NamedArg(arg)(name, rhs)
case _ => arg
}
if (isLeftAssoc(op.name)) {
val args: List[Tree] = right match {
case Parens(arg) => assignToNamedArg(arg) :: Nil
case Tuple(args) => args mapConserve assignToNamedArg
case _ => right :: Nil
}
val selectPos = Position(left.pos.start, op.pos.end, op.pos.start)
Apply(Select(left, op.name).withPos(selectPos), args)
} else {
val x = UniqueName.fresh()
val selectPos = Position(op.pos.start, right.pos.end, op.pos.start)
new InfixOpBlock(
Copy link
Member

Choose a reason for hiding this comment

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

InfixOpBlock is no longer used and could be removed.

ValDef(x, TypeTree(), left).withMods(synthetic),
Apply(Select(right, op.name).withPos(selectPos), Ident(x).withPos(left.pos)))
}
}

/** Create tree for for-comprehension `<for (enums) do body>` or
* `<for (enums) yield body>` where mapName and flatMapName are chosen
* corresponding to whether this is a for-do or a for-yield.
Expand Down Expand Up @@ -1066,10 +1067,10 @@ object desugar {
if (!op.isBackquoted && op.name == tpnme.raw.AMP) AndTypeTree(l, r) // l & r
else if (!op.isBackquoted && op.name == tpnme.raw.BAR) OrTypeTree(l, r) // l | r
else AppliedTypeTree(op, l :: r :: Nil) // op[l, r]
else if (ctx.mode is Mode.Pattern)
else {
assert(ctx.mode is Mode.Pattern) // expressions are handled separately by `binop`
Apply(op, l :: r :: Nil) // op(l, r)
else // l.op(r), or val x = r; l.op(x), plus handle named args specially
makeBinop(l, op, r)
}
case PostfixOp(t, op) =>
if ((ctx.mode is Mode.Type) && !op.isBackquoted && op.name == tpnme.raw.STAR) {
val seqType = if (ctx.compilationUnit.isJava) defn.ArrayType else defn.SeqType
Expand Down
10 changes: 0 additions & 10 deletions compiler/src/dotty/tools/dotc/ast/untpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -96,16 +96,6 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
override def isEmpty = true
}

/** A block arising from a right-associative infix operation, where, e.g.
*
* a +: b
*
* is expanded to
*
* { val x = a; b.+:(x) }
*/
class InfixOpBlock(leftOperand: Tree, rightOp: Tree) extends Block(leftOperand :: Nil, rightOp)

/** A block generated by the XML parser, only treated specially by
* `Positioned#checkPos` */
class XMLBlock(stats: List[Tree], expr: Tree) extends Block(stats, expr)
Expand Down
4 changes: 4 additions & 0 deletions compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,10 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
def argCtx(app: untpd.Tree)(implicit ctx: Context): Context =
if (untpd.isSelfConstrCall(app)) ctx.thisCallArgContext else ctx

/** Typecheck application. Result could be an `Apply` node,
* or, if application is an operator assignment, also an `Assign` or
* Block node.
*/
def typedApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context): Tree = {

def realApply(implicit ctx: Context): Tree = track("realApply") {
Expand Down
33 changes: 27 additions & 6 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -664,12 +664,7 @@ class Typer extends Namer

def typedBlock(tree: untpd.Block, pt: Type)(implicit ctx: Context) = track("typedBlock") {
val (exprCtx, stats1) = typedBlockStats(tree.stats)
val ept =
if (tree.isInstanceOf[untpd.InfixOpBlock])
// Right-binding infix operations are expanded to InfixBlocks, which may be followed by arguments.
// Example: `(a /: bs)(op)` expands to `{ val x = a; bs./:(x) } (op)` where `{...}` is an InfixBlock.
pt
else pt.notApplied
val ept = pt.notApplied
val expr1 = typedExpr(tree.expr, ept)(exprCtx)
ensureNoLocalRefs(
cpy.Block(tree)(stats1, expr1).withType(expr1.tpe), pt, localSyms(stats1))
Expand Down Expand Up @@ -1679,6 +1674,31 @@ class Typer extends Namer
res
}

/** Translate infix operation expression `l op r` to
*
* l.op(r) if `op` is left-associative
* { val x = l; r.op(l) } if `op` is right-associative call-by-value and `l` is impure
* r.op(l) if `op` is right-associative call-by-name or `l` is pure
*/
def typedInfixOp(tree: untpd.InfixOp, pt: Type)(implicit ctx: Context): Tree = {
val untpd.InfixOp(l, op, r) = tree
val app = typedApply(desugar.binop(l, op, r), pt)
if (untpd.isLeftAssoc(op.name)) app
else {
val defs = new mutable.ListBuffer[Tree]
def lift(app: Tree): Tree = (app: @unchecked) match {
case Apply(fn, args) =>
if (app.tpe.isError) app
else tpd.cpy.Apply(app)(fn, LiftImpure.liftArgs(defs, fn.tpe, args))
case Assign(lhs, rhs) =>
Copy link
Member

Choose a reason for hiding this comment

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

app is always an Apply node so I don't see how we can fall into this case or the next?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, typedApply can also produce an Assign node, namely if the application is an operator assignment. E.g.

x.+=(y)

gives

x = x.+(y)

I added a comment to typedApply pointing this out.

tpd.cpy.Assign(app)(lhs, lift(rhs))
case Block(stats, expr) =>
tpd.cpy.Block(app)(stats, lift(expr))
}
Applications.wrapDefs(defs, lift(app))
}
}

/** Retrieve symbol attached to given tree */
protected def retrieveSym(tree: untpd.Tree)(implicit ctx: Context) = tree.removeAttachment(SymOfTree) match {
case Some(sym) =>
Expand Down Expand Up @@ -1765,6 +1785,7 @@ class Typer extends Namer
case tree: untpd.TypedSplice => typedTypedSplice(tree)
case tree: untpd.UnApply => typedUnApply(tree, pt)
case tree: untpd.DependentTypeTree => typed(untpd.TypeTree().withPos(tree.pos), pt)
case tree: untpd.InfixOp if ctx.mode.isExpr => typedInfixOp(tree, pt)
case tree @ untpd.PostfixOp(qual, Ident(nme.WILDCARD)) => typedAsFunction(tree, pt)
case untpd.EmptyTree => tpd.EmptyTree
case _ => typedUnadapted(desugar(tree), pt)
Expand Down
2 changes: 1 addition & 1 deletion compiler/test-resources/repl/errmsgs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ scala> class Foo() { def bar: Int = 1 }; val foo = new Foo(); foo.barr
scala> val x: List[Int] = "foo" :: List(1)
1 | val x: List[Int] = "foo" :: List(1)
| ^^^^^
| found: String($1$)
| found: String("foo")
| required: Int
|
scala> { def f: Int = g; val x: Int = 1; def g: Int = 5; }
Expand Down
7 changes: 7 additions & 0 deletions tests/neg/i2808.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class C {}

object Test {
def foo1() = { println("foo1") ; 5 }
val c = new C
foo1() m1_: c // error
}
8 changes: 8 additions & 0 deletions tests/run/i2808.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
foo1
foo3
foo1
bar
bar
foo3
bar
bar
30 changes: 30 additions & 0 deletions tests/run/i2808.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
class C {
def m1_:(f: Int) = ()
def m2_:(f: => Int) = ()
def m3_:(f: Int)(implicit c: C) = ()
def m4_:(f: => Int)(implicit c: C) = ()

}

object Test {
def foo1() = { println("foo1") ; 5 }
def foo2() = { println("foo2") ; 5 }
def foo3() = { println("foo3") ; 5 }
def foo4() = { println("foo4") ; 5 }

def main(args: Array[String]): Unit = {
implicit val c = new C
foo1() m1_: c // foo1
foo2() m2_: c
foo3() m3_: c // foo3
foo4() m4_: c

def bar() = { println("bar"); c }
foo1() m1_: bar() // foo1
// bar
foo2() m2_: bar() // bar
foo3() m3_: bar() // foo3
// bar
foo4() m4_: bar() // bar
}
}