Skip to content

Commit d2d7d99

Browse files
authored
Merge pull request #3841 from dotty-staging/fix-#2808
Fix #2808: Modify lifting of infix operations
2 parents d33db04 + 355a8c7 commit d2d7d99

File tree

8 files changed

+105
-44
lines changed

8 files changed

+105
-44
lines changed

compiler/src/dotty/tools/dotc/ast/Desugar.scala

+28-27
Original file line numberDiff line numberDiff line change
@@ -725,6 +725,31 @@ object desugar {
725725
tree
726726
}
727727

728+
/** Translate infix operation expression
729+
*
730+
* l op r ==> l.op(r) if op is left-associative
731+
* ==> r.op(l) if op is right-associative
732+
*/
733+
def binop(left: Tree, op: Ident, right: Tree)(implicit ctx: Context): Apply = {
734+
def assignToNamedArg(arg: Tree) = arg match {
735+
case Assign(Ident(name), rhs) => cpy.NamedArg(arg)(name, rhs)
736+
case _ => arg
737+
}
738+
def makeOp(fn: Tree, arg: Tree, selectPos: Position) = {
739+
val args: List[Tree] = arg match {
740+
case Parens(arg) => assignToNamedArg(arg) :: Nil
741+
case Tuple(args) => args.mapConserve(assignToNamedArg)
742+
case _ => arg :: Nil
743+
}
744+
Apply(Select(fn, op.name).withPos(selectPos), args)
745+
}
746+
747+
if (isLeftAssoc(op.name))
748+
makeOp(left, right, Position(left.pos.start, op.pos.end, op.pos.start))
749+
else
750+
makeOp(right, left, Position(op.pos.start, right.pos.end))
751+
}
752+
728753
/** Make closure corresponding to function.
729754
* params => body
730755
* ==>
@@ -832,30 +857,6 @@ object desugar {
832857
Block(ldef, call)
833858
}
834859

835-
/** Translate infix operation expression left op right
836-
*/
837-
def makeBinop(left: Tree, op: Ident, right: Tree): Tree = {
838-
def assignToNamedArg(arg: Tree) = arg match {
839-
case Assign(Ident(name), rhs) => cpy.NamedArg(arg)(name, rhs)
840-
case _ => arg
841-
}
842-
if (isLeftAssoc(op.name)) {
843-
val args: List[Tree] = right match {
844-
case Parens(arg) => assignToNamedArg(arg) :: Nil
845-
case Tuple(args) => args mapConserve assignToNamedArg
846-
case _ => right :: Nil
847-
}
848-
val selectPos = Position(left.pos.start, op.pos.end, op.pos.start)
849-
Apply(Select(left, op.name).withPos(selectPos), args)
850-
} else {
851-
val x = UniqueName.fresh()
852-
val selectPos = Position(op.pos.start, right.pos.end, op.pos.start)
853-
new InfixOpBlock(
854-
ValDef(x, TypeTree(), left).withMods(synthetic),
855-
Apply(Select(right, op.name).withPos(selectPos), Ident(x).withPos(left.pos)))
856-
}
857-
}
858-
859860
/** Create tree for for-comprehension `<for (enums) do body>` or
860861
* `<for (enums) yield body>` where mapName and flatMapName are chosen
861862
* corresponding to whether this is a for-do or a for-yield.
@@ -1066,10 +1067,10 @@ object desugar {
10661067
if (!op.isBackquoted && op.name == tpnme.raw.AMP) AndTypeTree(l, r) // l & r
10671068
else if (!op.isBackquoted && op.name == tpnme.raw.BAR) OrTypeTree(l, r) // l | r
10681069
else AppliedTypeTree(op, l :: r :: Nil) // op[l, r]
1069-
else if (ctx.mode is Mode.Pattern)
1070+
else {
1071+
assert(ctx.mode is Mode.Pattern) // expressions are handled separately by `binop`
10701072
Apply(op, l :: r :: Nil) // op(l, r)
1071-
else // l.op(r), or val x = r; l.op(x), plus handle named args specially
1072-
makeBinop(l, op, r)
1073+
}
10731074
case PostfixOp(t, op) =>
10741075
if ((ctx.mode is Mode.Type) && !op.isBackquoted && op.name == tpnme.raw.STAR) {
10751076
val seqType = if (ctx.compilationUnit.isJava) defn.ArrayType else defn.SeqType

compiler/src/dotty/tools/dotc/ast/untpd.scala

-10
Original file line numberDiff line numberDiff line change
@@ -96,16 +96,6 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
9696
override def isEmpty = true
9797
}
9898

99-
/** A block arising from a right-associative infix operation, where, e.g.
100-
*
101-
* a +: b
102-
*
103-
* is expanded to
104-
*
105-
* { val x = a; b.+:(x) }
106-
*/
107-
class InfixOpBlock(leftOperand: Tree, rightOp: Tree) extends Block(leftOperand :: Nil, rightOp)
108-
10999
/** A block generated by the XML parser, only treated specially by
110100
* `Positioned#checkPos` */
111101
class XMLBlock(stats: List[Tree], expr: Tree) extends Block(stats, expr)

compiler/src/dotty/tools/dotc/typer/Applications.scala

+4
Original file line numberDiff line numberDiff line change
@@ -665,6 +665,10 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
665665
def argCtx(app: untpd.Tree)(implicit ctx: Context): Context =
666666
if (untpd.isSelfConstrCall(app)) ctx.thisCallArgContext else ctx
667667

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

670674
def realApply(implicit ctx: Context): Tree = track("realApply") {

compiler/src/dotty/tools/dotc/typer/Typer.scala

+27-6
Original file line numberDiff line numberDiff line change
@@ -664,12 +664,7 @@ class Typer extends Namer
664664

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

1677+
/** Translate infix operation expression `l op r` to
1678+
*
1679+
* l.op(r) if `op` is left-associative
1680+
* { val x = l; r.op(l) } if `op` is right-associative call-by-value and `l` is impure
1681+
* r.op(l) if `op` is right-associative call-by-name or `l` is pure
1682+
*/
1683+
def typedInfixOp(tree: untpd.InfixOp, pt: Type)(implicit ctx: Context): Tree = {
1684+
val untpd.InfixOp(l, op, r) = tree
1685+
val app = typedApply(desugar.binop(l, op, r), pt)
1686+
if (untpd.isLeftAssoc(op.name)) app
1687+
else {
1688+
val defs = new mutable.ListBuffer[Tree]
1689+
def lift(app: Tree): Tree = (app: @unchecked) match {
1690+
case Apply(fn, args) =>
1691+
if (app.tpe.isError) app
1692+
else tpd.cpy.Apply(app)(fn, LiftImpure.liftArgs(defs, fn.tpe, args))
1693+
case Assign(lhs, rhs) =>
1694+
tpd.cpy.Assign(app)(lhs, lift(rhs))
1695+
case Block(stats, expr) =>
1696+
tpd.cpy.Block(app)(stats, lift(expr))
1697+
}
1698+
Applications.wrapDefs(defs, lift(app))
1699+
}
1700+
}
1701+
16821702
/** Retrieve symbol attached to given tree */
16831703
protected def retrieveSym(tree: untpd.Tree)(implicit ctx: Context) = tree.removeAttachment(SymOfTree) match {
16841704
case Some(sym) =>
@@ -1765,6 +1785,7 @@ class Typer extends Namer
17651785
case tree: untpd.TypedSplice => typedTypedSplice(tree)
17661786
case tree: untpd.UnApply => typedUnApply(tree, pt)
17671787
case tree: untpd.DependentTypeTree => typed(untpd.TypeTree().withPos(tree.pos), pt)
1788+
case tree: untpd.InfixOp if ctx.mode.isExpr => typedInfixOp(tree, pt)
17681789
case tree @ untpd.PostfixOp(qual, Ident(nme.WILDCARD)) => typedAsFunction(tree, pt)
17691790
case untpd.EmptyTree => tpd.EmptyTree
17701791
case _ => typedUnadapted(desugar(tree), pt)

compiler/test-resources/repl/errmsgs

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ scala> class Foo() { def bar: Int = 1 }; val foo = new Foo(); foo.barr
5757
scala> val x: List[Int] = "foo" :: List(1)
5858
1 | val x: List[Int] = "foo" :: List(1)
5959
| ^^^^^
60-
| found: String($1$)
60+
| found: String("foo")
6161
| required: Int
6262
|
6363
scala> { def f: Int = g; val x: Int = 1; def g: Int = 5; }

tests/neg/i2808.scala

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
class C {}
2+
3+
object Test {
4+
def foo1() = { println("foo1") ; 5 }
5+
val c = new C
6+
foo1() m1_: c // error
7+
}

tests/run/i2808.check

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
foo1
2+
foo3
3+
foo1
4+
bar
5+
bar
6+
foo3
7+
bar
8+
bar

tests/run/i2808.scala

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
class C {
2+
def m1_:(f: Int) = ()
3+
def m2_:(f: => Int) = ()
4+
def m3_:(f: Int)(implicit c: C) = ()
5+
def m4_:(f: => Int)(implicit c: C) = ()
6+
7+
}
8+
9+
object Test {
10+
def foo1() = { println("foo1") ; 5 }
11+
def foo2() = { println("foo2") ; 5 }
12+
def foo3() = { println("foo3") ; 5 }
13+
def foo4() = { println("foo4") ; 5 }
14+
15+
def main(args: Array[String]): Unit = {
16+
implicit val c = new C
17+
foo1() m1_: c // foo1
18+
foo2() m2_: c
19+
foo3() m3_: c // foo3
20+
foo4() m4_: c
21+
22+
def bar() = { println("bar"); c }
23+
foo1() m1_: bar() // foo1
24+
// bar
25+
foo2() m2_: bar() // bar
26+
foo3() m3_: bar() // foo3
27+
// bar
28+
foo4() m4_: bar() // bar
29+
}
30+
}

0 commit comments

Comments
 (0)