On Thu, 24 Apr 2025 10:37:59 GMT, Per Minborg <pminb...@openjdk.org> wrote:

>> Implement JEP 502.
>> 
>> The PR passes tier1-tier3 tests.
>
> Per Minborg has updated the pull request incrementally with one additional 
> commit since the last revision:
> 
>   Make public constuctor private

src/java.base/share/classes/java/lang/StableValue.java line 389:

> 387:  * Invocations of {@link #setOrThrow(Object)} form a total order of zero 
> or more
> 388:  * exceptional invocations followed by zero (if the contents were 
> already set) or one
> 389:  * successful invocation. Since stable functions and stable collections 
> are built on top

How can an exceptional invocation of `setOrThrow` be followed by a successful 
invocation?

src/java.base/share/classes/java/lang/StableValue.java line 447:

> 445:         permits StableValueImpl {
> 446: 
> 447:     // Principal methods

Not important, not practical and hacky (exceptions as control flow), but 
technically all methods could be implemented in terms of `orElseSet`, so they 
could also be called "convenience methods":


trySet(c) {
    success = false
    orElseSet(() -> {success = true; return c})
    return succes
}

orElse(o) {
    try { return orElseSet(() -> throw) }
    catch { return o }
}

orElseThrow() {
    orElseSet(() -> throw)
}

isSet() {
    try { orElseSet(() -> throw) }
    catch { return false }
    return true
}

setOrThrow(c) {
    success = false
    orElseSet(() -> {success = true; return c})
    if (!success) throw
}

I guess these two comments (`// Principal methods` and `// Convenience 
methods`) could also just be omitted.

src/java.base/share/classes/java/lang/StableValue.java line 523:

> 521:      * @throws IllegalStateException if the contents was already set
> 522:      */
> 523:     void setOrThrow(T contents);

Should probably also mention that `IllegalStateException` can be caused by 
recursive initialization (see `trySet`).

src/java.base/share/classes/java/lang/StableValue.java line 577:

> 575:      * already under computation will block until a value is computed or 
> an exception is
> 576:      * thrown by the computing thread. The computing threads will then 
> observe the newly
> 577:      * computed value (if any) and will then never execute.

This sentence seems off. I think it should say "competing threads" instead of 
"computing threads". And what will they never execute?

src/java.base/share/classes/java/lang/StableValue.java line 688:

> 686:      * <p>
> 687:      * Any direct {@link List#subList(int, int) subList} or {@link 
> List#reversed()} views
> 688:      * of the returned list are also stable.

What about non-direct views? I think they could be made stable (i.e. have a 
non-evaluating `toString`) with few changes:

Override `subList` in 
`ImmutableCollections.StableList.StableReverseOrderListView` similar to 
`ReverseOrderListView.subList`:

@Override
public List<E> subList(int fromIndex, int toIndex) {
    int size = base.size();
    subListRangeCheck(fromIndex, toIndex, size);
    return new StableReverseOrderListView<>(base.subList(size - toIndex, size - 
fromIndex));
}


Override `reversed` in `ImmutableCollections.SubList`:

@Override
public List<E> reversed() {
    if (root instanceof StableList) {
        return new StableList.StableReverseOrderListView<>(this);
    } else {
        return super.reversed();
    }
}


Change `ImmutableCollections.StableList.StableReverseOrderListView.toString` 
(instead of copying and reversing, `StableUtil.renderElements` could also be 
modified to have a configurable iteration order):

@Override
public String toString() {
    final StableList<E> stable;
    final int from, to;
    if (base instanceof SubList<E> sub) {
        stable = (StableList<E>)sub.root;
        from = sub.offset;
        to = sub.offset + sub.size();
    } else {
        stable = (StableList<E>)base;
        from = 0;
        to = stable.delegates.length;
    }
    final StableValueImpl<E>[] reversed = ArraysSupport.reverse(
            Arrays.copyOfRange(stable.delegates, from, to));
    return StableUtil.renderElements(this, "StableList", reversed);
}


That should make sure that however many times and in whatever combination 
`reversed` and `subList` are called on a stable list, it will always be an 
instance of `ImmutableCollections.StableList`, 
`ImmutableCollections.StableList.StableReverseOrderListView` or 
`ImmutableCollections.SubList`. They all properly override `toString`. The view 
nesting depth is also bounded.

Visualized as a table (`SL` = `ImmutableCollections.StableList`, `Rev` = 
`ImmutableCollections.StableList.StableReverseOrderListView`, `Sub` = 
`ImmutableCollections.SubList`):
| view nesting  | calling `reversed` on that view returns | calling `subList` 
on that view returns |
| ------------- | --------------------------------------- | 
-------------------------------------- |
| `SL`          | `Rev(SL)`                               | `Sub(SL)`           
                   |
| `Rev(SL)`     | `SL`                                    | `Rev(Sub(SL))`      
                   |
| `Sub(SL)`     | `Rev(Sub(SL)`                           | `Sub(SL)`           
                   |
| `Rev(Sub(SL)` | `Sub(SL)`                               | `Rev(Sub(SL))`      
                   |

src/java.base/share/classes/java/lang/StableValue.java line 731:

> 729:      * <p>
> 730:      * Any direct {@link Map#values()} or {@link Map#entrySet()} views
> 731:      * of the returned map are also stable.

"direct views" implies there are non-direct views. What would these be? 
`Collection` and `Set` have no methods that return views. The only non-direct 
view I can think of are the `Map.Entry` instances in the entry set, but see 
https://github.com/openjdk/jdk/pull/23972#discussion_r2050713377.

src/java.base/share/classes/java/util/ImmutableCollections.java line 270:

> 268:         // A lazy list is not Serializable so, we cannot return 
> `List.of()` if size == 0
> 269:         return new StableList<>(size, mapper);
> 270:     }

Why have this function for stable lists but not for maps? Also uses "lazy list" 
instead of "stable list" in the comment.

src/java.base/share/classes/java/util/ImmutableCollections.java line 578:

> 576:         public String toString() {
> 577:             if (root instanceof StableList<E> stableList) {
> 578:                 return StableUtil.renderElements(root, "StableList", 
> stableList.delegates, offset, size);

Suggestion:

                return StableUtil.renderElements(this, "StableList", 
stableList.delegates, offset, size);

Other view collections (`ArrayList.SubList`, `ReverseOrderListView`, 
`AbstractMap.keySet`, `AbstractMap.values`, `HashMap.EntrySet`) use the view 
reference (and not the underlying collection) for detecting self containment, 
so `renderElements` should use `this` instead of `root` for `self`.

src/java.base/share/classes/java/util/ImmutableCollections.java line 898:

> 896:                 final StableValueImpl<E>[] reversed = 
> ArraysSupport.reverse(
> 897:                         Arrays.copyOf(delegates, delegates.length));
> 898:                 return StableUtil.renderElements(base, "Collection", 
> reversed);

Suggestion:

                return StableUtil.renderElements(this, "StableList", reversed);

All other calls use "Stable...". Other view collections (`ArrayList.SubList`, 
`ReverseOrderListView`, `AbstractMap.keySet`, `AbstractMap.values`, 
`HashMap.EntrySet`) use the view reference (and not the underlying collection) 
for detecting self containment, so `renderElements` should use `this` instead 
of `base` for `self`.

src/java.base/share/classes/java/util/ImmutableCollections.java line 1660:

> 1658:             public String toString() {
> 1659:                 final StableValueImpl<?>[] values = 
> delegate.values().toArray(GENERATOR);
> 1660:                 return StableUtil.renderElements(StableMap.this, 
> "StableMap", values);

Suggestion:

                return StableUtil.renderElements(this, "StableCollection", 
values);

Other view collections (`ArrayList.SubList`, `ReverseOrderListView`, 
`AbstractMap.keySet`, `AbstractMap.values`, `HashMap.EntrySet`) use the view 
reference (and not the underlying collection) for detecting self containment, 
so `renderElements` should use `this` instead of `StableMap.this` for `self`. 
`ImmutableCollections.StableMap.StableMapValues` is a `Collection`, not a 
`Map`, so use `"StableCollection"` instead of `"StableMap"`.

src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java 
line 51:

> 49:  * @param firstOrdinal the lowest ordinal used
> 50:  * @param delegates    a delegate array of inputs to StableValue mappings
> 51:  * @param original     the original Function

Are `enumType` and `member` missing intentionally?

src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java 
line 70:

> 68:         // Since we did the member.test above, we know the index is in 
> bounds
> 69:         delegate = delegates[index];
> 70:         return delegate.orElseSet(new Supplier<R>() {

Suggestion:

        // Since we did the member.test above, we know the index is in bounds
        return delegates[index].orElseSet(new Supplier<R>() {

I don't think the local variable is needed here.

src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java 
line 88:

> 86:     public String toString() {
> 87:         final E[] enumElements = enumType.getEnumConstants();
> 88:         final Collection<Map.Entry<E, StableValueImpl<R>>> entries = new 
> ArrayList<>(enumElements.length);

Suggestion:

        final Collection<Map.Entry<E, StableValueImpl<R>>> entries = new 
ArrayList<>(delegates.length);

Otherwise the list is bigger than needed (even `delegates.length` could be too 
big but should always be smaller than `enumElements.length`).

src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java 
line 102:

> 100:                                                               Function<? 
> super T, ? extends R> original) {
> 101:         // The input set is not empty
> 102:         final Class<E> enumType = 
> (Class<E>)inputs.iterator().next().getClass();

Suggestion:

        final Class<E> enumType = 
((E)inputs.iterator().next()).getDeclaringClass();

`Class.getEnumConstants` returns `null` for the class object of an enum 
constant with a body. This would lead to a NPE in the next line.

This code should trigger it:

enum E {
    A, B { public String toString() { return "b"; } }
}

StableValue.function(EnumSet.of(E.B), x -> x);

src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java line 
35:

> 33: import java.util.function.Supplier;
> 34: 
> 35: // Note: It would be possible to just use `LazyMap::get` with some 
> additional logic

Is there a class named `LazyMap`? Or was this comment not updated to use the 
"stable" term?

src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java 
line 36:

> 34: // Note: It would be possible to just use `LazyList::get` instead of this
> 35: // class but explicitly providing a class like this provides better
> 36: // debug capability, exception handling, and may provide better 
> performance.

This comment seems somewhat redundant, at least performance is already 
mentioned in the javadoc comment right beneath it. Also `LazyList`, see my 
comment for `StableFunction`.

src/java.base/share/classes/jdk/internal/lang/stable/StableUtil.java line 69:

> 67:             final String valueString;
> 68:             if (value == self) {
> 69:                 valueString = ("(this ") + selfName + ")";

Suggestion:

                valueString = "(this " + selfName + ")";

src/java.base/share/classes/jdk/internal/lang/stable/StableValueImpl.java line 
57:

> 55:             UNSAFE.objectFieldOffset(StableValueImpl.class, "contents");
> 56:     // Used to indicate a holder value is `null` (see field `value` below)
> 57:     // A wrapper method `nullSentinel()` is used for generic type 
> conversion.

Suggestion:

    // Used to indicate a holder value is `null` (see field `contents` below)

The `nullSentinel` method no longer exists, the field is named `contents`.

src/java.base/share/classes/jdk/internal/lang/stable/StableValueImpl.java line 
86:

> 84:         preventReentry();
> 85:         // Mutual exclusion is required here as `orElseSet` might also
> 86:         // attempt to modify the `wrappedValue`

Suggestion:

        // attempt to modify `this.contents`

src/java.base/share/classes/jdk/internal/lang/stable/StableValueImpl.java line 
161:

> 159: 
> 160:     @ForceInline
> 161:     public Object wrappedContentAcquire() {

Maybe name it `wrappedContentsAcquire` (with an "s")? It's called "contents" 
elsewhere.

src/java.base/share/classes/jdk/internal/lang/stable/StableValueImpl.java line 
188:

> 186:      */
> 187:     @ForceInline
> 188:     private boolean wrapAndSet(Object newValue) {

Suggestion:

    private boolean wrapAndSet(T newValue) {

-------------

PR Review Comment: https://git.openjdk.org/jdk/pull/23972#discussion_r2058468433
PR Review Comment: https://git.openjdk.org/jdk/pull/23972#discussion_r2058510873
PR Review Comment: https://git.openjdk.org/jdk/pull/23972#discussion_r2058567306
PR Review Comment: https://git.openjdk.org/jdk/pull/23972#discussion_r2058875533
PR Review Comment: https://git.openjdk.org/jdk/pull/23972#discussion_r2058903119
PR Review Comment: https://git.openjdk.org/jdk/pull/23972#discussion_r2058900859
PR Review Comment: https://git.openjdk.org/jdk/pull/23972#discussion_r2059151416
PR Review Comment: https://git.openjdk.org/jdk/pull/23972#discussion_r2059388757
PR Review Comment: https://git.openjdk.org/jdk/pull/23972#discussion_r2059383526
PR Review Comment: https://git.openjdk.org/jdk/pull/23972#discussion_r2059476366
PR Review Comment: https://git.openjdk.org/jdk/pull/23972#discussion_r2058957558
PR Review Comment: https://git.openjdk.org/jdk/pull/23972#discussion_r2058968283
PR Review Comment: https://git.openjdk.org/jdk/pull/23972#discussion_r2058974011
PR Review Comment: https://git.openjdk.org/jdk/pull/23972#discussion_r2059008635
PR Review Comment: https://git.openjdk.org/jdk/pull/23972#discussion_r2058940541
PR Review Comment: https://git.openjdk.org/jdk/pull/23972#discussion_r2058945089
PR Review Comment: https://git.openjdk.org/jdk/pull/23972#discussion_r2059387502
PR Review Comment: https://git.openjdk.org/jdk/pull/23972#discussion_r2058555891
PR Review Comment: https://git.openjdk.org/jdk/pull/23972#discussion_r2058574430
PR Review Comment: https://git.openjdk.org/jdk/pull/23972#discussion_r2058598520
PR Review Comment: https://git.openjdk.org/jdk/pull/23972#discussion_r2058662923

Reply via email to