From 9a2997c8d46ebc55399050ef7450513b86b173e7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 28 Jul 2018 16:12:15 +0200 Subject: [PATCH 1/9] Decouple transparent and erased properties It is at times useful to have `transparent` methods that are not `erased`. For instance, a transparent `unapply` might enable inline reductions, but we want to also keep it at runtime, because the same type might be matched at compile-time and at run-time. This commit decouples `transparent` and `erased` again. `transparent` does not imply `erased`. --- .../src/dotty/tools/dotc/config/Config.scala | 2 +- .../src/dotty/tools/dotc/core/Flags.scala | 7 ++- .../dotc/core/classfile/ClassfileParser.scala | 2 +- .../tools/dotc/transform/PostTyper.scala | 4 +- .../src/dotty/tools/dotc/typer/Namer.scala | 9 ---- .../dotty/tools/dotc/typer/RefChecks.scala | 6 +-- .../src/dotty/tools/dotc/typer/Typer.scala | 7 +-- docs/docs/typelevel.md | 34 ++++---------- tests/neg/implicitMatch-ambiguous.scala | 2 +- tests/neg/implicitMatch-syntax.scala | 16 ++++--- tests/neg/transparent-override/B_2.scala | 2 +- tests/neg/typelevel-noeta.scala | 2 +- tests/pos/i4773.scala | 2 +- tests/{neg => pos}/inline-i1773.scala | 2 +- tests/pos/typelevel-vector1.scala | 7 ++- tests/pos/typelevel-vector2.scala | 45 +++++++++++++++++++ tests/run/i4735/App_2.scala | 2 +- tests/run/implicitMatch.scala | 4 +- tests/run/typelevel-defaultValue.scala | 2 +- tests/run/typelevel-overrides.scala | 2 +- 20 files changed, 94 insertions(+), 65 deletions(-) rename tests/{neg => pos}/inline-i1773.scala (76%) create mode 100644 tests/pos/typelevel-vector2.scala diff --git a/compiler/src/dotty/tools/dotc/config/Config.scala b/compiler/src/dotty/tools/dotc/config/Config.scala index ea96d9dcd5d9..05aa89fab7b5 100644 --- a/compiler/src/dotty/tools/dotc/config/Config.scala +++ b/compiler/src/dotty/tools/dotc/config/Config.scala @@ -160,7 +160,7 @@ object Config { final val showCompletions = false /** If set, enables tracing */ - final val tracingEnabled = false + final val tracingEnabled = true /** Initial capacity of uniques HashMap. * Note: This MUST BE a power of two to work with util.HashSet diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index f6f949cf6e32..c86d64d7c10a 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -548,8 +548,8 @@ object Flags { /** Assumed to be pure */ final val StableOrErased = Stable | Erased - /** Labeled `private`, `final`, or `transparent` */ - final val EffectivelyFinal = Private | Final | Transparent + /** Labeled `private`, `final`, `transparent`, or `erasedd` */ + final val EffectivelyFinal = Private | Final | Transparent | Erased /** A private method */ final val PrivateMethod = allOf(Private, Method) @@ -563,6 +563,9 @@ object Flags { /** A transparent parameter */ final val TransparentParam = allOf(Transparent, Param) + /** A transparent erased method */ + final val TransparentErasedMethod = allOf(Transparent, Erased, Method) + /** An enum case */ final val EnumCase = allOf(Enum, Case) diff --git a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala index f9147b523ac5..2b5b2de5c6d1 100644 --- a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala +++ b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala @@ -761,7 +761,7 @@ class ClassfileParser( assert(cond, s"Unpickling ${classRoot.symbol.showLocated} from ${classRoot.symbol.associatedFile} is not allowed with -Yscala2-unpickler $allowed") - if (allowed != "always") { + if (false && allowed != "always") { failUnless(allowed != "never") val allowedList = allowed.split(":").toList val file = classRoot.symbol.associatedFile diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index a5b78a2c0ebf..204afcb24dfc 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -165,9 +165,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase def markAsMacro(c: Context): Unit = if (c.owner eq c.outer.owner) markAsMacro(c.outer) - else if (c.owner.isTransparentMethod) { - c.owner.setFlag(Macro) - } + else if (c.owner.isTransparentMethod) c.owner.setFlag(Macro | Erased) else if (!c.outer.owner.is(Package)) markAsMacro(c.outer) if (sym.isSplice || sym.isQuote) { diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 2933c3dc92a5..f79aeb4fec52 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1195,15 +1195,6 @@ class Namer { typer: Typer => instantiateDependent(restpe, typeParams, termParamss) ctx.methodType(tparams map symbolOfTree, termParamss, restpe, isJava = ddef.mods is JavaDefined) } - if (sym.is(Transparent) && - sym.unforcedAnnotation(defn.ForceInlineAnnot).isEmpty) - // Need to keep @forceInline annotated methods around to get to parity with Scala. - // This is necessary at least until we have full bootstrap. Right now - // dotty-bootstrapped involves running the Dotty compiler compiled with Scala 2 with - // a Dotty runtime library compiled with Dotty. If we erase @forceInline annotated - // methods, this means that the support methods in dotty.runtime.LazyVals vanish. - // But they are needed for running the lazy val implementations in the Scala-2 compiled compiler. - sym.setFlag(Erased) if (isConstructor) { // set result type tree to unit, but take the current class as result type of the symbol typedAheadType(ddef.tpt, defn.UnitType) diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index f3d16eb4fe0a..ec96612b5c19 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -143,7 +143,7 @@ object RefChecks { * 1.8.1 M's type is a subtype of O's type, or * 1.8.2 M is of type []S, O is of type ()T and S <: T, or * 1.8.3 M is of type ()S, O is of type []T and S <: T, or - * 1.9 M must not be a typelevel def or a Dotty macro def + * 1.9 M must not be an erased definition * 1.10. If M is a 2.x macro def, O cannot be deferred unless there's a concrete method overriding O. * 1.11. If M is not a macro def, O cannot be a macro def. * 2. Check that only abstract classes have deferred members @@ -376,8 +376,8 @@ object RefChecks { overrideError("may not override a non-lazy value") } else if (other.is(Lazy) && !other.isRealMethod && !member.is(Lazy)) { overrideError("must be declared lazy to override a lazy value") - } else if (member.is(Erased) && member.allOverriddenSymbols.forall(_.is(Deferred))) { // (1.9) - overrideError("is an erased method, may not override only deferred methods") + } else if (member.is(Erased)) { // (1.9) + overrideError("is an erased method, may not override anything") } else if (member.is(Macro, butNot = Scala2x)) { // (1.9) overrideError("is a macro, may not override anything") } else if (other.is(Deferred) && member.is(Scala2Macro) && member.extendedOverriddenSymbols.forall(_.is(Deferred))) { // (1.10) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 3310fb6dd8fc..9834e76a20da 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -974,8 +974,9 @@ class Typer extends Namer val unchecked = pt.isRef(defn.PartialFunctionClass) typed(desugar.makeCaseLambda(tree.cases, protoFormals.length, unchecked) withPos tree.pos, pt) case id @ untpd.ImplicitScrutinee() => - if (tree.getAttachment(PrepareTransparent.TopLevelMatch).isEmpty) - ctx.error(em"implicit match cannot be used here; it must occur as a toplevel match of a transparent method", tree.pos) + if (tree.getAttachment(PrepareTransparent.TopLevelMatch).isEmpty || + !ctx.owner.flagsUNSAFE.is(Erased)) + ctx.error(em"implicit match cannot be used here; it must occur as a toplevel match of an erased transparent method", tree.pos) val sel1 = id.withType(defn.ImplicitScrutineeTypeRef) typedMatchFinish(tree, sel1, sel1.tpe, pt) case _ => @@ -2351,7 +2352,7 @@ class Typer extends Namer // - the current tree is a synthetic apply which is not expandable (eta-expasion would simply undo that) if (arity >= 0 && !tree.symbol.isConstructor && - !tree.symbol.is(TransparentMethod) && + !tree.symbol.is(TransparentErasedMethod) && !ctx.mode.is(Mode.Pattern) && !(isSyntheticApply(tree) && !isExpandableApply)) simplify(typed(etaExpand(tree, wtp, arity), pt), pt, locked) diff --git a/docs/docs/typelevel.md b/docs/docs/typelevel.md index 8e09d9971e09..a215ec0d4140 100644 --- a/docs/docs/typelevel.md +++ b/docs/docs/typelevel.md @@ -229,34 +229,17 @@ The following rewrite rules are performed when simplifying inlined bodies: Dropping a binding might make other bindings redundant. Garbage collection proceeds until no further bindings can be dropped. -## Restrictions for Transparent and Typelevel Functions +## Restrictions for Transparent and Erased Functions Transparent methods are effectively final; they may not be overwritten. -If a transparent -method has a toplevel match expression or a toplevel splice `~` on its right-hand side, -it is classified as a typelevel method that can _only_ be executed at compile time. For typelevel methods two more restrictions apply: +Transparent methods with an implicit match expression on their right-hand side must in addition be declared `erased`. Erased transparent methods must be always fully applied. In addition, the restrictions on normal erased methods apply, including: - 1. They must be always fully applied. - 2. They may override other methods only if one of the overridden methods is concrete. + 1. They may not override other methods. + 2. They may not be referred to from a non-erased context. -The right hand side of a typelevel method is never invoked by dynamic dispatch. As an example consider a situation like the following: -```scala -class Iterable[T] { - def foreach(f: T => Unit): Unit = ... -} -class List[T] extends Iterable[T] { - override transparent def foreach(f: T => Unit): Unit = ... -} -val xs: Iterable[T] = ... -val ys: List[T] = ... -val zs: Iterable[T] = ys -xs.foreach(f) // calls Iterable's foreach -ys.foreach(f) // expands to the body of List's foreach -zs.foreach(f) // calls Iterable's foreach -``` -It follows that an overriding typelevel method should implement exactly the same semantics as the -method it overrides (but possibly more efficiently). +**Question:** We currently set `erased` automatically for macros, i.e. methods with a +right-hand side of the form `~...`. But we require it to be written explicitly for methods that have an implicit match as RHS. Should these situations be treated in the same instead? If yes, which of the two is preferable? ## Matching on Types @@ -415,7 +398,7 @@ There are some proposals to improve the situation in specific areas, for instanc By contrast, the new `implicit match` construct makes implicit search available in a functional context. To solve the problem of creating the right set, one would use it as follows: ```scala -transparent def setFor[T]: Set[T] = implicit match { +erased transparent def setFor[T]: Set[T] = implicit match { case ord: Ordering[T] => new TreeSet[T] case _ => new HashSet[T] } @@ -426,8 +409,7 @@ Patterns are tried in sequence. The first case with a pattern `x: T` such that a of type `T` can be summoned is chosen. The variable `x` is then bound to the implicit value for the remainder of the case. It can in turn be used as an implicit in the right hand side of the case. It is an error if one of the tested patterns gives rise to an ambiguous implicit search. -Implicit matches can only occur as toplevel match expressions of transparent methods. This ensures that -all implicit searches are done at compile-time. +Implicit matches can only occur as toplevel match expressions of methods that are both `erased` and `transparent`. This ensures that all implicit searches are done at compile-time. ## Transparent Values diff --git a/tests/neg/implicitMatch-ambiguous.scala b/tests/neg/implicitMatch-ambiguous.scala index c7d485b8a29b..60c83204064a 100644 --- a/tests/neg/implicitMatch-ambiguous.scala +++ b/tests/neg/implicitMatch-ambiguous.scala @@ -4,7 +4,7 @@ object Test { implicit val a1: A = new A implicit val a2: A = new A - transparent def f: Any = implicit match { + erased transparent def f: Any = implicit match { case _: A => ??? // error: ambiguous implicits } diff --git a/tests/neg/implicitMatch-syntax.scala b/tests/neg/implicitMatch-syntax.scala index 97a47f20f66b..e2e933784456 100644 --- a/tests/neg/implicitMatch-syntax.scala +++ b/tests/neg/implicitMatch-syntax.scala @@ -2,32 +2,36 @@ object Test { import collection.immutable.TreeSet import collection.immutable.HashSet - transparent def f1[T] = implicit implicit match { // error: repeated modifier // error: illegal modifier + erased transparent def f1[T] = implicit implicit match { // error: repeated modifier // error: illegal modifier case ord: Ordered[T] => new TreeSet[T] // error: no implicit case _ => new HashSet[T] } - transparent def f2[T] = implicit erased match { // error: illegal modifier + erased transparent def f2[T] = implicit erased match { // error: illegal modifier case ord: Ordered[T] => new TreeSet[T] // error: no implicit case _ => new HashSet[T] } - transparent def f3[T] = erased implicit match { // error: illegal modifier + erased transparent def f3[T] = erased implicit match { // error: illegal modifier case ord: Ordered[T] => new TreeSet[T] // error: no implicit case _ => new HashSet[T] } - transparent def f4() = implicit match { + erased transparent def f4() = implicit match { case Nil => ??? // error: not a legal pattern case x :: xs => ??? // error: not a legal pattern } - transparent def f5[T] = locally { implicit match { // error: implicit match cannot be used here + erased transparent def f5[T] = locally { implicit match { // error: implicit match cannot be used here case _ => new HashSet[T] }} - def f6[T] = implicit match { // error: implicit match cannot be used here + transparent def f6[T] = implicit match { // error: implicit match cannot be used here + case _ => new HashSet[T] + } + + erased def f7[T] = implicit match { // error: implicit match cannot be used here case _ => new HashSet[T] } } \ No newline at end of file diff --git a/tests/neg/transparent-override/B_2.scala b/tests/neg/transparent-override/B_2.scala index c4613f91e9e8..e350479d13cd 100644 --- a/tests/neg/transparent-override/B_2.scala +++ b/tests/neg/transparent-override/B_2.scala @@ -1,5 +1,5 @@ class B extends A { - transparent def f(x: Int): Int = x match { // error + erased transparent def f(x: Int): Int = x match { // error case 0 => 1 case _ => x } diff --git a/tests/neg/typelevel-noeta.scala b/tests/neg/typelevel-noeta.scala index c25cd6971b6b..86c10ceebf22 100644 --- a/tests/neg/typelevel-noeta.scala +++ b/tests/neg/typelevel-noeta.scala @@ -2,7 +2,7 @@ object Test { def anyValue[T]: T = ??? - transparent def test(x: Int) = x match { + erased transparent def test(x: Int) = x match { case _: Byte => case _: Char => } diff --git a/tests/pos/i4773.scala b/tests/pos/i4773.scala index 9ed2e098682b..26a5e1a9073c 100644 --- a/tests/pos/i4773.scala +++ b/tests/pos/i4773.scala @@ -3,5 +3,5 @@ import scala.quoted._ object Foo { transparent def foo2(): Unit = ~foo2Impl() def foo2Impl(): Expr[Unit] = '() - transparent def foo(): Unit = foo2() + erased transparent def foo(): Unit = foo2() } diff --git a/tests/neg/inline-i1773.scala b/tests/pos/inline-i1773.scala similarity index 76% rename from tests/neg/inline-i1773.scala rename to tests/pos/inline-i1773.scala index 9e377b23213f..adf601f3d69e 100644 --- a/tests/neg/inline-i1773.scala +++ b/tests/pos/inline-i1773.scala @@ -7,7 +7,7 @@ object Test { } def main(args: Array[String]): Unit = { - val q"class $name extends $parent" = new Object // error: method unapply is used + val q"class $name extends $parent" = new Object println(name) println(parent) } diff --git a/tests/pos/typelevel-vector1.scala b/tests/pos/typelevel-vector1.scala index d48dadd12331..b9d0e2c6992e 100644 --- a/tests/pos/typelevel-vector1.scala +++ b/tests/pos/typelevel-vector1.scala @@ -23,8 +23,13 @@ object Test { val z: S[S[S[S[Z]]]] = y transparent def concat[T, N1 <: Nat, N2 <: Nat](xs: Vec[T, N1], ys: Vec[T, N2]): Vec[T, _] = { - val length = Typed(add(erasedValue[N1], erasedValue[N2])) + erased val length = Typed(add(erasedValue[N1], erasedValue[N2])) Vec[T, length.Type](xs.elems ++ ys.elems) } + + val xs = Vec[Int, S[S[Z]]](List(1, 2)) + val ys = Vec[Int, S[Z]](List(3)) + val zs = concat(xs, ys) + val zsc: Vec[Int, S[S[S[Z]]]] = zs } diff --git a/tests/pos/typelevel-vector2.scala b/tests/pos/typelevel-vector2.scala new file mode 100644 index 000000000000..d40479924131 --- /dev/null +++ b/tests/pos/typelevel-vector2.scala @@ -0,0 +1,45 @@ +object typelevel { + case class Typed[T](value: T) { type Type = T } + erased def erasedValue[T]: T = ??? +} + +sealed trait Nat +case object Z extends Nat +case class S[N <: Nat](n: N) extends Nat + +object Nat { + type Z = Z.type + transparent def fromInt(n: Int): Nat = n match { + case 0 => Z + case n1 => S(fromInt(n - 1)) + } +} + +import Nat.Z + +sealed trait Vec[+A] { type Len <: Nat } +case object VNil extends Vec[Nothing] { type Len = Z } +case class VCons[+A, TL <: Vec[A]](hd: A, tl: TL) extends Vec[A] { type Len = S[tl.Len]} + +object Vec { + type VNil = VNil.type +} + +import Vec.VNil + +object Concat { + transparent def concat[A](xs: Vec[A], ys: Vec[A]): Vec[A] = + xs match { + case VNil => ys + case VCons(hd, tl) => VCons(hd, concat(tl, ys)) + } +} + +import Concat.concat + +object Test { + val v1 = VCons(1, VCons(2, VNil)) + val v2 = VCons(3, VCons(4, VCons(5, VNil))) + val v3 = concat(v1, v2) + val v3l: v3.Len = Nat.fromInt(5) +} \ No newline at end of file diff --git a/tests/run/i4735/App_2.scala b/tests/run/i4735/App_2.scala index 3f374a321031..77a1e2cf1937 100644 --- a/tests/run/i4735/App_2.scala +++ b/tests/run/i4735/App_2.scala @@ -10,5 +10,5 @@ object Test { } class Unrolled(arr: Array[Int]) extends AnyVal { - transparent def foreach(f: => Int => Unit): Unit = Macro.unrolledForeach(3, arr, f) + erased transparent def foreach(f: => Int => Unit): Unit = Macro.unrolledForeach(3, arr, f) } diff --git a/tests/run/implicitMatch.scala b/tests/run/implicitMatch.scala index c11888e8897b..54e2016b5391 100644 --- a/tests/run/implicitMatch.scala +++ b/tests/run/implicitMatch.scala @@ -2,7 +2,7 @@ object Test extends App { import collection.immutable.TreeSet import collection.immutable.HashSet - transparent def f[T]() = implicit match { + erased transparent def f[T]() = implicit match { case ord: Ordering[T] => new TreeSet[T] case _ => new HashSet[T] } @@ -11,7 +11,7 @@ object Test extends App { class B implicit val b: B = new B - transparent def g = implicit match { + erased transparent def g = implicit match { case _: A => println("A") case _: B => println("B") } diff --git a/tests/run/typelevel-defaultValue.scala b/tests/run/typelevel-defaultValue.scala index c31b363f3b18..2baf4cbcd206 100644 --- a/tests/run/typelevel-defaultValue.scala +++ b/tests/run/typelevel-defaultValue.scala @@ -5,7 +5,7 @@ object typelevel { object Test extends App { - transparent def defaultValue[T]: Option[Any] = typelevel.erasedValue[T] match { + erased transparent def defaultValue[T]: Option[Any] = typelevel.erasedValue[T] match { case _: Byte => Some(0: Byte) case c: Char => Some(0: Char) case d @ (_: Short) => Some(0: Short) diff --git a/tests/run/typelevel-overrides.scala b/tests/run/typelevel-overrides.scala index 67a9512048f5..10f44b380f3c 100644 --- a/tests/run/typelevel-overrides.scala +++ b/tests/run/typelevel-overrides.scala @@ -25,5 +25,5 @@ object Test extends App { val b: B = new B assert(b.f(0) == 0) val c: A = new C - assert(c.f(0) == 1, c.f(0)) + assert(c.f(0) == 0, c.f(0)) } \ No newline at end of file From 09cd3eeb42454db54085e2fd9d4a8dab611f28af Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 29 Jul 2018 19:15:15 +0200 Subject: [PATCH 2/9] Implement tweak In bodies of transparent unerased method we inline only transparent erased methods. --- compiler/src/dotty/tools/dotc/core/TypeOps.scala | 4 ++++ compiler/src/dotty/tools/dotc/typer/Inliner.scala | 6 +++++- tests/run/i4735/App_2.scala | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index af13b3f22ed5..565f94ec387f 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -276,6 +276,10 @@ trait TypeOps { this: Context => // TODO: Make standalone object. /** Are we in a transparent method body? */ def inTransparentMethod = owner.ownersIterator.exists(_.isTransparentMethod) + /** Are we in a transparent method body? */ + def inErasedTransparentMethod = owner.ownersIterator.exists(sym => + sym.isTransparentMethod && sym.is(Erased)) + /** Is `feature` enabled in class `owner`? * This is the case if one of the following two alternatives holds: * diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 516ded001526..6ac1d38642dc 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -58,8 +58,12 @@ object Inliner { /** Should call with method `meth` be inlined in this context? */ def isInlineable(meth: Symbol)(implicit ctx: Context): Boolean = { + /** Suppress inlining of + * - non-erased methods inside a transparent method, + * - all methods inside an erased transparent method + */ def suppressInline = - ctx.inTransparentMethod || + ctx.inTransparentMethod && (!meth.is(Erased) || ctx.inErasedTransparentMethod) || ctx.settings.YnoInline.value || ctx.isAfterTyper || ctx.reporter.hasErrors diff --git a/tests/run/i4735/App_2.scala b/tests/run/i4735/App_2.scala index 77a1e2cf1937..547f210ac07b 100644 --- a/tests/run/i4735/App_2.scala +++ b/tests/run/i4735/App_2.scala @@ -7,6 +7,7 @@ object Test { System.out.println(2*x) } } + } class Unrolled(arr: Array[Int]) extends AnyVal { From edf98e3bfb1000756ccb9f96f48e10e577a2a9a7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 29 Jul 2018 20:06:43 +0200 Subject: [PATCH 3/9] Change macro labelling - Mark a method with a toplevel splice as a macro already when we register the inline info. This is needed so that such methods are recognized as erased the first time someone wants to inline them. - Improve error message for splice outside quote. --- .../reporting/diagnostic/ErrorMessageID.java | 1 + .../dotc/reporting/diagnostic/messages.scala | 8 ++++++++ .../tools/dotc/transform/PostTyper.scala | 19 +++---------------- .../tools/dotc/transform/ReifyQuotes.scala | 6 +++++- .../tools/dotc/typer/PrepareTransparent.scala | 10 ++++++++++ tests/neg/quote-macro-splice.scala | 18 +++++++++--------- tests/pos/i4773.scala | 2 +- 7 files changed, 37 insertions(+), 27 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java b/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java index 8233e3305d2a..2ac008a64683 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java @@ -131,6 +131,7 @@ public enum ErrorMessageID { MatchCaseOnlyNullWarningID, ImportRenamedTwiceID, TypeTestAlwaysSucceedsID, + SpliceOutsideQuotesID, ; public int errorNumber() { diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index ef0e416ae1a2..9e382963c426 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -2105,4 +2105,12 @@ object messages { } val explanation = "" } + + case class SpliceOutsideQuotes() extends Message(SpliceOutsideQuotesID) { + val kind = "Syntax" + val msg = "splice outside quotes" + val explanation = + """A splice may only appear inside quotes '{ ... }, + |or else it must be the whole right hand side of a transparent method.""".stripMargin + } } diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 204afcb24dfc..a8cd8f9d91d7 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -156,23 +156,10 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase } } - /** 1. If we are in a transparent method but not in a nested quote, mark the transparent method - * as a macro. - * - * 2. If selection is a quote or splice node, record that fact in the current compilation unit. + /** If selection is a quote or splice node, record that fact in the current compilation unit. */ - private def handleMeta(sym: Symbol)(implicit ctx: Context): Unit = { - - def markAsMacro(c: Context): Unit = - if (c.owner eq c.outer.owner) markAsMacro(c.outer) - else if (c.owner.isTransparentMethod) c.owner.setFlag(Macro | Erased) - else if (!c.outer.owner.is(Package)) markAsMacro(c.outer) - - if (sym.isSplice || sym.isQuote) { - markAsMacro(ctx) - ctx.compilationUnit.containsQuotesOrSplices = true - } - } + private def handleMeta(sym: Symbol)(implicit ctx: Context): Unit = + if (sym.isSplice || sym.isQuote) ctx.compilationUnit.containsQuotesOrSplices = true private object dropInlines extends TreeMap { override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match { diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index b30b65f45001..8b2e899c26cd 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -11,7 +11,7 @@ import SymUtils._ import NameKinds._ import dotty.tools.dotc.ast.tpd.Tree import typer.Implicits.SearchFailureType - +import reporting.diagnostic.messages._ import scala.collection.mutable import dotty.tools.dotc.core.StdNames._ import dotty.tools.dotc.core.quoted._ @@ -242,6 +242,10 @@ class ReifyQuotes extends MacroTransformWithImplicits { !sym.is(Param) || levelOK(sym.owner) } + /** Issue a "splice outside quote" error unless we are in the body of a transparent method */ + def spliceOutsideQuotes(pos: Position)(implicit ctx: Context): Unit = + ctx.error(SpliceOutsideQuotes(), 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 diff --git a/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala b/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala index 927ce6f4f053..8aa695336720 100644 --- a/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala +++ b/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala @@ -20,6 +20,7 @@ import ProtoTypes.selectionProto import SymDenotations.SymDenotation import Annotations._ import transform.{ExplicitOuter, AccessProxies} +import transform.SymUtils._ import Inferencing.fullyDefinedType import config.Printers.inlining import ErrorReporting.errorTree @@ -250,6 +251,15 @@ object PrepareTransparent { inlined.updateAnnotation(LazyBodyAnnotation { _ => implicit val ctx = inlineCtx val rawBody = treeExpr(ctx) + + def markAsMacro(body: Tree): Unit = body match { + case _: Select if body.symbol.isSplice => inlined.setFlag(Erased | Macro) + case Block(Nil, expr) => markAsMacro(expr) + case Block(expr :: Nil, Literal(Constant(()))) => markAsMacro(expr) + case _ => + } + markAsMacro(rawBody) + val typedBody = if (ctx.reporter.hasErrors) rawBody else ctx.compilationUnit.inlineAccessors.makeInlineable(rawBody) diff --git a/tests/neg/quote-macro-splice.scala b/tests/neg/quote-macro-splice.scala index 52c7042c32d0..d97d5212a995 100644 --- a/tests/neg/quote-macro-splice.scala +++ b/tests/neg/quote-macro-splice.scala @@ -2,23 +2,23 @@ import scala.quoted._ object Test { - transparent def foo1: Int = { // error + transparent def foo1: Int = { println() - ~impl(1.toExpr) + ~impl(1.toExpr) // error: splice outside quotes } - transparent def foo2: Int = { // error - ~impl(1.toExpr) - ~impl(2.toExpr) + transparent def foo2: Int = { + ~impl(1.toExpr) // error: splice outside quotes + ~impl(2.toExpr) // error: splice outside quotes } - transparent def foo3: Int = { // error + transparent def foo3: Int = { val a = 1 - ~impl('(a)) + ~impl('(a)) // error: splice outside quotes } - transparent def foo4: Int = { // error - ~impl('(1)) + transparent def foo4: Int = { + ~impl('(1)) // error: splice outside quotes 1 } diff --git a/tests/pos/i4773.scala b/tests/pos/i4773.scala index 26a5e1a9073c..9ed2e098682b 100644 --- a/tests/pos/i4773.scala +++ b/tests/pos/i4773.scala @@ -3,5 +3,5 @@ import scala.quoted._ object Foo { transparent def foo2(): Unit = ~foo2Impl() def foo2Impl(): Expr[Unit] = '() - erased transparent def foo(): Unit = foo2() + transparent def foo(): Unit = foo2() } From 272cebc97afb8b8647917f7aea0f135e3b866bf8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 29 Jul 2018 21:21:14 +0200 Subject: [PATCH 4/9] Fix test We need the `erased` on foo because otherwise we get the error -------------------------------- 6 | transparent def foo(): Unit = foo2() | ^^^^^^ |Could not find macro class Foo$ in classpath. The most common reason for that is that you cannot use transparent macro implementations in the same compilation run that defines them --- tests/pos/i4773.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/pos/i4773.scala b/tests/pos/i4773.scala index 9ed2e098682b..26a5e1a9073c 100644 --- a/tests/pos/i4773.scala +++ b/tests/pos/i4773.scala @@ -3,5 +3,5 @@ import scala.quoted._ object Foo { transparent def foo2(): Unit = ~foo2Impl() def foo2Impl(): Expr[Unit] = '() - transparent def foo(): Unit = foo2() + erased transparent def foo(): Unit = foo2() } From b573150ef83ca4d6a829ab139127128b3e56d40e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 30 Jul 2018 10:27:06 +0200 Subject: [PATCH 5/9] Revert accidentally committed files --- compiler/src/dotty/tools/dotc/config/Config.scala | 2 +- .../src/dotty/tools/dotc/core/classfile/ClassfileParser.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/config/Config.scala b/compiler/src/dotty/tools/dotc/config/Config.scala index 05aa89fab7b5..ea96d9dcd5d9 100644 --- a/compiler/src/dotty/tools/dotc/config/Config.scala +++ b/compiler/src/dotty/tools/dotc/config/Config.scala @@ -160,7 +160,7 @@ object Config { final val showCompletions = false /** If set, enables tracing */ - final val tracingEnabled = true + final val tracingEnabled = false /** Initial capacity of uniques HashMap. * Note: This MUST BE a power of two to work with util.HashSet diff --git a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala index 2b5b2de5c6d1..f9147b523ac5 100644 --- a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala +++ b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala @@ -761,7 +761,7 @@ class ClassfileParser( assert(cond, s"Unpickling ${classRoot.symbol.showLocated} from ${classRoot.symbol.associatedFile} is not allowed with -Yscala2-unpickler $allowed") - if (false && allowed != "always") { + if (allowed != "always") { failUnless(allowed != "never") val allowedList = allowed.split(":").toList val file = classRoot.symbol.associatedFile From 25c04af010a83fcabb401afbe7e0408a14d85187 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 30 Jul 2018 10:28:21 +0200 Subject: [PATCH 6/9] Fix typo and renaming --- compiler/src/dotty/tools/dotc/core/Flags.scala | 2 +- compiler/src/dotty/tools/dotc/transform/PostTyper.scala | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index c86d64d7c10a..70685e75e375 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -548,7 +548,7 @@ object Flags { /** Assumed to be pure */ final val StableOrErased = Stable | Erased - /** Labeled `private`, `final`, `transparent`, or `erasedd` */ + /** Labeled `private`, `final`, `transparent`, or `erased` */ final val EffectivelyFinal = Private | Final | Transparent | Erased /** A private method */ diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index a8cd8f9d91d7..4b19e4a85775 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -158,7 +158,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase /** If selection is a quote or splice node, record that fact in the current compilation unit. */ - private def handleMeta(sym: Symbol)(implicit ctx: Context): Unit = + private def reportMeta(sym: Symbol)(implicit ctx: Context): Unit = if (sym.isSplice || sym.isQuote) ctx.compilationUnit.containsQuotesOrSplices = true private object dropInlines extends TreeMap { @@ -171,13 +171,13 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase override def transform(tree: Tree)(implicit ctx: Context): Tree = try tree match { case tree: Ident if !tree.isType => - handleMeta(tree.symbol) + reportMeta(tree.symbol) tree.tpe match { case tpe: ThisType => This(tpe.cls).withPos(tree.pos) case _ => tree } case tree @ Select(qual, name) => - handleMeta(tree.symbol) + reportMeta(tree.symbol) if (name.isTypeName) { Checking.checkRealizable(qual.tpe, qual.pos.focus) super.transform(tree)(ctx.addMode(Mode.Type)) From dcdd51d16e4cbefcdc80edcbe7bdff3b7f154050 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 30 Jul 2018 10:38:31 +0200 Subject: [PATCH 7/9] Fix RefCheck rules --- .../src/dotty/tools/dotc/typer/RefChecks.scala | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index ec96612b5c19..906584025dfa 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -143,16 +143,14 @@ object RefChecks { * 1.8.1 M's type is a subtype of O's type, or * 1.8.2 M is of type []S, O is of type ()T and S <: T, or * 1.8.3 M is of type ()S, O is of type []T and S <: T, or - * 1.9 M must not be an erased definition - * 1.10. If M is a 2.x macro def, O cannot be deferred unless there's a concrete method overriding O. - * 1.11. If M is not a macro def, O cannot be a macro def. + * 1.9 If M is an erased definition, it must override at least one concrete member * 2. Check that only abstract classes have deferred members * 3. Check that concrete classes do not have deferred definitions * that are not implemented in a subclass. * 4. Check that every member with an `override` modifier * overrides some other member. * TODO check that classes are not overridden - * TODO This still needs to be cleaned up; the current version is a staright port of what was there + * TODO This still needs to be cleaned up; the current version is a straight port of what was there * before, but it looks too complicated and method bodies are far too large. */ private def checkAllOverrides(clazz: Symbol)(implicit ctx: Context): Unit = { @@ -376,14 +374,8 @@ object RefChecks { overrideError("may not override a non-lazy value") } else if (other.is(Lazy) && !other.isRealMethod && !member.is(Lazy)) { overrideError("must be declared lazy to override a lazy value") - } else if (member.is(Erased)) { // (1.9) - overrideError("is an erased method, may not override anything") - } else if (member.is(Macro, butNot = Scala2x)) { // (1.9) - overrideError("is a macro, may not override anything") - } else if (other.is(Deferred) && member.is(Scala2Macro) && member.extendedOverriddenSymbols.forall(_.is(Deferred))) { // (1.10) - overrideError("cannot be used here - term macros cannot override abstract methods") - } else if (other.is(Macro) && !member.is(Macro)) { // (1.11) - overrideError("cannot be used here - only term macros can override term macros") + } else if (member.is(Erased) && other.is(Deferred) && member.extendedOverriddenSymbols.forall(_.is(Deferred))) { // (1.9) + overrideError("is erased, cannot override only abstract methods") } else if (!compatibleTypes(memberTp(self), otherTp(self)) && !compatibleTypes(memberTp(upwardsSelf), otherTp(upwardsSelf))) { overrideError("has incompatible type" + err.whyNoMatchStr(memberTp(self), otherTp(self))) From 6bf58a50c8b3c2984c1c18ce3e081ee551b2201f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 30 Jul 2018 14:23:12 +0200 Subject: [PATCH 8/9] Make implicit match imply erased --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 8 +++++--- docs/docs/typelevel.md | 5 +++-- tests/neg/implicitMatch-ambiguous.scala | 2 +- tests/neg/implicitMatch-syntax.scala | 16 ++++++---------- 4 files changed, 15 insertions(+), 16 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 9834e76a20da..549288a556a9 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -974,9 +974,11 @@ class Typer extends Namer val unchecked = pt.isRef(defn.PartialFunctionClass) typed(desugar.makeCaseLambda(tree.cases, protoFormals.length, unchecked) withPos tree.pos, pt) case id @ untpd.ImplicitScrutinee() => - if (tree.getAttachment(PrepareTransparent.TopLevelMatch).isEmpty || - !ctx.owner.flagsUNSAFE.is(Erased)) - ctx.error(em"implicit match cannot be used here; it must occur as a toplevel match of an erased transparent method", tree.pos) + if (tree.getAttachment(PrepareTransparent.TopLevelMatch).isDefined) + ctx.owner.setFlag(Erased) + else + ctx.error(em"implicit match cannot be used here; it must occur as a toplevel match of a transparent method", tree.pos) + val sel1 = id.withType(defn.ImplicitScrutineeTypeRef) typedMatchFinish(tree, sel1, sel1.tpe, pt) case _ => diff --git a/docs/docs/typelevel.md b/docs/docs/typelevel.md index a215ec0d4140..c156e2fa4b0d 100644 --- a/docs/docs/typelevel.md +++ b/docs/docs/typelevel.md @@ -233,7 +233,7 @@ The following rewrite rules are performed when simplifying inlined bodies: Transparent methods are effectively final; they may not be overwritten. -Transparent methods with an implicit match expression on their right-hand side must in addition be declared `erased`. Erased transparent methods must be always fully applied. In addition, the restrictions on normal erased methods apply, including: +Transparent methods with a toplevel implicit match or macro splice are classified `erased` - an `erased` modifier can be given for them, but it is redundant. Erased transparent methods must be always fully applied. In addition, the restrictions on normal erased methods apply, including: 1. They may not override other methods. 2. They may not be referred to from a non-erased context. @@ -409,7 +409,8 @@ Patterns are tried in sequence. The first case with a pattern `x: T` such that a of type `T` can be summoned is chosen. The variable `x` is then bound to the implicit value for the remainder of the case. It can in turn be used as an implicit in the right hand side of the case. It is an error if one of the tested patterns gives rise to an ambiguous implicit search. -Implicit matches can only occur as toplevel match expressions of methods that are both `erased` and `transparent`. This ensures that all implicit searches are done at compile-time. +Implicit matches can only occur as toplevel match expressions of a `transparent` method. +If a transpatent method contains implicit matches, it is classified as `erased` - an `erased` modifier can be given for it, but it is redundant. This ensures that all implicit searches are done at compile-time. ## Transparent Values diff --git a/tests/neg/implicitMatch-ambiguous.scala b/tests/neg/implicitMatch-ambiguous.scala index 60c83204064a..c7d485b8a29b 100644 --- a/tests/neg/implicitMatch-ambiguous.scala +++ b/tests/neg/implicitMatch-ambiguous.scala @@ -4,7 +4,7 @@ object Test { implicit val a1: A = new A implicit val a2: A = new A - erased transparent def f: Any = implicit match { + transparent def f: Any = implicit match { case _: A => ??? // error: ambiguous implicits } diff --git a/tests/neg/implicitMatch-syntax.scala b/tests/neg/implicitMatch-syntax.scala index e2e933784456..9a0ea94489fd 100644 --- a/tests/neg/implicitMatch-syntax.scala +++ b/tests/neg/implicitMatch-syntax.scala @@ -2,36 +2,32 @@ object Test { import collection.immutable.TreeSet import collection.immutable.HashSet - erased transparent def f1[T] = implicit implicit match { // error: repeated modifier // error: illegal modifier + transparent def f1[T] = implicit implicit match { // error: repeated modifier // error: illegal modifier case ord: Ordered[T] => new TreeSet[T] // error: no implicit case _ => new HashSet[T] } - erased transparent def f2[T] = implicit erased match { // error: illegal modifier + transparent def f2[T] = implicit erased match { // error: illegal modifier case ord: Ordered[T] => new TreeSet[T] // error: no implicit case _ => new HashSet[T] } - erased transparent def f3[T] = erased implicit match { // error: illegal modifier + transparent def f3[T] = erased implicit match { // error: illegal modifier case ord: Ordered[T] => new TreeSet[T] // error: no implicit case _ => new HashSet[T] } - erased transparent def f4() = implicit match { + transparent def f4() = implicit match { case Nil => ??? // error: not a legal pattern case x :: xs => ??? // error: not a legal pattern } - erased transparent def f5[T] = locally { implicit match { // error: implicit match cannot be used here + transparent def f5[T] = locally { implicit match { // error: implicit match cannot be used here case _ => new HashSet[T] }} - transparent def f6[T] = implicit match { // error: implicit match cannot be used here - case _ => new HashSet[T] - } - - erased def f7[T] = implicit match { // error: implicit match cannot be used here + erased def f6[T] = implicit match { // error: implicit match cannot be used here case _ => new HashSet[T] } } \ No newline at end of file From 370125ec68dd2e64dbb01da0ce9a398b279815ae Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 1 Aug 2018 16:18:07 +0200 Subject: [PATCH 9/9] Fix rebase breakage --- compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index 8b2e899c26cd..0d0b2807b304 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -304,7 +304,7 @@ class ReifyQuotes extends MacroTransformWithImplicits { outer.checkType(pos).foldOver(acc, tp) } else { - if (tp.isTerm) ctx.error(i"splice outside quotes", pos) + if (tp.isTerm) spliceOutsideQuotes(pos) tp } case tp: NamedType => @@ -435,7 +435,7 @@ class ReifyQuotes extends MacroTransformWithImplicits { if (ctx.reporter.hasErrors) splice else transform(evaluatedSplice) } else if (!ctx.owner.is(Transparent)) { // level 0 outside a transparent definition - ctx.error(i"splice outside quotes or transparent method", splice.pos) + spliceOutsideQuotes(splice.pos) splice } else if (Splicer.canBeSpliced(splice.qualifier)) { // level 0 inside a transparent definition