Skip to content

Proper laziness for by-name args of right-associative operators #5969

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 1 commit into from
Nov 29, 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
6 changes: 3 additions & 3 deletions spec/06-expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -698,9 +698,9 @@ This expression is then interpreted as $e.\mathit{op}(e_1,\ldots,e_n)$.

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

### Assignment Operators

Expand Down
2 changes: 1 addition & 1 deletion src/compiler/scala/tools/nsc/ast/parser/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -841,7 +841,7 @@ self =>
if (treeInfo.isLeftAssoc(op)) {
Apply(mkSelection(left), arguments)
} else {
val x = freshTermName()
val x = freshTermName(nme.RIGHT_ASSOC_OP_PREFIX)
Block(
List(ValDef(Modifiers(symtab.Flags.SYNTHETIC | symtab.Flags.ARTIFACT), x, TypeTree(), stripParens(left))),
Apply(mkSelection(right), List(Ident(x))))
Expand Down
31 changes: 28 additions & 3 deletions src/compiler/scala/tools/nsc/typechecker/Typers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper

final val shortenImports = false

// All typechecked RHS of ValDefs for right-associative operator desugaring
private val rightAssocValDefs = new mutable.AnyRefMap[Symbol, Tree]
// Symbols of ValDefs for right-associative operator desugaring which are passed by name and have been inlined
private val inlinedRightAssocValDefs = new mutable.HashSet[Symbol]

// allows override of the behavior of the resetTyper method w.r.t comments
def resetDocComments() = clearDocComments()

Expand All @@ -53,6 +58,8 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
resetContexts()
resetImplicits()
resetDocComments()
rightAssocValDefs.clear()
inlinedRightAssocValDefs.clear()
}

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

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

treeCopy.Block(block, statsTyped, expr1)
// Remove ValDef for right-associative by-value operator desugaring which has been inlined into expr1
val statsTyped2 = statsTyped match {
case (vd: ValDef) :: Nil if inlinedRightAssocValDefs remove vd.symbol => Nil
case _ => statsTyped
}

treeCopy.Block(block, statsTyped2, expr1)
.setType(if (treeInfo.isExprSafeToInline(block)) expr1.tpe else expr1.tpe.deconst)
} finally {
// enable escaping privates checking from the outside and recycle
Expand Down Expand Up @@ -3611,6 +3627,15 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
case _ => tp
}

// Inline RHS of ValDef for right-associative by-value operator desugaring
val args2 = (args1, mt.params) match {
case ((ident: Ident) :: Nil, param :: Nil) if param.isByNameParam && rightAssocValDefs.contains(ident.symbol) =>
inlinedRightAssocValDefs += ident.symbol
val rhs = rightAssocValDefs.remove(ident.symbol).get
rhs.changeOwner(ident.symbol -> context.owner) :: Nil
case _ => args1
}

/*
* This is translating uses of List() into Nil. This is less
* than ideal from a consistency standpoint, but it shouldn't be
Expand All @@ -3622,7 +3647,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
if (args.isEmpty && canTranslateEmptyListToNil && fun.symbol.isInitialized && ListModule.hasCompleteInfo && (fun.symbol == List_apply))
atPos(tree.pos)(gen.mkNil setType restpe)
else
constfold(treeCopy.Apply(tree, fun, args1) setType ifPatternSkipFormals(restpe))
constfold(treeCopy.Apply(tree, fun, args2) setType ifPatternSkipFormals(restpe))
}
checkDead.updateExpr(fun) {
handleMonomorphicCall
Expand Down
1 change: 1 addition & 0 deletions src/reflect/scala/reflect/internal/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,7 @@ trait StdNames {
val FRESH_SUFFIX = "macro$" // uses a keyword to avoid collisions with mangled names
val QUAL_PREFIX = "qual$"
val NAMEDARG_PREFIX = "x$"
val RIGHT_ASSOC_OP_PREFIX = "rassoc$"

// Compiler internal names
val ANYname: NameType = "<anyname>"
Expand Down
29 changes: 29 additions & 0 deletions test/files/run/t1980.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
1. defining
foo 1
foo 2
foo 3
1. forcing
bar 1
bar 2
bar 3
2. defining
2. forcing
hi
1
hi
1
3. defining
3. forcing
hi
1
4. defining
4. forcing
1
1
1
5. defining
Int 1
Int 3
5. forcing
String 4
String 2
75 changes: 75 additions & 0 deletions test/files/run/t1980.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
class LazyList[+A](expr: => LazyList.Evaluated[A]) {
def #:: [B >: A](elem: => B): LazyList[B] = new LazyList(Some((elem, this)))
def ##:: [B >: A](elem: B): LazyList[B] = new LazyList(Some((elem, this)))
def force: Unit = expr.foreach(_._2.force)
}

object LazyList {
type Evaluated[+A] = Option[(A, LazyList[A])]
object Empty extends LazyList[Nothing](None)
def empty[A]: LazyList[A] = Empty
}

object Test extends App {
def foo(i: Int) = { println("foo "+i); i }
def bar(i: Int) = { println("bar "+i); i }
println("1. defining")
val xs1 = foo(1) ##:: foo(2) ##:: foo(3) ##:: LazyList.empty
val xs2 = bar(1) #:: bar(2) #:: bar(3) #:: LazyList.empty
println("1. forcing")
xs1.force
xs2.force

{
println("2. defining")
class C { def f_:(x: => Int)(implicit y: Int): () => Int = (() => x) }
val c = new C
implicit val i = 1
def k = { println("hi"); 1 }
val x1 = c.f_:(k)
val x2 = k f_: c
println("2. forcing")
println(x1())
println(x2())
}

{
println("3. defining")
class C { def f_:[T](x: => T): () => T = (() => x) }
val c = new C
def k = { println("hi"); 1 }
val x1 = k f_:[Any] c
println("3. forcing")
println(x1())
}

// Ensure the owner chain is changed correctly when inlining
{
println("4. defining")
class C { def f_:(x: => Int): () => Int = (() => x) }
val c = new C
val x1 = c.f_:({ val xxx = 1; xxx })
val x2 = { val yyy = 1; yyy } f_: c
val x3 = { val yyy = 1; yyy } f_: c
println("4. forcing")
println(x1())
println(x2())
println(x3())
}

// Overloaded operator with by-name and by-value variants
{
println("5. defining")
class C {
val saved = new collection.mutable.ArrayBuffer[() => String]
def force: Unit = saved.foreach(_.apply())
def :: (x: Int): C = this
def :: (x: => String): C = { saved += (() => x); this }
}
def genI(i: Int): Int = { println("Int "+i); i }
def genS(s: String): String = { println("String "+s); s }
val c = genI(1) :: genS("2") :: genI(3) :: genS("4") :: (new C)
println("5. forcing")
c.force
}
}