Skip to content

Commit

Permalink
Treat new Array(0) as immutable (#19192)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
liufengyun authored Dec 14, 2023
2 parents 8033e30 + 81db8cd commit 423cd6c
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 10 deletions.
25 changes: 15 additions & 10 deletions compiler/src/dotty/tools/dotc/transform/init/Objects.scala
Original file line number Diff line number Diff line change
Expand Up @@ -565,7 +565,7 @@ object Objects:

// --------------------------- domain operations -----------------------------

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

extension (a: Value)
def join(b: Value): Value =
Expand Down Expand Up @@ -884,9 +884,14 @@ object Objects:

case outer: (Ref | Cold.type | Bottom.type) =>
if klass == defn.ArrayClass then
val arr = OfArray(State.currentObject, summon[Regions.Data])
Heap.writeJoin(arr.addr, Bottom)
arr
args.head.tree.tpe match
case ConstantType(Constants.Constant(0)) =>
// new Array(0)
Bottom
case _ =>
val arr = OfArray(State.currentObject, summon[Regions.Data])
Heap.writeJoin(arr.addr, Bottom)
arr
else
// Widen the outer to finitize the domain. Arguments already widened in `evalArgs`.
val (outerWidened, envWidened) =
Expand Down Expand Up @@ -1328,7 +1333,7 @@ object Objects:
case _ => List()

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

if fun.symbol.name == nme.unapplySeq then
Expand Down Expand Up @@ -1425,15 +1430,15 @@ object Objects:
// call .lengthCompare or .length
val lengthCompareDenot = getMemberMethod(scrutineeType, nme.lengthCompare, lengthCompareType)
if lengthCompareDenot.exists then
call(scrutinee, lengthCompareDenot.symbol, TraceValue(Bottom, summon[Trace]) :: Nil, scrutineeType, superType = NoType, needResolve = true)
call(scrutinee, lengthCompareDenot.symbol, ArgInfo(Bottom, summon[Trace], EmptyTree) :: Nil, scrutineeType, superType = NoType, needResolve = true)
else
val lengthDenot = getMemberMethod(scrutineeType, nme.length, lengthType)
call(scrutinee, lengthDenot.symbol, Nil, scrutineeType, superType = NoType, needResolve = true)
end if

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

if isWildcardStarArgList(pats) then
if pats.size == 1 then
Expand All @@ -1444,7 +1449,7 @@ object Objects:
else
// call .drop
val dropDenot = getMemberMethod(scrutineeType, nme.drop, applyType(elemType))
val dropRes = call(scrutinee, dropDenot.symbol, TraceValue(Bottom, summon[Trace]) :: Nil, scrutineeType, superType = NoType, needResolve = true)
val dropRes = call(scrutinee, dropDenot.symbol, ArgInfo(Bottom, summon[Trace], EmptyTree) :: Nil, scrutineeType, superType = NoType, needResolve = true)
for pat <- pats.init do evalPattern(applyRes, pat)
evalPattern(dropRes, pats.last)
end if
Expand Down Expand Up @@ -1546,7 +1551,7 @@ object Objects:
case _ =>
res.widen(1)

argInfos += TraceValue(widened, trace.add(arg.tree))
argInfos += ArgInfo(widened, trace.add(arg.tree), arg.tree)
}
argInfos.toList

Expand Down Expand Up @@ -1644,7 +1649,7 @@ object Objects:
// The parameter check of traits comes late in the mixin phase.
// To avoid crash we supply hot values for erroneous parent calls.
// See tests/neg/i16438.scala.
val args: List[ArgInfo] = ctor.info.paramInfoss.flatten.map(_ => new ArgInfo(Bottom, Trace.empty))
val args: List[ArgInfo] = ctor.info.paramInfoss.flatten.map(_ => new ArgInfo(Bottom, Trace.empty, EmptyTree))
extendTrace(superParent) {
superCall(tref, ctor, args, tasks)
}
Expand Down
10 changes: 10 additions & 0 deletions tests/init-global/pos/array-size-zero.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
object A:
val emptyArray = new Array(0)

object B:
def build(data: Int*) =
if data.size == 0 then A.emptyArray else Array(data)

val arr = build(5, 6)
val first = arr(0)

0 comments on commit 423cd6c

Please sign in to comment.