Skip to content

Commit 69ee4d9

Browse files
committed
Require erased values to be pure expressions
1 parent ea8f4ee commit 69ee4d9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+90
-93
lines changed

compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe
228228
case OnlyFullyDependentAppliedConstructorTypeID // errorNumber: 212
229229
case PointlessAppliedConstructorTypeID // errorNumber: 213
230230
case IllegalContextBoundsID // errorNumber: 214
231+
case ErasedNotPureID // errornumber 215
231232

232233
def errorNumber = ordinal - 1
233234

compiler/src/dotty/tools/dotc/reporting/messages.scala

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3535,3 +3535,35 @@ final class IllegalContextBounds(using Context) extends SyntaxMsg(IllegalContext
35353535
override protected def explain(using Context): String = ""
35363536

35373537
end IllegalContextBounds
3538+
3539+
final class ErasedNotPure(tree: tpd.Tree, isArgument: Boolean, isImplicit: Boolean)(using Context) extends TypeMsg(ErasedNotPureID):
3540+
def what =
3541+
if isArgument then s"${if isImplicit then "implicit " else ""}argument to an erased parameter"
3542+
else "right-hand-side of an erased value"
3543+
override protected def msg(using Context): String =
3544+
i"$what fails to be a pure expression"
3545+
3546+
override protected def explain(using Context): String =
3547+
def alternatives =
3548+
if tree.symbol == defn.Compiletime_erasedValue then
3549+
i"""An accepted (but unsafe) alternative for this expression uses function
3550+
|
3551+
| caps.unsafe.unsafeErasedValue
3552+
|
3553+
|instead."""
3554+
else
3555+
"""A pure expression is an expression that is clearly side-effect free and terminating.
3556+
|Some examples of pure expressions are:
3557+
| - literals,
3558+
| - references to values,
3559+
| - side-effect-free instance creations,
3560+
| - applications of inline functions to pure arguments."""
3561+
3562+
i"""The $what must be a pure expression, but I found:
3563+
|
3564+
| $tree
3565+
|
3566+
|This expression is not classified to be pure.
3567+
|$alternatives"""
3568+
end ErasedNotPure
3569+

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

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import core.Names.*
1414
import core.StdNames.*
1515
import core.NameOps.*
1616
import core.Periods.currentStablePeriod
17-
import core.NameKinds.{AdaptedClosureName, BodyRetainerName, DirectMethName}
17+
import core.NameKinds.{AdaptedClosureName, BodyRetainerName, DirectMethName, InlineScrutineeName}
1818
import core.Scopes.newScopeWith
1919
import core.Decorators.*
2020
import core.Constants.*
@@ -583,16 +583,11 @@ object Erasure {
583583
checkNotErasedClass(tree)
584584
end checkNotErased
585585

586-
def checkPureErased(tree: untpd.Tree, isArgument: Boolean)(using Context): Unit =
587-
if false then inContext(preErasureCtx):
588-
if tpd.isPureExpr(tree.asInstanceOf[tpd.Tree]) then
589-
val tree1 = tree.asInstanceOf[tpd.Tree]
590-
println(i"$tree1 is pure, ${tree1.tpe.widen}")
591-
else
592-
def what =
593-
if isArgument then "argument to erased parameter"
594-
else "right-hand-side of erased value"
595-
report.error(em"$what fails to be a pure expression", tree.srcPos)
586+
def checkPureErased(tree: untpd.Tree, isArgument: Boolean, isImplicit: Boolean = false)(using Context): Unit =
587+
val tree1 = tree.asInstanceOf[tpd.Tree]
588+
inContext(preErasureCtx):
589+
if !tpd.isPureExpr(tree1) then
590+
report.error(ErasedNotPure(tree1, isArgument, isImplicit), tree1.srcPos)
596591

597592
private def checkNotErasedClass(tp: Type, tree: untpd.Tree)(using Context): Unit = tp match
598593
case JavaArrayType(et) =>
@@ -861,7 +856,8 @@ object Erasure {
861856
case mt: MethodType if mt.hasErasedParams =>
862857
args.lazyZip(mt.paramErasureStatuses).flatMap: (arg, isErased) =>
863858
if isErased then
864-
checkPureErased(arg, isArgument = true)
859+
checkPureErased(arg, isArgument = true,
860+
isImplicit = mt.isImplicitMethod && arg.span.isSynthetic)
865861
Nil
866862
else
867863
arg :: Nil
@@ -933,9 +929,10 @@ object Erasure {
933929

934930
override def typedValDef(vdef: untpd.ValDef, sym: Symbol)(using Context): Tree =
935931
if sym.isEffectivelyErased then
936-
checkPureErased(vdef.rhs, isArgument = false)
932+
if !sym.name.is(InlineScrutineeName) then
933+
checkPureErased(vdef.rhs, isArgument = false)
937934
erasedDef(sym)
938-
else
935+
else trace(i"erasing $vdef"):
939936
checkNotErasedClass(sym.info, vdef)
940937
super.typedValDef(untpd.cpy.ValDef(vdef)(
941938
tpt = untpd.TypedSplice(TypeTree(sym.info).withSpan(vdef.tpt.span))), sym)
@@ -946,7 +943,6 @@ object Erasure {
946943
*/
947944
override def typedDefDef(ddef: untpd.DefDef, sym: Symbol)(using Context): Tree =
948945
if sym.isEffectivelyErased || sym.name.is(BodyRetainerName) then
949-
checkPureErased(ddef.rhs, isArgument = false)
950946
erasedDef(sym)
951947
else
952948
checkNotErasedClass(sym.info.finalResultType, ddef)

library/src/scala/CanThrow.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@ erased class CanThrow[-E <: Exception] extends caps.SharedCapability
1212

1313
@experimental
1414
object unsafeExceptions:
15-
inline given canThrowAny: CanThrow[Exception] = compiletime.erasedValue
15+
inline given canThrowAny: CanThrow[Exception] = caps.unsafe.unsafeErasedValue
1616

File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

tests/run/erased-frameless.scala renamed to tests/invalid/run/erased-frameless.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ trait Dataset[T] {
2828
// Use c.label to do an untyped select on actual Spark Dataset, and
2929
// cast the result to TypedDataset[A]
3030

31-
def col[S <: String, A](s: S) (using erased ev: Exists[T, s.type, A]) =
31+
inline def col[S <: String, A](s: S) (using erased ev: Exists[T, s.type, A]) =
3232
new Column[T, A](s) // ev is only here to check than this is safe, it's never used at runtime!
3333

3434
def collect(): Vector[T]
@@ -71,17 +71,17 @@ case class Column[T, A](label: String)
7171
trait Exists[T, K, V]
7272

7373
object Exists {
74-
implicit def derive[T, H <: HList, K, V](implicit g: LabelledGeneric[T] { type Repr = H }, s: Selector[H, K, V]): Exists[T, K, V] = {
74+
inline implicit def derive[T, H <: HList, K, V](implicit g: LabelledGeneric[T] { type Repr = H }, s: Selector[H, K, V]): Exists[T, K, V] = {
7575
println("Exists.derive")
7676
null
7777
}
7878

79-
implicit def caseFound[T <: HList, K <: String, V]: Selector[R[K, V] :: T, K, V] = {
79+
inline implicit def caseFound[T <: HList, K <: String, V]: Selector[R[K, V] :: T, K, V] = {
8080
println("Selector.caseFound")
8181
null
8282
}
8383

84-
implicit def caseRecur[H, T <: HList, K <: String, V](implicit i: Selector[T, K, V]): Selector[H :: T, K, V] = {
84+
inline implicit def caseRecur[H, T <: HList, K <: String, V](implicit i: Selector[T, K, V]): Selector[H :: T, K, V] = {
8585
println("Selector.caseRecur")
8686
null
8787
}

tests/neg/erased-6.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//> using options -language:experimental.erasedDefinitions
22

33
object Test {
4-
erased val foo: Foo = new Foo
4+
erased val foo: Foo = new Foo // error, Foo is not noInits
55
foo.x() // error
66
foo.y // error
77
foo.z // error

tests/neg/erased-class.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ erased class AA
44
erased class BB extends AA // ok
55

66
@main def Test =
7-
val f1: Array[AA] = compiletime.erasedValue // error // error
8-
def f2(x: Int): Array[AA] = compiletime.erasedValue // error // error
9-
def bar: AA = compiletime.erasedValue // ok
10-
val baz: AA = compiletime.erasedValue // ok
7+
val f1: Array[AA] = caps.unsafe.unsafeErasedValue // error // error
8+
def f2(x: Int): Array[AA] = caps.unsafe.unsafeErasedValue // error // error
9+
def bar: AA = caps.unsafe.unsafeErasedValue // ok
10+
val baz: AA = caps.unsafe.unsafeErasedValue // ok

tests/neg/erasedValueb.check

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
-- Error: tests/neg/erasedValueb.scala:7:7 -----------------------------------------------------------------------------
2+
7 | foo0(erasedValue[Int]) // error
3+
| ^^^^^^^^^^^
4+
| method erasedValue is declared as `erased`, but is in fact used
5+
-- [E215] Type Error: tests/neg/erasedValueb.scala:8:18 ----------------------------------------------------------------
6+
8 | foo1(erasedValue[Int]) // error
7+
| ^^^^^^^^^^^^^^^^
8+
| argument to an erased parameter fails to be a pure expression
9+
|
10+
| longer explanation available when compiling with `-explain`

tests/neg/erasedValueb.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@ object Test {
55
def foo0(a: Int): Int = 3
66
def foo1(erased a: Int): Int = 3
77
foo0(erasedValue[Int]) // error
8-
foo1(erasedValue[Int])
8+
foo1(erasedValue[Int]) // error
99
}

tests/pos-custom-args/captures/try.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def foo(x: Boolean): Int throws Fail =
1414
if x then 1 else raise(Fail())
1515

1616
def handle[E <: Exception, R](op: (erased CanThrow[E]) -> R)(handler: E -> R): R =
17-
erased val x: CanThrow[E] = ??? : CanThrow[E]
17+
erased val x = caps.unsafe.unsafeErasedValue[CanThrow[E]]
1818
try op(x)
1919
catch case ex: E => handler(ex)
2020

tests/pos/erased-asInstanceOf.scala

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

1212
val ds: Dataset = ???
1313

14-
lazy val collD = new Column
14+
val collD = new Column
1515

1616
ds.select(collD)
1717

tests/pos/erased-conforms.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ erased class <::<[-From, +To] extends ErasedTerm
55

66
erased class =::=[From, To] extends (From <::< To)
77

8-
inline given [X] => (X =::= X) = scala.compiletime.erasedValue
8+
inline given [X] => (X =::= X) = scala.caps.unsafe.unsafeErasedValue
99

1010
extension [From](x: From)
1111
inline def cast[To](using From <::< To): To = x.asInstanceOf[To] // Safe cast because we know `From <:< To`

tests/pos/erased-pure.scala

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import language.experimental.erasedDefinitions
2+
import caps.unsafe.unsafeErasedValue
23

34
inline def id[T](x: T) = x
45

@@ -8,6 +9,7 @@ def foo[T](erased x: T): Unit = ()
89

910
class Pair[A, B](x: A, y: B)
1011

12+
case class Pair2[A, B](x: A, y: B)
1113

1214
def Test =
1315
foo(C())
@@ -17,7 +19,8 @@ def Test =
1719
foo(Pair(C(), "hello" + "world"))
1820
foo(id(Pair(id(C()), id("hello" + "world"))))
1921

20-
21-
22+
//erased val x1 = Pair(unsafeErasedValue[Int], unsafeErasedValue[String])
23+
//erased val x2 = Pair2(unsafeErasedValue[Int], unsafeErasedValue[String])
24+
erased val x3 = Tuple2(unsafeErasedValue[Int], unsafeErasedValue[String])
2225

2326

tests/pos/i11864.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ final class CallbackTo[+A] {
4040
object CallbackTo {
4141

4242
type MapGuard[A] = { type Out = A }
43-
inline given MapGuard: [A] => MapGuard[A] = compiletime.erasedValue
43+
inline given MapGuard: [A] => MapGuard[A] = caps.unsafe.unsafeErasedValue
4444

4545
def traverse[A, B](ta: List[A]): CallbackTo[List[B]] =
4646
val x: CallbackTo[List[A] => List[B]] = ???

tests/pos/i11896.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import scala.language.experimental.erasedDefinitions
22

33
type X
4-
inline def x: X = compiletime.erasedValue
4+
inline def x: X = caps.unsafe.unsafeErasedValue
55

66
def foo(using erased X): Unit = ()
77

tests/pos/i5938.scala

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import scala.language.experimental.erasedDefinitions
22

33
import compiletime.summonFrom
4-
import compiletime.erasedValue
54

65
trait Link[T, A]
76

@@ -15,7 +14,7 @@ transparent inline def link[T] =
1514

1615
class Foo
1716
object Foo {
18-
erased implicit val barLink: Link[Foo, Bar.type] = erasedValue
17+
erased implicit val barLink: Link[Foo, Bar.type] = caps.unsafe.unsafeErasedValue
1918
}
2019

2120
implicit object Bar {

tests/pos/i7868.scala

Lines changed: 0 additions & 42 deletions
This file was deleted.

tests/pos/matchtype.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import scala.language.experimental.erasedDefinitions
2-
import compiletime.erasedValue
2+
import caps.unsafe.unsafeErasedValue as erasedValue
33
import compiletime.ops.int.S
44
object Test {
55
type T[X] = X match {

tests/pos/phantom-Evidence.scala

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import scala.language.experimental.erasedDefinitions
2+
import annotation.publicInBinary
23

34
/** In this implementation variant of =:= (called =::=) we erase all instantiations and definitions of =::= */
45
object WithNormalState {
@@ -11,9 +12,9 @@ object WithNormalState {
1112
object Instance {
1213
def newInstance(): Instance[Off] = new Instance[Off]
1314
}
14-
class Instance[S <: State] private {
15-
def getOnInstance (using erased ev: S =::= Off): Instance[On] = new Instance[On] // phantom parameter ev is erased
16-
def getOffInstance (using erased ev: S =::= On): Instance[Off] = new Instance[Off] // phantom parameter ev is erased
15+
class Instance[S <: State] @publicInBinary private {
16+
inline def getOnInstance (using erased ev: S =::= Off): Instance[On] = new Instance[On] // phantom parameter ev is erased
17+
inline def getOffInstance (using erased ev: S =::= On): Instance[Off] = new Instance[Off] // phantom parameter ev is erased
1718
}
1819

1920
def run() = {
@@ -26,5 +27,5 @@ object WithNormalState {
2627

2728
object Utils {
2829
type =::=[From, To]
29-
inline given tpEquals: [A] => (A =::= A) = compiletime.erasedValue
30+
inline given tpEquals: [A] => (A =::= A) = caps.unsafe.unsafeErasedValue
3031
}

tests/run/erased-18.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ object Test {
1111
)(foo)
1212
}
1313

14-
def foo = {
15-
println("foo")
14+
inline def foo = {
15+
//println("foo")
1616
42
1717
}
1818
}

tests/run/erased-machine-state.scala

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,15 @@ final class Off extends State
99
@implicitNotFound("State must be Off")
1010
class IsOff[S <: State]
1111
object IsOff {
12-
implicit def isOff: IsOff[Off] = {
13-
println("isOff")
12+
inline implicit def isOff: IsOff[Off] = {
1413
new IsOff[Off]
1514
}
1615
}
1716

1817
@implicitNotFound("State must be On")
1918
class IsOn[S <: State]
2019
object IsOn {
21-
implicit def isOn: IsOn[On] = {
22-
println("isOn")
20+
inline implicit def isOn: IsOn[On] = {
2321
new IsOn[On]
2422
}
2523
}

tests/run/erased-poly-ref.scala

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,9 @@ object Test {
88

99
def fun(erased a: Int): Unit = println("fun")
1010

11-
def foo[P](erased x: Int)(erased y: Int): Int = 0
11+
inline def foo[P](erased x: Int)(erased y: Int): Int = 0
1212

13-
def bar(x: Int) = {
14-
println(x)
13+
inline def bar(x: Int) = {
1514
x
1615
}
1716
}

tests/run/i11996.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ final class UnivEq[A]
44

55
object UnivEq:
66
inline def force[A]: UnivEq[A] =
7-
compiletime.erasedValue
7+
caps.unsafe.unsafeErasedValue
88

99
extension [A](a: A)
1010
inline def ==*[B >: A](b: B)(using erased UnivEq[B]): Boolean = a == b

0 commit comments

Comments
 (0)