From adf40fd366b7c791dda32ff33b04a6ebf2bf05d0 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 18 Jan 2018 18:13:29 +0100 Subject: [PATCH 01/12] Explain type lifting Based on feedback with the early implementation, we found out that we need type lifting in the framework. This commit explains what needs to be done in the meta programming doc. --- .../reference/symmetric-meta-programming.md | 70 ++++++++++++++++--- 1 file changed, 60 insertions(+), 10 deletions(-) diff --git a/docs/docs/reference/symmetric-meta-programming.md b/docs/docs/reference/symmetric-meta-programming.md index 95141f1a0ce1..26daacb421ed 100644 --- a/docs/docs/reference/symmetric-meta-programming.md +++ b/docs/docs/reference/symmetric-meta-programming.md @@ -72,13 +72,18 @@ The two types can be defined in package `scala.quoted` as follows: package scala.quoted - abstract class Expr[T] { + sealed abstract class Expr[T] { def unary_~: T // splice operation } - class Type[T] { + sealed abstract class Type[T] { type unary_~ = T // splice type } +Both `Expr` and `Type` are abstract and sealed, so all constructors for +these types are provided by the system. One way to construct values of +these types is by quoting, the other is by type-specific lifting +operations that will be discussed later on. + ### The Phase Consistency Principle A fundamental *phase consistency principle* (PCP) regulates accesses @@ -163,14 +168,57 @@ quote but no splice between the parameter binding of `T` and its usage. But the code can be made phase correct by adding a binding of a `Type[T]` tag: - def reflect[T, U](f: Expr[T] => Expr[U]): Expr[T => U] = { - val Ttag = new Type[T] - ’{ (x: ~Ttag) => ~f(’(x)) - } + def reflect[T, U](f: Expr[T] => Expr[U])(implicit t: Type[T]): Expr[T => U] = + ’{ (x: ~t) => ~f(’(x)) + +In this version of `reflect`, the type of `x` is now the result of +splicing the `Type` value `t`. This operation _is_ splice correct -- there +is one quote and one splice between the use of `t` and its definition. + +To avoid clutter, the Scala implementation tries to convert any phase-incorrect +reference to a type `T` to a type-splice, by rewriting `T` to `~implicitly[Type[T]]`. +For instance, the user-level definition of `reflect` + + def reflect[T: Type, U](f: Expr[T] => Expr[U]) Expr[T => U] = + ’{ (x: T) => ~f(’(x)) } + +would be rewritten to + + def reflect[T: Type, U](f: Expr[T] => Expr[U]) Expr[T => U] = + ’{ (x: ~implicitly[Type[T]]) => ~f(’(x)) } + +The `implicitly` query succeeds because there is an implicit value of +type `Type[T]` available (namely the evidence parameter corresponding +to the context bound `: Type`), and the reference to that value is +phase-correct. If that was not the case, the phase inconsistency for +`T` would be reported as an error. + +### Lifting Types + +The previous section has shown that the metaprogramming framework has +to be able to take a type `T` and convert it to a type tree of type +`Type[T]` that can be reified. This means that all free variables of +the type tree refer to types and values defined in the current stage. + +For a reference to a global class, this is easy: Just issue the fully +qualified name of the class. Members of reifiable types are handled by +just reifying the containing type together with the member name. But +what to do for references to type parameters or local type definitions +that are not defined in the current stage? Here, we cannot construct +the `Type[T]` tree directly, so we need to get it from a recursive +implicit search. For instance, to implemenent + + implicitly[Type[List[T]]] + +where `T` is not defined in the current stage, we construct the type constructor +of `List` applied to the splice of the result of searching for an implicit `Type[T]`: + + '[List[~implicitly[Type[T]]]] -To avoid clutter, the Scala implementation will add these tags -automatically in the case of a PCP violation involving types. As a consequence, -types can be effectively ignored for phase consistency checking. +This is in exactly the algorithm that Scala 2 uses to search for type tags. +In fact Scala 2's type tag feature can be understood as a more ad-hoc version of +`quoted.Type`. As was the case for type tags, the implicit search for a `quoted.Type` +is handled by the compiler, using the algorithm sketched above. ### Example Expansion @@ -529,7 +577,9 @@ a `List` is liftable if its element type is: In the end, `Liftable` resembles very much a serialization framework. Like the latter it can be derived systematically for all -collections, case classes and enums. +collections, case classes and enums. Note also that the implicit synthesis +of "type-tag" values of type `Type[T]` is essentially the type-level +analogue of lifting. Using lifting, we can now give the missing definition of `showExpr` in the introductory example: From 6a60743fc7a0f4e5f433819b7d71a97b5a5c2835 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 18 Jan 2018 18:21:20 +0100 Subject: [PATCH 02/12] Revert [Symmetric -> Principled] meta programming It's not so easy to see what is symmetric about it. "Principled" is bolder, but why not, since we do introduce a new principle? --- docs/docs/reference/simple-smp.md | 2 +- docs/docs/reference/symmetric-meta-programming.md | 14 +++++++------- docs/sidebar.yml | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/docs/reference/simple-smp.md b/docs/docs/reference/simple-smp.md index 733810d4cd19..bc7b4b68c617 100644 --- a/docs/docs/reference/simple-smp.md +++ b/docs/docs/reference/simple-smp.md @@ -8,7 +8,7 @@ title: "The Meta-theory of Symmetric Meta-programming" 23.12.2017 This note presents a simplified variant of -[symmetric meta-programming](./symmetric-meta-programming.md) +[principled meta-programming](./principled-meta-programming.md) and sketches its soundness proof. The variant treats only dialogues between two stages. A program can have quotes which can contain splices (which can contain quotes, which can contain splices, and so diff --git a/docs/docs/reference/symmetric-meta-programming.md b/docs/docs/reference/symmetric-meta-programming.md index 26daacb421ed..d682629038ab 100644 --- a/docs/docs/reference/symmetric-meta-programming.md +++ b/docs/docs/reference/symmetric-meta-programming.md @@ -1,11 +1,11 @@ --- layout: doc-page -title: "Symmetric Meta Programming" +title: "Principled Meta Programming" --- -# Symmetric Meta Programming +# Principled Meta Programming -Symmetric meta programming is a new framework for staging and for some +Principled meta programming is a new framework for staging and for some forms of macros. It is expressed as strongly and statically typed code using two fundamental operations: quotations and splicing. A novel aspect of the approach is that these two operations are @@ -16,7 +16,7 @@ splices in exactly the same way. ### Quotes and Splices -Symmetric meta programming is built on two well-known fundamental +Principled meta programming is built on two well-known fundamental operations: quotation and splicing. Quotation is expressed as `'(...)` or `'{...}` for expressions (both forms are equivalent) and as `'[...]` for types. Splicing is expressed as a prefix `~` operator. @@ -113,13 +113,13 @@ create nor remove quotes or splices individually. So the PCP ensures that program elaboration will lead to neither of the two unwanted situations described above. -In what concerns the range of features it covers, symmetric meta programming is +In what concerns the range of features it covers, principled meta programming is quite close to the MetaML family of languages. One difference is that MetaML does not have an equivalent of the PCP - quoted code in MetaML _can_ access variables in its immediately enclosing environment, with some restrictions and caveats since such accesses involve serialization. However, this does not constitute a fundamental gain in -expressiveness. Symmetric meta programming allows to define a `Liftable` +expressiveness. Principled meta programming allows to define a `Liftable` type-class which can implement such accesses within the confines of the PCP. This is explained further in a later section. @@ -307,7 +307,7 @@ Here’s an application of `map` and how it rewrites to optimized code: ### Relationship with Inline and Macros -Seen by itself, symmetric meta-programming looks more like a +Seen by itself, principled meta-programming looks more like a framework for staging than one for compile-time meta programming with macros. But combined with Dotty’s `inline` it can be turned into a compile-time system. The idea is that macro elaboration can be diff --git a/docs/sidebar.yml b/docs/sidebar.yml index baca398e3e89..caa4d3c5b921 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -47,8 +47,8 @@ sidebar: url: docs/reference/trait-parameters.html - title: Inline url: docs/reference/inline.html - - title: Symmetric Meta Programming - url: docs/reference/symmetric-meta-programming.html + - title: Meta Programming + url: docs/reference/principled-meta-programming.html - title: By-Name Implicits url: docs/reference/implicit-by-name-parameters.html - title: Auto Parameter Tupling From 5125c8ed42bccb807fec6b34a092a417fb079d78 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 18 Jan 2018 19:14:13 +0100 Subject: [PATCH 03/12] Add quoted.Type.apply This is intended as a stub. Calls to this method will be replaced by implicit type tag searches. Also, rework definition of quote and splice methods. This could also have contributed in the slowdown we were seeing in #3721. --- .../src/dotty/tools/dotc/core/Definitions.scala | 14 ++++++++++---- .../src/dotty/tools/dotc/core/Denotations.scala | 3 +++ .../dotty/tools/dotc/transform/ReifyQuotes.scala | 2 +- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index c389742a62e9..103a6275a2a6 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -615,14 +615,20 @@ class Definitions { lazy val QuotedExprType = ctx.requiredClassRef("scala.quoted.Expr") def QuotedExprClass(implicit ctx: Context) = QuotedExprType.symbol.asClass - def QuotedExpr_~(implicit ctx: Context) = QuotedExprClass.requiredMethod(nme.UNARY_~) - def QuotedExpr_run(implicit ctx: Context) = QuotedExprClass.requiredMethod(nme.run) + lazy val QuotedExpr_spliceR = QuotedExprClass.requiredMethod(nme.UNARY_~) + def QuotedExpr_~(implicit ctx: Context) = QuotedExpr_spliceR.symbol + lazy val QuotedExpr_runR = QuotedExprClass.requiredMethodRef(nme.run) + def QuotedExpr_run(implicit ctx: Context) = QuotedExpr_runR.symbol lazy val QuotedTypeType = ctx.requiredClassRef("scala.quoted.Type") def QuotedTypeClass(implicit ctx: Context) = QuotedTypeType.symbol.asClass - def QuotedType_~(implicit ctx: Context) = - QuotedTypeClass.info.member(tpnme.UNARY_~).symbol.asType + lazy val QuotedType_spliceR = QuotedTypeClass.requiredType(tpnme.UNARY_~).typeRef + def QuotedType_~ = QuotedType_spliceR.symbol + + lazy val QuotedTypeModule = QuotedTypeClass.companionModule + lazy val QuotedType_applyR = QuotedTypeModule.requiredMethodRef(nme.apply) + def QuotedType_apply(implicit ctx: Context) = QuotedType_applyR.symbol def Unpickler_unpickleExpr = ctx.requiredMethod("scala.runtime.quoted.Unpickler.unpickleExpr") def Unpickler_unpickleType = ctx.requiredMethod("scala.runtime.quoted.Unpickler.unpickleType") diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index 4614f5e325ca..70517a5ec7ad 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -313,6 +313,9 @@ object Denotations { def requiredClass(name: PreName)(implicit ctx: Context): ClassSymbol = info.member(name.toTypeName).requiredSymbol(_.isClass).asClass + def requiredType(name: PreName)(implicit ctx: Context): TypeSymbol = + info.member(name.toTypeName).requiredSymbol(_.isType).asType + /** The alternative of this denotation that has a type matching `targetType` when seen * as a member of type `site`, `NoDenotation` if none exists. */ diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index 8b02aa8e63fb..0694ac0883f5 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -22,7 +22,7 @@ import dotty.tools.dotc.core.quoted._ /** Translates quoted terms and types to `unpickle` method calls. * Checks that the phase consistency principle (PCP) holds. */ -class ReifyQuotes extends MacroTransform { +class ReifyQuotes extends MacroTransformWithImplicits { import ast.tpd._ override def phaseName: String = "reifyQuotes" From de2d33c3b5e78983179502b50c74e1d5897c46f9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 21 Jan 2018 12:19:40 +0100 Subject: [PATCH 04/12] Lift only implicitly given Types We need to come back to the earlier scheme that a lifted type needs to refer to an implicit type tag. --- .../tools/dotc/transform/ReifyQuotes.scala | 59 ++++++++++++------- tests/neg/quote-0.scala | 6 +- tests/pos/quote-1.scala | 3 +- tests/pos/quote-liftable.scala | 2 +- 4 files changed, 47 insertions(+), 23 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index 0694ac0883f5..959c9845f30b 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -13,6 +13,7 @@ import tasty.TreePickler.Hole import MegaPhase.MiniPhase import SymUtils._ import NameKinds.OuterSelectName +import typer.Implicits.SearchFailureType import scala.collection.mutable import dotty.tools.dotc.core.StdNames._ @@ -101,7 +102,7 @@ class ReifyQuotes extends MacroTransformWithImplicits { * @param level the current level, where quotes add one and splices subtract one level * @param levels a stacked map from symbols to the levels in which they were defined */ - private class Reifier(inQuote: Boolean, val outer: Reifier, val level: Int, levels: LevelInfo) extends Transformer { + private class Reifier(inQuote: Boolean, val outer: Reifier, val level: Int, levels: LevelInfo) extends ImplicitsTransformer { import levels._ /** A nested reifier for a quote (if `isQuote = true`) or a splice (if not) */ @@ -114,12 +115,12 @@ class ReifyQuotes extends MacroTransformWithImplicits { /** A list of embedded quotes (if `inSplice = true`) or splices (if `inQuote = true`) */ val embedded = new mutable.ListBuffer[Tree] - /** A map from type ref T to "expression of type `quoted.Type[T]`". + /** A map from type ref T to expressions of type `quoted.Type[T]`". * These will be turned into splices using `addTags` */ - val importedTypes = new mutable.LinkedHashSet[TypeRef]() + val importedTags = new mutable.LinkedHashMap[TypeRef, Tree]() - /** Assuming typeTagOfRef = `Type1 -> tag1, ..., TypeN -> tagN`, the expression + /** Assuming importedTags = `Type1 -> tag1, ..., TypeN -> tagN`, the expression * * { type = .unary_~ * ... @@ -127,16 +128,15 @@ class ReifyQuotes extends MacroTransformWithImplicits { * * } * - * where all references to `TypeI` in `expr` are rewired to point to the locally + * references to `TypeI` in `expr` are rewired to point to the locally * defined versions. As a side effect, prepend the expressions `tag1, ..., `tagN` - * as splices to `buf`. + * as splices to `embedded`. */ def addTags(expr: Tree)(implicit ctx: Context): Tree = - if (importedTypes.isEmpty) expr + if (importedTags.isEmpty) expr else { - val trefs = importedTypes.toList - val typeDefs = for (tref <- trefs) yield { - val tag = New(defn.QuotedTypeType.appliedTo(tref), Nil) // FIXME: should be an implicitly inferred defn.QuotedTypeType.appliedTo(tref) + val itags = importedTags.toList + val typeDefs = for ((tref, tag) <- itags) yield { val rhs = transform(tag.select(tpnme.UNARY_~)) val alias = ctx.typeAssigner.assignType(untpd.TypeBoundsTree(rhs, rhs), rhs, rhs) val original = tref.symbol.asType @@ -146,9 +146,9 @@ class ReifyQuotes extends MacroTransformWithImplicits { info = TypeAlias(tag.tpe.select(tpnme.UNARY_~))) ctx.typeAssigner.assignType(untpd.TypeDef(original.name, alias), local) } - importedTypes.clear() + importedTags.clear() Block(typeDefs, - new SubstMap(substFrom = trefs.map(_.symbol), substTo = typeDefs.map(_.symbol)) + new SubstMap(substFrom = itags.map(_._1.symbol), substTo = typeDefs.map(_.symbol)) .apply(expr)) } @@ -180,6 +180,29 @@ class ReifyQuotes extends MacroTransformWithImplicits { def spliceOutsideQuotes(pos: Position)(implicit ctx: Context) = ctx.error(i"splice outside quotes", pos) + /** Try to heal phase-inconsistent reference to type `T` using a local type definition. + * @return None if successful + * @return Some(msg) if unsuccessful where `msg` is a potentially empty error message + * to be added to the "inconsistent phase" message. + */ + def tryHeal(tp: Type, pos: Position)(implicit ctx: Context): Option[String] = tp match { + case tp: TypeRef => + val reqType = defn.QuotedTypeType.appliedTo(tp) + val tag = ctx.typer.inferImplicitArg(reqType, pos) + tag.tpe match { + case fail: SearchFailureType => + Some(i""" + | + | The access would be accepted with the right type tag, but + | ${ctx.typer.missingArgMsg(tag, reqType, "")}""") + case _ => + importedTags(tp) = nested(isQuote = false).transform(tag) + None + } + case _ => + Some("") + } + /** Check reference to `sym` for phase consistency, where `tp` is the underlying type * by which we refer to `sym`. */ @@ -192,14 +215,10 @@ class ReifyQuotes extends MacroTransformWithImplicits { if (!isThis && sym.maybeOwner.isType) check(sym.owner, sym.owner.thisType, pos) else if (sym.exists && !sym.isStaticOwner && !levelOK(sym)) - tp match { - case tp: TypeRef => - importedTypes += tp - case _ => - ctx.error(em"""access to $symStr from wrong staging level: - | - the definition is at level ${levelOf(sym)}, - | - but the access is at level $level.""", pos) - } + for (errMsg <- tryHeal(tp, pos)) + ctx.error(em"""access to $symStr from wrong staging level: + | - the definition is at level ${levelOf(sym)}, + | - but the access is at level $level.$errMsg""", pos) } /** Check all named types and this-types in a given type for phase consistency. */ diff --git a/tests/neg/quote-0.scala b/tests/neg/quote-0.scala index 37e2fe02c141..6ee4c560c1a4 100644 --- a/tests/neg/quote-0.scala +++ b/tests/neg/quote-0.scala @@ -15,7 +15,11 @@ class Test { '((y: Expr[Int]) => ~y ) // error: wrong staging level def f[T](t: Type[T], x: Expr[T]) = '{ - val z2 = ~x // OK + val z2 = ~x // error: wrong staging level + } + + def g[T](implicit t: Type[T], x: Expr[T]) = '{ + val z2 = ~x // ok } } diff --git a/tests/pos/quote-1.scala b/tests/pos/quote-1.scala index 8d2a3ee243a8..669a0037b6bf 100644 --- a/tests/pos/quote-1.scala +++ b/tests/pos/quote-1.scala @@ -2,7 +2,7 @@ import scala.quoted._ object Test { - def f[T](x: Expr[T])(t: Type[T]) = '{ + def f[T](x: Expr[T])(implicit t: Type[T]) = '{ val y: t.unary_~ = x.unary_~ val z = ~x } @@ -13,3 +13,4 @@ object Test { def g(es: Expr[String], t: Type[String]) = f('{ (~es + "!") :: Nil })('[List[~t]]) } + diff --git a/tests/pos/quote-liftable.scala b/tests/pos/quote-liftable.scala index d3dd2ff57bf5..0388a1da6432 100644 --- a/tests/pos/quote-liftable.scala +++ b/tests/pos/quote-liftable.scala @@ -17,7 +17,7 @@ object Test { if (b) '(true) else '(false) } - implicit def ListIsLiftable[T: Liftable]: Liftable[List[T]] = new { + implicit def ListIsLiftable[T: Liftable: Type]: Liftable[List[T]] = new { def toExpr(xs: List[T]): Expr[List[T]] = xs match { case x :: xs1 => '{ ~implicitly[Liftable[T]].toExpr(x) :: ~toExpr(xs1) } case Nil => '(Nil: List[T]) From 2f71763a7d287c1cb218b368419d3ca8511e7c58 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 21 Jan 2018 12:30:51 +0100 Subject: [PATCH 05/12] Main sharing of TypeBoundsTree bounds through (un)pickling --- compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala | 2 +- compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala | 5 ++++- compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala | 4 +++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala index d0b5f03dc7b6..ded5386b2446 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala @@ -102,7 +102,7 @@ Standard-Section: "ASTs" TopLevelStat* REFINEDtpt Length underlying_Term refinement_Stat* APPLIEDtpt Length tycon_Term arg_Term* POLYtpt Length TypeParam* body_Term - TYPEBOUNDStpt Length low_Term high_Term + TYPEBOUNDStpt Length low_Term high_Term? ANNOTATEDtpt Length underlying_Term fullAnnotation_Term ANDtpt Length left_Term right_Term ORtpt Length left_Term right_Term diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index da299fc61bf3..374d3fd4ddfb 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -552,7 +552,10 @@ class TreePickler(pickler: TastyPickler) { withLength { pickleParams(tparams); pickleTree(body) } case TypeBoundsTree(lo, hi) => writeByte(TYPEBOUNDStpt) - withLength { pickleTree(lo); pickleTree(hi) } + withLength { + pickleTree(lo); + if (hi ne lo) pickleTree(hi) + } case Hole(idx, args) => writeByte(HOLE) withLength { diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 38ffe3963f05..f892837288b7 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -1038,7 +1038,9 @@ class TreeUnpickler(reader: TastyReader, val body = readTpt() LambdaTypeTree(tparams, body) case TYPEBOUNDStpt => - TypeBoundsTree(readTpt(), readTpt()) + val lo = readTpt() + val hi = if (currentAddr == end) lo else readTpt() + TypeBoundsTree(lo, hi) case HOLE => readHole(end) case _ => From 17e579f9092f26fcae9f38ecac70141d6fb78152 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 21 Jan 2018 12:31:01 +0100 Subject: [PATCH 06/12] Fix test --- tests/run-with-compiler/i3823-c.check | 7 +++++-- tests/run-with-compiler/i3823-c.scala | 7 +++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/run-with-compiler/i3823-c.check b/tests/run-with-compiler/i3823-c.check index ff7845fd9de5..7f63de0f0622 100644 --- a/tests/run-with-compiler/i3823-c.check +++ b/tests/run-with-compiler/i3823-c.check @@ -1,4 +1,7 @@ { - val z: Int = 2 - () + type T = Int + { + val z: T = 2 + () + } } diff --git a/tests/run-with-compiler/i3823-c.scala b/tests/run-with-compiler/i3823-c.scala index 0c288b091b9f..a6162b49439b 100644 --- a/tests/run-with-compiler/i3823-c.scala +++ b/tests/run-with-compiler/i3823-c.scala @@ -5,8 +5,7 @@ object Test { def f[T](x: Expr[T])(implicit t: Type[T]) = '{ val z = ~x } - // FIXME uncomment next line - // println(f('(2))(Type.IntTag).show) - println("{\n val z: Int = 2\n ()\n}") // TODO remove line + println(f('(2))(Type.IntTag).show) } -} \ No newline at end of file +} + From 365393337cb59881f589a2272db854fd145f2689 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 21 Jan 2018 17:10:41 +0100 Subject: [PATCH 07/12] Add type tag support Not sure this is the right way. It feels a bit roundabout to take the detour from types to type trees. But I am not sure we can correctly quote types without trees. --- .../dotty/tools/dotc/typer/Implicits.scala | 81 ++++++++++++++++++- tests/pos/typetags.scala | 11 +++ 2 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 tests/pos/typetags.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index c49331f43883..885c7504d9aa 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -553,9 +553,7 @@ trait Implicits { self: Typer => } /** Find an implicit argument for parameter `formal`. - * @param error An error handler that gets an error message parameter - * which is itself parameterized by another string, - * indicating where the implicit parameter is needed + * Return a failure as a SearchFailureType in the type of the returned tree. */ def inferImplicitArg(formal: Type, pos: Position)(implicit ctx: Context): Tree = { @@ -582,6 +580,81 @@ trait Implicits { self: Typer => EmptyTree } + def synthesizedTypeTag(tpe: Type)(implicit ctx: Context): Tree = { + var ok = true + + def toParamSyms(tpe: LambdaType): List[Symbol { type ThisName = tpe.ThisName }] = + (tpe.paramNames, tpe.paramInfos).zipped.map((name, bounds) => + ctx.newSymbol(ctx.owner, name, Param, bounds)) + + def toTreeAndDefs(tpe: Type): (List[TypeSymbol], List[List[TermSymbol]], Tree) = tpe match { + case tpe: TypeLambda => + val tsyms = toParamSyms(tpe) + val (Nil, vsymss, result) = toTreeAndDefs(tpe.resultType.substParams(tpe, tsyms.map(_.typeRef))) + (tsyms, vsymss, result) + case tpe: TermLambda => + val vsyms = toParamSyms(tpe) + val (Nil, vsymss, result) = toTreeAndDefs(tpe.resultType.substParams(tpe, vsyms.map(_.termRef))) + (Nil, vsyms :: vsymss, result) + case _ => + (Nil, Nil, toTree(tpe)) + } + + def refinedToTree(tpe: Type, refinements: List[Tree], refineCls: ClassSymbol): Tree = tpe.stripTypeVar match { + case RefinedType(parent, rname, rinfo) => + val isMethod = rinfo.isInstanceOf[MethodOrPoly] + val sym = ctx.newSymbol(refineCls, rname, if (isMethod) Method else EmptyFlags, rinfo) + val refinement = rname match { + case rname: TypeName => + TypeDef(sym.asType) + case rname: TermName => + if (isMethod) { + val (tparams, vparamss, resTpt) = toTreeAndDefs(rinfo.asInstanceOf[MethodOrPoly]) + DefDef(sym.asTerm, tparams, vparamss, resTpt.tpe, EmptyTree) + } + else ValDef(sym.asTerm) + } + refinedToTree(parent, refinement :: refinements, refineCls) + case _ => + RefinedTypeTree(toTree(tpe), refinements, refineCls) + } + + def toTree(tpe: Type): Tree = tpe.stripTypeVar match { + case tpe @ TypeRef(NoPrefix, _) => + val tag = inferImplicitArg(defn.QuotedTypeType.appliedTo(tpe), pos) + ok &= !tag.tpe.isInstanceOf[SearchFailureType] + tag.select(defn.QuotedType_~) + case tpe: NamedType => + ref(tpe) + case tpe: SingletonType => + singleton(tpe) + case AppliedType(tycon, args) => + AppliedTypeTree(toTree(tycon), args.map(toTree)) + case AndType(l, r) => + AndTypeTree(toTree(l), toTree(r)) + case OrType(l, r) => + OrTypeTree(toTree(l), toTree(r)) + case tp: HKTypeLambda => + val tsyms = toParamSyms(tp) + toTree(tp.resType.substParams(tp, tsyms.map(_.typeRef))) + case tpe: RecType => + refinedToTree(tpe.parent, Nil, ctx.newRefinedClassSymbol()) + case tpe: RefinedType => + refinedToTree(tpe, Nil, ctx.newRefinedClassSymbol()) + case TypeAlias(alias) => + val aliasTree = toTree(alias) + TypeBoundsTree(aliasTree, aliasTree) + case TypeBounds(lo, hi) => + TypeBoundsTree(toTree(lo), toTree(hi)) + case _ => + EmptyTree + } + + val tag = toTree(tpe) + if (ok) ref(defn.typeQuoteMethod).appliedToTypeTrees(tag :: Nil) + else EmptyTree + } + /** If `formal` is of the form Eq[T, U], where no `Eq` instance exists for * either `T` or `U`, synthesize `Eq.eqAny[T, U]` as solution. */ @@ -644,6 +717,8 @@ trait Implicits { self: Typer => tree else if (formalValue.isRef(defn.ClassTagClass)) synthesizedClassTag(formalValue).orElse(tree) + else if (formalValue.isRef(defn.QuotedTypeClass)) + synthesizedTypeTag(formalValue).orElse(tree) else if (formalValue.isRef(defn.EqClass)) synthesizedEq(formalValue).orElse(tree) else diff --git a/tests/pos/typetags.scala b/tests/pos/typetags.scala new file mode 100644 index 000000000000..509ccb6087d0 --- /dev/null +++ b/tests/pos/typetags.scala @@ -0,0 +1,11 @@ +import scala.quoted._ + +object Test { + + def f[T: Type] = { + implicitly[Type[Int]] + implicitly[Type[List[Int]]] + implicitly[Type[T]] + implicitly[Type[List[T]]] + } +} From f7bec70b8b33f79178db371f648da5ecbe402e79 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 21 Jan 2018 18:30:20 +0100 Subject: [PATCH 08/12] Rename doc file --- ...mmetric-meta-programming.md => principled-meta-programming.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/docs/reference/{symmetric-meta-programming.md => principled-meta-programming.md} (100%) diff --git a/docs/docs/reference/symmetric-meta-programming.md b/docs/docs/reference/principled-meta-programming.md similarity index 100% rename from docs/docs/reference/symmetric-meta-programming.md rename to docs/docs/reference/principled-meta-programming.md From e8df9a00a6c9740f2b602f8f9a86361da01f67bb Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 22 Jan 2018 00:48:06 +0100 Subject: [PATCH 09/12] Avoid SO in typetag search --- .../dotty/tools/dotc/typer/Implicits.scala | 184 +++++++++--------- 1 file changed, 95 insertions(+), 89 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 885c7504d9aa..4e6495d1099d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -560,99 +560,105 @@ trait Implicits { self: Typer => /** If `formal` is of the form ClassTag[T], where `T` is a class type, * synthesize a class tag for `T`. */ - def synthesizedClassTag(formal: Type)(implicit ctx: Context): Tree = - formal.argInfos match { - case arg :: Nil => - fullyDefinedType(arg, "ClassTag argument", pos) match { - case defn.ArrayOf(elemTp) => - val etag = inferImplicitArg(defn.ClassTagType.appliedTo(elemTp), pos) - if (etag.tpe.isError) EmptyTree else etag.select(nme.wrap) - case tp if hasStableErasure(tp) && !defn.isBottomClass(tp.typeSymbol) => - ref(defn.ClassTagModule) - .select(nme.apply) - .appliedToType(tp) - .appliedTo(clsOf(erasure(tp))) - .withPos(pos) - case tp => - EmptyTree - } - case _ => - EmptyTree - } + def synthesizedClassTag(formal: Type): Tree = formal.argInfos match { + case arg :: Nil => + fullyDefinedType(arg, "ClassTag argument", pos) match { + case defn.ArrayOf(elemTp) => + val etag = inferImplicitArg(defn.ClassTagType.appliedTo(elemTp), pos) + if (etag.tpe.isError) EmptyTree else etag.select(nme.wrap) + case tp if hasStableErasure(tp) && !defn.isBottomClass(tp.typeSymbol) => + ref(defn.ClassTagModule) + .select(nme.apply) + .appliedToType(tp) + .appliedTo(clsOf(erasure(tp))) + .withPos(pos) + case tp => + EmptyTree + } + case _ => + EmptyTree + } - def synthesizedTypeTag(tpe: Type)(implicit ctx: Context): Tree = { - var ok = true - - def toParamSyms(tpe: LambdaType): List[Symbol { type ThisName = tpe.ThisName }] = - (tpe.paramNames, tpe.paramInfos).zipped.map((name, bounds) => - ctx.newSymbol(ctx.owner, name, Param, bounds)) - - def toTreeAndDefs(tpe: Type): (List[TypeSymbol], List[List[TermSymbol]], Tree) = tpe match { - case tpe: TypeLambda => - val tsyms = toParamSyms(tpe) - val (Nil, vsymss, result) = toTreeAndDefs(tpe.resultType.substParams(tpe, tsyms.map(_.typeRef))) - (tsyms, vsymss, result) - case tpe: TermLambda => - val vsyms = toParamSyms(tpe) - val (Nil, vsymss, result) = toTreeAndDefs(tpe.resultType.substParams(tpe, vsyms.map(_.termRef))) - (Nil, vsyms :: vsymss, result) - case _ => - (Nil, Nil, toTree(tpe)) - } + def synthesizedTypeTag(formal: Type): Tree = formal.argInfos match { + case tpe :: Nil => + var ok = true + + def toParamSyms(tpe: LambdaType): List[Symbol { type ThisName = tpe.ThisName }] = + (tpe.paramNames, tpe.paramInfos).zipped.map((name, bounds) => + ctx.newSymbol(ctx.owner, name, Param, bounds)) + + def toTreeAndDefs(tpe: Type): (List[TypeSymbol], List[List[TermSymbol]], Tree) = tpe match { + case tpe: TypeLambda => + val tsyms = toParamSyms(tpe) + val (Nil, vsymss, result) = toTreeAndDefs(tpe.resultType.substParams(tpe, tsyms.map(_.typeRef))) + (tsyms, vsymss, result) + case tpe: TermLambda => + val vsyms = toParamSyms(tpe) + val (Nil, vsymss, result) = toTreeAndDefs(tpe.resultType.substParams(tpe, vsyms.map(_.termRef))) + (Nil, vsyms :: vsymss, result) + case _ => + (Nil, Nil, toTree(tpe)) + } - def refinedToTree(tpe: Type, refinements: List[Tree], refineCls: ClassSymbol): Tree = tpe.stripTypeVar match { - case RefinedType(parent, rname, rinfo) => - val isMethod = rinfo.isInstanceOf[MethodOrPoly] - val sym = ctx.newSymbol(refineCls, rname, if (isMethod) Method else EmptyFlags, rinfo) - val refinement = rname match { - case rname: TypeName => - TypeDef(sym.asType) - case rname: TermName => - if (isMethod) { - val (tparams, vparamss, resTpt) = toTreeAndDefs(rinfo.asInstanceOf[MethodOrPoly]) - DefDef(sym.asTerm, tparams, vparamss, resTpt.tpe, EmptyTree) - } - else ValDef(sym.asTerm) - } - refinedToTree(parent, refinement :: refinements, refineCls) - case _ => - RefinedTypeTree(toTree(tpe), refinements, refineCls) - } + def refinedToTree(tpe: Type, refinements: List[Tree], refineCls: ClassSymbol): Tree = tpe.stripTypeVar match { + case RefinedType(parent, rname, rinfo) => + val isMethod = rinfo.isInstanceOf[MethodOrPoly] + val sym = ctx.newSymbol(refineCls, rname, if (isMethod) Method else EmptyFlags, rinfo) + val refinement = rname match { + case rname: TypeName => + TypeDef(sym.asType) + case rname: TermName => + if (isMethod) { + val (tparams, vparamss, resTpt) = toTreeAndDefs(rinfo.asInstanceOf[MethodOrPoly]) + DefDef(sym.asTerm, tparams, vparamss, resTpt.tpe, EmptyTree) + } + else ValDef(sym.asTerm) + } + refinedToTree(parent, refinement :: refinements, refineCls) + case _ => + RefinedTypeTree(toTree(tpe), refinements, refineCls) + } - def toTree(tpe: Type): Tree = tpe.stripTypeVar match { - case tpe @ TypeRef(NoPrefix, _) => - val tag = inferImplicitArg(defn.QuotedTypeType.appliedTo(tpe), pos) - ok &= !tag.tpe.isInstanceOf[SearchFailureType] - tag.select(defn.QuotedType_~) - case tpe: NamedType => - ref(tpe) - case tpe: SingletonType => - singleton(tpe) - case AppliedType(tycon, args) => - AppliedTypeTree(toTree(tycon), args.map(toTree)) - case AndType(l, r) => - AndTypeTree(toTree(l), toTree(r)) - case OrType(l, r) => - OrTypeTree(toTree(l), toTree(r)) - case tp: HKTypeLambda => - val tsyms = toParamSyms(tp) - toTree(tp.resType.substParams(tp, tsyms.map(_.typeRef))) - case tpe: RecType => - refinedToTree(tpe.parent, Nil, ctx.newRefinedClassSymbol()) - case tpe: RefinedType => - refinedToTree(tpe, Nil, ctx.newRefinedClassSymbol()) - case TypeAlias(alias) => - val aliasTree = toTree(alias) - TypeBoundsTree(aliasTree, aliasTree) - case TypeBounds(lo, hi) => - TypeBoundsTree(toTree(lo), toTree(hi)) - case _ => - EmptyTree - } + def toTree(tpe: Type): Tree = tpe.stripTypeVar match { + case tpe @ TypeRef(NoPrefix, _) => + inferImplicit(defn.QuotedTypeType.appliedTo(tpe), EmptyTree, pos) match { + case SearchSuccess(tag, _, _) => + tag.select(defn.QuotedType_~) + case _ => + ok = false + EmptyTree + } + case tpe: NamedType => + ref(tpe) + case tpe: SingletonType => + singleton(tpe) + case AppliedType(tycon, args) => + AppliedTypeTree(toTree(tycon), args.map(toTree)) + case AndType(l, r) => + AndTypeTree(toTree(l), toTree(r)) + case OrType(l, r) => + OrTypeTree(toTree(l), toTree(r)) + case tp: HKTypeLambda => + val tsyms = toParamSyms(tp) + toTree(tp.resType.substParams(tp, tsyms.map(_.typeRef))) + case tpe: RecType => + refinedToTree(tpe.parent, Nil, ctx.newRefinedClassSymbol()) + case tpe: RefinedType => + refinedToTree(tpe, Nil, ctx.newRefinedClassSymbol()) + case TypeAlias(alias) => + val aliasTree = toTree(alias) + TypeBoundsTree(aliasTree, aliasTree) + case TypeBounds(lo, hi) => + TypeBoundsTree(toTree(lo), toTree(hi)) + case _ => + EmptyTree + } - val tag = toTree(tpe) - if (ok) ref(defn.typeQuoteMethod).appliedToTypeTrees(tag :: Nil) - else EmptyTree + val tag = toTree(tpe) + if (ok) ref(defn.typeQuoteMethod).appliedToTypeTrees(tag :: Nil) + else EmptyTree + case _ => + EmptyTree } /** If `formal` is of the form Eq[T, U], where no `Eq` instance exists for From 02714dfe6f2cd7af07d2fa358c15c24991f0210e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 22 Jan 2018 10:54:46 +0100 Subject: [PATCH 10/12] Allow top-level splices for types --- compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index 959c9845f30b..9c496d4ddfec 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -228,7 +228,7 @@ class ReifyQuotes extends MacroTransformWithImplicits { case tp: NamedType if tp.symbol.isSplice => if (inQuote) outer.checkType(pos).foldOver(acc, tp) else { - spliceOutsideQuotes(pos) + if (tp.isTerm) spliceOutsideQuotes(pos) tp } case tp: NamedType => From 80f2c7662775d5eb1002411cdf0f27229bdadf38 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 24 Jan 2018 09:45:35 +0100 Subject: [PATCH 11/12] Simplify TypeTag generation Generate a type with embedded quotes instead of a tree. --- .../dotty/tools/dotc/typer/Implicits.scala | 91 ++++--------------- 1 file changed, 16 insertions(+), 75 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 4e6495d1099d..f167f2a45ff0 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -580,82 +580,23 @@ trait Implicits { self: Typer => } def synthesizedTypeTag(formal: Type): Tree = formal.argInfos match { - case tpe :: Nil => - var ok = true - - def toParamSyms(tpe: LambdaType): List[Symbol { type ThisName = tpe.ThisName }] = - (tpe.paramNames, tpe.paramInfos).zipped.map((name, bounds) => - ctx.newSymbol(ctx.owner, name, Param, bounds)) - - def toTreeAndDefs(tpe: Type): (List[TypeSymbol], List[List[TermSymbol]], Tree) = tpe match { - case tpe: TypeLambda => - val tsyms = toParamSyms(tpe) - val (Nil, vsymss, result) = toTreeAndDefs(tpe.resultType.substParams(tpe, tsyms.map(_.typeRef))) - (tsyms, vsymss, result) - case tpe: TermLambda => - val vsyms = toParamSyms(tpe) - val (Nil, vsymss, result) = toTreeAndDefs(tpe.resultType.substParams(tpe, vsyms.map(_.termRef))) - (Nil, vsyms :: vsymss, result) - case _ => - (Nil, Nil, toTree(tpe)) - } - - def refinedToTree(tpe: Type, refinements: List[Tree], refineCls: ClassSymbol): Tree = tpe.stripTypeVar match { - case RefinedType(parent, rname, rinfo) => - val isMethod = rinfo.isInstanceOf[MethodOrPoly] - val sym = ctx.newSymbol(refineCls, rname, if (isMethod) Method else EmptyFlags, rinfo) - val refinement = rname match { - case rname: TypeName => - TypeDef(sym.asType) - case rname: TermName => - if (isMethod) { - val (tparams, vparamss, resTpt) = toTreeAndDefs(rinfo.asInstanceOf[MethodOrPoly]) - DefDef(sym.asTerm, tparams, vparamss, resTpt.tpe, EmptyTree) - } - else ValDef(sym.asTerm) - } - refinedToTree(parent, refinement :: refinements, refineCls) - case _ => - RefinedTypeTree(toTree(tpe), refinements, refineCls) - } - - def toTree(tpe: Type): Tree = tpe.stripTypeVar match { - case tpe @ TypeRef(NoPrefix, _) => - inferImplicit(defn.QuotedTypeType.appliedTo(tpe), EmptyTree, pos) match { - case SearchSuccess(tag, _, _) => - tag.select(defn.QuotedType_~) - case _ => - ok = false - EmptyTree - } - case tpe: NamedType => - ref(tpe) - case tpe: SingletonType => - singleton(tpe) - case AppliedType(tycon, args) => - AppliedTypeTree(toTree(tycon), args.map(toTree)) - case AndType(l, r) => - AndTypeTree(toTree(l), toTree(r)) - case OrType(l, r) => - OrTypeTree(toTree(l), toTree(r)) - case tp: HKTypeLambda => - val tsyms = toParamSyms(tp) - toTree(tp.resType.substParams(tp, tsyms.map(_.typeRef))) - case tpe: RecType => - refinedToTree(tpe.parent, Nil, ctx.newRefinedClassSymbol()) - case tpe: RefinedType => - refinedToTree(tpe, Nil, ctx.newRefinedClassSymbol()) - case TypeAlias(alias) => - val aliasTree = toTree(alias) - TypeBoundsTree(aliasTree, aliasTree) - case TypeBounds(lo, hi) => - TypeBoundsTree(toTree(lo), toTree(hi)) - case _ => - EmptyTree + case arg :: Nil => + object bindFreeVars extends TypeMap { + var ok = true + def apply(t: Type) = t match { + case t @ TypeRef(NoPrefix, _) => + inferImplicit(defn.QuotedTypeType.appliedTo(t), EmptyTree, pos) match { + case SearchSuccess(tag, _, _) if tag.tpe.isStable => + tag.tpe.select(defn.QuotedType_~) + case _ => + ok = false + t + } + case _ => t + } } - - val tag = toTree(tpe) - if (ok) ref(defn.typeQuoteMethod).appliedToTypeTrees(tag :: Nil) + val tag = bindFreeVars(arg) + if (bindFreeVars.ok) ref(defn.typeQuoteMethod).appliedToType(tag) else EmptyTree case _ => EmptyTree From 58677ed1d2684d70f65ff511f16d8475c5ccdefb Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 27 Jan 2018 10:34:49 +0100 Subject: [PATCH 12/12] Fix docs --- docs/docs/reference/principled-meta-programming.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/reference/principled-meta-programming.md b/docs/docs/reference/principled-meta-programming.md index d682629038ab..1adc3f8ce89d 100644 --- a/docs/docs/reference/principled-meta-programming.md +++ b/docs/docs/reference/principled-meta-programming.md @@ -169,7 +169,7 @@ usage. But the code can be made phase correct by adding a binding of a `Type[T]` tag: def reflect[T, U](f: Expr[T] => Expr[U])(implicit t: Type[T]): Expr[T => U] = - ’{ (x: ~t) => ~f(’(x)) + ’{ (x: ~t) => ~f(’(x)) } In this version of `reflect`, the type of `x` is now the result of splicing the `Type` value `t`. This operation _is_ splice correct -- there @@ -177,7 +177,7 @@ is one quote and one splice between the use of `t` and its definition. To avoid clutter, the Scala implementation tries to convert any phase-incorrect reference to a type `T` to a type-splice, by rewriting `T` to `~implicitly[Type[T]]`. -For instance, the user-level definition of `reflect` +For instance, the user-level definition of `reflect`: def reflect[T: Type, U](f: Expr[T] => Expr[U]) Expr[T => U] = ’{ (x: T) => ~f(’(x)) }