You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Nullability: a demand overruled by a null-tolerant analysed override (Joiner's useForNull) #85
#68's tolerance walk fails on a parameter the callee demands non-null (demanded(m, pi)). The call may be virtual: guava's Joiner.appendTo calls toString(parts.next()); the base Joiner.toString(part) does requireNonNull(part), but the useForNull override reads part and demands nothing (part == null ? nullText : ...). For the Kotlin printer a parameter must be nullable when some dispatch target accepts null, so a demand in the declaring method should be overruled by an analysed override that reads the parameter and demands nothing of it.
The rule was tried (reverted, not in any commit): in tolerates(), case ParameterInfo, if (demanded(..)) { if (toleratedByOverride(m, pi)) continue; return false; }, with implementations.get(m) giving the overrides. It did not decide Joiner on its own because the walk never reaches toString: nothing in the successors map connects the anonymous AbstractList.get()'s return to appendTo's parts.next(). That hop is the library's element slot (Iterable<E>'s Arg(.., 0) / iterator().next()). Two parts, then:
an edge from the return of an override of AbstractList.get / Iterator.next to the element slot of the object (the anonymous class instance), so that iterable(..)'s return carries first/second/rest as elements;
the override rule above, gated.
Until then the return of a library override is a stop (gate NOLIBRARYRETURN, ws/maddi-null-libreturn, fb21cf26): tolerant by construction, which is what guava's annotations say but not a derivation.
Check against #68's exclusions: hook (an empty override is no evidence: read(o, op) handles it) and overridesAnalysed.
Found while fixing the #72 4a follow-ups (the switch fork exposed Joiner's first/second/rest as UNSAFE in the oracle).
Implemented in 9c250e5 (ws/maddi-null, not merged yet), both parts:
Edge into the element slot.GraphCompletion.overrideReturnsIntoSlots adds an edge from an anonymous class's override of a library superclass method that returns a type parameter (AbstractList.get → E) to the anonymous this's slot for that parameter. That slot already reached what the creation is assigned to (MethodWalker.anonymousThisTarget).
Two more changes were needed under the Kotlin policy. Contracts.overrideContracts no longer holds such a return non-null (overridesKotlinNonNullReturn had made it a contract). And the closure lets the value into the slot as content rather than as an asserted write (NullGraph.elementHandedOut). Otherwise the result would be get(): Any? overriding AbstractList<Any>.get(): Any.
Gate NOOVERRIDERETURNSLOTS.
The override rule. A demand in the declaring method is overruled by an analysed override that reads the parameter and demands nothing. The walk then continues in that override's parameter. Gate NOOVERRIDETOLERATES.
A third change was needed for the walk to get through:
Tolerance.declaredSlot no longer ends the walk at an anonymous class's type argument or at a private method's return slot. Neither is a declaration a caller sees. Gate NOINTERNALSLOTS.
Result (TestTolerance.toleranceThroughOverrideAndAnonymousElement, Kotlin policy):
join(first, second, rest): first and second are Object?, rest is Object[].
$1.get(int) is Object? and iterable(..) is Iterable<Object?>.
Without the useForNull override, first stays Object, as it does with either of the two main gates set.
Before this change, the Kotlin policy had first as Object: the walk stopped at the contract on get's return.
The analyzer suite passes (683, 0 failed). Pending: a guava/langchain4j printer comparison with all three gates set.
Two regressions from 9c250e5, both fixed on ws/maddi-null (measured by maddi-all on laser1, lanes o/a/c, against devel 4179d4e):
Cycle of delegating overrides (d5f1cfd, gate NOOVERRIDECYCLES). guava's Ordering.min(Iterable) demands its argument; ReverseOrdering and ReverseNaturalOrdering only forward it to max(Iterable), whose overrides forward it back. Every override counted as tolerating, and none reads null. Effect: 62 oracle NOISE rows; ReverseOrdering.kt and ReverseNaturalOrdering.kt out of the J2K bytecode. Now an override overrules a demand only when it compares the parameter with null, or when its own walk ends tolerant (least fixpoint).
Override return slot leaking into written type arguments (6a84d39, gate NOPRIVATECREATOR). jenkins' public RingBufferLogHandler.getView(): the nullable element reached SlaveLogFetcher extends MasterToSlaveCallable<List<LogRecord>, …> and an anonymous FeedAdapter<LogRecord>, which the family does not rewrite. That gave 3 Kotlin type errors. The slot now applies only to an anonymous object created in a private method; guava's Joiner.iterable is private.
Merged into devel in dd13ad9: the original fix 9c250e5 and both regression fixes, d5f1cfd and 6a84d39. The guava oracle ratchet moves to 1,779 noise in a follow-up commit.
Activity
bnaudts commentedon Nov 4, 2026
Implemented in 9c250e5 (ws/maddi-null, not merged yet), both parts:
GraphCompletion.overrideReturnsIntoSlotsadds an edge from an anonymous class's override of a library superclass method that returns a type parameter (AbstractList.get→E) to the anonymousthis's slot for that parameter. That slot already reached what the creation is assigned to (MethodWalker.anonymousThisTarget).Contracts.overrideContractsno longer holds such a return non-null (overridesKotlinNonNullReturnhad made it a contract). And the closure lets the value into the slot as content rather than as an asserted write (NullGraph.elementHandedOut). Otherwise the result would beget(): Any?overridingAbstractList<Any>.get(): Any.NOOVERRIDERETURNSLOTS.NOOVERRIDETOLERATES.A third change was needed for the walk to get through:
Tolerance.declaredSlotno longer ends the walk at an anonymous class's type argument or at a private method's return slot. Neither is a declaration a caller sees. GateNOINTERNALSLOTS.Result (
TestTolerance.toleranceThroughOverrideAndAnonymousElement, Kotlin policy):join(first, second, rest): first and second areObject?, rest isObject[].$1.get(int)isObject?anditerable(..)isIterable<Object?>.useForNulloverride, first staysObject, as it does with either of the two main gates set.Object: the walk stopped at the contract on get's return.The analyzer suite passes (683, 0 failed). Pending: a guava/langchain4j printer comparison with all three gates set.
Nullability: a demand overruled by a null-tolerant override, through …
bnaudts commentedon Nov 5, 2026
Two regressions from 9c250e5, both fixed on ws/maddi-null (measured by maddi-all on laser1, lanes o/a/c, against devel 4179d4e):
NOOVERRIDECYCLES). guava'sOrdering.min(Iterable)demands its argument;ReverseOrderingandReverseNaturalOrderingonly forward it tomax(Iterable), whose overrides forward it back. Every override counted as tolerating, and none reads null. Effect: 62 oracle NOISE rows;ReverseOrdering.ktandReverseNaturalOrdering.ktout of the J2K bytecode. Now an override overrules a demand only when it compares the parameter with null, or when its own walk ends tolerant (least fixpoint).NOPRIVATECREATOR). jenkins' publicRingBufferLogHandler.getView(): the nullable element reachedSlaveLogFetcher extends MasterToSlaveCallable<List<LogRecord>, …>and an anonymousFeedAdapter<LogRecord>, which the family does not rewrite. That gave 3 Kotlin type errors. The slot now applies only to an anonymous object created in a private method; guava'sJoiner.iterableis private.Joiner.ktstill printsobject : AbstractList<Any?>(). Analyzer suite 686/0; one flaky run of TestStaticAndSelfFields is filed as #95.bnaudts commentedon Nov 5, 2026
Merged into devel in dd13ad9: the original fix 9c250e5 and both regression fixes, d5f1cfd and 6a84d39. The guava oracle ratchet moves to 1,779 noise in a follow-up commit.
Nullability: a cycle of delegating overrides does not overrule a dema…
Nullability: an override return slot only for an anonymous object cre…
Nullability oracle ratchet: 1,779 noise on devel (#85)