Skip to content

Commit 423cd6c

Browse files
authored
Treat new Array(0) as immutable (#19192)
An array of size 0 is immutable, thus we can safely abstract them with the bottom value. For the rules to be simple and understandable, we usually want to avoid such fine-tuning. However, given that we expect such code patterns to be rare and we want to avoid changes in the standard library, we fine-tune the analysis as a compromise.
2 parents 8033e30 + 81db8cd commit 423cd6c

File tree

2 files changed

+25
-10
lines changed

2 files changed

+25
-10
lines changed

compiler/src/dotty/tools/dotc/transform/init/Objects.scala

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -565,7 +565,7 @@ object Objects:
565565

566566
// --------------------------- domain operations -----------------------------
567567

568-
type ArgInfo = TraceValue[Value]
568+
case class ArgInfo(value: Value, trace: Trace, tree: Tree)
569569

570570
extension (a: Value)
571571
def join(b: Value): Value =
@@ -884,9 +884,14 @@ object Objects:
884884

885885
case outer: (Ref | Cold.type | Bottom.type) =>
886886
if klass == defn.ArrayClass then
887-
val arr = OfArray(State.currentObject, summon[Regions.Data])
888-
Heap.writeJoin(arr.addr, Bottom)
889-
arr
887+
args.head.tree.tpe match
888+
case ConstantType(Constants.Constant(0)) =>
889+
// new Array(0)
890+
Bottom
891+
case _ =>
892+
val arr = OfArray(State.currentObject, summon[Regions.Data])
893+
Heap.writeJoin(arr.addr, Bottom)
894+
arr
890895
else
891896
// Widen the outer to finitize the domain. Arguments already widened in `evalArgs`.
892897
val (outerWidened, envWidened) =
@@ -1328,7 +1333,7 @@ object Objects:
13281333
case _ => List()
13291334

13301335
val implicitArgsAfterScrutinee = evalArgs(implicits.map(Arg.apply), thisV, klass)
1331-
val args = implicitArgsBeforeScrutinee(fun) ++ (TraceValue(scrutinee, summon[Trace]) :: implicitArgsAfterScrutinee)
1336+
val args = implicitArgsBeforeScrutinee(fun) ++ (ArgInfo(scrutinee, summon[Trace], EmptyTree) :: implicitArgsAfterScrutinee)
13321337
val unapplyRes = call(receiver, funRef.symbol, args, funRef.prefix, superType = NoType, needResolve = true)
13331338

13341339
if fun.symbol.name == nme.unapplySeq then
@@ -1425,15 +1430,15 @@ object Objects:
14251430
// call .lengthCompare or .length
14261431
val lengthCompareDenot = getMemberMethod(scrutineeType, nme.lengthCompare, lengthCompareType)
14271432
if lengthCompareDenot.exists then
1428-
call(scrutinee, lengthCompareDenot.symbol, TraceValue(Bottom, summon[Trace]) :: Nil, scrutineeType, superType = NoType, needResolve = true)
1433+
call(scrutinee, lengthCompareDenot.symbol, ArgInfo(Bottom, summon[Trace], EmptyTree) :: Nil, scrutineeType, superType = NoType, needResolve = true)
14291434
else
14301435
val lengthDenot = getMemberMethod(scrutineeType, nme.length, lengthType)
14311436
call(scrutinee, lengthDenot.symbol, Nil, scrutineeType, superType = NoType, needResolve = true)
14321437
end if
14331438

14341439
// call .apply
14351440
val applyDenot = getMemberMethod(scrutineeType, nme.apply, applyType(elemType))
1436-
val applyRes = call(scrutinee, applyDenot.symbol, TraceValue(Bottom, summon[Trace]) :: Nil, scrutineeType, superType = NoType, needResolve = true)
1441+
val applyRes = call(scrutinee, applyDenot.symbol, ArgInfo(Bottom, summon[Trace], EmptyTree) :: Nil, scrutineeType, superType = NoType, needResolve = true)
14371442

14381443
if isWildcardStarArgList(pats) then
14391444
if pats.size == 1 then
@@ -1444,7 +1449,7 @@ object Objects:
14441449
else
14451450
// call .drop
14461451
val dropDenot = getMemberMethod(scrutineeType, nme.drop, applyType(elemType))
1447-
val dropRes = call(scrutinee, dropDenot.symbol, TraceValue(Bottom, summon[Trace]) :: Nil, scrutineeType, superType = NoType, needResolve = true)
1452+
val dropRes = call(scrutinee, dropDenot.symbol, ArgInfo(Bottom, summon[Trace], EmptyTree) :: Nil, scrutineeType, superType = NoType, needResolve = true)
14481453
for pat <- pats.init do evalPattern(applyRes, pat)
14491454
evalPattern(dropRes, pats.last)
14501455
end if
@@ -1546,7 +1551,7 @@ object Objects:
15461551
case _ =>
15471552
res.widen(1)
15481553

1549-
argInfos += TraceValue(widened, trace.add(arg.tree))
1554+
argInfos += ArgInfo(widened, trace.add(arg.tree), arg.tree)
15501555
}
15511556
argInfos.toList
15521557

@@ -1644,7 +1649,7 @@ object Objects:
16441649
// The parameter check of traits comes late in the mixin phase.
16451650
// To avoid crash we supply hot values for erroneous parent calls.
16461651
// See tests/neg/i16438.scala.
1647-
val args: List[ArgInfo] = ctor.info.paramInfoss.flatten.map(_ => new ArgInfo(Bottom, Trace.empty))
1652+
val args: List[ArgInfo] = ctor.info.paramInfoss.flatten.map(_ => new ArgInfo(Bottom, Trace.empty, EmptyTree))
16481653
extendTrace(superParent) {
16491654
superCall(tref, ctor, args, tasks)
16501655
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
object A:
2+
val emptyArray = new Array(0)
3+
4+
object B:
5+
def build(data: Int*) =
6+
if data.size == 0 then A.emptyArray else Array(data)
7+
8+
val arr = build(5, 6)
9+
val first = arr(0)
10+

0 commit comments

Comments
 (0)