Skip to content

Commit 6df2c6e

Browse files
committed
Fix constToLiteral
We previously converted an expression with constant type to a literal if the expression was idempotent. This can hide side effects in the case where the expression is a selection from an object or lazy val. Demanding purity instead prodcues tons of errors involving inline vals on objects. We now demand idempotency if the expression refers to an inline val (or an operation over an inline val), and purity elsewhere. Fixes #2266
1 parent 7032915 commit 6df2c6e

File tree

6 files changed

+41
-26
lines changed

6 files changed

+41
-26
lines changed

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

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -577,21 +577,28 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
577577
// blocks returning a class literal alone, even if they're idempotent.
578578
tree1
579579
case ConstantType(value) =>
580-
if (isIdempotentExpr(tree1)) Literal(value).withSpan(tree.span)
581-
else {
582-
def keepPrefix(pre: Tree) =
580+
def dropOp(t: Tree): Tree = t match
581+
case Select(pre, _) if t.tpe.isInstanceOf[ConstantType] =>
582+
// it's a primitive unary operator
583+
pre
584+
case Apply(TypeApply(Select(pre, nme.getClass_), _), Nil) =>
585+
pre
586+
case _ =>
587+
tree1
588+
589+
val countsAsPure =
590+
if dropOp(tree1).symbol.isInlineVal
591+
then isIdempotentExpr(tree1)
592+
else isPureExpr(tree1)
593+
594+
if countsAsPure then Literal(value).withSpan(tree.span)
595+
else
596+
val pre = dropOp(tree1)
597+
if pre eq tree1 then tree1
598+
else
599+
// it's a primitive unary operator or getClass call;
600+
// Simplify `pre.op` to `{ pre; v }` where `v` is the value of `pre.op`
583601
Block(pre :: Nil, Literal(value)).withSpan(tree.span)
584-
585-
tree1 match {
586-
case Select(pre, _) if tree1.tpe.isInstanceOf[ConstantType] =>
587-
// it's a primitive unary operator; Simplify `pre.op` to `{ pre; v }` where `v` is the value of `pre.op`
588-
keepPrefix(pre)
589-
case Apply(TypeApply(Select(pre, nme.getClass_), _), Nil) =>
590-
keepPrefix(pre)
591-
case _ =>
592-
tree1
593-
}
594-
}
595602
case _ => tree1
596603
}
597604
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,11 @@ object SymUtils:
110110
self.isCoDefinedGiven(res.typeSymbol)
111111
self.isAllOf(Given | Method) && isCodefined(self.info)
112112

113+
// TODO Scala 3.x: only check for inline vals (no final ones)
114+
def isInlineVal(using Context) =
115+
self.isOneOf(FinalOrInline, butNot = Mutable)
116+
&& (!self.is(Method) || self.is(Accessor))
117+
113118
def useCompanionAsSumMirror(using Context): Boolean =
114119
def companionExtendsSum(using Context): Boolean =
115120
self.linkedClass.isSubClass(defn.Mirror_SumClass)

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

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1694,11 +1694,6 @@ class Namer { typer: Typer =>
16941694
case _ =>
16951695
approxTp
16961696

1697-
// println(s"final inherited for $sym: ${inherited.toString}") !!!
1698-
// println(s"owner = ${sym.owner}, decls = ${sym.owner.info.decls.show}")
1699-
// TODO Scala 3.1: only check for inline vals (no final ones)
1700-
def isInlineVal = sym.isOneOf(FinalOrInline, butNot = Method | Mutable)
1701-
17021697
var rhsCtx = ctx.fresh.addMode(Mode.InferringReturnType)
17031698
if sym.isInlineMethod then rhsCtx = rhsCtx.addMode(Mode.InlineableBody)
17041699
if sym.is(ExtensionMethod) then rhsCtx = rhsCtx.addMode(Mode.InExtensionMethod)
@@ -1732,7 +1727,7 @@ class Namer { typer: Typer =>
17321727
// don't strip @uncheckedVariance annot for default getters
17331728
TypeOps.simplify(tp.widenTermRefExpr,
17341729
if defaultTp.exists then TypeOps.SimplifyKeepUnchecked() else null) match
1735-
case ctp: ConstantType if isInlineVal => ctp
1730+
case ctp: ConstantType if sym.isInlineVal => ctp
17361731
case tp => TypeComparer.widenInferred(tp, pt)
17371732

17381733
// Replace aliases to Unit by Unit itself. If we leave the alias in
@@ -1743,7 +1738,7 @@ class Namer { typer: Typer =>
17431738
def lhsType = fullyDefinedType(cookedRhsType, "right-hand side", mdef.span)
17441739
//if (sym.name.toString == "y") println(i"rhs = $rhsType, cooked = $cookedRhsType")
17451740
if (inherited.exists)
1746-
if (isInlineVal) lhsType else inherited
1741+
if sym.isInlineVal then lhsType else inherited
17471742
else {
17481743
if (sym.is(Implicit))
17491744
mdef match {

tests/pos/java-annot/S.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
object C {
2-
val cs: "cs" = "cs"
2+
final val cs: "cs" = "cs"
33
}
44

55
object S {

tests/run/i2266.check

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
X
2+
true
3+
1

tests/run/i2266.scala

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
object Test extends App {
2-
def f = {
3-
lazy val x: true = { println("X"); true }
4-
println(x)
2+
lazy val x: true = { println("X"); true }
3+
println(x)
4+
5+
object Inner {
6+
println("Y") // not printed
7+
inline val y = 1
58
}
6-
f
9+
println(Inner.y)
10+
11+
inline val MV = Int.MaxValue
712
}
813

0 commit comments

Comments
 (0)