From c8a9329ff9b2a6ea99eef7234c9f84c0bfe0166b Mon Sep 17 00:00:00 2001 From: Den Shabalin Date: Wed, 11 Sep 2013 19:22:48 +0200 Subject: add syntactic extractor for assignment-like trees There are three kinds of assign-like trees: 1. Assign(lhs, rhs) // $lhs = $rhs 3. AssignOrNamedArg(lhs, rhs) // $lhs = $rhs 2. Apply(Select(f, nme.update), args :+ rhs) // $f(..$args) = $rhs New syntactic combinator unifies all of them and lets users not to think about these implementations details. --- .../scala/tools/nsc/ast/parser/Parsers.scala | 2 +- .../scala/tools/nsc/ast/parser/TreeBuilder.scala | 8 -------- .../scala/tools/reflect/quasiquotes/Reifiers.scala | 8 ++++---- src/reflect/scala/reflect/api/BuildUtils.scala | 7 +++++++ .../scala/reflect/internal/BuildUtils.scala | 12 ++++++++++- src/reflect/scala/reflect/internal/StdNames.scala | 1 + src/reflect/scala/reflect/internal/TreeGen.scala | 8 ++++++++ test/files/neg/macro-quasiquotes.check | 2 +- .../quasiquotes/TermConstructionProps.scala | 24 ++++++++++++++++++++++ .../quasiquotes/TermDeconstructionProps.scala | 20 ++++++++++++++++++ 10 files changed, 77 insertions(+), 15 deletions(-) diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index 07e24900e9..a894e38ab4 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala @@ -1403,7 +1403,7 @@ self => if (in.token == EQUALS) { t match { case Ident(_) | Select(_, _) | Apply(_, _) => - t = atPos(t.pos.startOrPoint, in.skipToken()) { makeAssign(t, expr()) } + t = atPos(t.pos.startOrPoint, in.skipToken()) { gen.mkAssign(t, expr()) } case _ => } } else if (in.token == COLON) { diff --git a/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala b/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala index 91ff530e05..0d93a1b427 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala @@ -191,14 +191,6 @@ abstract class TreeBuilder { } } - /** Create a tree representing an assignment */ - def makeAssign(lhs: Tree, rhs: Tree): Tree = lhs match { - case Apply(fn, args) => - Apply(atPos(fn.pos) { Select(fn, nme.update) }, args ::: List(rhs)) - case _ => - Assign(lhs, rhs) - } - /** Tree for `od op`, start is start0 if od.pos is borked. */ def makePostfixSelect(start0: Int, end: Int, od: Tree, op: Name): Tree = { val start = if (od.pos.isDefined) od.pos.startOrPoint else start0 diff --git a/src/compiler/scala/tools/reflect/quasiquotes/Reifiers.scala b/src/compiler/scala/tools/reflect/quasiquotes/Reifiers.scala index c2d8bcdcd6..0d1fb6be07 100644 --- a/src/compiler/scala/tools/reflect/quasiquotes/Reifiers.scala +++ b/src/compiler/scala/tools/reflect/quasiquotes/Reifiers.scala @@ -10,7 +10,7 @@ trait Reifiers { self: Quasiquotes => import global.build.{SyntacticClassDef, SyntacticTraitDef, SyntacticModuleDef, SyntacticDefDef, SyntacticValDef, SyntacticVarDef, SyntacticBlock, SyntacticApplied, SyntacticTypeApplied, - SyntacticFunction, SyntacticNew} + SyntacticFunction, SyntacticNew, SyntacticAssign} import global.treeInfo._ import global.definitions._ import Cardinality._ @@ -71,9 +71,9 @@ trait Reifiers { self: Quasiquotes => reifyBuildCall(nme.SyntacticValDef, mods, name, tpt, rhs) case SyntacticVarDef(mods, name, tpt, rhs) => reifyBuildCall(nme.SyntacticVarDef, mods, name, tpt, rhs) - case SyntacticApplied(fun, argss) if argss.length > 1 => - reifyBuildCall(nme.SyntacticApplied, fun, argss) - case SyntacticApplied(fun, argss @ (_ :+ (_ :+ Placeholder(_, _, DotDotDot)))) => + case SyntacticAssign(lhs, rhs) => + reifyBuildCall(nme.SyntacticAssign, lhs, rhs) + case SyntacticApplied(fun, argss) if argss.nonEmpty => reifyBuildCall(nme.SyntacticApplied, fun, argss) case SyntacticTypeApplied(fun, targs) if targs.nonEmpty => reifyBuildCall(nme.SyntacticTypeApplied, fun, targs) diff --git a/src/reflect/scala/reflect/api/BuildUtils.scala b/src/reflect/scala/reflect/api/BuildUtils.scala index 60c2a81947..551c27bf9c 100644 --- a/src/reflect/scala/reflect/api/BuildUtils.scala +++ b/src/reflect/scala/reflect/api/BuildUtils.scala @@ -193,5 +193,12 @@ private[reflect] trait BuildUtils { self: Universe => def apply(mods: Modifiers, name: TermName, tpt: Tree, rhs: Tree): ValDef def unapply(tree: Tree): Option[(Modifiers, TermName, Tree, Tree)] } + + val SyntacticAssign: SyntacticAssignExtractor + + trait SyntacticAssignExtractor { + def apply(lhs: Tree, rhs: Tree): Tree + def unapply(tree: Tree): Option[(Tree, Tree)] + } } } diff --git a/src/reflect/scala/reflect/internal/BuildUtils.scala b/src/reflect/scala/reflect/internal/BuildUtils.scala index 2584dcb117..1049f83d46 100644 --- a/src/reflect/scala/reflect/internal/BuildUtils.scala +++ b/src/reflect/scala/reflect/internal/BuildUtils.scala @@ -139,7 +139,7 @@ trait BuildUtils { self: SymbolTable => object SyntacticApplied extends SyntacticAppliedExtractor { def apply(tree: Tree, argss: List[List[Tree]]): Tree = - argss.foldLeft(tree) { Apply(_, _) } + argss.foldLeft(tree) { (f, args) => Apply(f, args.map(treeInfo.assignmentToMaybeNamedArg)) } def unapply(tree: Tree): Some[(Tree, List[List[Tree]])] = { val treeInfo.Applied(fun, targs, argss) = tree @@ -400,6 +400,16 @@ trait BuildUtils { self: SymbolTable => object SyntacticValDef extends SyntacticValDefBase { val isMutable = false } object SyntacticVarDef extends SyntacticValDefBase { val isMutable = true } + + object SyntacticAssign extends SyntacticAssignExtractor { + def apply(lhs: Tree, rhs: Tree): Tree = gen.mkAssign(lhs, rhs) + def unapply(tree: Tree): Option[(Tree, Tree)] = tree match { + case Assign(lhs, rhs) => Some((lhs, rhs)) + case AssignOrNamedArg(lhs, rhs) => Some((lhs, rhs)) + case Apply(Select(fn, nme.update), args :+ rhs) => Some((atPos(fn.pos)(Apply(fn, args)), rhs)) + case _ => None + } + } } val build: BuildApi = new BuildImpl diff --git a/src/reflect/scala/reflect/internal/StdNames.scala b/src/reflect/scala/reflect/internal/StdNames.scala index 6407a3979c..f4eae5590a 100644 --- a/src/reflect/scala/reflect/internal/StdNames.scala +++ b/src/reflect/scala/reflect/internal/StdNames.scala @@ -602,6 +602,7 @@ trait StdNames { val SelectFromTypeTree: NameType = "SelectFromTypeTree" val StringContext: NameType = "StringContext" val SyntacticApplied: NameType = "SyntacticApplied" + val SyntacticAssign: NameType = "SyntacticAssign" val SyntacticBlock: NameType = "SyntacticBlock" val SyntacticClassDef: NameType = "SyntacticClassDef" val SyntacticDefDef: NameType = "SyntacticDefDef" diff --git a/src/reflect/scala/reflect/internal/TreeGen.scala b/src/reflect/scala/reflect/internal/TreeGen.scala index 26adf20c52..0050df67d8 100644 --- a/src/reflect/scala/reflect/internal/TreeGen.scala +++ b/src/reflect/scala/reflect/internal/TreeGen.scala @@ -443,4 +443,12 @@ abstract class TreeGen extends macros.TreeBuilder { case head :: Nil => head case _ => gen.mkBlock(stats) } + + /** Create a tree representing an assignment */ + def mkAssign(lhs: Tree, rhs: Tree): Tree = lhs match { + case Apply(fn, args) => + Apply(atPos(fn.pos)(Select(fn, nme.update)), args :+ rhs) + case _ => + Assign(lhs, rhs) + } } diff --git a/test/files/neg/macro-quasiquotes.check b/test/files/neg/macro-quasiquotes.check index a2d48723b5..96ef75dd32 100644 --- a/test/files/neg/macro-quasiquotes.check +++ b/test/files/neg/macro-quasiquotes.check @@ -1,6 +1,6 @@ Macros_1.scala:14: error: macro implementation has wrong shape: required: (x: Impls.this.c.Expr[Int]): Impls.this.c.Expr[Any] - found : (x: Impls.this.c.universe.Block): Impls.this.c.universe.Apply + found : (x: Impls.this.c.universe.Block): Impls.this.c.universe.Tree type mismatch for parameter x: Impls.this.c.Expr[Int] does not conform to Impls.this.c.universe.Block def m3(x: Int) = macro Impls.impl3 ^ diff --git a/test/files/scalacheck/quasiquotes/TermConstructionProps.scala b/test/files/scalacheck/quasiquotes/TermConstructionProps.scala index c6cca85c81..753ad1aa59 100644 --- a/test/files/scalacheck/quasiquotes/TermConstructionProps.scala +++ b/test/files/scalacheck/quasiquotes/TermConstructionProps.scala @@ -167,4 +167,28 @@ object TermConstructionProps extends QuasiquoteProperties("term construction") { val x = q"val x: Int = 1" assertThrows[IllegalArgumentException] { q"($x) => x" } } + + property("assign variable") = test { + val v = q"v" + val value = q"foo" + assertEqAst(q"$v = $value", "v = foo") + } + + property("assign update 1") = test { + val v = q"v" + val args = q"1" :: q"2" :: Nil + val value = q"foo" + assertEqAst(q"$v(..$args) = $value", "v(1, 2) = foo") + } + + property("assign update 2") = test { + val a = q"v(0)" + val value = q"foo" + assertEqAst(q"$a = $value", "v(0) = foo") + } + + property("assign or named arg") = test { + val assignx = q"x = 1" + assertEqAst(q"f($assignx)", "f(x = 1)") + } } diff --git a/test/files/scalacheck/quasiquotes/TermDeconstructionProps.scala b/test/files/scalacheck/quasiquotes/TermDeconstructionProps.scala index 45c7ee4bb7..22d4b1ce4f 100644 --- a/test/files/scalacheck/quasiquotes/TermDeconstructionProps.scala +++ b/test/files/scalacheck/quasiquotes/TermDeconstructionProps.scala @@ -91,4 +91,24 @@ object TermDeconstructionProps extends QuasiquoteProperties("term deconstruction matches("new { val early = 1} with Parent[Int] { body }") matches("new Foo { selfie => }") } + + property("exhaustive assign pattern") = test { + def matches(tree: Tree) { val q"$rhs = $lhs" = tree } + matches(parse("left = right")) + matches(parse("arr(1) = 2")) + matches(AssignOrNamedArg(EmptyTree, EmptyTree)) + } + + property("deconstruct update 1") = test { + val q"$obj(..$args) = $value" = q"foo(bar) = baz" + assert(obj ≈ q"foo") + assert(args ≈ List(q"bar")) + assert(value ≈ q"baz") + } + + property("deconstruct update 2") = test { + val q"$left = $value" = q"foo(bar) = baz" + assert(left ≈ q"foo(bar)") + assert(value ≈ q"baz") + } } -- cgit v1.2.3