From 6fcd4382653a21835abff536d7e2f42d9ef01787 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 13 Mar 2020 12:01:12 +0100 Subject: [PATCH 1/7] Fix #8530: Support inline unapply --- .../tools/dotc/typer/PrepareInlineable.scala | 2 -- .../src/dotty/tools/dotc/typer/Typer.scala | 18 ++++++++++ tests/neg/inine-unnapply.scala | 5 --- tests/neg/inline-unapply.scala | 15 --------- tests/pos/i8530.scala | 26 +++++++++++++++ tests/pos/inine-unnapply.scala | 5 +++ tests/pos/inline-unapply.scala | 15 +++++++++ tests/run-macros/i8530-2.check | 1 + tests/run-macros/i8530/App_2.scala | 12 +++++++ tests/run-macros/i8530/Macro_1.scala | 8 +++++ tests/run/i8530.check | 5 +++ tests/run/i8530.scala | 33 +++++++++++++++++++ 12 files changed, 123 insertions(+), 22 deletions(-) delete mode 100644 tests/neg/inine-unnapply.scala delete mode 100644 tests/neg/inline-unapply.scala create mode 100644 tests/pos/i8530.scala create mode 100644 tests/pos/inine-unnapply.scala create mode 100644 tests/pos/inline-unapply.scala create mode 100644 tests/run-macros/i8530-2.check create mode 100644 tests/run-macros/i8530/App_2.scala create mode 100644 tests/run-macros/i8530/Macro_1.scala create mode 100644 tests/run/i8530.check create mode 100644 tests/run/i8530.scala diff --git a/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala b/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala index d19e4f5b6f0d..c00ee84431d5 100644 --- a/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala +++ b/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala @@ -246,8 +246,6 @@ object PrepareInlineable { ctx.error(em"Implementation restriction: No inline methods allowed where opaque type aliases are in scope", inlined.sourcePos) if (ctx.outer.inInlineMethod) ctx.error(ex"Implementation restriction: nested inline methods are not supported", inlined.sourcePos) - if (inlined.name.isUnapplyName) - ctx.error(em"Implementation restriction: inline ${inlined.name} methods are not supported", inlined.sourcePos) if (inlined.is(Macro) && !ctx.isAfterTyper) { diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 0b10792d84a8..71978bc9bcef 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1250,6 +1250,24 @@ class Typer extends Namer if (bounds != null) sym.info = bounds } b + case t: UnApply if t.symbol.is(Inline) => + // An inline unapply `P.unapply` in a plattern `P(x1,x2,...)` is transformed into + // `{ class $anon { def unapply(x1: T1, x2: T2, ...): R = P.unapply(x1, x2, ...) }; new $anon }.unapply` + // and the call `P.unapply(x1, x2, ...)` is inlined. + val sym = t.symbol + val cls = ctx.newNormalizedClassSymbol(ctx.owner, tpnme.ANON_CLASS, Synthetic | Final, List(defn.ObjectType), coord = sym.coord) + val constr = ctx.newConstructor(cls, Synthetic, Nil, Nil, coord = sym.coord).entered + val unappplySym = ctx.newSymbol(cls, sym.name.toTermName, Synthetic | Method, sym.info, coord = sym.coord).entered + val unapply = polyDefDef(unappplySym, targs => argss => + Inliner.inlineCall(ref(sym).appliedToTypes(targs).appliedToArgss(argss).withSpan(t.span)) + ) + val cdef = ClassDef(cls, DefDef(constr), List(unapply)) + val newUnapply = Block(cdef :: Nil, New(cls.typeRef, Nil)) + val targs = t.fun match + case TypeApply(_, targs) => targs + case _ => Nil + val newFun = newUnapply.select(unappplySym).appliedToTypeTrees(targs).withSpan(t.span) + cpy.UnApply(t)(newFun, t.implicits, t.patterns) case t => t } } diff --git a/tests/neg/inine-unnapply.scala b/tests/neg/inine-unnapply.scala deleted file mode 100644 index db82997e5648..000000000000 --- a/tests/neg/inine-unnapply.scala +++ /dev/null @@ -1,5 +0,0 @@ - -object Foo { - inline def unapply(x: Any): Boolean = ??? // error: Implementation restriction: inline unapply methods are not supported - inline def unapplySeq(x: Any): Seq[Any] = ??? // error: Implementation restriction: inline unapplySeq methods are not supported -} diff --git a/tests/neg/inline-unapply.scala b/tests/neg/inline-unapply.scala deleted file mode 100644 index 818bc38f9c82..000000000000 --- a/tests/neg/inline-unapply.scala +++ /dev/null @@ -1,15 +0,0 @@ -object Test { - - class C(val x: Int, val y: Int) - - inline def unapply(c: C): Some[(Int, Int)] = Some((c.x, c.y)) // error: Implementation restriction: inline unapply methods are not supported - -} -object Test2 { - - class C(x: Int, y: Int) - - inline def unapply(c: C): Option[(Int, Int)] = inline c match { // error: Implementation restriction: inline unapply methods are not supported - case x: C => Some((1, 1)) - } -} \ No newline at end of file diff --git a/tests/pos/i8530.scala b/tests/pos/i8530.scala new file mode 100644 index 000000000000..05ab6fb02a04 --- /dev/null +++ b/tests/pos/i8530.scala @@ -0,0 +1,26 @@ +object MyBoooleanUnapply: + inline def unapply(x: Int): Boolean = true + +object MyOptionUnapply: + inline def unapply(x: Int): Option[Long] = Some(x) + +object MyUnapplyImplicits: + inline def unapply(x: Int)(using DummyImplicit): Option[Long] = Some(x) + +object MyPolyUnapply: + inline def unapply[T](x: T): Option[T] = Some(x) + +object MySeqUnapply: + inline def unapplySeq(x: Int): Seq[Int] = Seq(x, x) + +object MyWhiteboxUnapply: + inline def unapply(x: Int) <: Option[Any] = Some(x) + +def test: Unit = + val x = 5 match + case MyBoooleanUnapply() => + case MyOptionUnapply(y) => y: Long + case MyUnapplyImplicits(y) => y: Long + case MyPolyUnapply(a) => a: Int + case MySeqUnapply(a, b) => (a: Int, b: Int) + case MyWhiteboxUnapply(x) => x: Int diff --git a/tests/pos/inine-unnapply.scala b/tests/pos/inine-unnapply.scala new file mode 100644 index 000000000000..22ad8eb3526c --- /dev/null +++ b/tests/pos/inine-unnapply.scala @@ -0,0 +1,5 @@ + +object Foo { + inline def unapply(x: Any): Boolean = ??? + inline def unapplySeq(x: Any): Seq[Any] = ??? +} diff --git a/tests/pos/inline-unapply.scala b/tests/pos/inline-unapply.scala new file mode 100644 index 000000000000..e352f08e2315 --- /dev/null +++ b/tests/pos/inline-unapply.scala @@ -0,0 +1,15 @@ +object Test { + + class C(val x: Int, val y: Int) + + inline def unapply(c: C): Some[(Int, Int)] = Some((c.x, c.y)) + +} +object Test2 { + + class C(x: Int, y: Int) + + inline def unapply(c: C): Option[(Int, Int)] = inline c match { + case x: C => Some((1, 1)) + } +} diff --git a/tests/run-macros/i8530-2.check b/tests/run-macros/i8530-2.check new file mode 100644 index 000000000000..257cc5642cb1 --- /dev/null +++ b/tests/run-macros/i8530-2.check @@ -0,0 +1 @@ +foo diff --git a/tests/run-macros/i8530/App_2.scala b/tests/run-macros/i8530/App_2.scala new file mode 100644 index 000000000000..3d0d6b665128 --- /dev/null +++ b/tests/run-macros/i8530/App_2.scala @@ -0,0 +1,12 @@ + +object Test { + def main(args: Array[String]): Unit = { + 0 match + case Succ(n) => ??? + case _ => + + 2 match + case Succ(n) => assert(n == 1) + } + +} diff --git a/tests/run-macros/i8530/Macro_1.scala b/tests/run-macros/i8530/Macro_1.scala new file mode 100644 index 000000000000..2d49b71383e3 --- /dev/null +++ b/tests/run-macros/i8530/Macro_1.scala @@ -0,0 +1,8 @@ +import scala.quoted._ + +object Succ: + + inline def unapply(n: Int): Option[Int] = ${ impl('n) } + + private def impl(n: Expr[Int])(using QuoteContext): Expr[Option[Int]] = + '{ if $n == 0 then None else Some($n - 1)} diff --git a/tests/run/i8530.check b/tests/run/i8530.check new file mode 100644 index 000000000000..c8f75a704ff7 --- /dev/null +++ b/tests/run/i8530.check @@ -0,0 +1,5 @@ +MyBoooleanUnapply +2 +3 +(4,5) +5 diff --git a/tests/run/i8530.scala b/tests/run/i8530.scala new file mode 100644 index 000000000000..55a0aa4f92a4 --- /dev/null +++ b/tests/run/i8530.scala @@ -0,0 +1,33 @@ +object MyBoooleanUnapply: + inline def unapply(x: Int): Boolean = true + +object MyOptionUnapply: + inline def unapply(x: Int): Option[Long] = Some(x) + +object MyPolyUnapply: + inline def unapply[T](x: T): Option[T] = Some(x) + +object MySeqUnapply: + inline def unapplySeq(x: Int): Seq[Int] = Seq(x, x + 1) + +object MyWhiteboxUnapply: + inline def unapply(x: Int) <: Option[Any] = Some(x) + + +@main def Test = + 1 match + case MyBoooleanUnapply() => println("MyBoooleanUnapply") + + 2 match + case MyOptionUnapply(y) => println(y) + + 3 match + case MyPolyUnapply(a) => println(a) + + 4 match + case MySeqUnapply(a, b) => println((a, b)) + + 5 match + case MyWhiteboxUnapply(x) => println(x: Int) + +end Test From 181eaca2523697492b92c9d2e632dcd949a3fa9b Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 24 Mar 2020 09:35:33 +0100 Subject: [PATCH 2/7] Use unapply tree fun as prefix --- .../src/dotty/tools/dotc/typer/Typer.scala | 2 +- tests/run/i8530-b.scala | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 tests/run/i8530-b.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 71978bc9bcef..56885e70dacd 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1259,7 +1259,7 @@ class Typer extends Namer val constr = ctx.newConstructor(cls, Synthetic, Nil, Nil, coord = sym.coord).entered val unappplySym = ctx.newSymbol(cls, sym.name.toTermName, Synthetic | Method, sym.info, coord = sym.coord).entered val unapply = polyDefDef(unappplySym, targs => argss => - Inliner.inlineCall(ref(sym).appliedToTypes(targs).appliedToArgss(argss).withSpan(t.span)) + Inliner.inlineCall(t.fun.appliedToTypes(targs).appliedToArgss(argss).withSpan(t.span))(ctx.withOwner(unappplySym)) ) val cdef = ClassDef(cls, DefDef(constr), List(unapply)) val newUnapply = Block(cdef :: Nil, New(cls.typeRef, Nil)) diff --git a/tests/run/i8530-b.scala b/tests/run/i8530-b.scala new file mode 100644 index 000000000000..80171bc4d178 --- /dev/null +++ b/tests/run/i8530-b.scala @@ -0,0 +1,18 @@ +import scala.compiletime.erasedValue + +class MyRegex[Pattern <: String & Singleton/*Literal constant*/]: + inline def unapplySeq(s: CharSequence): Option[List[String]] = + inline erasedValue[Pattern] match + case "foo" => if s == "foo" then Some(Nil) else None + case _ => valueOf[Pattern].r.unapplySeq(s) + +@main def Test: Unit = + val myRegexp1 = new MyRegex["foo"] + val myRegexp2 = new MyRegex["f(o+)"] + "foo" match + case myRegexp1() => // Match ok + case myRegexp2(x) => ??? + "foooo" match + case myRegexp1() => ??? + case myRegexp2(x) => + assert(x == "oooo") \ No newline at end of file From 4f7f34c07e3a0fe06b2b423016ff17db060d38e5 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 24 Mar 2020 10:38:05 +0100 Subject: [PATCH 3/7] Propagate targs to the unapply placeholder --- .../src/dotty/tools/dotc/typer/Typer.scala | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 56885e70dacd..90921b6a3b57 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1257,16 +1257,21 @@ class Typer extends Namer val sym = t.symbol val cls = ctx.newNormalizedClassSymbol(ctx.owner, tpnme.ANON_CLASS, Synthetic | Final, List(defn.ObjectType), coord = sym.coord) val constr = ctx.newConstructor(cls, Synthetic, Nil, Nil, coord = sym.coord).entered - val unappplySym = ctx.newSymbol(cls, sym.name.toTermName, Synthetic | Method, sym.info, coord = sym.coord).entered - val unapply = polyDefDef(unappplySym, targs => argss => - Inliner.inlineCall(t.fun.appliedToTypes(targs).appliedToArgss(argss).withSpan(t.span))(ctx.withOwner(unappplySym)) - ) - val cdef = ClassDef(cls, DefDef(constr), List(unapply)) - val newUnapply = Block(cdef :: Nil, New(cls.typeRef, Nil)) + val targs = t.fun match case TypeApply(_, targs) => targs case _ => Nil - val newFun = newUnapply.select(unappplySym).appliedToTypeTrees(targs).withSpan(t.span) + val unapplyInfo = sym.info match + case info: PolyType => info.instantiate(targs.map(_.tpe)) + case info => info + + val unappplySym = ctx.newSymbol(cls, sym.name.toTermName, Synthetic | Method, unapplyInfo, coord = sym.coord).entered + val unapply = DefDef(unappplySym, argss => + Inliner.inlineCall(t.fun.appliedToArgss(argss).withSpan(t.span))(ctx.withOwner(unappplySym)) + ) + val cdef = ClassDef(cls, DefDef(constr), List(unapply)) + val newUnapply = Block(cdef :: Nil, New(cls.typeRef, Nil)) + val newFun = newUnapply.select(unappplySym).withSpan(t.span) cpy.UnApply(t)(newFun, t.implicits, t.patterns) case t => t } From caff1375f87b6b9cdb9f2d3b0355f9a460b8a30b Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 24 Mar 2020 13:02:34 +0100 Subject: [PATCH 4/7] Remove placeholer inserted by inlined patterns --- compiler/src/dotty/tools/dotc/Compiler.scala | 1 + .../tools/dotc/transform/InlinePatterns.scala | 80 +++++++++++++++++++ .../src/dotty/tools/dotc/typer/Typer.scala | 5 +- tests/pos/i8530.scala | 2 +- tests/run/i8530.scala | 2 +- 5 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/transform/InlinePatterns.scala diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 16b7a890f7ce..49c28f1ea030 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -79,6 +79,7 @@ class Compiler { new StringInterpolatorOpt, // Optimizes raw and s string interpolators by rewriting them to string concatentations new CrossCastAnd) :: // Normalize selections involving intersection types. List(new PruneErasedDefs, // Drop erased definitions from scopes and simplify erased expressions + new InlinePatterns, // Remove placeholders of inlined patterns new VCInlineMethods, // Inlines calls to value class methods new SeqLiterals, // Express vararg arguments as arrays new InterceptedMethods, // Special handling of `==`, `|=`, `getClass` methods diff --git a/compiler/src/dotty/tools/dotc/transform/InlinePatterns.scala b/compiler/src/dotty/tools/dotc/transform/InlinePatterns.scala new file mode 100644 index 000000000000..c90e21329c7b --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/InlinePatterns.scala @@ -0,0 +1,80 @@ +package dotty.tools +package dotc +package transform + +import core._ +import MegaPhase._ +import Symbols._, Contexts._, Types._, Decorators._ +import StdNames.nme +import NameOps._ +import Names._ +import ast.Trees._ +import ast.TreeTypeMap + +/** Rewrite an application + * + * {new { def unapply(x0: X0)(x1: X1,..., xn: Xn) = b }}.unapply(y0)(y1, ..., yn) + * + * where + * + * - the method is `unapply` or `unapplySeq` + * - the method does not have type parameters + * + * to + * + * [xi := yi]b + * + * This removes placeholders added by inline `unapply`/`unapplySeq` patterns. + */ +class InlinePatterns extends MiniPhase: + import ast.tpd._ + + def phaseName: String = "inlinePatterns" + + override def runsAfterGroupsOf: Set[String] = Set(PatternMatcher.name) + + override def transformApply(app: Apply)(using ctx: Context): Tree = + if app.symbol.name.isUnapplyName && !app.tpe.isInstanceOf[MethodicType] then + app match + case App(Select(fn, name), argss) => + val app1 = betaReduce(app, fn, name, argss.flatten) + if app1 ne app then ctx.log(i"beta reduce $app -> $app1") + app1 + case _ => + app + else app + + private object App: + def unapply(app: Tree): (Tree, List[List[Tree]]) = + app match + case Apply(App(fn, argss), args) => (fn, argss :+ args) + case _ => (app, Nil) + + private def betaReduce(tree: Apply, fn: Tree, name: Name, args: List[Tree])(using ctx: Context): Tree = + fn match + case Block(Nil, expr) => betaReduce(tree, expr, name, args) + case Block(TypeDef(_, template: Template) :: Nil, Apply(Select(New(_),_), Nil)) if template.constr.rhs.isEmpty => + template.body match + case List(ddef @ DefDef(`name`, _, _, _, _)) => + val bindings = List.newBuilder[ValDef] + val vparams = ddef.vparamss.flatten + val argSyms = + for (arg, param) <- args.zip(vparams) yield + arg.tpe.dealias match + case ref @ TermRef(NoPrefix, _) if isPurePath(arg) => + ref.symbol + case _ => + val binding = SyntheticValDef(param.name, arg) + bindings += binding + binding.symbol + seq( + bindings.result(), + TreeTypeMap( + oldOwners = ddef.symbol :: Nil, + newOwners = ctx.owner :: Nil, + substFrom = vparams.map(_.symbol), + substTo = argSyms).transform(ddef.rhs) + ) + + case _ => tree + case _ => tree diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 90921b6a3b57..b8d3a47f8b66 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1252,8 +1252,11 @@ class Typer extends Namer b case t: UnApply if t.symbol.is(Inline) => // An inline unapply `P.unapply` in a plattern `P(x1,x2,...)` is transformed into - // `{ class $anon { def unapply(x1: T1, x2: T2, ...): R = P.unapply(x1, x2, ...) }; new $anon }.unapply` + // `{ class $anon { def unapply(t0: T0)(using t1: T1, t2: T2, ...): R = P.unapply(t0)(using t1, t2, ...) }; new $anon }.unapply` // and the call `P.unapply(x1, x2, ...)` is inlined. + // This serves as a placeholder for the inlined body until the `patternMatcher` phase. After pattern matcher + // transforms the patterns into terms, the `inlinePatterns` phase removes this anonymous class by β-reducing + // the call to the `unapply`. val sym = t.symbol val cls = ctx.newNormalizedClassSymbol(ctx.owner, tpnme.ANON_CLASS, Synthetic | Final, List(defn.ObjectType), coord = sym.coord) val constr = ctx.newConstructor(cls, Synthetic, Nil, Nil, coord = sym.coord).entered diff --git a/tests/pos/i8530.scala b/tests/pos/i8530.scala index 05ab6fb02a04..2eab9f9e74c8 100644 --- a/tests/pos/i8530.scala +++ b/tests/pos/i8530.scala @@ -14,7 +14,7 @@ object MySeqUnapply: inline def unapplySeq(x: Int): Seq[Int] = Seq(x, x) object MyWhiteboxUnapply: - inline def unapply(x: Int) <: Option[Any] = Some(x) + transparent inline def unapply(x: Int): Option[Any] = Some(x) def test: Unit = val x = 5 match diff --git a/tests/run/i8530.scala b/tests/run/i8530.scala index 55a0aa4f92a4..bbbc52587ee0 100644 --- a/tests/run/i8530.scala +++ b/tests/run/i8530.scala @@ -11,7 +11,7 @@ object MySeqUnapply: inline def unapplySeq(x: Int): Seq[Int] = Seq(x, x + 1) object MyWhiteboxUnapply: - inline def unapply(x: Int) <: Option[Any] = Some(x) + transparent inline def unapply(x: Int): Option[Any] = Some(x) @main def Test = From 8b776bd2b526660fb8d060e6388687cfe0f8df72 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 31 Mar 2020 16:18:27 +0200 Subject: [PATCH 5/7] =?UTF-8?q?Factor=20out=20common=20=CE=B2-reduction=20?= =?UTF-8?q?code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tools/dotc/transform/BetaReduce.scala | 48 +++++++++++++------ .../tools/dotc/transform/InlinePatterns.scala | 25 ++-------- 2 files changed, 37 insertions(+), 36 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/BetaReduce.scala b/compiler/src/dotty/tools/dotc/transform/BetaReduce.scala index eb1b1860d67d..8b9b5e446da0 100644 --- a/compiler/src/dotty/tools/dotc/transform/BetaReduce.scala +++ b/compiler/src/dotty/tools/dotc/transform/BetaReduce.scala @@ -46,18 +46,38 @@ class BetaReduce extends MiniPhase: fn match case Typed(expr, _) => betaReduce(tree, expr, args) case Block(Nil, expr) => betaReduce(tree, expr, args) - case Block((anonFun: DefDef) :: Nil, closure: Closure) => - val argSyms = - for arg <- args yield - arg.tpe.dealias match - case ref @ TermRef(NoPrefix, _) if isPurePath(arg) => ref.symbol - case _ => NoSymbol - val vparams = anonFun.vparamss.head - if argSyms.forall(_.exists) && argSyms.hasSameLengthAs(vparams) then - TreeTypeMap( - oldOwners = anonFun.symbol :: Nil, - newOwners = ctx.owner :: Nil, - substFrom = vparams.map(_.symbol), - substTo = argSyms).transform(anonFun.rhs) - else tree + case Block((anonFun: DefDef) :: Nil, closure: Closure) => BetaReduce(tree)(anonFun, args, true) case _ => tree + +object BetaReduce: + import ast.tpd._ + + /** Beta-reduces a call to `ddef` with arguments `argSyms` */ + def apply(tree: Tree)(ddef: DefDef, args: List[Tree], noBindings: Boolean)(using ctx: Context) = + val bindings = List.newBuilder[ValDef] + val vparams = ddef.vparamss.iterator.flatten.toList + val argSyms = + for (arg, param) <- args.zip(vparams) yield + arg.tpe.dealias match + case ref @ TermRef(NoPrefix, _) if isPurePath(arg) => + ref.symbol + case _ => + if noBindings then // TODO always generate bindings + NoSymbol + else + val binding = SyntheticValDef(param.name, arg) + bindings += binding + binding.symbol + + if argSyms.forall(_.exists) && argSyms.hasSameLengthAs(vparams) then // TODO assert rather than fail silently + seq( + bindings.result(), + TreeTypeMap( + oldOwners = ddef.symbol :: Nil, + newOwners = ctx.owner :: Nil, + substFrom = vparams.map(_.symbol), + substTo = argSyms + ).transform(ddef.rhs) + ) + else + tree diff --git a/compiler/src/dotty/tools/dotc/transform/InlinePatterns.scala b/compiler/src/dotty/tools/dotc/transform/InlinePatterns.scala index c90e21329c7b..2eb60f074ad5 100644 --- a/compiler/src/dotty/tools/dotc/transform/InlinePatterns.scala +++ b/compiler/src/dotty/tools/dotc/transform/InlinePatterns.scala @@ -31,6 +31,8 @@ class InlinePatterns extends MiniPhase: def phaseName: String = "inlinePatterns" + // This phase needs to run after because it need to transform trees that are generated + // by the pattern matcher but are still not visible in that group of phases. override def runsAfterGroupsOf: Set[String] = Set(PatternMatcher.name) override def transformApply(app: Apply)(using ctx: Context): Tree = @@ -52,29 +54,8 @@ class InlinePatterns extends MiniPhase: private def betaReduce(tree: Apply, fn: Tree, name: Name, args: List[Tree])(using ctx: Context): Tree = fn match - case Block(Nil, expr) => betaReduce(tree, expr, name, args) case Block(TypeDef(_, template: Template) :: Nil, Apply(Select(New(_),_), Nil)) if template.constr.rhs.isEmpty => template.body match - case List(ddef @ DefDef(`name`, _, _, _, _)) => - val bindings = List.newBuilder[ValDef] - val vparams = ddef.vparamss.flatten - val argSyms = - for (arg, param) <- args.zip(vparams) yield - arg.tpe.dealias match - case ref @ TermRef(NoPrefix, _) if isPurePath(arg) => - ref.symbol - case _ => - val binding = SyntheticValDef(param.name, arg) - bindings += binding - binding.symbol - seq( - bindings.result(), - TreeTypeMap( - oldOwners = ddef.symbol :: Nil, - newOwners = ctx.owner :: Nil, - substFrom = vparams.map(_.symbol), - substTo = argSyms).transform(ddef.rhs) - ) - + case List(ddef @ DefDef(`name`, _, _, _, _)) => BetaReduce(tree)(ddef, args, false) case _ => tree case _ => tree From aa755f5f51dbf20de77d4d3e344a9f021cae3872 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 31 Mar 2020 16:35:09 +0200 Subject: [PATCH 6/7] Move inline unapply logic from Typer to Inliner --- .../src/dotty/tools/dotc/typer/Inliner.scala | 42 +++++++++++++++++++ .../src/dotty/tools/dotc/typer/Typer.scala | 27 +----------- 2 files changed, 43 insertions(+), 26 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index fe21741080cd..a62c3508d02a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -124,6 +124,48 @@ object Inliner { ) } + /** Try to inline a pattern with an inline unapply method. Fail with error if the maximal + * inline depth is exceeded. + * + * @param unapp The tree of the pattern to inline + * @return An `Unapply` with a `fun` containing the inlined call to the unapply + */ + def inlinedUnapply(unapp: tpd.UnApply)(using ctx: Context): Tree = { + // We cannot inline the unapply directly, since the pattern matcher relies on unapply applications + // as signposts what to do. On the other hand, we can do the inlining only in typer, not afterwards. + // So the trick is to create a "wrapper" unapply in an anonymous class that has the inlined unapply + // as its right hand side. The call to the wrapper unapply serves as the signpost for pattern matching. + // After pattern matching, the anonymous class is removed in phase InlinePatterns with a beta reduction step. + // + // An inline unapply `P.unapply` in a plattern `P(x1,x2,...)` is transformed into + // `{ class $anon { def unapply(t0: T0)(using t1: T1, t2: T2, ...): R = P.unapply(t0)(using t1, t2, ...) }; new $anon }.unapply` + // and the call `P.unapply(x1, x2, ...)` is inlined. + // This serves as a placeholder for the inlined body until the `patternMatcher` phase. After pattern matcher + // transforms the patterns into terms, the `inlinePatterns` phase removes this anonymous class by β-reducing + // the call to the `unapply`. + + val UnApply(fun, implicits, patterns) = unapp + val sym = unapp.symbol + val cls = ctx.newNormalizedClassSymbol(ctx.owner, tpnme.ANON_CLASS, Synthetic | Final, List(defn.ObjectType), coord = sym.coord) + val constr = ctx.newConstructor(cls, Synthetic, Nil, Nil, coord = sym.coord).entered + + val targs = fun match + case TypeApply(_, targs) => targs + case _ => Nil + val unapplyInfo = sym.info match + case info: PolyType => info.instantiate(targs.map(_.tpe)) + case info => info + + val unappplySym = ctx.newSymbol(cls, sym.name.toTermName, Synthetic | Method, unapplyInfo, coord = sym.coord).entered + val unapply = DefDef(unappplySym, argss => + inlineCall(fun.appliedToArgss(argss).withSpan(unapp.span))(ctx.withOwner(unappplySym)) + ) + val cdef = ClassDef(cls, DefDef(constr), List(unapply)) + val newUnapply = Block(cdef :: Nil, New(cls.typeRef, Nil)) + val newFun = newUnapply.select(unappplySym).withSpan(unapp.span) + cpy.UnApply(unapp)(newFun, implicits, patterns) + } + /** For a retained inline method, another method that keeps track of * the body that is kept at runtime. For instance, an inline method * diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index b8d3a47f8b66..263dc4e32575 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1250,32 +1250,7 @@ class Typer extends Namer if (bounds != null) sym.info = bounds } b - case t: UnApply if t.symbol.is(Inline) => - // An inline unapply `P.unapply` in a plattern `P(x1,x2,...)` is transformed into - // `{ class $anon { def unapply(t0: T0)(using t1: T1, t2: T2, ...): R = P.unapply(t0)(using t1, t2, ...) }; new $anon }.unapply` - // and the call `P.unapply(x1, x2, ...)` is inlined. - // This serves as a placeholder for the inlined body until the `patternMatcher` phase. After pattern matcher - // transforms the patterns into terms, the `inlinePatterns` phase removes this anonymous class by β-reducing - // the call to the `unapply`. - val sym = t.symbol - val cls = ctx.newNormalizedClassSymbol(ctx.owner, tpnme.ANON_CLASS, Synthetic | Final, List(defn.ObjectType), coord = sym.coord) - val constr = ctx.newConstructor(cls, Synthetic, Nil, Nil, coord = sym.coord).entered - - val targs = t.fun match - case TypeApply(_, targs) => targs - case _ => Nil - val unapplyInfo = sym.info match - case info: PolyType => info.instantiate(targs.map(_.tpe)) - case info => info - - val unappplySym = ctx.newSymbol(cls, sym.name.toTermName, Synthetic | Method, unapplyInfo, coord = sym.coord).entered - val unapply = DefDef(unappplySym, argss => - Inliner.inlineCall(t.fun.appliedToArgss(argss).withSpan(t.span))(ctx.withOwner(unappplySym)) - ) - val cdef = ClassDef(cls, DefDef(constr), List(unapply)) - val newUnapply = Block(cdef :: Nil, New(cls.typeRef, Nil)) - val newFun = newUnapply.select(unappplySym).withSpan(t.span) - cpy.UnApply(t)(newFun, t.implicits, t.patterns) + case t: UnApply if t.symbol.is(Inline) => Inliner.inlinedUnapply(t) case t => t } } From efc258d2859e092286a87814176992a2e289c985 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 31 Mar 2020 16:41:20 +0200 Subject: [PATCH 7/7] =?UTF-8?q?Allow=20more=20=20=CE=B2-reductions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tools/dotc/transform/BetaReduce.scala | 37 +++++++++---------- .../tools/dotc/transform/InlinePatterns.scala | 2 +- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/BetaReduce.scala b/compiler/src/dotty/tools/dotc/transform/BetaReduce.scala index 8b9b5e446da0..6591e847c099 100644 --- a/compiler/src/dotty/tools/dotc/transform/BetaReduce.scala +++ b/compiler/src/dotty/tools/dotc/transform/BetaReduce.scala @@ -3,6 +3,7 @@ package dotc package transform import core._ +import Flags._ import MegaPhase._ import Symbols._, Contexts._, Types._, Decorators._ import StdNames.nme @@ -46,38 +47,34 @@ class BetaReduce extends MiniPhase: fn match case Typed(expr, _) => betaReduce(tree, expr, args) case Block(Nil, expr) => betaReduce(tree, expr, args) - case Block((anonFun: DefDef) :: Nil, closure: Closure) => BetaReduce(tree)(anonFun, args, true) + case Block((anonFun: DefDef) :: Nil, closure: Closure) => BetaReduce(anonFun, args) case _ => tree object BetaReduce: import ast.tpd._ /** Beta-reduces a call to `ddef` with arguments `argSyms` */ - def apply(tree: Tree)(ddef: DefDef, args: List[Tree], noBindings: Boolean)(using ctx: Context) = + def apply(ddef: DefDef, args: List[Tree])(using ctx: Context) = val bindings = List.newBuilder[ValDef] val vparams = ddef.vparamss.iterator.flatten.toList + assert(args.hasSameLengthAs(vparams)) val argSyms = for (arg, param) <- args.zip(vparams) yield arg.tpe.dealias match case ref @ TermRef(NoPrefix, _) if isPurePath(arg) => ref.symbol case _ => - if noBindings then // TODO always generate bindings - NoSymbol - else - val binding = SyntheticValDef(param.name, arg) - bindings += binding - binding.symbol + val flags = Synthetic | (param.symbol.flags & Erased) + val binding = ValDef(ctx.newSymbol(ctx.owner, param.name, flags, arg.tpe.widen, coord = arg.span), arg) + bindings += binding + binding.symbol - if argSyms.forall(_.exists) && argSyms.hasSameLengthAs(vparams) then // TODO assert rather than fail silently - seq( - bindings.result(), - TreeTypeMap( - oldOwners = ddef.symbol :: Nil, - newOwners = ctx.owner :: Nil, - substFrom = vparams.map(_.symbol), - substTo = argSyms - ).transform(ddef.rhs) - ) - else - tree + val expansion = TreeTypeMap( + oldOwners = ddef.symbol :: Nil, + newOwners = ctx.owner :: Nil, + substFrom = vparams.map(_.symbol), + substTo = argSyms + ).transform(ddef.rhs) + + seq(bindings.result(), expansion) + end apply diff --git a/compiler/src/dotty/tools/dotc/transform/InlinePatterns.scala b/compiler/src/dotty/tools/dotc/transform/InlinePatterns.scala index 2eb60f074ad5..c1e11be46a6c 100644 --- a/compiler/src/dotty/tools/dotc/transform/InlinePatterns.scala +++ b/compiler/src/dotty/tools/dotc/transform/InlinePatterns.scala @@ -56,6 +56,6 @@ class InlinePatterns extends MiniPhase: fn match case Block(TypeDef(_, template: Template) :: Nil, Apply(Select(New(_),_), Nil)) if template.constr.rhs.isEmpty => template.body match - case List(ddef @ DefDef(`name`, _, _, _, _)) => BetaReduce(tree)(ddef, args, false) + case List(ddef @ DefDef(`name`, _, _, _, _)) => BetaReduce(ddef, args) case _ => tree case _ => tree