Skip to content

Commit a93c5f3

Browse files
committed
Treat new Array(0) as immutable
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.
1 parent 7a56da7 commit a93c5f3

File tree

2 files changed

+28
-13
lines changed

2 files changed

+28
-13
lines changed

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

Lines changed: 18 additions & 13 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 =
@@ -875,7 +875,7 @@ object Objects:
875875
* @param ctor The symbol of the target constructor.
876876
* @param args The arguments passsed to the constructor.
877877
*/
878-
def instantiate(outer: Value, klass: ClassSymbol, ctor: Symbol, args: List[ArgInfo]): Contextual[Value] = log("instantiating " + klass.show + ", outer = " + outer + ", args = " + args.map(_.value.show), printer, (_: Value).show) {
878+
def instantiate(outer: Value, klass: ClassSymbol, ctor: Symbol, args: List[ArgInfo], inKlass: ClassSymbol): Contextual[Value] = log("instantiating " + klass.show + ", outer = " + outer + ", args = " + args.map(_.value.show), printer, (_: Value).show) {
879879
outer match
880880

881881
case _ : Fun | _: OfArray =>
@@ -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) =
@@ -909,7 +914,7 @@ object Objects:
909914
instance
910915

911916
case ValueSet(values) =>
912-
values.map(ref => instantiate(ref, klass, ctor, args)).join
917+
values.map(ref => instantiate(ref, klass, ctor, args, inKlass)).join
913918
}
914919

915920
/** Handle local variable definition, `val x = e` or `var x = e`.
@@ -1083,7 +1088,7 @@ object Objects:
10831088
val cls = tref.classSymbol.asClass
10841089
withTrace(trace2) {
10851090
val outer = outerValue(tref, thisV, klass)
1086-
instantiate(outer, cls, ctor, args)
1091+
instantiate(outer, cls, ctor, args, klass)
10871092
}
10881093

10891094
case Apply(ref, arg :: Nil) if ref.symbol == defn.InitRegionMethod =>
@@ -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)