Inline singleton pieces in `NestedSet#splitIfExceedsMaximumSize`. When splitting a `NestedSet` whose successor count is not evenly divisible by `maxDegree`, the final piece would previously be stored as a single-element `Object[]`. When added as a transitive member to a new `NestedSet`, this violated the invariant that non-leaf transitive arrays have length >= 2. Fix by directly inlining singleton pieces into the pieces array. PiperOrigin-RevId: 971463001 Change-Id: If49958361412fbe45a9e35b2670d310f6147b645
diff --git a/src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSet.java b/src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSet.java index ce6fe3c..76daac3 100644 --- a/src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSet.java +++ b/src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSet.java
@@ -685,15 +685,19 @@ if (nsuccs <= maxDegree) { return this; } - Object[][] pieces = new Object[ceildiv(nsuccs, maxDegree)][]; + Object[] pieces = new Object[ceildiv(nsuccs, maxDegree)]; for (int i = 0; i < pieces.length; i++) { - int max = Math.min((i + 1) * maxDegree, succs.length); - pieces[i] = Arrays.copyOfRange(succs, i * maxDegree, max); + int start = i * maxDegree; + int end = Math.min(start + maxDegree, succs.length); + if (end - start == 1) { + // We cannot have non-leaves of size 1, so inline the singleton. + pieces[i] = succs[start]; + } else { + pieces[i] = Arrays.copyOfRange(succs, start, end); + } } int depth = getApproxDepth() + 1; // may be an overapproximation - // TODO(adonovan): (preexisting): if the last piece is a singleton, it must be inlined. - // Each piece is now smaller than maxDegree, but there may be many pieces. // Recursively split pieces. (The recursion affects only the root; it // does not traverse into successors.) In practice, maxDegree is large
diff --git a/src/test/java/com/google/devtools/build/lib/collect/nestedset/NestedSetTopologyTest.java b/src/test/java/com/google/devtools/build/lib/collect/nestedset/NestedSetTopologyTest.java index a736e41..7fdde6a 100644 --- a/src/test/java/com/google/devtools/build/lib/collect/nestedset/NestedSetTopologyTest.java +++ b/src/test/java/com/google/devtools/build/lib/collect/nestedset/NestedSetTopologyTest.java
@@ -168,6 +168,17 @@ assertThat(s.getApproxDepth()).isEqualTo(4); } + @Test + public void split_singletonPieceInlined() { + NestedSet<String> set = + NestedSetBuilder.<String>stableOrder().addAll(Arrays.asList("a", "b", "c")).build(); + NestedSet<String> split = set.splitIfExceedsMaximumSize(2); + Object[] children = (Object[]) split.getChildren(); + assertThat(children).hasLength(2); + assertThat(children[0]).isEqualTo(new Object[] {"a", "b"}); + assertThat(children[1]).isEqualTo("c"); // Singleton becomes a leaf. + } + private static <T> List<T> collectCheckSize(NestedSet<T> set, int maxSize) { return collectCheckSize(new ArrayList<>(), set, maxSize); }