Skip to content

Commit 1299850

Browse files
committed
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`.
1 parent c2de29d commit 1299850

20 files changed

+94
-65
lines changed

compiler/src/dotty/tools/dotc/config/Config.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ object Config {
160160
final val showCompletions = false
161161

162162
/** If set, enables tracing */
163-
final val tracingEnabled = false
163+
final val tracingEnabled = true
164164

165165
/** Initial capacity of uniques HashMap.
166166
* Note: This MUST BE a power of two to work with util.HashSet

compiler/src/dotty/tools/dotc/core/Flags.scala

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -548,8 +548,8 @@ object Flags {
548548
/** Assumed to be pure */
549549
final val StableOrErased = Stable | Erased
550550

551-
/** Labeled `private`, `final`, or `transparent` */
552-
final val EffectivelyFinal = Private | Final | Transparent
551+
/** Labeled `private`, `final`, `transparent`, or `erasedd` */
552+
final val EffectivelyFinal = Private | Final | Transparent | Erased
553553

554554
/** A private method */
555555
final val PrivateMethod = allOf(Private, Method)
@@ -563,6 +563,9 @@ object Flags {
563563
/** A transparent parameter */
564564
final val TransparentParam = allOf(Transparent, Param)
565565

566+
/** A transparent erased method */
567+
final val TransparentErasedMethod = allOf(Transparent, Erased, Method)
568+
566569
/** An enum case */
567570
final val EnumCase = allOf(Enum, Case)
568571

compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -761,7 +761,7 @@ class ClassfileParser(
761761
assert(cond,
762762
s"Unpickling ${classRoot.symbol.showLocated} from ${classRoot.symbol.associatedFile} is not allowed with -Yscala2-unpickler $allowed")
763763

764-
if (allowed != "always") {
764+
if (false && allowed != "always") {
765765
failUnless(allowed != "never")
766766
val allowedList = allowed.split(":").toList
767767
val file = classRoot.symbol.associatedFile

compiler/src/dotty/tools/dotc/transform/PostTyper.scala

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,9 +165,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
165165

166166
def markAsMacro(c: Context): Unit =
167167
if (c.owner eq c.outer.owner) markAsMacro(c.outer)
168-
else if (c.owner.isTransparentMethod) {
169-
c.owner.setFlag(Macro)
170-
}
168+
else if (c.owner.isTransparentMethod) c.owner.setFlag(Macro | Erased)
171169
else if (!c.outer.owner.is(Package)) markAsMacro(c.outer)
172170

173171
if (sym.isSplice || sym.isQuote) {

compiler/src/dotty/tools/dotc/typer/Namer.scala

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1195,15 +1195,6 @@ class Namer { typer: Typer =>
11951195
instantiateDependent(restpe, typeParams, termParamss)
11961196
ctx.methodType(tparams map symbolOfTree, termParamss, restpe, isJava = ddef.mods is JavaDefined)
11971197
}
1198-
if (sym.is(Transparent) &&
1199-
sym.unforcedAnnotation(defn.ForceInlineAnnot).isEmpty)
1200-
// Need to keep @forceInline annotated methods around to get to parity with Scala.
1201-
// This is necessary at least until we have full bootstrap. Right now
1202-
// dotty-bootstrapped involves running the Dotty compiler compiled with Scala 2 with
1203-
// a Dotty runtime library compiled with Dotty. If we erase @forceInline annotated
1204-
// methods, this means that the support methods in dotty.runtime.LazyVals vanish.
1205-
// But they are needed for running the lazy val implementations in the Scala-2 compiled compiler.
1206-
sym.setFlag(Erased)
12071198
if (isConstructor) {
12081199
// set result type tree to unit, but take the current class as result type of the symbol
12091200
typedAheadType(ddef.tpt, defn.UnitType)

compiler/src/dotty/tools/dotc/typer/RefChecks.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ object RefChecks {
143143
* 1.8.1 M's type is a subtype of O's type, or
144144
* 1.8.2 M is of type []S, O is of type ()T and S <: T, or
145145
* 1.8.3 M is of type ()S, O is of type []T and S <: T, or
146-
* 1.9 M must not be a typelevel def or a Dotty macro def
146+
* 1.9 M must not be an erased definition
147147
* 1.10. If M is a 2.x macro def, O cannot be deferred unless there's a concrete method overriding O.
148148
* 1.11. If M is not a macro def, O cannot be a macro def.
149149
* 2. Check that only abstract classes have deferred members
@@ -376,8 +376,8 @@ object RefChecks {
376376
overrideError("may not override a non-lazy value")
377377
} else if (other.is(Lazy) && !other.isRealMethod && !member.is(Lazy)) {
378378
overrideError("must be declared lazy to override a lazy value")
379-
} else if (member.is(Erased) && member.allOverriddenSymbols.forall(_.is(Deferred))) { // (1.9)
380-
overrideError("is an erased method, may not override only deferred methods")
379+
} else if (member.is(Erased)) { // (1.9)
380+
overrideError("is an erased method, may not override anything")
381381
} else if (member.is(Macro, butNot = Scala2x)) { // (1.9)
382382
overrideError("is a macro, may not override anything")
383383
} else if (other.is(Deferred) && member.is(Scala2Macro) && member.extendedOverriddenSymbols.forall(_.is(Deferred))) { // (1.10)

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -974,8 +974,9 @@ class Typer extends Namer
974974
val unchecked = pt.isRef(defn.PartialFunctionClass)
975975
typed(desugar.makeCaseLambda(tree.cases, protoFormals.length, unchecked) withPos tree.pos, pt)
976976
case id @ untpd.ImplicitScrutinee() =>
977-
if (tree.getAttachment(PrepareTransparent.TopLevelMatch).isEmpty)
978-
ctx.error(em"implicit match cannot be used here; it must occur as a toplevel match of a transparent method", tree.pos)
977+
if (tree.getAttachment(PrepareTransparent.TopLevelMatch).isEmpty ||
978+
!ctx.owner.flagsUNSAFE.is(Erased))
979+
ctx.error(em"implicit match cannot be used here; it must occur as a toplevel match of an erased transparent method", tree.pos)
979980
val sel1 = id.withType(defn.ImplicitScrutineeTypeRef)
980981
typedMatchFinish(tree, sel1, sel1.tpe, pt)
981982
case _ =>
@@ -2351,7 +2352,7 @@ class Typer extends Namer
23512352
// - the current tree is a synthetic apply which is not expandable (eta-expasion would simply undo that)
23522353
if (arity >= 0 &&
23532354
!tree.symbol.isConstructor &&
2354-
!tree.symbol.is(TransparentMethod) &&
2355+
!tree.symbol.is(TransparentErasedMethod) &&
23552356
!ctx.mode.is(Mode.Pattern) &&
23562357
!(isSyntheticApply(tree) && !isExpandableApply))
23572358
simplify(typed(etaExpand(tree, wtp, arity), pt), pt, locked)

docs/docs/typelevel.md

Lines changed: 8 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -229,34 +229,17 @@ The following rewrite rules are performed when simplifying inlined bodies:
229229
Dropping a binding might make other bindings redundant. Garbage collection proceeds until no further bindings
230230
can be dropped.
231231

232-
## Restrictions for Transparent and Typelevel Functions
232+
## Restrictions for Transparent and Erased Functions
233233

234234
Transparent methods are effectively final; they may not be overwritten.
235235

236-
If a transparent
237-
method has a toplevel match expression or a toplevel splice `~` on its right-hand side,
238-
it is classified as a typelevel method that can _only_ be executed at compile time. For typelevel methods two more restrictions apply:
236+
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:
239237

240-
1. They must be always fully applied.
241-
2. They may override other methods only if one of the overridden methods is concrete.
238+
1. They may not override other methods.
239+
2. They may not be referred to from a non-erased context.
242240

243-
The right hand side of a typelevel method is never invoked by dynamic dispatch. As an example consider a situation like the following:
244-
```scala
245-
class Iterable[T] {
246-
def foreach(f: T => Unit): Unit = ...
247-
}
248-
class List[T] extends Iterable[T] {
249-
override transparent def foreach(f: T => Unit): Unit = ...
250-
}
251-
val xs: Iterable[T] = ...
252-
val ys: List[T] = ...
253-
val zs: Iterable[T] = ys
254-
xs.foreach(f) // calls Iterable's foreach
255-
ys.foreach(f) // expands to the body of List's foreach
256-
zs.foreach(f) // calls Iterable's foreach
257-
```
258-
It follows that an overriding typelevel method should implement exactly the same semantics as the
259-
method it overrides (but possibly more efficiently).
241+
**Question:** We currently set `erased` automatically for macros, i.e. methods with a
242+
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?
260243

261244
## Matching on Types
262245

@@ -415,7 +398,7 @@ There are some proposals to improve the situation in specific areas, for instanc
415398
By contrast, the new `implicit match` construct makes implicit search available in a functional context. To solve
416399
the problem of creating the right set, one would use it as follows:
417400
```scala
418-
transparent def setFor[T]: Set[T] = implicit match {
401+
erased transparent def setFor[T]: Set[T] = implicit match {
419402
case ord: Ordering[T] => new TreeSet[T]
420403
case _ => new HashSet[T]
421404
}
@@ -426,8 +409,7 @@ Patterns are tried in sequence. The first case with a pattern `x: T` such that a
426409
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.
427410
It is an error if one of the tested patterns gives rise to an ambiguous implicit search.
428411

429-
Implicit matches can only occur as toplevel match expressions of transparent methods. This ensures that
430-
all implicit searches are done at compile-time.
412+
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.
431413

432414
## Transparent Values
433415

tests/neg/implicitMatch-ambiguous.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ object Test {
44
implicit val a1: A = new A
55
implicit val a2: A = new A
66

7-
transparent def f: Any = implicit match {
7+
erased transparent def f: Any = implicit match {
88
case _: A => ??? // error: ambiguous implicits
99
}
1010

tests/neg/implicitMatch-syntax.scala

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,36 @@ object Test {
22
import collection.immutable.TreeSet
33
import collection.immutable.HashSet
44

5-
transparent def f1[T] = implicit implicit match { // error: repeated modifier // error: illegal modifier
5+
erased transparent def f1[T] = implicit implicit match { // error: repeated modifier // error: illegal modifier
66
case ord: Ordered[T] => new TreeSet[T] // error: no implicit
77
case _ => new HashSet[T]
88

99
}
1010

11-
transparent def f2[T] = implicit erased match { // error: illegal modifier
11+
erased transparent def f2[T] = implicit erased match { // error: illegal modifier
1212
case ord: Ordered[T] => new TreeSet[T] // error: no implicit
1313
case _ => new HashSet[T]
1414
}
1515

16-
transparent def f3[T] = erased implicit match { // error: illegal modifier
16+
erased transparent def f3[T] = erased implicit match { // error: illegal modifier
1717
case ord: Ordered[T] => new TreeSet[T] // error: no implicit
1818
case _ => new HashSet[T]
1919
}
2020

21-
transparent def f4() = implicit match {
21+
erased transparent def f4() = implicit match {
2222
case Nil => ??? // error: not a legal pattern
2323
case x :: xs => ??? // error: not a legal pattern
2424
}
2525

26-
transparent def f5[T] = locally { implicit match { // error: implicit match cannot be used here
26+
erased transparent def f5[T] = locally { implicit match { // error: implicit match cannot be used here
2727
case _ => new HashSet[T]
2828
}}
2929

30-
def f6[T] = implicit match { // error: implicit match cannot be used here
30+
transparent def f6[T] = implicit match { // error: implicit match cannot be used here
31+
case _ => new HashSet[T]
32+
}
33+
34+
erased def f7[T] = implicit match { // error: implicit match cannot be used here
3135
case _ => new HashSet[T]
3236
}
3337
}

tests/neg/transparent-override/B_2.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
class B extends A {
2-
transparent def f(x: Int): Int = x match { // error
2+
erased transparent def f(x: Int): Int = x match { // error
33
case 0 => 1
44
case _ => x
55
}

tests/neg/typelevel-noeta.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
object Test {
33
def anyValue[T]: T = ???
44

5-
transparent def test(x: Int) = x match {
5+
erased transparent def test(x: Int) = x match {
66
case _: Byte =>
77
case _: Char =>
88
}

tests/pos/i4773.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@ import scala.quoted._
33
object Foo {
44
transparent def foo2(): Unit = ~foo2Impl()
55
def foo2Impl(): Expr[Unit] = '()
6-
transparent def foo(): Unit = foo2()
6+
erased transparent def foo(): Unit = foo2()
77
}

tests/neg/inline-i1773.scala renamed to tests/pos/inline-i1773.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ object Test {
77
}
88

99
def main(args: Array[String]): Unit = {
10-
val q"class $name extends $parent" = new Object // error: method unapply is used
10+
val q"class $name extends $parent" = new Object
1111
println(name)
1212
println(parent)
1313
}

tests/pos/typelevel-vector1.scala

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,13 @@ object Test {
2323
val z: S[S[S[S[Z]]]] = y
2424

2525
transparent def concat[T, N1 <: Nat, N2 <: Nat](xs: Vec[T, N1], ys: Vec[T, N2]): Vec[T, _] = {
26-
val length = Typed(add(erasedValue[N1], erasedValue[N2]))
26+
erased val length = Typed(add(erasedValue[N1], erasedValue[N2]))
2727
Vec[T, length.Type](xs.elems ++ ys.elems)
2828
}
29+
30+
val xs = Vec[Int, S[S[Z]]](List(1, 2))
31+
val ys = Vec[Int, S[Z]](List(3))
32+
val zs = concat(xs, ys)
33+
val zsc: Vec[Int, S[S[S[Z]]]] = zs
2934
}
3035

tests/pos/typelevel-vector2.scala

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
object typelevel {
2+
case class Typed[T](value: T) { type Type = T }
3+
erased def erasedValue[T]: T = ???
4+
}
5+
6+
sealed trait Nat
7+
case object Z extends Nat
8+
case class S[N <: Nat](n: N) extends Nat
9+
10+
object Nat {
11+
type Z = Z.type
12+
transparent def fromInt(n: Int): Nat = n match {
13+
case 0 => Z
14+
case n1 => S(fromInt(n - 1))
15+
}
16+
}
17+
18+
import Nat.Z
19+
20+
sealed trait Vec[+A] { type Len <: Nat }
21+
case object VNil extends Vec[Nothing] { type Len = Z }
22+
case class VCons[+A, TL <: Vec[A]](hd: A, tl: TL) extends Vec[A] { type Len = S[tl.Len]}
23+
24+
object Vec {
25+
type VNil = VNil.type
26+
}
27+
28+
import Vec.VNil
29+
30+
object Concat {
31+
transparent def concat[A](xs: Vec[A], ys: Vec[A]): Vec[A] =
32+
xs match {
33+
case VNil => ys
34+
case VCons(hd, tl) => VCons(hd, concat(tl, ys))
35+
}
36+
}
37+
38+
import Concat.concat
39+
40+
object Test {
41+
val v1 = VCons(1, VCons(2, VNil))
42+
val v2 = VCons(3, VCons(4, VCons(5, VNil)))
43+
val v3 = concat(v1, v2)
44+
val v3l: v3.Len = Nat.fromInt(5)
45+
}

tests/run/i4735/App_2.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ object Test {
1010
}
1111

1212
class Unrolled(arr: Array[Int]) extends AnyVal {
13-
transparent def foreach(f: => Int => Unit): Unit = Macro.unrolledForeach(3, arr, f)
13+
erased transparent def foreach(f: => Int => Unit): Unit = Macro.unrolledForeach(3, arr, f)
1414
}

tests/run/implicitMatch.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ object Test extends App {
22
import collection.immutable.TreeSet
33
import collection.immutable.HashSet
44

5-
transparent def f[T]() = implicit match {
5+
erased transparent def f[T]() = implicit match {
66
case ord: Ordering[T] => new TreeSet[T]
77
case _ => new HashSet[T]
88
}
@@ -11,7 +11,7 @@ object Test extends App {
1111
class B
1212
implicit val b: B = new B
1313

14-
transparent def g = implicit match {
14+
erased transparent def g = implicit match {
1515
case _: A => println("A")
1616
case _: B => println("B")
1717
}

tests/run/typelevel-defaultValue.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ object typelevel {
55

66
object Test extends App {
77

8-
transparent def defaultValue[T]: Option[Any] = typelevel.erasedValue[T] match {
8+
erased transparent def defaultValue[T]: Option[Any] = typelevel.erasedValue[T] match {
99
case _: Byte => Some(0: Byte)
1010
case c: Char => Some(0: Char)
1111
case d @ (_: Short) => Some(0: Short)

tests/run/typelevel-overrides.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,5 @@ object Test extends App {
2525
val b: B = new B
2626
assert(b.f(0) == 0)
2727
val c: A = new C
28-
assert(c.f(0) == 1, c.f(0))
28+
assert(c.f(0) == 0, c.f(0))
2929
}

0 commit comments

Comments
 (0)