Skip to content

Commit bb370bd

Browse files
cpovirkGoogle Java Core Libraries
authored and
Google Java Core Libraries
committed
Tweak SubjectUtils.
RELNOTES=n/a PiperOrigin-RevId: 765325292
1 parent c7e87b8 commit bb370bd

File tree

3 files changed

+74
-10
lines changed

3 files changed

+74
-10
lines changed

core/src/main/java/com/google/common/truth/IterableSubject.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -634,8 +634,8 @@ private static String keyToServeAsHeader(String label, DuplicateGroupedAndTyped
634634
* an associated value, so it won't factor into alignment.
635635
*/
636636
String key = keyToGoWithElementsString(label, elements);
637-
if (elements.homogeneousTypeToDisplay != null) {
638-
key += " (" + elements.homogeneousTypeToDisplay + ")";
637+
if (elements.getHomogeneousTypeToDisplay() != null) {
638+
key += " (" + elements.getHomogeneousTypeToDisplay() + ")";
639639
}
640640
return key;
641641
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright (C) 2021 Google, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
15+
package com.google.common.truth;
16+
17+
import org.jspecify.annotations.Nullable;
18+
19+
/** A utility method to perform unchecked casts to suppress errors produced by nullness analyses. */
20+
final class NullnessCasts {
21+
/**
22+
* Accepts a {@code @Nullable T} and returns a plain {@code T}, without performing any check that
23+
* that conversion is safe.
24+
*
25+
* <p>This method is intended to help with usages of type parameters that have parametric
26+
* nullness. If a type parameter instead ranges over only non-null types (or if the type is a
27+
* non-variable type, like {@code String}), then code should almost never use this method,
28+
* preferring instead to call {@code requireNonNull} so as to benefit from its runtime check.
29+
*
30+
* <p>An example use case for this method is in implementing an {@code Iterator<T>} whose {@code
31+
* next} field is lazily initialized. The type of that field would be {@code @Nullable T}, and the
32+
* code would be responsible for populating a "real" {@code T} (which might still be the value
33+
* {@code null}!) before returning it to callers. Depending on how the code is structured, a
34+
* nullness analysis might not understand that the field has been populated. To avoid that problem
35+
* without having to add {@code @SuppressWarnings}, the code can call this method.
36+
*
37+
* <p>Why <i>not</i> just add {@code SuppressWarnings}? The problem is that this method is
38+
* typically useful for {@code return} statements. That leaves the code with two options: Either
39+
* add the suppression to the whole method (which turns off checking for a large section of code),
40+
* or extract a variable, and put the suppression on that. However, a local variable typically
41+
* doesn't work: Because nullness analyses typically infer the nullness of local variables,
42+
* there's no way to assign a {@code @Nullable T} to a field {@code T foo;} and instruct the
43+
* analysis that that means "plain {@code T}" rather than the inferred type {@code @Nullable T}.
44+
* (Even if supported added {@code @NonNull}, that would not help, since the problem case
45+
* addressed by this method is the case in which {@code T} has parametric nullness -- and thus its
46+
* value may be legitimately {@code null}.)
47+
*/
48+
@SuppressWarnings("nullness")
49+
static <T extends @Nullable Object> T uncheckedCastNullableTToT(@Nullable T t) {
50+
return t;
51+
}
52+
53+
private NullnessCasts() {}
54+
}

core/src/main/java/com/google/common/truth/SubjectUtils.java

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import static com.google.common.collect.Iterables.isEmpty;
2121
import static com.google.common.collect.Iterables.transform;
2222
import static com.google.common.collect.Multisets.immutableEntry;
23+
import static com.google.common.truth.NullnessCasts.uncheckedCastNullableTToT;
2324

2425
import com.google.common.base.Equivalence;
2526
import com.google.common.base.Equivalence.Wrapper;
@@ -49,15 +50,20 @@ private SubjectUtils() {}
4950

5051
static final String HUMAN_UNDERSTANDABLE_EMPTY_STRING = "\"\" (empty String)";
5152

52-
static <T extends @Nullable Object> List<T> accumulate(T first, T second, T @Nullable ... rest) {
53+
static <T extends @Nullable Object> List<T> accumulate(T first, T second, T @Nullable [] rest) {
5354
// rest should never be deliberately null, so assume that the caller passed null
5455
// in the third position but intended it to be the third element in the array of values.
5556
// Javac makes the opposite inference, so handle that here.
5657
List<T> items = new ArrayList<>(2 + ((rest == null) ? 1 : rest.length));
5758
items.add(first);
5859
items.add(second);
5960
if (rest == null) {
60-
items.add((T) null);
61+
/*
62+
* This cast is probably not actually safe as used in IterableSubject.UsingCorrespondence. But
63+
* that whole API is stuck being type-unsafe unless we re-generify IterableSubject:
64+
* b/145689657#comment1.
65+
*/
66+
items.add(uncheckedCastNullableTToT(null));
6167
} else {
6268
items.addAll(asList(rest));
6369
}
@@ -195,15 +201,19 @@ protected int doHash(Object o) {
195201
* elements and even to output different elements on different lines.
196202
*/
197203
static final class DuplicateGroupedAndTyped {
198-
final NonHashingMultiset<?> valuesAndMaybeTypes;
199-
final @Nullable String homogeneousTypeToDisplay;
204+
private final NonHashingMultiset<?> valuesAndMaybeTypes;
205+
private final @Nullable String homogeneousTypeToDisplay;
200206

201207
DuplicateGroupedAndTyped(
202208
NonHashingMultiset<?> valuesAndMaybeTypes, @Nullable String homogeneousTypeToDisplay) {
203209
this.valuesAndMaybeTypes = valuesAndMaybeTypes;
204210
this.homogeneousTypeToDisplay = homogeneousTypeToDisplay;
205211
}
206212

213+
@Nullable String getHomogeneousTypeToDisplay() {
214+
return homogeneousTypeToDisplay;
215+
}
216+
207217
int totalCopies() {
208218
return valuesAndMaybeTypes.totalCopies();
209219
}
@@ -257,10 +267,10 @@ public String toString() {
257267
* <p>Example: {@code hasMatchingToStringPair([1L, 2L], [1]) == true}
258268
*/
259269
static boolean hasMatchingToStringPair(Iterable<?> items1, Iterable<?> items2) {
260-
if (isEmpty(items1) || isEmpty(items2)) {
261-
return false; // Bail early to avoid calling hashCode() on the elements unnecessarily.
262-
}
263-
return !retainMatchingToString(items1, items2).isEmpty();
270+
// Bail early for empty iterables to avoid calling hashCode() on the elements unnecessarily.
271+
return !isEmpty(items1)
272+
&& !isEmpty(items2)
273+
&& !retainMatchingToString(items1, items2).isEmpty();
264274
}
265275

266276
static String objectToTypeName(@Nullable Object item) {

0 commit comments

Comments
 (0)