Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -449,13 +449,43 @@ && nullableElements(object)) {
.add(new TextImpl("ignoreCase = true")).add(SymbolEnum.RIGHT_PARENTHESIS);
}
// Iterators.<@Nullable Entry<K, V>>getNext(it, null): the type-use annotation's state, as in a class header
String typeArguments = !mc.typeArguments().isEmpty() ? mc.typeArguments().stream()
.map(t -> KotlinTypeName.of(KotlinTypePrinter.declared(t), q)).collect(java.util.stream.Collectors.joining(", ", "<", ">"))
String typeArguments = !mc.typeArguments().isEmpty() ? explicitTypeArguments(mc, q)
: comparingKeyCall(mc) ? comparedType(mc, q) : resultOnlyOrLambdaOnly(mc, q);
b.add(new TextImpl(KotlinNames.name(mc.methodInfo().name()) + (typeArguments == null ? "" : typeArguments)));
return b.add(arguments(mc.parameterExpressions(), mc.methodInfo(), mc, q));
}

/**
* Java's explicit type arguments, {@code Ranges.<Integer>range(2, 4)}, with the type-use annotation's state; and,
* where {@link #widened} prints the call into a declaration whose type argument is nullable, with that state:
* Kotlin's generics are invariant, {@code range<Int>} is no {@code MutableList<Int?>}.
*/
private static String explicitTypeArguments(MethodCall mc, Qualification q) {
List<ParameterizedType> arguments = new ArrayList<>(mc.typeArguments().stream().map(KotlinTypePrinter::declared).toList());
java.util.Map.Entry<MethodCall, ParameterizedType> t = TYPE_ARGUMENTS_TARGET.get();
io.codelaser.maddi.cst.api.info.MethodInfo m = mc.methodInfo();
if (t != null && t.getKey() == mc && m.returnType() != null && m.typeParameters().size() == arguments.size()) {
Map<io.codelaser.maddi.cst.api.type.NamedType, ParameterizedType> concrete;
try {
concrete = m.returnType().formalToConcrete(t.getValue());
} catch (RuntimeException | AssertionError e) {
concrete = Map.of();
}
for (int i = 0; i < arguments.size(); i++) {
ParameterizedType target = concrete.get(m.typeParameters().get(i));
ParameterizedType argument = arguments.get(i);
if (target != null && target.wildcard() == null && KotlinNullability.isNullable(target)
&& sameUpToStates(target, argument)) {
arguments.set(i, argument.withNullable(io.codelaser.maddi.cst.api.type.NullableState.NULLABLE));
}
}
}
return arguments.stream().map(a -> KotlinTypeName.of(a, q)).collect(java.util.stream.Collectors.joining(", ", "<", ">"));
}

/** The call with explicit type arguments that {@link #widened} prints into a declaration of this type. */
private static final ThreadLocal<java.util.Map.Entry<MethodCall, ParameterizedType>> TYPE_ARGUMENTS_TARGET = new ThreadLocal<>();

/** The method returns a type whose type arguments Kotlin types nullable: {@code Ordering<@Nullable S>}. */
private static boolean nullableArgument(io.codelaser.maddi.cst.api.info.MethodInfo methodInfo) {
ParameterizedType verdict = KotlinNullability.returnType(methodInfo);
Expand Down Expand Up @@ -1561,6 +1591,19 @@ && sameUpToStates(c.parameterizedType(), target)) {
TO_ARRAY_TARGET.set(previous);
}
}
if (target != null && target.arrays() == 0 && unwrap(e) instanceof MethodCall mc && mc.methodInfo() != null
&& !mc.typeArguments().isEmpty() && target.parameters().stream().anyMatch(KotlinNullability::isNullable)
&& !optionalOfNullable(e, target) && !rawOf(rawValueType(e), target)) {
// List<Integer> r = Ranges.<Integer>range(2, 4) into a MutableList<Int?>: Kotlin's generics are invariant,
// so the explicit type arguments take the states of what the call initializes, range<Int?>
java.util.Map.Entry<MethodCall, ParameterizedType> previous = TYPE_ARGUMENTS_TARGET.get();
TYPE_ARGUMENTS_TARGET.set(java.util.Map.entry(mc, target));
try {
return print(e, q);
} finally {
TYPE_ARGUMENTS_TARGET.set(previous);
}
}
if (unwrap(e) instanceof Cast c && target != null && target.arrays() > 0
&& c.parameterizedType().arrays() == target.arrays() && sameUpToStates(c.parameterizedType(), target)
&& KotlinTypeName.primitiveArray(c.parameterizedType().componentType()) == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1961,4 +1961,29 @@ static int size() {
contains(kotlin, "val r: MutableList<Int?>");
compiles(kotlin);
}

/**
* As {@link #localTypedWhereGenericCallInfersNonNull}, with Java's explicit type argument: {@code
* Ranges2.<Integer>range(2, 4)} into a local whose elements are nullable, because {@code r.add(null)} writes a null
* into them. Kotlin's generics are invariant: the explicit type argument takes the local's state, {@code
* range<Int?>}; {@code range<Int>} is a {@code MutableList<Int>}, which the {@code MutableList<Int?>} local rejects.
*/
@Test
public void explicitTypeArgumentTakesLocalTypeArgumentState() {
KotlinPrintOptions options = new KotlinPrintOptions(new ByName(Set.of("r<>")), KotlinPrintOptions.NullCheck.ASSERT);
String kotlin = kotlin("""
package a;
import java.util.*;
class Ranges2 {
static <T> List<T> range(T lower, T upper) { List<T> l = new ArrayList<>(); l.add(lower); l.add(upper); return l; }
static int size() {
List<Integer> r = Ranges2.<Integer>range(2, 4);
r.add(null);
return r.size();
}
}
""", options);
contains(kotlin, "range<Int?>(2, 4)");
compiles(kotlin);
}
}
Loading