Skip to content

Commit ade6ff2

Browse files
committed
Improve handling of compiletime.erasedValue
1 parent 2be0ec5 commit ade6ff2

File tree

7 files changed

+112
-22
lines changed

7 files changed

+112
-22
lines changed

compiler/src/dotty/tools/dotc/ast/TreeInfo.scala

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -667,8 +667,6 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
667667
* or its type is a constant type
668668
* IdempotentPath if reference is lazy and stable
669669
* Impure otherwise
670-
* @DarkDimius: need to make sure that lazy accessor methods have Lazy and Stable
671-
* flags set.
672670
*/
673671
def refPurity(tree: Tree)(using Context): PurityLevel = {
674672
val sym = tree.symbol

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -569,7 +569,6 @@ object Flags {
569569
val EnumCase: FlagSet = Case | Enum
570570
val CovariantLocal: FlagSet = Covariant | Local // A covariant type parameter
571571
val ContravariantLocal: FlagSet = Contravariant | Local // A contravariant type parameter
572-
val EffectivelyErased = PhantomSymbol | Erased
573572
val ConstructorProxyModule: FlagSet = PhantomSymbol | Module
574573
val CaptureParam: FlagSet = PhantomSymbol | StableRealizable | Synthetic
575574
val DefaultParameter: FlagSet = HasDefault | Param // A Scala 2x default parameter

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1051,10 +1051,14 @@ object SymDenotations {
10511051
&& owner.ne(defn.StringContextClass)
10521052

10531053
/** An erased value or an erased inline method or field */
1054+
def isErased(using Context): Boolean =
1055+
is(Erased) || defn.erasedValueMethods.contains(symbol)
1056+
1057+
/** An erased value, a phantom symbol or an erased inline method or field */
10541058
def isEffectivelyErased(using Context): Boolean =
1055-
isOneOf(EffectivelyErased)
1059+
isErased
1060+
|| is(PhantomSymbol)
10561061
|| is(Inline) && !isRetainedInline && !hasAnnotation(defn.ScalaStaticAnnot)
1057-
|| defn.erasedValueMethods.contains(symbol)
10581062

10591063
/** Is this a member that will become public in the generated binary */
10601064
def hasPublicInBinary(using Context): Boolean =

compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala

Lines changed: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,19 @@ class InlineReducer(inliner: Inliner)(using Context):
172172

173173
val isImplicit = scrutinee.isEmpty
174174

175+
val unusable: util.EqHashSet[Symbol] = util.EqHashSet()
176+
177+
/** Adjust internaly generated value definitions;
178+
* - If the RHS refers to an erased symbol, mark the val as erased
179+
* - If the RHS refers to an erased symbol, mark the val as unsuable
180+
*/
181+
def adjustErased(sym: TermSymbol, rhs: Tree): Unit =
182+
rhs.foreachSubTree:
183+
case id: Ident if id.symbol.isErased =>
184+
sym.setFlag(Erased)
185+
if unusable.contains(id.symbol) then unusable += sym
186+
case _ =>
187+
175188
/** Try to match pattern `pat` against scrutinee reference `scrut`. If successful add
176189
* bindings for variables bound in this pattern to `caseBindingMap`.
177190
*/
@@ -184,10 +197,11 @@ class InlineReducer(inliner: Inliner)(using Context):
184197
/** Create a binding of a pattern bound variable with matching part of
185198
* scrutinee as RHS and type that corresponds to RHS.
186199
*/
187-
def newTermBinding(sym: TermSymbol, rhs: Tree): Unit = {
188-
val copied = sym.copy(info = rhs.tpe.widenInlineScrutinee, coord = sym.coord, flags = sym.flags &~ Case).asTerm
200+
def newTermBinding(sym: TermSymbol, rhs: Tree): Unit =
201+
val copied = sym.copy(info = rhs.tpe.widenInlineScrutinee, coord = sym.coord,
202+
flags = sym.flags &~ Case).asTerm
203+
adjustErased(copied, rhs)
189204
caseBindingMap += ((sym, ValDef(copied, constToLiteral(rhs)).withSpan(sym.span)))
190-
}
191205

192206
def newTypeBinding(sym: TypeSymbol, alias: Type): Unit = {
193207
val copied = sym.copy(info = TypeAlias(alias), coord = sym.coord).asType
@@ -306,6 +320,7 @@ class InlineReducer(inliner: Inliner)(using Context):
306320
case (Nil, Nil) => true
307321
case (pat :: pats1, selector :: selectors1) =>
308322
val elem = newSym(InlineBinderName.fresh(), Synthetic, selector.tpe.widenInlineScrutinee).asTerm
323+
adjustErased(elem, selector)
309324
val rhs = constToLiteral(selector)
310325
elem.defTree = rhs
311326
caseBindingMap += ((NoSymbol, ValDef(elem, rhs).withSpan(elem.span)))
@@ -341,16 +356,18 @@ class InlineReducer(inliner: Inliner)(using Context):
341356
val scrutineeSym = newSym(InlineScrutineeName.fresh(), Synthetic, scrutType).asTerm
342357
val scrutineeBinding = normalizeBinding(ValDef(scrutineeSym, scrutinee))
343358

344-
// If scrutinee has embedded `compiletime.erasedValue[T]` expressions, convert them to
345-
// mark scrutineeSym as Erased. This means that the scrutinee cannot be referenced in
346-
// the reduced term. It is NOT checked that scrutinee is a pure expression, since
347-
// there is a special case in Erase that exempts the RHS of an erased scrutinee definition.
348-
if scrutinee.existsSubTree:
349-
case tree @ TypeApply(fn, args) => tree.symbol == defn.Compiletime_erasedValue
350-
case _ => false
351-
then
359+
// If scrutinee has embedded references to `compiletime.erasedValue` or to
360+
// other erased values, mark scrutineeSym as Erased. In addition, if scrutinee
361+
// is not a pure expression, mark scrutineeSym as unusable. The reason is that
362+
// scrutinee would then fail the tests in erasure that demand that the RHS of
363+
// an erased val is a pure expression. At the end of the inline match reduction
364+
// we throw out all unusable vals and check that the remaining code does not refer
365+
// to unusable symbols.
366+
// Note that compiletime.erasedValue is treated as erased but not pure, so scrutinees
367+
// containing references to it becomes unusable.
368+
if scrutinee.existsSubTree(_.symbol.isErased) then
352369
scrutineeSym.setFlag(Erased)
353-
370+
if !tpd.isPureExpr(scrutinee) then unusable += scrutineeSym
354371

355372
def reduceCase(cdef: CaseDef): MatchReduxWithGuard = {
356373
val caseBindingMap = new mutable.ListBuffer[(Symbol, MemberDef)]()
@@ -393,7 +410,25 @@ class InlineReducer(inliner: Inliner)(using Context):
393410
case _ => None
394411
}
395412

396-
recur(cases)
413+
for (bindings, expr) <- recur(cases) yield
414+
// drop unusable vals and check that no referenes to unusable symbols remain
415+
val cleanupUnusable = new TreeMap:
416+
override def transform(tree: Tree)(using Context): Tree =
417+
tree match
418+
case tree: ValDef if unusable.contains(tree.symbol) => EmptyTree
419+
case id: Ident if unusable.contains(id.symbol) =>
420+
report.error(
421+
em"""${id.symbol} is unusable in ${ctx.owner} because it refers to an erased expression
422+
|in the selector of an inline match that reduces to
423+
|
424+
|${Block(bindings, expr)}""",
425+
tree.srcPos)
426+
tree
427+
case _ => super.transform(tree)
428+
429+
val bindings1 = bindings.mapConserve(cleanupUnusable.transform).collect:
430+
case mdef: MemberDef => mdef
431+
(bindings1, cleanupUnusable.transform(expr))
397432
}
398433
end InlineReducer
399434

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

Lines changed: 2 additions & 3 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, InlineScrutineeName}
17+
import core.NameKinds.{AdaptedClosureName, BodyRetainerName, DirectMethName}
1818
import core.Scopes.newScopeWith
1919
import core.Decorators.*
2020
import core.Constants.*
@@ -929,8 +929,7 @@ object Erasure {
929929

930930
override def typedValDef(vdef: untpd.ValDef, sym: Symbol)(using Context): Tree =
931931
if sym.isEffectivelyErased then
932-
if !sym.name.is(InlineScrutineeName) then
933-
checkPureErased(vdef.rhs, isArgument = false)
932+
checkPureErased(vdef.rhs, isArgument = false)
934933
erasedDef(sym)
935934
else trace(i"erasing $vdef"):
936935
checkNotErasedClass(sym.info, vdef)

tests/neg/i23406.check

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
-- Error: tests/neg/i23406.scala:18:7 ----------------------------------------------------------------------------------
2+
18 | funny[String] // error
3+
| ^^^^^^^^^^^^^
4+
| value x is unusable in method Test because it refers to an erased expression
5+
| in the selector of an inline match that reduces to
6+
|
7+
| {
8+
| erased val $scrutinee1: String = compiletime.package$package.erasedValue[String]
9+
| erased val x: String = $scrutinee1
10+
| {
11+
| x:String
12+
| }
13+
| }
14+
|--------------------------------------------------------------------------------------------------------------------
15+
|Inline stack trace
16+
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
17+
|This location contains code that was inlined from i23406.scala:7
18+
7 | case x: String => x
19+
| ^
20+
--------------------------------------------------------------------------------------------------------------------
21+
-- Error: tests/neg/i23406.scala:19:9 ----------------------------------------------------------------------------------
22+
19 | problem[String] // error
23+
| ^^^^^^^^^^^^^^^
24+
| value x is unusable in method Test because it refers to an erased expression
25+
| in the selector of an inline match that reduces to
26+
|
27+
| {
28+
| erased val $scrutinee2: String = compiletime.package$package.erasedValue[String]
29+
| erased val x: String = $scrutinee2
30+
| {
31+
| foo(x)
32+
| }
33+
| }
34+
|--------------------------------------------------------------------------------------------------------------------
35+
|Inline stack trace
36+
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
37+
|This location contains code that was inlined from i23406.scala:11
38+
11 | case x: String => foo(x)
39+
| ^
40+
--------------------------------------------------------------------------------------------------------------------

tests/neg/i23406.scala

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
1+
import language.experimental.erasedDefinitions
2+
3+
def foo(erased x: String): String = ""
4+
15
inline def funny[T]: String =
26
inline compiletime.erasedValue[T] match
37
case x: String => x
48

5-
@main def Test = funny[String] // error
9+
inline def problem[T]: String =
10+
inline compiletime.erasedValue[T] match
11+
case x: String => foo(x)
12+
13+
inline def ok[T]: String =
14+
inline compiletime.erasedValue[T] match
15+
case x: String => "hi"
16+
17+
def Test =
18+
funny[String] // error
19+
problem[String] // error
20+
ok[String]

0 commit comments

Comments
 (0)