Skip to content

Commit c76b245

Browse files
committed
Proper laziness for by-name args of right-associative operators
This fixes scala/bug#1980 as specified in SIP-34 (http://docs.scala-lang.org/sips/right-associative-by-name-operators.html) by changing the desugaring of right-associative operator syntax such that by-name operands now get the same desugaring as left-associative operators (except for the reversed operands, of course). Only by-value operands are pulled out into intermediate vals to preserve their left-to-right evaluation order. The implementation is as follows: - `Parsers` still performs the `val` desugaring for all calls, except that the generated synthetic names use the new `RIGHT_ASSOC_OP_PREFIX` to identify them later. - Everything else happens in `Typers`: After typechecking a ValDef resulting from desugaring of a right-associative operator its Symbol and RHS are stored in a Map (without knowing at that point if they are by-name or by-value). - After typechecking a method application with an Ident for one of these Symbols, check if the parameter is by-name in which case it is replaced with the RHS and the Symbol added to a Set. - After typechecking a Block, check for a leading ValDef with the Symbol that was inlined and remove the ValDef. Fixes scala/bug#1980
1 parent 74ca535 commit c76b245

File tree

6 files changed

+137
-7
lines changed

6 files changed

+137
-7
lines changed

spec/06-expressions.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -698,9 +698,9 @@ This expression is then interpreted as $e.\mathit{op}(e_1,\ldots,e_n)$.
698698

699699
A left-associative binary
700700
operation $e_1;\mathit{op};e_2$ is interpreted as $e_1.\mathit{op}(e_2)$. If $\mathit{op}$ is
701-
right-associative, the same operation is interpreted as
702-
`{ val $x$=$e_1$; $e_2$.$\mathit{op}$($x\,$) }`, where $x$ is a fresh
703-
name.
701+
right-associative and its parameter is passed by name, the same operation is interpreted as
702+
$e_2.\mathit{op}(e_1)$. If $\mathit{op}$ is right-associative and its parameter is passed by value,
703+
it is interpreted as `{ val $x$=$e_1$; $e_2$.$\mathit{op}$($x\,$) }`, where $x$ is a fresh name.
704704

705705
### Assignment Operators
706706

src/compiler/scala/tools/nsc/ast/parser/Parsers.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -841,7 +841,7 @@ self =>
841841
if (treeInfo.isLeftAssoc(op)) {
842842
Apply(mkSelection(left), arguments)
843843
} else {
844-
val x = freshTermName()
844+
val x = freshTermName(nme.RIGHT_ASSOC_OP_PREFIX)
845845
Block(
846846
List(ValDef(Modifiers(symtab.Flags.SYNTHETIC | symtab.Flags.ARTIFACT), x, TypeTree(), stripParens(left))),
847847
Apply(mkSelection(right), List(Ident(x))))

src/compiler/scala/tools/nsc/typechecker/Typers.scala

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
4545

4646
final val shortenImports = false
4747

48+
// All typechecked RHS of ValDefs for right-associative operator desugaring
49+
private val rightAssocValDefs = new mutable.AnyRefMap[Symbol, Tree]
50+
// Symbols of ValDefs for right-associative operator desugaring which are passed by name and have been inlined
51+
private val inlinedRightAssocValDefs = new mutable.HashSet[Symbol]
52+
4853
// allows override of the behavior of the resetTyper method w.r.t comments
4954
def resetDocComments() = clearDocComments()
5055

@@ -53,6 +58,8 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
5358
resetContexts()
5459
resetImplicits()
5560
resetDocComments()
61+
rightAssocValDefs.clear()
62+
inlinedRightAssocValDefs.clear()
5663
}
5764

5865
sealed abstract class SilentResult[+T] {
@@ -2067,7 +2074,10 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
20672074
} else tpt1.tpe
20682075
transformedOrTyped(vdef.rhs, EXPRmode | BYVALmode, tpt2)
20692076
}
2070-
treeCopy.ValDef(vdef, typedMods, sym.name, tpt1, checkDead(rhs1)) setType NoType
2077+
val vdef1 = treeCopy.ValDef(vdef, typedMods, sym.name, tpt1, checkDead(rhs1)) setType NoType
2078+
if (sym.isSynthetic && sym.name.startsWith(nme.RIGHT_ASSOC_OP_PREFIX))
2079+
rightAssocValDefs += ((sym, vdef1.rhs))
2080+
vdef1
20712081
}
20722082

20732083
/** Enter all aliases of local parameter accessors.
@@ -2478,7 +2488,13 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
24782488
if (result0.nonEmpty) checkPure(result0, supple = true)
24792489
}
24802490

2481-
treeCopy.Block(block, statsTyped, expr1)
2491+
// Remove ValDef for right-associative by-value operator desugaring which has been inlined into expr1
2492+
val statsTyped2 = statsTyped match {
2493+
case (vd: ValDef) :: Nil if inlinedRightAssocValDefs remove vd.symbol => Nil
2494+
case _ => statsTyped
2495+
}
2496+
2497+
treeCopy.Block(block, statsTyped2, expr1)
24822498
.setType(if (treeInfo.isExprSafeToInline(block)) expr1.tpe else expr1.tpe.deconst)
24832499
} finally {
24842500
// enable escaping privates checking from the outside and recycle
@@ -3611,6 +3627,15 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
36113627
case _ => tp
36123628
}
36133629

3630+
// Inline RHS of ValDef for right-associative by-value operator desugaring
3631+
val args2 = (args1, mt.params) match {
3632+
case ((ident: Ident) :: Nil, param :: Nil) if param.isByNameParam && rightAssocValDefs.contains(ident.symbol) =>
3633+
inlinedRightAssocValDefs += ident.symbol
3634+
val rhs = rightAssocValDefs.remove(ident.symbol).get
3635+
rhs.changeOwner(ident.symbol -> context.owner) :: Nil
3636+
case _ => args1
3637+
}
3638+
36143639
/*
36153640
* This is translating uses of List() into Nil. This is less
36163641
* than ideal from a consistency standpoint, but it shouldn't be
@@ -3622,7 +3647,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
36223647
if (args.isEmpty && canTranslateEmptyListToNil && fun.symbol.isInitialized && ListModule.hasCompleteInfo && (fun.symbol == List_apply))
36233648
atPos(tree.pos)(gen.mkNil setType restpe)
36243649
else
3625-
constfold(treeCopy.Apply(tree, fun, args1) setType ifPatternSkipFormals(restpe))
3650+
constfold(treeCopy.Apply(tree, fun, args2) setType ifPatternSkipFormals(restpe))
36263651
}
36273652
checkDead.updateExpr(fun) {
36283653
handleMonomorphicCall

src/reflect/scala/reflect/internal/StdNames.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,7 @@ trait StdNames {
333333
val FRESH_SUFFIX = "macro$" // uses a keyword to avoid collisions with mangled names
334334
val QUAL_PREFIX = "qual$"
335335
val NAMEDARG_PREFIX = "x$"
336+
val RIGHT_ASSOC_OP_PREFIX = "rassoc$"
336337

337338
// Compiler internal names
338339
val ANYname: NameType = "<anyname>"

test/files/run/t1980.check

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
1. defining
2+
foo 1
3+
foo 2
4+
foo 3
5+
1. forcing
6+
bar 1
7+
bar 2
8+
bar 3
9+
2. defining
10+
2. forcing
11+
hi
12+
1
13+
hi
14+
1
15+
3. defining
16+
3. forcing
17+
hi
18+
1
19+
4. defining
20+
4. forcing
21+
1
22+
1
23+
1
24+
5. defining
25+
Int 1
26+
Int 3
27+
5. forcing
28+
String 4
29+
String 2

test/files/run/t1980.scala

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
class LazyList[+A](expr: => LazyList.Evaluated[A]) {
2+
def #:: [B >: A](elem: => B): LazyList[B] = new LazyList(Some((elem, this)))
3+
def ##:: [B >: A](elem: B): LazyList[B] = new LazyList(Some((elem, this)))
4+
def force: Unit = expr.foreach(_._2.force)
5+
}
6+
7+
object LazyList {
8+
type Evaluated[+A] = Option[(A, LazyList[A])]
9+
object Empty extends LazyList[Nothing](None)
10+
def empty[A]: LazyList[A] = Empty
11+
}
12+
13+
object Test extends App {
14+
def foo(i: Int) = { println("foo "+i); i }
15+
def bar(i: Int) = { println("bar "+i); i }
16+
println("1. defining")
17+
val xs1 = foo(1) ##:: foo(2) ##:: foo(3) ##:: LazyList.empty
18+
val xs2 = bar(1) #:: bar(2) #:: bar(3) #:: LazyList.empty
19+
println("1. forcing")
20+
xs1.force
21+
xs2.force
22+
23+
{
24+
println("2. defining")
25+
class C { def f_:(x: => Int)(implicit y: Int): () => Int = (() => x) }
26+
val c = new C
27+
implicit val i = 1
28+
def k = { println("hi"); 1 }
29+
val x1 = c.f_:(k)
30+
val x2 = k f_: c
31+
println("2. forcing")
32+
println(x1())
33+
println(x2())
34+
}
35+
36+
{
37+
println("3. defining")
38+
class C { def f_:[T](x: => T): () => T = (() => x) }
39+
val c = new C
40+
def k = { println("hi"); 1 }
41+
val x1 = k f_:[Any] c
42+
println("3. forcing")
43+
println(x1())
44+
}
45+
46+
// Ensure the owner chain is changed correctly when inlining
47+
{
48+
println("4. defining")
49+
class C { def f_:(x: => Int): () => Int = (() => x) }
50+
val c = new C
51+
val x1 = c.f_:({ val xxx = 1; xxx })
52+
val x2 = { val yyy = 1; yyy } f_: c
53+
val x3 = { val yyy = 1; yyy } f_: c
54+
println("4. forcing")
55+
println(x1())
56+
println(x2())
57+
println(x3())
58+
}
59+
60+
// Overloaded operator with by-name and by-value variants
61+
{
62+
println("5. defining")
63+
class C {
64+
val saved = new collection.mutable.ArrayBuffer[() => String]
65+
def force: Unit = saved.foreach(_.apply())
66+
def :: (x: Int): C = this
67+
def :: (x: => String): C = { saved += (() => x); this }
68+
}
69+
def genI(i: Int): Int = { println("Int "+i); i }
70+
def genS(s: String): String = { println("String "+s); s }
71+
val c = genI(1) :: genS("2") :: genI(3) :: genS("4") :: (new C)
72+
println("5. forcing")
73+
c.force
74+
}
75+
}

0 commit comments

Comments
 (0)