[kotlin] K2 J2K: Initial generic nullability inference

TODOs are listed in the J2KNullityInferrer Javadoc.

KTIJ-29147

GitOrigin-RevId: 2949b6d7c6b72b586ea6d3af89d0f2e655edcc6d
This commit is contained in:
Alexey Belkov
2024-06-21 16:44:27 +04:00
committed by intellij-monorepo-bot
parent 64e631f7c9
commit 713072b892
100 changed files with 2861 additions and 123 deletions

View File

@@ -5196,6 +5196,11 @@ public abstract class NewJavaToKotlinConverterSingleFileTestGenerated extends Ab
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
@TestMetadata("argumentForNotNullRecordParameter.java")
public void testArgumentForNotNullRecordParameter() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullability/argumentForNotNullRecordParameter.java");
}
@TestMetadata("arrayAccess.java")
public void testArrayAccess() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullability/arrayAccess.java");
@@ -5470,15 +5475,140 @@ public abstract class NewJavaToKotlinConverterSingleFileTestGenerated extends Ab
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
@TestMetadata("argumentForNotNullRecordParameter.java")
public void testArgumentForNotNullRecordParameter() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/argumentForNotNullRecordParameter.java");
}
@TestMetadata("array.java")
public void testArray() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/array.java");
}
@TestMetadata("arrayNewNoInitializerTodo.java")
public void testArrayNewNoInitializerTodo() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/arrayNewNoInitializerTodo.java");
}
@TestMetadata("assignment.java")
public void testAssignment() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/assignment.java");
}
@TestMetadata("complex.java")
public void testComplex() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/complex.java");
}
@TestMetadata("javaInteropTodo.java")
public void testJavaInteropTodo() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/javaInteropTodo.java");
}
@TestMetadata("kotlinInteropTodo.java")
public void testKotlinInteropTodo() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/kotlinInteropTodo.java");
}
@TestMetadata("libraryMethodsTodo.java")
public void testLibraryMethodsTodo() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/libraryMethodsTodo.java");
}
@TestMetadata("methodCallArgument.java")
public void testMethodCallArgument() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/methodCallArgument.java");
}
@TestMetadata("notNullIterationParameterArray.java")
public void testNotNullIterationParameterArray() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/notNullIterationParameterArray.java");
}
@TestMetadata("notNullIterationParameterCollections.java")
public void testNotNullIterationParameterCollections() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/notNullIterationParameterCollections.java");
}
@TestMetadata("notNullIterationParameterMethodCall.java")
public void testNotNullIterationParameterMethodCall() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/notNullIterationParameterMethodCall.java");
}
@TestMetadata("notNullParameterAsArgument.java")
public void testNotNullParameterAsArgument() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/notNullParameterAsArgument.java");
}
@TestMetadata("notNullTypeArgumentFromKotlinTypeParameterInCall.java")
public void testNotNullTypeArgumentFromKotlinTypeParameterInCall() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/notNullTypeArgumentFromKotlinTypeParameterInCall.java");
}
@TestMetadata("notNullTypeArgumentFromKotlinTypeParameterInDeclaration.java")
public void testNotNullTypeArgumentFromKotlinTypeParameterInDeclaration() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/notNullTypeArgumentFromKotlinTypeParameterInDeclaration.java");
}
@TestMetadata("nullabilityAnnotations.java")
public void testNullabilityAnnotations() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/nullabilityAnnotations.java");
}
@TestMetadata("propagateFromArrayParameter.java")
public void testPropagateFromArrayParameter() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/propagateFromArrayParameter.java");
}
@TestMetadata("propagateFromConstructorParameter.java")
public void testPropagateFromConstructorParameter() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/propagateFromConstructorParameter.java");
}
@TestMetadata("propagateFromField.java")
public void testPropagateFromField() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/propagateFromField.java");
}
@TestMetadata("propagateFromMethodReturnType.java")
public void testPropagateFromMethodReturnType() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/propagateFromMethodReturnType.java");
}
@TestMetadata("propagateFromParameterToField.java")
public void testPropagateFromParameterToField() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/propagateFromParameterToField.java");
}
@TestMetadata("propagateFromVariableRecursive.java")
public void testPropagateFromVariableRecursive() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/propagateFromVariableRecursive.java");
}
@TestMetadata("returnMethodCallExprTodo.java")
public void testReturnMethodCallExprTodo() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/returnMethodCallExprTodo.java");
}
@TestMetadata("simple.java")
public void testSimple() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/simple.java");
}
@TestMetadata("typeArgumentTodo.java")
public void testTypeArgumentTodo() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/typeArgumentTodo.java");
}
@TestMetadata("typeParameterNotNullUpperBound.java")
public void testTypeParameterNotNullUpperBound() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/typeParameterNotNullUpperBound.java");
}
@TestMetadata("wildcard.java")
public void testWildcard() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/wildcard.java");
}
}
@RunWith(JUnit3RunnerWithInners.class)

View File

@@ -5196,6 +5196,11 @@ public abstract class K2JavaToKotlinConverterSingleFileTestGenerated extends Abs
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
@TestMetadata("argumentForNotNullRecordParameter.java")
public void testArgumentForNotNullRecordParameter() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullability/argumentForNotNullRecordParameter.java");
}
@TestMetadata("arrayAccess.java")
public void testArrayAccess() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullability/arrayAccess.java");
@@ -5470,15 +5475,140 @@ public abstract class K2JavaToKotlinConverterSingleFileTestGenerated extends Abs
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
@TestMetadata("argumentForNotNullRecordParameter.java")
public void testArgumentForNotNullRecordParameter() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/argumentForNotNullRecordParameter.java");
}
@TestMetadata("array.java")
public void testArray() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/array.java");
}
@TestMetadata("arrayNewNoInitializerTodo.java")
public void testArrayNewNoInitializerTodo() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/arrayNewNoInitializerTodo.java");
}
@TestMetadata("assignment.java")
public void testAssignment() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/assignment.java");
}
@TestMetadata("complex.java")
public void testComplex() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/complex.java");
}
@TestMetadata("javaInteropTodo.java")
public void testJavaInteropTodo() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/javaInteropTodo.java");
}
@TestMetadata("kotlinInteropTodo.java")
public void testKotlinInteropTodo() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/kotlinInteropTodo.java");
}
@TestMetadata("libraryMethodsTodo.java")
public void testLibraryMethodsTodo() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/libraryMethodsTodo.java");
}
@TestMetadata("methodCallArgument.java")
public void testMethodCallArgument() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/methodCallArgument.java");
}
@TestMetadata("notNullIterationParameterArray.java")
public void testNotNullIterationParameterArray() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/notNullIterationParameterArray.java");
}
@TestMetadata("notNullIterationParameterCollections.java")
public void testNotNullIterationParameterCollections() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/notNullIterationParameterCollections.java");
}
@TestMetadata("notNullIterationParameterMethodCall.java")
public void testNotNullIterationParameterMethodCall() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/notNullIterationParameterMethodCall.java");
}
@TestMetadata("notNullParameterAsArgument.java")
public void testNotNullParameterAsArgument() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/notNullParameterAsArgument.java");
}
@TestMetadata("notNullTypeArgumentFromKotlinTypeParameterInCall.java")
public void testNotNullTypeArgumentFromKotlinTypeParameterInCall() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/notNullTypeArgumentFromKotlinTypeParameterInCall.java");
}
@TestMetadata("notNullTypeArgumentFromKotlinTypeParameterInDeclaration.java")
public void testNotNullTypeArgumentFromKotlinTypeParameterInDeclaration() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/notNullTypeArgumentFromKotlinTypeParameterInDeclaration.java");
}
@TestMetadata("nullabilityAnnotations.java")
public void testNullabilityAnnotations() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/nullabilityAnnotations.java");
}
@TestMetadata("propagateFromArrayParameter.java")
public void testPropagateFromArrayParameter() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/propagateFromArrayParameter.java");
}
@TestMetadata("propagateFromConstructorParameter.java")
public void testPropagateFromConstructorParameter() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/propagateFromConstructorParameter.java");
}
@TestMetadata("propagateFromField.java")
public void testPropagateFromField() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/propagateFromField.java");
}
@TestMetadata("propagateFromMethodReturnType.java")
public void testPropagateFromMethodReturnType() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/propagateFromMethodReturnType.java");
}
@TestMetadata("propagateFromParameterToField.java")
public void testPropagateFromParameterToField() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/propagateFromParameterToField.java");
}
@TestMetadata("propagateFromVariableRecursive.java")
public void testPropagateFromVariableRecursive() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/propagateFromVariableRecursive.java");
}
@TestMetadata("returnMethodCallExprTodo.java")
public void testReturnMethodCallExprTodo() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/returnMethodCallExprTodo.java");
}
@TestMetadata("simple.java")
public void testSimple() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/simple.java");
}
@TestMetadata("typeArgumentTodo.java")
public void testTypeArgumentTodo() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/typeArgumentTodo.java");
}
@TestMetadata("typeParameterNotNullUpperBound.java")
public void testTypeParameterNotNullUpperBound() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/typeParameterNotNullUpperBound.java");
}
@TestMetadata("wildcard.java")
public void testWildcard() throws Exception {
runTest("../../shared/tests/testData/newJ2k/nullabilityGenerics/wildcard.java");
}
}
@RunWith(JUnit3RunnerWithInners.class)

View File

@@ -2,7 +2,8 @@ progress.text={0} - phase {1,number,#} of {2,number,#}
subphase.progress.text={0} ({1,number,#}/{2,number,#}) - phase {3,number,#} of {4,number,#}
progress.searching.usages.to.update=Searching usages to update\u2026
phase.converting.j2k=Converting Java code to Kotlin code
j2k.phase.preprocessing=Preprocessing Java files
j2k.phase.converting=Converting Java code to Kotlin code
j2k.applying.conversions=Applying conversions\u2026
j2k.custom.preprocessing=Running custom preprocessors

View File

@@ -4,12 +4,14 @@ package org.jetbrains.kotlin.nj2k;
import com.intellij.codeInsight.Nullability;
import com.intellij.codeInsight.NullabilityAnnotationInfo;
import com.intellij.codeInsight.NullableNotNullManager;
import com.intellij.codeInsight.daemon.impl.analysis.JavaGenericsUtil;
import com.intellij.codeInspection.dataFlow.DfaNullability;
import com.intellij.codeInspection.dataFlow.DfaPsiUtil;
import com.intellij.codeInspection.dataFlow.DfaUtil;
import com.intellij.codeInspection.dataFlow.NullabilityUtil;
import com.intellij.codeInspection.dataFlow.inference.JavaSourceInference;
import com.intellij.psi.*;
import com.intellij.psi.impl.light.LightRecordCanonicalConstructor;
import com.intellij.psi.impl.source.PsiClassReferenceType;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.SearchScope;
import com.intellij.psi.search.searches.OverridingMethodsSearch;
@@ -17,8 +19,8 @@ import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Query;
import com.siyeh.ig.psiutils.ExpressionUtils;
import com.siyeh.ig.psiutils.MethodCallUtils;
import com.siyeh.ig.psiutils.VariableAccessUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -26,10 +28,22 @@ import org.jetbrains.kotlin.idea.base.plugin.KotlinPluginModeProvider;
import java.util.*;
import static org.jetbrains.kotlin.nj2k.NullabilityUtilsKt.*;
/**
* This is a copy of com.intellij.codeInspection.inferNullity.NullityInferrer
* with some modifications to better support J2K. These modifications may be later
* ported back, and this copy may be deleted.
* This class is based on com.intellij.codeInspection.inferNullity.NullityInferrer
* and extended for better J2K support.
* TODO:
* support inference for type arguments in case the type parameter is used as both an input and an output type (Collections.emptyList())
* support method calls of external methods (especially Kotlin interop)
* improve support for array types and initializers
* support propagation of generic nullability from lambda parameters
* take overridden signatures into account when inferring parameter nullability
* support collections mutability inference
* try to migrate from direct usage of PsiType's
* additional profiling/optimization
* document how it works
* convert to Kotlin
*/
@SuppressWarnings("DuplicatedCode")
class J2KNullityInferrer {
@@ -43,15 +57,34 @@ class J2KNullityInferrer {
// because a PsiType object can sometimes be recreated (for example, by PsiNewExpressionImpl.getType()),
// and we won't find it in our IdentityHashMap.
// We should try to migrate this logic to PsiTypeElements, which are more persistent.
private final Set<PsiType> myNotNullTypes = Collections.newSetFromMap(new IdentityHashMap<>());
private final Set<PsiType> myNullableTypes = Collections.newSetFromMap(new IdentityHashMap<>());
private final Set<PsiType> notNullTypes = Collections.newSetFromMap(new IdentityHashMap<>());
private final Set<PsiType> nullableTypes = Collections.newSetFromMap(new IdentityHashMap<>());
// Some types point to a PsiJavaCodeReferenceElement, in this case we mark them as well.
// This helps to avoid the problem with recreated types (described above),
// because two different types may point to the same PsiJavaCodeReferenceElement.
private final Set<PsiJavaCodeReferenceElement> notNullElements = new HashSet<>();
private final Set<PsiJavaCodeReferenceElement> nullableElements = new HashSet<>();
// Caches
private final Map<PsiVariable, Collection<PsiReference>> variableReferences = new HashMap<>();
private final Map<PsiVariable, Collection<PsiExpression>> variableAssignmentRightHandSides = new HashMap<>();
private final Map<PsiParameter, List<PsiReferenceExpression>> parameterReferences = new HashMap<>();
Set<PsiType> getNotNullTypes() {
return myNotNullTypes;
return notNullTypes;
}
Set<PsiType> getNullableTypes() {
return myNullableTypes;
return nullableTypes;
}
Set<PsiJavaCodeReferenceElement> getNotNullElements() {
return notNullElements;
}
Set<PsiJavaCodeReferenceElement> getNullableElements() {
return nullableElements;
}
private boolean expressionIsNeverNull(@Nullable PsiExpression expression) {
@@ -88,12 +121,7 @@ class J2KNullityInferrer {
return false;
}
SearchScope scope = getScope(variable);
if (scope == null) {
return false;
}
final Query<PsiReference> references = ReferencesSearch.search(variable, scope);
final Collection<PsiReference> references = variableReferences.get(variable);
for (final PsiReference reference : references) {
final PsiElement element = reference.getElement();
if (!(element instanceof PsiReferenceExpression)) {
@@ -124,7 +152,7 @@ class J2KNullityInferrer {
return false;
}
final Query<PsiReference> references = ReferencesSearch.search(variable, scope);
final Collection<PsiReference> references = variableReferences.get(variable);
for (final PsiReference reference : references) {
final PsiElement element = reference.getElement();
if (!(element instanceof PsiReferenceExpression)) {
@@ -165,7 +193,14 @@ class J2KNullityInferrer {
}
public void collect(@NotNull PsiFile file) {
// Step 1: mark all types with known nullability inferred from Java DFA
runPreprocessing(file);
inferNullabilityIteratively(file);
flushCaches();
}
// Mark all types with known nullability inferred from Java DFA
// and cache some variable reference searches
private void runPreprocessing(@NotNull PsiFile file) {
file.accept(new JavaRecursiveElementWalkingVisitor() {
@Override
public void visitTypeElement(@NotNull PsiTypeElement typeElement) {
@@ -177,9 +212,41 @@ class J2KNullityInferrer {
case NOT_NULL -> registerNotNullType(type);
}
}
});
// Step 2: infer nullability for the rest of types
@Override
public void visitVariable(@NotNull PsiVariable variable) {
super.visitVariable(variable);
if (variable.getType() instanceof PsiPrimitiveType) return;
SearchScope scope = getScope(variable);
Collection<PsiReference> references = Collections.emptyList();
if (scope != null) {
references = ReferencesSearch.search(variable, scope).findAll();
}
variableReferences.put(variable, references);
Collection<PsiExpression> rightHandSides = DfaPsiUtil.getVariableAssignmentsInFile(variable, false, null);
variableAssignmentRightHandSides.put(variable, rightHandSides);
}
@Override
public void visitParameter(@NotNull PsiParameter parameter) {
super.visitParameter(parameter);
if (parameter.getType() instanceof PsiPrimitiveType) return;
List<PsiReferenceExpression> references = Collections.emptyList();
final PsiElement grandParent = parameter.getDeclarationScope();
if (grandParent instanceof PsiMethod method) {
if (method.getBody() != null) {
references = VariableAccessUtils.getVariableReferences(parameter, method);
}
}
parameterReferences.put(parameter, references);
}
});
}
private void inferNullabilityIteratively(@NotNull PsiFile file) {
int prevNumAnnotationsAdded;
int pass = 0;
do {
@@ -191,6 +258,12 @@ class J2KNullityInferrer {
while (prevNumAnnotationsAdded < numAnnotationsAdded && pass < MAX_PASSES);
}
private void flushCaches() {
variableReferences.clear();
variableAssignmentRightHandSides.clear();
parameterReferences.clear();
}
private void registerNullableAnnotation(@NotNull PsiModifierListOwner declaration) {
registerAnnotation(declaration, true);
}
@@ -217,12 +290,16 @@ class J2KNullityInferrer {
return null;
}
private void registerNullableType(@NotNull PsiType type) {
registerTypeNullability(type, true);
private void registerNullableType(@Nullable PsiType type) {
if (type != null) {
registerTypeNullability(type, true);
}
}
private void registerNotNullType(@NotNull PsiType type) {
registerTypeNullability(type, false);
private void registerNotNullType(@Nullable PsiType type) {
if (type != null) {
registerTypeNullability(type, false);
}
}
private void registerTypeNullability(@NotNull PsiType type, boolean isNullable) {
@@ -238,28 +315,42 @@ class J2KNullityInferrer {
return;
}
// We conservatively skip type parameter references for now (their nullability is always considered unknown)
// TODO support it
if (type instanceof PsiClassType classType) {
PsiClass psiClass = classType.resolve();
if (psiClass instanceof PsiTypeParameter) {
return;
}
}
PsiJavaCodeReferenceElement element = null;
if (type instanceof PsiClassReferenceType classReferenceType) {
element = classReferenceType.getReference();
}
PsiType unwrappedType = unwrap(type);
if (isNullable) {
myNullableTypes.add(unwrappedType);
myNotNullTypes.remove(unwrappedType);
nullableTypes.add(unwrappedType);
if (element != null) nullableElements.add(element);
notNullTypes.remove(unwrappedType);
if (element != null) notNullElements.remove(element);
} else {
myNotNullTypes.add(unwrappedType);
notNullTypes.add(unwrappedType);
if (element != null) notNullElements.add(element);
}
numAnnotationsAdded++;
}
private boolean registerAnnotationByNullAssignmentStatus(PsiVariable variable) {
private void registerAnnotationByNullAssignmentStatus(PsiVariable variable) {
if (variableNeverAssignedNull(variable)) {
registerNotNullAnnotation(variable);
return true;
} else if (variableSometimesAssignedNull(variable)) {
registerNullableAnnotation(variable);
return true;
}
return false;
}
private boolean isNotNull(@Nullable PsiModifierListOwner owner) {
@@ -272,9 +363,10 @@ class J2KNullityInferrer {
return isNotNull(type);
}
private boolean isNotNull(@NotNull PsiType type) {
private boolean isNotNull(@Nullable PsiType type) {
if (type == null) return false;
PsiType unwrappedType = unwrap(type);
return myNotNullTypes.contains(unwrappedType);
return notNullTypes.contains(unwrappedType);
}
private boolean isNullable(@Nullable PsiModifierListOwner owner) {
@@ -287,9 +379,10 @@ class J2KNullityInferrer {
return isNullable(type);
}
private boolean isNullable(@NotNull PsiType type) {
private boolean isNullable(@Nullable PsiType type) {
if (type == null) return false;
PsiType unwrappedType = unwrap(type);
return myNullableTypes.contains(unwrappedType);
return nullableTypes.contains(unwrappedType);
}
private static @NotNull PsiType unwrap(@NotNull PsiType type) {
@@ -302,19 +395,18 @@ class J2KNullityInferrer {
return type;
}
private boolean hasNullability(@NotNull PsiModifierListOwner owner) {
private boolean hasRawNullability(@NotNull PsiType type) {
return isNullable(type) || isNotNull(type);
}
private boolean hasRawNullability(@NotNull PsiModifierListOwner owner) {
NullableNotNullManager manager = NullableNotNullManager.getInstance(owner.getProject());
NullabilityAnnotationInfo info = manager.findEffectiveNullabilityInfo(owner);
if (info != null && !info.isInferred() && info.getNullability() != Nullability.UNKNOWN) return true;
PsiType type = getType(owner);
if (type == null) return false;
return hasNullability(type);
}
private boolean hasNullability(@NotNull PsiType type) {
return isNullable(type) || isNotNull(type);
return hasRawNullability(type);
}
private class NullityInferrerVisitor extends JavaRecursiveElementWalkingVisitor {
@@ -326,6 +418,9 @@ class J2KNullityInferrer {
return;
}
// TODO deal with overrides somehow
unifyNullabilityOfMethodReturnTypeAndReturnedExpressions(method);
final Collection<PsiMethod> overridingMethods = OverridingMethodsSearch.search(method).findAll();
for (final PsiMethod overridingMethod : overridingMethods) {
if (isNullable(overridingMethod)) {
@@ -340,11 +435,11 @@ class J2KNullityInferrer {
return;
}
if (hasNullability(method)) {
if (hasRawNullability(method)) {
return;
}
Nullability nullability = DfaUtil.inferMethodNullability(method);
Nullability nullability = getMethodNullabilityByDfa(method);
switch (nullability) {
case NULLABLE -> registerNullableAnnotation(method);
@@ -359,15 +454,60 @@ class J2KNullityInferrer {
}
}
@Override
public void visitLocalVariable(@NotNull PsiLocalVariable variable) {
super.visitLocalVariable(variable);
if (variable.getType() instanceof PsiPrimitiveType || hasNullability(variable)) {
private void unifyNullabilityOfMethodReturnTypeAndReturnedExpressions(@NotNull PsiMethod method) {
if (KotlinPluginModeProvider.Companion.isK1Mode()) return;
PsiType methodReturnType = method.getReturnType();
if (methodReturnType == null) return;
PsiReturnStatement[] statements = PsiUtil.findReturnStatements(method);
if (statements.length == 0) {
// Stick to nullable by default for empty methods (ex. in interfaces)
return;
}
if (registerAnnotationByNullAssignmentStatus(variable)) return;
boolean allRawReturnValueTypesAreNotNull = true;
boolean rawMethodReturnTypeIsNotNull = isNotNull(methodReturnType);
for (PsiReturnStatement statement : statements) {
PsiExpression returnValue = statement.getReturnValue();
if (returnValue == null) continue;
PsiType returnValueType = getReferenceType(returnValue);
if (returnValueType == null) continue;
if (rawMethodReturnTypeIsNotNull) {
// TODO simplify
propagateRawNullabilityWithPsiContext(methodReturnType, returnValueType);
propagateRawNullabilityWithPsiContext(methodReturnType, returnValue.getType());
}
if (!isNotNull(returnValueType)) {
allRawReturnValueTypesAreNotNull = false;
}
// TODO Looks questionable to update parts of type arguments like this
unifyGenericNullability(methodReturnType, returnValueType); // TODO extend to `returnValue.getType()`?
}
if (allRawReturnValueTypesAreNotNull) {
registerNotNullType(methodReturnType);
}
}
@Override
public void visitLocalVariable(@NotNull PsiLocalVariable variable) {
super.visitLocalVariable(variable);
PsiType variableType = variable.getType();
if (variableType instanceof PsiPrimitiveType) return;
if (!hasRawNullability(variableType)) {
registerAnnotationByNullAssignmentStatus(variable);
}
inferNullabilityFromVariableReferences(variable);
propagateNullabilityFromVariable(variable);
}
private void inferNullabilityFromVariableReferences(@NotNull PsiVariable variable) {
@@ -376,10 +516,7 @@ class J2KNullityInferrer {
return;
}
SearchScope scope = getScope(variable);
if (scope == null) return;
final Query<PsiReference> references = ReferencesSearch.search(variable, scope);
final Collection<PsiReference> references = variableReferences.get(variable);
for (final PsiReference reference : references) {
final PsiElement element = reference.getElement();
if (!(element instanceof PsiReferenceExpression referenceExpression)) {
@@ -390,19 +527,89 @@ class J2KNullityInferrer {
PsiTreeUtil.skipParentsOfType(referenceExpression, PsiParenthesizedExpression.class, PsiTypeCastExpression.class);
processReference(variable, referenceExpression, refParent);
}
}
if (isNullable(variable)) {
// We determined that the variable is nullable, so there is no need to check the rest of references.
// But if it is not-null, we keep going because we may find that it is nullable later, after all.
break;
private void propagateNullabilityFromVariable(PsiVariable variable) {
if (KotlinPluginModeProvider.Companion.isK1Mode()) return;
PsiType variableType = variable.getType();
Collection<PsiExpression> rightHandSides = variableAssignmentRightHandSides.get(variable);
for (PsiExpression expr : rightHandSides) {
PsiType exprType = expr.getType();
PsiType referenceType = getReferenceType(expr);
if (isNullable(exprType) || isNullable(referenceType)) {
registerNullableType(variableType);
} else if (isNotNull(variableType)) {
propagateRawNullabilityWithPsiContext(variableType, exprType);
propagateRawNullabilityWithPsiContext(variableType, referenceType);
}
unifyGenericNullabilityWithPsiContext(variableType, exprType);
unifyGenericNullabilityWithPsiContext(variableType, referenceType);
}
}
// TODO rethink how to use this properly
private static @Nullable PsiType getReferenceType(PsiExpression expression) {
if (expression instanceof PsiReferenceExpression referenceExpression) {
PsiElement target = referenceExpression.resolve();
if (target instanceof PsiVariable variable) {
return variable.getType();
}
} else if (expression instanceof PsiMethodCallExpression methodCallExpression) {
PsiMethod method = methodCallExpression.resolveMethod();
if (method != null) {
return method.getReturnType();
}
}
return expression.getType();
}
private void unifyGenericNullabilityWithPsiContext(@Nullable PsiType type1, @Nullable PsiType type2) {
propagateGenericNullabilityWithPsiContext(type1, type2);
propagateGenericNullabilityWithPsiContext(type2, type1);
}
private void propagateGenericNullabilityWithPsiContext(
@Nullable PsiType originType,
@Nullable PsiType targetType
) {
if (originType == null || targetType == null) return;
int prevCount = numAnnotationsAdded;
propagateGenericNullability(originType, targetType, false);
if (prevCount == numAnnotationsAdded) {
// Nullability is already the same, we can stop here
return;
}
PsiType psiContextType = getPsiContextType(targetType);
if (psiContextType != null) {
propagateGenericNullabilityWithPsiContext(originType, psiContextType);
}
}
private static @Nullable PsiType getPsiContextType(@NotNull PsiType type) { // TODO it is not a chain!
if (!(type instanceof PsiClassType classType)) return null;
PsiElement psiContext = classType.getPsiContext();
if (!(psiContext instanceof PsiJavaCodeReferenceElement javaCodeReferenceElement)) return null;
PsiElement codeReferenceElementParent = javaCodeReferenceElement.getParent();
if (!(codeReferenceElementParent instanceof PsiTypeElement typeElement)) return null;
PsiType psiContextType = typeElement.getType();
if (type == psiContextType) return null;
return psiContextType;
}
@Override
public void visitParameter(@NotNull PsiParameter parameter) {
super.visitParameter(parameter);
if (parameter.getType() instanceof PsiPrimitiveType || hasNullability(parameter)) {
if (parameter.getType() instanceof PsiPrimitiveType) {
return;
}
@@ -411,13 +618,15 @@ class J2KNullityInferrer {
if (method.getBody() != null) {
if (JavaSourceInference.inferNullability(parameter) == Nullability.NOT_NULL) {
registerNotNullAnnotation(parameter);
return;
}
for (PsiReferenceExpression expr : VariableAccessUtils.getVariableReferences(parameter, method)) {
List<PsiReferenceExpression> references = parameterReferences.get(parameter);
for (PsiReferenceExpression expr : references) {
final PsiElement parent =
PsiTreeUtil.skipParentsOfType(expr, PsiParenthesizedExpression.class, PsiTypeCastExpression.class);
if (processReference(parameter, expr, parent)) return;
processReference(parameter, expr, parent);
if (isNotNull(method)) {
PsiElement toReturn = parent;
if (parent instanceof PsiConditionalExpression &&
@@ -426,18 +635,19 @@ class J2KNullityInferrer {
}
if (toReturn instanceof PsiReturnStatement) {
registerNotNullAnnotation(parameter);
return;
}
}
}
}
} else if (grandParent instanceof PsiForeachStatement) {
for (PsiReference reference : ReferencesSearch.search(parameter, new LocalSearchScope(grandParent))) {
} else if (grandParent instanceof PsiForeachStatement foreachStatement) {
for (PsiReference reference : ReferencesSearch.search(parameter, new LocalSearchScope(foreachStatement))) {
final PsiElement place = reference.getElement();
if (place instanceof PsiReferenceExpression expr) {
final PsiElement parent =
PsiTreeUtil.skipParentsOfType(expr, PsiParenthesizedExpression.class, PsiTypeCastExpression.class);
if (processReference(parameter, expr, parent)) return;
if (processReference(parameter, expr, parent)) {
propagateRawNullabilityToIterableComponentType(foreachStatement);
}
}
}
} else {
@@ -445,18 +655,53 @@ class J2KNullityInferrer {
}
}
private void propagateRawNullabilityToIterableComponentType(@NotNull PsiForeachStatement foreachStatement) {
if (KotlinPluginModeProvider.Companion.isK1Mode()) return;
PsiType parameterType = foreachStatement.getIterationParameter().getType();
if (!isNullable(parameterType) && !isNotNull(parameterType)) return;
PsiExpression iteratedValue = foreachStatement.getIteratedValue();
if (iteratedValue == null) return;
PsiType componentType = JavaGenericsUtil.getCollectionItemType(iteratedValue);
if (componentType == null) return;
propagateRawNullabilityWithPsiContext(parameterType, componentType);
}
private void propagateRawNullabilityWithPsiContext(@Nullable PsiType originType, @Nullable PsiType targetType) {
if (originType == null || targetType == null) return;
if (isNullable(targetType) || isNotNull(targetType)) return; // TODO is this too restrictive check?
if (isNullable(originType)) {
registerNullableType(targetType);
} else if (isNotNull(originType)) {
registerNotNullType(targetType);
}
PsiType psiContextType = getPsiContextType(targetType);
if (psiContextType != null) {
propagateRawNullabilityWithPsiContext(originType, psiContextType);
}
}
private boolean processReference(@NotNull PsiVariable variable, @NotNull PsiReferenceExpression expr, PsiElement parent) {
if (NullabilityUtilsKt.isUsedInAutoUnboxingContext(expr)) {
if (isUsedInAutoUnboxingContext(expr)) {
registerNotNullAnnotation(variable);
return true;
}
if (PsiUtil.isAccessedForWriting(expr)) return true;
if (NullabilityUtilsKt.getDfaNullability(expr) == DfaNullability.NOT_NULL) {
if (getExpressionDfaNullability(expr) == DfaNullability.NOT_NULL) {
// If Java DFA can determine that the nullability of the reference is definitely not-null,
// we are probably inside an "instance of" check (which is like a smart cast for Java DFA).
// So we give up trying to guess the nullability of the declaration from this reference.
//
// However, try to process an argument reference to propagate generic nullability,
// regardless of top-level nullability inferred from DFA,
// since it is orthogonal to the nullability of type arguments.
processArgumentReference(variable, expr, false);
return false;
}
@@ -520,14 +765,18 @@ class J2KNullityInferrer {
return true;
}
if (processArgumentReference(variable, expr)) {
if (processArgumentReference(variable, expr, true)) {
return true;
}
return false;
}
private boolean processArgumentReference(@NotNull PsiVariable variable, @NotNull PsiReferenceExpression expr) {
private boolean processArgumentReference(
@NotNull PsiVariable variable,
@NotNull PsiReferenceExpression expr,
boolean updateRawNullability
) {
final PsiCall call = PsiTreeUtil.getParentOfType(expr, PsiCall.class);
if (call == null) return false;
final PsiExpressionList argumentList = call.getArgumentList();
@@ -544,11 +793,21 @@ class J2KNullityInferrer {
//not vararg
if (idx >= parameters.length) return false;
final PsiParameter resolvedToParam = parameters[idx];
boolean isArray = variable.getType() instanceof PsiArrayType;
boolean isVarArgs = variable instanceof PsiParameter parameter && parameter.isVarArgs();
PsiVariable resolvedToParam = parameters[idx];
if (resolvedToParam instanceof LightRecordCanonicalConstructor.LightRecordConstructorParameter) {
resolvedToParam = ((LightRecordCanonicalConstructor.LightRecordConstructorParameter) resolvedToParam).getRecordComponent();
}
if (resolvedToParam == null) return false;
if (isNotNull(resolvedToParam) || (isArray && !isVarArgs && resolvedToParam.isVarArgs())) {
PsiType variableType = variable.getType();
PsiType parameterType = resolvedToParam.getType();
unifyGenericNullability(variableType, parameterType);
if (!updateRawNullability) return false;
if (isNotNull(resolvedToParam) ||
(variableType instanceof PsiArrayType && !isVarArgs(variable) && isVarArgs(resolvedToParam))) {
// In the case of varargs in Kotlin, the spread operator needs to be applied to a not-null array
registerNotNullAnnotation(variable);
return true;
@@ -557,16 +816,160 @@ class J2KNullityInferrer {
return false;
}
private static boolean isVarArgs(PsiVariable variable) {
if (variable instanceof PsiParameter) {
return ((PsiParameter) variable).isVarArgs();
} else if (variable instanceof PsiRecordComponent) {
return ((PsiRecordComponent) variable).isVarArgs();
}
return false;
}
private void unifyGenericNullability(PsiType type1, PsiType type2) {
propagateGenericNullability(type1, type2, false);
propagateGenericNullability(type2, type1, false);
}
/**
* Updates nullability of the target type and its type arguments recursively from the origin type.
*/
private void propagateGenericNullability(PsiType originType, PsiType targetType, boolean updateRawType) {
if (KotlinPluginModeProvider.Companion.isK1Mode()) return;
if (updateRawType) {
if (isNotNull(originType)) {
registerNotNullType(targetType);
} else if (isNullable(originType)) {
registerNullableType(targetType);
}
}
if (originType instanceof PsiArrayType originArrayType && targetType instanceof PsiArrayType targetArrayType) {
PsiType originComponentType = originArrayType.getComponentType();
PsiType targetComponentType = targetArrayType.getComponentType();
propagateGenericNullability(originComponentType, targetComponentType, true);
}
if (!(originType instanceof PsiClassType originClassType)) return;
if (!(targetType instanceof PsiClassType targetClassType)) return;
PsiType[] originTypeArguments = originClassType.getParameters();
PsiType[] targetTypeArguments = targetClassType.getParameters();
if (originTypeArguments.length != targetTypeArguments.length) return;
for (int i = 0; i < originTypeArguments.length; i++) {
PsiType originTypeArgument = originTypeArguments[i];
PsiType targetTypeArgument = targetTypeArguments[i];
propagateGenericNullability(originTypeArgument, targetTypeArgument, true);
}
}
@Override
public void visitField(@NotNull PsiField field) {
super.visitField(field);
if (field instanceof PsiEnumConstant || field.getType() instanceof PsiPrimitiveType || hasNullability(field)) {
return;
if (field instanceof PsiEnumConstant) return;
PsiType fieldType = field.getType();
if (fieldType instanceof PsiPrimitiveType) return;
if (!hasRawNullability(fieldType)) {
registerAnnotationByNullAssignmentStatus(field);
}
if (registerAnnotationByNullAssignmentStatus(field)) return;
if (!isPrivate(field)) return;
inferNullabilityFromVariableReferences(field);
propagateNullabilityFromVariable(field);
}
@Override
public void visitReferenceElement(@NotNull PsiJavaCodeReferenceElement reference) {
if (KotlinPluginModeProvider.Companion.isK1Mode()) return;
// Update the nullability of type arguments in constructor calls
PsiElement target = reference.resolve();
if (!(target instanceof PsiClass klass)) return;
PsiTypeParameter[] typeParameters = klass.getTypeParameters();
PsiType[] typeArguments = reference.getTypeParameters();
updateNullabilityOfTypeArguments(typeParameters, typeArguments);
}
// If type parameters come from Kotlin, we propagate their nullability to the type arguments
// TODO support not only raw but generic propagation
private void updateNullabilityOfTypeArguments(PsiTypeParameter[] typeParameters, PsiType[] typeArguments) {
if (KotlinPluginModeProvider.Companion.isK1Mode()) return;
if (typeParameters.length != typeArguments.length) return;
for (int i = 0; i < typeParameters.length; i++) {
PsiTypeParameter typeParameter = typeParameters[i];
PsiType typeArgument = typeArguments[i];
Nullability nullability = getTypeParameterNullability(typeParameter);
switch (nullability) {
case NULLABLE -> registerNullableType(typeArgument);
case NOT_NULL -> registerNotNullType(typeArgument);
}
}
}
@Override
public void visitMethodCallExpression(@NotNull PsiMethodCallExpression expression) {
if (KotlinPluginModeProvider.Companion.isK1Mode()) return;
super.visitMethodCallExpression(expression);
PsiMethod method = expression.resolveMethod();
if (method == null) return;
// Unify generic nullability of parameter-argument pairs
PsiExpression[] argumentList = expression.getArgumentList().getExpressions();
for (PsiExpression argument : argumentList) {
PsiParameter parameter = MethodCallUtils.getParameterForArgument(argument);
if (parameter == null) continue;
PsiType parameterType = parameter.getType();
PsiType argumentType = getReferenceType(argument);
unifyGenericNullability(parameterType, argumentType);
}
// Update nullability of type arguments
PsiTypeParameter[] typeParameters = method.getTypeParameters();
PsiType[] typeArguments = expression.getTypeArguments();
updateNullabilityOfTypeArguments(typeParameters, typeArguments);
}
@Override
public void visitNewExpression(@NotNull PsiNewExpression expression) {
if (KotlinPluginModeProvider.Companion.isK1Mode()) return;
super.visitNewExpression(expression);
// Update array initializer component type nullability
// based on the nullability of initializing member expressions
if (!(expression.getType() instanceof PsiArrayType arrayType)) return;
PsiType componentType = arrayType.getComponentType();
PsiArrayInitializerExpression arrayInitializer = expression.getArrayInitializer();
if (arrayInitializer == null) return;
PsiType arrayInitializerType = arrayInitializer.getType();
if (!(arrayInitializerType instanceof PsiArrayType arrayInitializerArrayType)) return;
PsiType arrayInitializerComponentType = arrayInitializerArrayType.getComponentType();
PsiExpression[] initializers = arrayInitializer.getInitializers();
if (initializers.length == 0) return;
for (PsiExpression initializer : initializers) {
Nullability nullability = NullabilityUtil.getExpressionNullability(initializer, true);
if (nullability == Nullability.NULLABLE) {
registerNullableType(componentType);
registerNullableType(arrayInitializerComponentType);
return;
} else if (nullability == Nullability.UNKNOWN) {
return;
}
}
// At this point, all array initializer expressions are not-null
registerNotNullType(componentType);
registerNotNullType(arrayInitializerComponentType);
}
}
}

View File

@@ -33,7 +33,7 @@ import org.jetbrains.kotlin.asJava.elements.KtLightMethod
import org.jetbrains.kotlin.idea.base.plugin.KotlinPluginModeProvider.Companion.isK1Mode
import org.jetbrains.kotlin.idea.base.psi.kotlinFqName
import org.jetbrains.kotlin.idea.j2k.content
import org.jetbrains.kotlin.j2k.Nullability.NotNull
import org.jetbrains.kotlin.j2k.Nullability.*
import org.jetbrains.kotlin.j2k.ReferenceSearcher
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.FqName
@@ -63,16 +63,16 @@ class JavaToJKTreeBuilder(
private val declarationMapper = DeclarationMapper(expressionTreeMapper, withBody = bodyFilter == null)
private val formattingCollector = FormattingCollector()
// Per-file property with collected nullability information for various declarations.
// Per-file property with collected nullability information.
// Needs to be flushed before building the tree for each root element.
private var declarationNullabilityInfo: DeclarationNullabilityInfo? = null
private var nullabilityInfo: NullabilityInfo? = null
companion object {
private val LOG = Logger.getInstance("@org.jetbrains.kotlin.nj2k.JavaToJKTreeBuilder")
}
fun buildTree(psi: PsiElement, saveImports: Boolean): JKTreeRoot? {
declarationNullabilityInfo = null
nullabilityInfo = null
return when (psi) {
is PsiJavaFile -> psi.toJK()
@@ -1160,8 +1160,13 @@ class JavaToJKTreeBuilder(
LOG.error(t)
}
declarationNullabilityInfo = DeclarationNullabilityInfo(nullityInferrer.nullableTypes, nullityInferrer.notNullTypes)
typeFactory.declarationNullabilityInfo = declarationNullabilityInfo
nullabilityInfo = NullabilityInfo(
nullityInferrer.nullableTypes,
nullityInferrer.notNullTypes,
nullityInferrer.nullableElements,
nullityInferrer.notNullElements
)
typeFactory.nullabilityInfo = nullabilityInfo
}
private fun PsiImportList?.toJK(saveImports: Boolean): JKImportList =

View File

@@ -2,16 +2,20 @@
package org.jetbrains.kotlin.nj2k
import com.intellij.codeInsight.daemon.impl.quickfix.AddTypeArgumentsFix
import com.intellij.ide.actions.QualifiedNameProviderUtil
import com.intellij.openapi.application.runReadAction
import com.intellij.openapi.module.Module
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.project.Project
import com.intellij.psi.*
import com.intellij.psi.infos.MethodCandidateInfo
import org.jetbrains.kotlin.analysis.api.KaSession
import org.jetbrains.kotlin.analysis.api.analyze
import org.jetbrains.kotlin.analysis.api.permissions.KaAllowAnalysisOnEdt
import org.jetbrains.kotlin.analysis.api.permissions.allowAnalysisOnEdt
import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl
import org.jetbrains.kotlin.idea.base.plugin.KotlinPluginModeProvider.Companion.isK2Mode
import org.jetbrains.kotlin.idea.base.projectStructure.languageVersionSettings
import org.jetbrains.kotlin.idea.base.projectStructure.productionOrTestSourceModuleInfo
import org.jetbrains.kotlin.idea.base.projectStructure.toKaModule
@@ -22,12 +26,10 @@ import org.jetbrains.kotlin.nj2k.J2KConversionPhase.*
import org.jetbrains.kotlin.nj2k.externalCodeProcessing.NewExternalCodeProcessing
import org.jetbrains.kotlin.nj2k.printing.JKCodeBuilder
import org.jetbrains.kotlin.nj2k.types.JKTypeFactory
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtImportList
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
import org.jetbrains.kotlin.resolve.ImportPath
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
class NewJavaToKotlinConverter(
val project: Project,
@@ -37,7 +39,7 @@ class NewJavaToKotlinConverter(
) : JavaToKotlinConverter() {
val phasesCount = J2KConversionPhase.entries.size
val referenceSearcher: ReferenceSearcher = IdeaReferenceSearcher
private val phaseDescription: String = KotlinNJ2KBundle.message("phase.converting.j2k")
private val phaseDescription: String = KotlinNJ2KBundle.message("j2k.phase.converting")
override fun filesToKotlin(
files: List<PsiJavaFile>,
@@ -57,7 +59,14 @@ class NewJavaToKotlinConverter(
postprocessorExtensions: List<J2kPostprocessorExtension>
): FilesResult {
val withProgressProcessor = NewJ2kWithProgressProcessor(progressIndicator, files, postProcessor.phasesCount + phasesCount)
return withProgressProcessor.process {
// TODO looks like the progress dialog doesn't appear immediately, but should
withProgressProcessor.updateState(fileIndex = null, phase = PREPROCESSING, KotlinNJ2KBundle.message("j2k.phase.preprocessing"))
if (isK2Mode()) {
runK2Preprocessing(files)
}
PreprocessorExtensionsRunner.runProcessors(project, files, preprocessorExtensions)
val (results, externalCodeProcessing, context) = runReadAction {
@@ -83,6 +92,52 @@ class NewJavaToKotlinConverter(
}
}
// A preprocessing to add explicit type arguments (needed for K2 nullability inference).
// Resolve is done as a separate step for optimization.
private fun runK2Preprocessing(files: List<PsiJavaFile>) {
val set = mutableSetOf<PsiMethodCallExpression>()
runReadAction {
for (file in files) {
file.accept(object : JavaRecursiveElementWalkingVisitor() {
override fun visitMethodCallExpression(expression: PsiMethodCallExpression) {
super.visitMethodCallExpression(expression)
if (expression.typeArguments.isNotEmpty()) return
val resolveResult = expression.resolveMethodGenerics()
if (resolveResult is MethodCandidateInfo && resolveResult.isApplicable) {
val method = resolveResult.element
if (method.isConstructor || !method.hasTypeParameters()) return
// Avoid incorrect type arguments insertion that will lead to red code
QualifiedNameProviderUtil.getQualifiedName(method)?.let { methodName ->
if (methodName.startsWith("java.util.stream.Stream#collect") ||
methodName.startsWith("java.util.stream.Collectors")
) {
return
}
}
}
set += expression
}
})
}
}
runUndoTransparentActionInEdt(inWriteAction = true) {
for (expression in set) {
val typeArgumentList = AddTypeArgumentsFix.addTypeArguments(expression, null, false)
?.safeAs<PsiMethodCallExpression>()
?.typeArgumentList
?: continue
expression.typeArgumentList.replace(typeArgumentList)
}
}
}
@OptIn(KaAllowAnalysisOnEdt::class)
fun elementsToKotlin(
inputElements: List<PsiElement>,
@@ -263,8 +318,9 @@ private fun WithProgressProcessor.updateState(fileIndex: Int?, phase: J2KConvers
}
private enum class J2KConversionPhase(val phaseNumber: Int) {
BUILD_AST(0),
RUN_CONVERSIONS(1),
PRINT_CODE(2),
CREATE_FILES(3)
PREPROCESSING(0),
BUILD_AST(1),
RUN_CONVERSIONS(2),
PRINT_CODE(3),
CREATE_FILES(4)
}

View File

@@ -8,7 +8,7 @@ import org.jetbrains.kotlin.analysis.api.KaSession
import org.jetbrains.kotlin.j2k.Nullability.NotNull
import org.jetbrains.kotlin.nj2k.NewJ2kConverterContext
import org.jetbrains.kotlin.nj2k.RecursiveConversion
import org.jetbrains.kotlin.nj2k.getDfaNullability
import org.jetbrains.kotlin.nj2k.getExpressionDfaNullability
import org.jetbrains.kotlin.nj2k.psi
import org.jetbrains.kotlin.nj2k.tree.*
import org.jetbrains.kotlin.nj2k.types.updateNullability
@@ -56,7 +56,7 @@ class NullabilityConversion(context: NewJ2kConverterContext) : RecursiveConversi
// If Java DFA knows that the cast expression is not null, it is safe to cast to the not-null type
psiCastedExpression != null -> {
val dfaNullability = getDfaNullability(psiCastedExpression)
val dfaNullability = getExpressionDfaNullability(psiCastedExpression)
dfaNullability == DfaNullability.NOT_NULL
}

View File

@@ -1,25 +1,47 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.nj2k
import com.intellij.codeInsight.Nullability
import com.intellij.codeInspection.dataFlow.CommonDataflow
import com.intellij.codeInspection.dataFlow.DfaNullability
import com.intellij.codeInspection.dataFlow.DfaUtil
import com.intellij.codeInspection.dataFlow.types.DfReferenceType
import com.intellij.psi.PsiExpression
import com.intellij.psi.PsiPrimitiveType
import com.intellij.psi.PsiReferenceExpression
import com.intellij.psi.PsiType
import com.intellij.psi.util.TypeConversionUtil
import com.intellij.openapi.util.Key
import com.intellij.psi.*
import com.intellij.psi.util.*
import com.siyeh.ig.psiutils.ExpectedTypeUtils
import org.jetbrains.kotlin.analysis.api.analyze
import org.jetbrains.kotlin.analysis.api.types.KaTypeNullability
import org.jetbrains.kotlin.asJava.elements.KtLightDeclaration
import org.jetbrains.kotlin.psi.KtTypeParameter
/**
* The set of possible declaration types is determined by [J2KNullityInferrer],
* currently: PsiMethod, PsiLocalVariable, PsiParameter, PsiField.
* A collection of types with known nullability as determined by [J2KNullityInferrer].
* This information is used to set the nullability of created `JKType`s in [org.jetbrains.kotlin.nj2k.types.JKTypeFactory].
*/
internal data class DeclarationNullabilityInfo(
internal data class NullabilityInfo(
val nullableTypes: Set<PsiType>,
val notNullTypes: Set<PsiType>,
val nullableElements: Set<PsiJavaCodeReferenceElement>,
val notNullElements: Set<PsiJavaCodeReferenceElement>,
)
// Only Kotlin type parameters are supported
internal fun getTypeParameterNullability(psiTypeParameter: PsiTypeParameter): Nullability {
val ktTypeParameter = (psiTypeParameter as? KtLightDeclaration<*, *>)?.kotlinOrigin as? KtTypeParameter
?: return Nullability.UNKNOWN
analyze(ktTypeParameter) {
for (upperBound in ktTypeParameter.symbol.upperBounds) {
if (upperBound.nullability == KaTypeNullability.NON_NULLABLE) {
return Nullability.NOT_NULL
}
}
return Nullability.NULLABLE
}
}
internal fun isUsedInAutoUnboxingContext(expr: PsiReferenceExpression): Boolean {
val exprType = expr.type
if (!TypeConversionUtil.isAssignableFromPrimitiveWrapper(exprType)) return false
@@ -31,9 +53,19 @@ internal fun isUsedInAutoUnboxingContext(expr: PsiReferenceExpression): Boolean
return expectedType.isAssignableFrom(unboxedType)
}
internal fun getDfaNullability(expr: PsiExpression): DfaNullability? {
// The result is cached by the Java DFA itself (this function is still hot, though)
internal fun getExpressionDfaNullability(expr: PsiExpression): DfaNullability? {
val dataflowResult = CommonDataflow.getDataflowResult(expr) ?: return null
val dfType = dataflowResult.getDfType(expr)
if (dfType !is DfReferenceType) return null
return dfType.getNullability()
}
}
// This function should not be called very often, but the computation is expensive, so it's better to cache it anyway
internal fun getMethodNullabilityByDfa(method: PsiMethod): Nullability {
return CachedValuesManager.getManager(method.project).getCachedValue(method, METHOD_NULLABILITY_KEY, {
CachedValueProvider.Result(DfaUtil.inferMethodNullability(method), PsiModificationTracker.MODIFICATION_COUNT)
}, false)
}
private val METHOD_NULLABILITY_KEY = Key.create<CachedValue<Nullability>>("METHOD_NULLABILITY")

View File

@@ -3,6 +3,7 @@
package org.jetbrains.kotlin.nj2k.types
import com.intellij.psi.*
import com.intellij.psi.impl.source.PsiClassReferenceType
import org.jetbrains.kotlin.analysis.api.KaSession
import org.jetbrains.kotlin.analysis.api.types.KaClassType
import org.jetbrains.kotlin.analysis.api.types.KaStarTypeProjection
@@ -12,7 +13,7 @@ import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.j2k.Nullability
import org.jetbrains.kotlin.j2k.Nullability.*
import org.jetbrains.kotlin.name.FqNameUnsafe
import org.jetbrains.kotlin.nj2k.DeclarationNullabilityInfo
import org.jetbrains.kotlin.nj2k.NullabilityInfo
import org.jetbrains.kotlin.nj2k.JKSymbolProvider
import org.jetbrains.kotlin.nj2k.symbols.JKClassSymbol
import org.jetbrains.kotlin.nj2k.symbols.JKTypeParameterSymbol
@@ -20,7 +21,7 @@ import org.jetbrains.kotlin.nj2k.symbols.JKUnresolvedClassSymbol
import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType
class JKTypeFactory(val symbolProvider: JKSymbolProvider) {
internal var declarationNullabilityInfo: DeclarationNullabilityInfo? = null
internal var nullabilityInfo: NullabilityInfo? = null
fun fromPsiType(type: PsiType): JKType = createPsiType(type)
@@ -68,13 +69,7 @@ class JKTypeFactory(val symbolProvider: JKSymbolProvider) {
val types by lazy(LazyThreadSafetyMode.NONE) { DefaultTypes() }
private fun createPsiType(type: PsiType): JKType {
val nullability = declarationNullabilityInfo?.let {
when {
it.nullableTypes.contains(type) -> Nullable
it.notNullTypes.contains(type) -> NotNull
else -> Default
}
} ?: Default
val nullability = getNullability(type)
return when (type) {
is PsiClassType -> {
@@ -147,6 +142,17 @@ class JKTypeFactory(val symbolProvider: JKSymbolProvider) {
}
}
private fun getNullability(type: PsiType): Nullability {
val info = nullabilityInfo ?: return Default
val referenceElement = if (type is PsiClassReferenceType) type.reference else null
val nullability = when {
info.nullableTypes.contains(type) || info.nullableElements.contains(referenceElement) -> Nullable
info.notNullTypes.contains(type) || info.notNullElements.contains(referenceElement) -> NotNull
else -> Default
}
return nullability
}
context(KaSession)
private fun createKaType(type: KaType): JKType {
return when (type) {

View File

@@ -1,4 +1,3 @@
// IGNORE_K2
// !FORCE_NOT_NULL_TYPES: false
// !SPECIFY_LOCAL_VARIABLE_TYPE_BY_DEFAULT: true
import java.util.HashSet;

View File

@@ -1,4 +1,3 @@
// IGNORE_K2
import java.util.HashSet;
class Foo {

View File

@@ -1,3 +1,4 @@
// TODO: `name` parameter should be nullable in K2, looks like a bug in PsiClassType#getPsiContext()
public class TestClass {
private static String getCheckKey(String category, String name, boolean createWithProject) {
return category + ':' + name + ':' + createWithProject;

View File

@@ -1,5 +1,6 @@
// TODO: `name` parameter should be nullable in K2, looks like a bug in PsiClassType#getPsiContext()
object TestClass {
private fun getCheckKey(category: String?, name: String?, createWithProject: Boolean): String {
private fun getCheckKey(category: String?, name: String, createWithProject: Boolean): String {
return category + ':' + name + ':' + createWithProject
}
}

View File

@@ -1,3 +1,4 @@
// TODO: `name` parameter should be nullable in K2, looks like a bug in PsiClassType#getPsiContext()
object TestClass {
private fun getCheckKey(category: String, name: String, createWithProject: Boolean): String {
return "$category:$name:$createWithProject"

View File

@@ -0,0 +1,8 @@
package demo
internal class Test {
fun test(vararg args: Any?) {
var args = args
args = arrayOf<Int?>(1, 2, 3)
}
}

View File

@@ -3,6 +3,6 @@ package demo
internal class Test {
fun test(vararg args: Any?) {
var args = args
args = arrayOf<Int?>(1, 2, 3)
args = arrayOf<Int>(1, 2, 3)
}
}

View File

@@ -1,4 +1,3 @@
// IGNORE_K2
// !ADD_JAVA_API
import javaApi.WithVarargConstructor;

View File

@@ -0,0 +1,11 @@
internal class C {
var s: String = ""
}
internal class D {
fun foo(c: C) {
if (null == c.s) {
println("null")
}
}
}

View File

@@ -1,5 +1,5 @@
internal class C {
var s: String = ""
var s: String? = ""
}
internal class D {

View File

@@ -0,0 +1,11 @@
// JVM_TARGET: 17
import org.jetbrains.annotations.NotNull;
public class J {
public void test(String s) {
new D(s);
}
}
record D(@NotNull String s) {
}

View File

@@ -0,0 +1,16 @@
// ERROR: Primary constructor of data class must only have property ('val' / 'var') parameters.
// ERROR: Non-constructor properties with backing field in '@JvmRecord' class are prohibited.
class J {
fun test(s: String) {
D(s)
}
}
@JvmRecord
internal data class D(s: String) {
val s: String
init {
this.s = s
}
}

View File

@@ -0,0 +1,8 @@
class J {
fun test(s: String) {
D(s)
}
}
@JvmRecord
internal data class D(val s: String)

View File

@@ -0,0 +1,15 @@
// JVM_TARGET: 17
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
public class C {
private final ArrayList<String> field = new ArrayList<>();
public void foo() {
new D(field);
}
}
record D(@NotNull ArrayList<@NotNull String> param) {
}

View File

@@ -0,0 +1,18 @@
// ERROR: Primary constructor of data class must only have property ('val' / 'var') parameters.
// ERROR: Non-constructor properties with backing field in '@JvmRecord' class are prohibited.
class C {
private val field = ArrayList<String>()
fun foo() {
D(field)
}
}
@JvmRecord
internal data class D(param: ArrayList<String>) {
val param: ArrayList<String>
init {
this.param = param
}
}

View File

@@ -0,0 +1,10 @@
class C {
private val field = ArrayList<String>()
fun foo() {
D(field)
}
}
@JvmRecord
internal data class D(val param: ArrayList<String>)

View File

@@ -0,0 +1,54 @@
// TODO support array initializers
class ArrayArgument {
void test(String[] array) {
for (String s : array) {
System.out.println(s.hashCode());
}
takesArray(array);
}
private void takesArray(String[] array) {
}
}
class ArrayMethodCall {
void test() {
String[] array = strings();
for (String s : array) {
System.out.println(s.hashCode());
}
}
private String[] strings() {
return strings2();
}
private String[] strings2() {
return new String[0];
}
}
class ArrayParameter {
void test(String[] param) {
String[] array = param;
for (String s : array) {
System.out.println(s.hashCode());
}
}
}
class ArrayField {
String[] field = new String[0];
void test() {
String[] array = field;
for (String s : array) {
System.out.println(s.hashCode());
}
}
}

View File

@@ -0,0 +1,56 @@
// ERROR: Initializer type mismatch: expected 'kotlin.Array<kotlin.String>', actual 'kotlin.Array<kotlin.String?>'.
// ERROR: Type mismatch: inferred type is 'kotlin.Array<T?>', but 'kotlin.Array<kotlin.String>' was expected.
// ERROR: Return type mismatch: expected 'kotlin.Array<kotlin.String>', actual 'kotlin.Array<kotlin.String?>'.
// TODO support array initializers
internal class ArrayArgument {
fun test(array: Array<String>) {
for (s in array) {
println(s.hashCode())
}
takesArray(array)
}
private fun takesArray(array: Array<String>?) {
}
}
internal class ArrayMethodCall {
fun test() {
val array = strings()
for (s in array) {
println(s.hashCode())
}
}
private fun strings(): Array<String> {
return strings2()
}
private fun strings2(): Array<String> {
return arrayOfNulls<String>(0)
}
}
internal class ArrayParameter {
fun test(param: Array<String>) {
val array = param
for (s in array) {
println(s.hashCode())
}
}
}
internal class ArrayField {
var field: Array<String> = arrayOfNulls<String>(0)
fun test() {
val array = field
for (s in array) {
println(s.hashCode())
}
}
}

View File

@@ -0,0 +1,53 @@
// TODO support array initializers
internal class ArrayArgument {
fun test(array: Array<String>) {
for (s in array) {
println(s.hashCode())
}
takesArray(array)
}
private fun takesArray(array: Array<String>) {
}
}
internal class ArrayMethodCall {
fun test() {
val array = strings()
for (s in array) {
println(s.hashCode())
}
}
private fun strings(): Array<String?> {
return strings2()
}
private fun strings2(): Array<String?> {
return arrayOfNulls(0)
}
}
internal class ArrayParameter {
fun test(param: Array<String>) {
val array = param
for (s in array) {
println(s.hashCode())
}
}
}
internal class ArrayField {
var field: Array<String?> = arrayOfNulls(0)
fun test() {
val array = field
for (s in array) {
println(s.hashCode())
}
}
}

View File

@@ -0,0 +1,9 @@
class ArrayField {
void test() {
String[] array = new String[0];
for (String s : array) {
System.out.println(s.length());
}
}
}

View File

@@ -0,0 +1,11 @@
// ERROR: Initializer type mismatch: expected 'kotlin.Array<kotlin.String>', actual 'kotlin.Array<kotlin.String?>'.
// ERROR: Type mismatch: inferred type is 'kotlin.Array<T?>', but 'kotlin.Array<kotlin.String>' was expected.
internal class ArrayField {
fun test() {
val array: Array<String> = arrayOfNulls<String>(0)
for (s in array) {
println(s.length)
}
}
}

View File

@@ -0,0 +1,10 @@
// ERROR: Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String?
internal class ArrayField {
fun test() {
val array = arrayOfNulls<String>(0)
for (s in array) {
println(s.length)
}
}
}

View File

@@ -0,0 +1,69 @@
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
class AssignmentLocal {
ArrayList<String> field = new ArrayList<>();
void test(ArrayList<String> param) {
ArrayList<String> local = field;
if (local.isEmpty()) {
local = initLocal();
} else {
local = param;
}
}
private ArrayList<@NotNull String> initLocal() {
return new ArrayList<>();
}
}
class AssignmentLocalReverse {
ArrayList<String> field = new ArrayList<>();
void test(ArrayList<String> param) {
ArrayList<@NotNull String> local = field;
if (local.isEmpty()) {
local = initLocal();
} else {
local = param;
}
}
private ArrayList<String> initLocal() {
return new ArrayList<>();
}
}
class AssignmentField {
ArrayList<String> field = new ArrayList<>();
void test(ArrayList<String> param) {
if (field.isEmpty()) {
field = initField();
} else {
field = param;
}
}
private ArrayList<@NotNull String> initField() {
return new ArrayList<>();
}
}
class AssignmentFieldReverse {
ArrayList<@NotNull String> field = new ArrayList<>();
void test(ArrayList<String> param) {
if (field.isEmpty()) {
field = initField();
} else {
field = param;
}
}
private ArrayList<String> initField() {
return new ArrayList<>();
}
}

View File

@@ -0,0 +1,65 @@
internal class AssignmentLocal {
var field: ArrayList<String> = ArrayList<String>()
fun test(param: ArrayList<String>) {
var local = field
if (local.isEmpty()) {
local = initLocal()
} else {
local = param
}
}
private fun initLocal(): ArrayList<String> {
return ArrayList<String>()
}
}
internal class AssignmentLocalReverse {
var field: ArrayList<String> = ArrayList<String>()
fun test(param: ArrayList<String>) {
var local = field
if (local.isEmpty()) {
local = initLocal()
} else {
local = param
}
}
private fun initLocal(): ArrayList<String> {
return ArrayList<String>()
}
}
internal class AssignmentField {
var field: ArrayList<String> = ArrayList<String>()
fun test(param: ArrayList<String>) {
if (field.isEmpty()) {
field = initField()
} else {
field = param
}
}
private fun initField(): ArrayList<String> {
return ArrayList<String>()
}
}
internal class AssignmentFieldReverse {
var field: ArrayList<String> = ArrayList<String>()
fun test(param: ArrayList<String>) {
if (field.isEmpty()) {
field = initField()
} else {
field = param
}
}
private fun initField(): ArrayList<String> {
return ArrayList<String>()
}
}

View File

@@ -0,0 +1,65 @@
internal class AssignmentLocal {
var field: ArrayList<String> = ArrayList()
fun test(param: ArrayList<String>) {
var local = field
local = if (local.isEmpty()) {
initLocal()
} else {
param
}
}
private fun initLocal(): ArrayList<String> {
return ArrayList()
}
}
internal class AssignmentLocalReverse {
var field: ArrayList<String> = ArrayList()
fun test(param: ArrayList<String>) {
var local = field
local = if (local.isEmpty()) {
initLocal()
} else {
param
}
}
private fun initLocal(): ArrayList<String> {
return ArrayList()
}
}
internal class AssignmentField {
var field: ArrayList<String> = ArrayList()
fun test(param: ArrayList<String>) {
field = if (field.isEmpty()) {
initField()
} else {
param
}
}
private fun initField(): ArrayList<String> {
return ArrayList()
}
}
internal class AssignmentFieldReverse {
var field: ArrayList<String> = ArrayList()
fun test(param: ArrayList<String>) {
field = if (field.isEmpty()) {
initField()
} else {
param
}
}
private fun initField(): ArrayList<String> {
return ArrayList()
}
}

View File

@@ -0,0 +1,19 @@
import java.util.ArrayList;
public class Main {
public ArrayList<String> f1() {
return new ArrayList<>();
}
public ArrayList<String> f2() {
ArrayList<String> list = f1();
f3(list);
return list;
}
public void f3(ArrayList<String> list) {
for (String item : list) {
System.out.println(item.length());
}
}
}

View File

@@ -0,0 +1,17 @@
class Main {
fun f1(): ArrayList<String> {
return ArrayList<String>()
}
fun f2(): ArrayList<String> {
val list = f1()
f3(list)
return list
}
fun f3(list: ArrayList<String>) {
for (item in list) {
println(item.length)
}
}
}

View File

@@ -0,0 +1,17 @@
class Main {
fun f1(): ArrayList<String> {
return ArrayList()
}
fun f2(): ArrayList<String> {
val list = f1()
f3(list)
return list
}
fun f3(list: ArrayList<String>) {
for (item in list) {
println(item.length)
}
}
}

View File

@@ -0,0 +1,39 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
public class J {
@Nullable ArrayList<@Nullable String> field1;
@Nullable ArrayList<@NotNull String> field2;
@NotNull ArrayList<@Nullable String> field3 = new ArrayList<>();
@NotNull ArrayList<@NotNull String> field4 = new ArrayList<>();
public @Nullable ArrayList<@Nullable String> return1() {
return null;
}
public @Nullable ArrayList<@NotNull String> return2() {
return null;
}
public @NotNull ArrayList<@Nullable String> return3() {
return new ArrayList<>();
}
public @NotNull ArrayList<@NotNull String> return4() {
return new ArrayList<>();
}
public void argument1(@Nullable ArrayList<@Nullable String> x) {
}
public void argument2(@Nullable ArrayList<@NotNull String> x) {
}
public void argument3(@NotNull ArrayList<@Nullable String> x) {
}
public void argument4(@NotNull ArrayList<@NotNull String> x) {
}
}

View File

@@ -0,0 +1,44 @@
import java.util.ArrayList;
public class Foo {
void testAssignment(J j) {
ArrayList<String> l1 = j.return1();
ArrayList<String> l2 = j.return2();
ArrayList<String> l3 = j.return3();
ArrayList<String> l4 = j.return4();
ArrayList<String> l5 = j.field1;
ArrayList<String> l6 = j.field2;
ArrayList<String> l7 = j.field3;
ArrayList<String> l8 = j.field4;
}
void testArgument(
J j,
ArrayList<String> l1,
ArrayList<String> l2,
ArrayList<String> l3,
ArrayList<String> l4
) {
j.argument1(l1);
j.argument2(l2);
j.argument3(l3);
j.argument4(l4);
}
ArrayList<String> testReturn1(J j) {
return j.return1();
}
ArrayList<String> testReturn2(J j) {
return j.return2();
}
ArrayList<String> testReturn3(J j) {
return j.return3();
}
ArrayList<String> testReturn4(J j) {
return j.return4();
}
}

View File

@@ -0,0 +1,42 @@
class Foo {
fun testAssignment(j: J) {
val l1 = j.return1()
val l2 = j.return2()
val l3 = j.return3()
val l4 = j.return4()
val l5 = j.field1
val l6 = j.field2
val l7 = j.field3
val l8 = j.field4
}
fun testArgument(
j: J,
l1: ArrayList<String?>?,
l2: ArrayList<String?>?,
l3: ArrayList<String?>,
l4: ArrayList<String?>
) {
j.argument1(l1)
j.argument2(l2)
j.argument3(l3)
j.argument4(l4)
}
fun testReturn1(j: J): ArrayList<String?>? {
return j.return1()
}
fun testReturn2(j: J): ArrayList<String?>? {
return j.return2()
}
fun testReturn3(j: J): ArrayList<String?> {
return j.return3()
}
fun testReturn4(j: J): ArrayList<String?> {
return j.return4()
}
}

View File

@@ -0,0 +1,42 @@
class Foo {
fun testAssignment(j: J) {
val l1 = j.return1()
val l2 = j.return2()
val l3 = j.return3()
val l4 = j.return4()
val l5 = j.field1
val l6 = j.field2
val l7 = j.field3
val l8 = j.field4
}
fun testArgument(
j: J,
l1: ArrayList<String?>?,
l2: ArrayList<String?>?,
l3: ArrayList<String?>,
l4: ArrayList<String?>
) {
j.argument1(l1)
j.argument2(l2)
j.argument3(l3)
j.argument4(l4)
}
fun testReturn1(j: J): ArrayList<String>? {
return j.return1()
}
fun testReturn2(j: J): ArrayList<String>? {
return j.return2()
}
fun testReturn3(j: J): ArrayList<String> {
return j.return3()
}
fun testReturn4(j: J): ArrayList<String> {
return j.return4()
}
}

View File

@@ -0,0 +1,13 @@
import java.util.ArrayList
class K {
fun return1(): ArrayList<String?>? = null
fun return2(): ArrayList<String>? = null
fun return3(): ArrayList<String?> = ArrayList()
fun return4(): ArrayList<String> = ArrayList()
fun argument1(x: ArrayList<String?>?) {}
fun argument2(x: ArrayList<String>?) {}
fun argument3(x: ArrayList<String?>) {}
fun argument4(x: ArrayList<String>) {}
}

View File

@@ -0,0 +1,39 @@
import java.util.ArrayList;
public class Foo {
void testAssignment(K k) {
ArrayList<String> l1 = k.return1();
ArrayList<String> l2 = k.return2();
ArrayList<String> l3 = k.return3();
ArrayList<String> l4 = k.return4();
}
void testArgument(
K k,
ArrayList<String> l1,
ArrayList<String> l2,
ArrayList<String> l3,
ArrayList<String> l4
) {
k.argument1(l1);
k.argument2(l2);
k.argument3(l3);
k.argument4(l4);
}
ArrayList<String> testReturn1(K k) {
return k.return1();
}
ArrayList<String> testReturn2(K k) {
return k.return2();
}
ArrayList<String> testReturn3(K k) {
return k.return3();
}
ArrayList<String> testReturn4(K k) {
return k.return4();
}
}

View File

@@ -0,0 +1,45 @@
// ERROR: Return type mismatch: expected 'java.util.ArrayList<kotlin.String?>', actual 'java.util.ArrayList<kotlin.String>'.
// ERROR: Return type mismatch: expected 'java.util.ArrayList<kotlin.String?>?', actual 'java.util.ArrayList<kotlin.String>?'.
// ERROR: Argument type mismatch: actual type is 'java.util.ArrayList<kotlin.String?>?', but 'java.util.ArrayList<kotlin.String>?' was expected.
// ERROR: Argument type mismatch: actual type is 'java.util.ArrayList<kotlin.String?>', but 'java.util.ArrayList<kotlin.String>' was expected.
// ERROR: Initializer type mismatch: expected 'java.util.ArrayList<kotlin.String?>?', actual 'java.util.ArrayList<kotlin.String>?'.
// ERROR: Type mismatch: inferred type is 'java.util.ArrayList<kotlin.String>?', but 'java.util.ArrayList<kotlin.String?>?' was expected.
// ERROR: Initializer type mismatch: expected 'java.util.ArrayList<kotlin.String?>', actual 'java.util.ArrayList<kotlin.String>'.
// ERROR: Type mismatch: inferred type is 'java.util.ArrayList<kotlin.String>', but 'java.util.ArrayList<kotlin.String?>' was expected.
class Foo {
fun testAssignment(k: K) {
val l1 = k.return1()
val l2: ArrayList<String?>? = k.return2()
val l3 = k.return3()
val l4: ArrayList<String?> = k.return4()
}
fun testArgument(
k: K,
l1: ArrayList<String?>?,
l2: ArrayList<String?>?,
l3: ArrayList<String?>,
l4: ArrayList<String?>
) {
k.argument1(l1)
k.argument2(l2)
k.argument3(l3)
k.argument4(l4)
}
fun testReturn1(k: K): ArrayList<String?>? {
return k.return1()
}
fun testReturn2(k: K): ArrayList<String?>? {
return k.return2()
}
fun testReturn3(k: K): ArrayList<String?> {
return k.return3()
}
fun testReturn4(k: K): ArrayList<String?> {
return k.return4()
}
}

View File

@@ -0,0 +1,39 @@
// ERROR: Type mismatch: inferred type is kotlin.collections.ArrayList<String?>? /* = java.util.ArrayList<String?>? */ but java.util.ArrayList<String>? was expected
// ERROR: Type mismatch: inferred type is kotlin.collections.ArrayList<String?> /* = java.util.ArrayList<String?> */ but java.util.ArrayList<String> was expected
class Foo {
fun testAssignment(k: K) {
val l1 = k.return1()
val l2 = k.return2()
val l3 = k.return3()
val l4 = k.return4()
}
fun testArgument(
k: K,
l1: ArrayList<String?>?,
l2: ArrayList<String?>?,
l3: ArrayList<String?>,
l4: ArrayList<String?>
) {
k.argument1(l1)
k.argument2(l2)
k.argument3(l3)
k.argument4(l4)
}
fun testReturn1(k: K): ArrayList<String?>? {
return k.return1()
}
fun testReturn2(k: K): ArrayList<String>? {
return k.return2()
}
fun testReturn3(k: K): ArrayList<String?> {
return k.return3()
}
fun testReturn4(k: K): ArrayList<String> {
return k.return4()
}
}

View File

@@ -0,0 +1,42 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
// TODO handle the case when type argument is used in the method return type (make it not-null)
public class J {
Set<String> notNullSet = Collections.emptySet();
Collection<String> notNullCollection = Collections.emptyList();
Collection<String> nullableCollection = Collections.emptyList();
List<@NotNull String> notNullList = List.of();
List<@Nullable String> nullableList = List.of();
void foo() {
for (String s : notNullSet) {
System.out.println(s.length());
}
for (String s : notNullCollection) {
System.out.println(s.length());
}
for (String s : nullableCollection) {
if (s != null) {
System.out.println(s.length());
}
}
takeNotNullCollection(Collections.emptyList());
takeNotNullCollection(returnNotNullCollection());
}
private void takeNotNullCollection(Collection<@NotNull String> strings) {
}
Collection<String> returnNotNullCollection() {
return Collections.emptyList();
}
}

View File

@@ -0,0 +1,41 @@
// ERROR: Return type mismatch: expected 'kotlin.collections.MutableCollection<kotlin.String>', actual 'kotlin.collections.MutableList<kotlin.String?>'.
// ERROR: Initializer type mismatch: expected 'kotlin.collections.MutableCollection<kotlin.String>', actual 'kotlin.collections.MutableList<kotlin.String?>'.
// ERROR: Type mismatch: inferred type is 'kotlin.String?', but 'kotlin.String' was expected.
// ERROR: Type mismatch: inferred type is 'kotlin.String?', but 'kotlin.String' was expected.
// ERROR: Initializer type mismatch: expected 'kotlin.collections.MutableSet<kotlin.String>', actual 'kotlin.collections.MutableSet<kotlin.String?>'.
// ERROR: Type mismatch: inferred type is 'kotlin.String?', but 'kotlin.String' was expected.
// ERROR: Type mismatch: inferred type is 'kotlin.String?', but 'kotlin.String' was expected.
// TODO handle the case when type argument is used in the method return type (make it not-null)
class J {
var notNullSet: MutableSet<String> = mutableSetOf<String?>()
var notNullCollection: MutableCollection<String> = mutableListOf<String?>()
var nullableCollection: MutableCollection<String?> = mutableListOf<String?>()
var notNullList: MutableList<String> = mutableListOf<String>()
var nullableList: MutableList<String?> = mutableListOf<String?>()
fun foo() {
for (s in notNullSet) {
println(s.length)
}
for (s in notNullCollection) {
println(s.length)
}
for (s in nullableCollection) {
if (s != null) {
println(s.length)
}
}
takeNotNullCollection(mutableListOf<String>())
takeNotNullCollection(returnNotNullCollection())
}
private fun takeNotNullCollection(strings: MutableCollection<String>?) {
}
fun returnNotNullCollection(): MutableCollection<String> {
return mutableListOf<String?>()
}
}

View File

@@ -0,0 +1,34 @@
// TODO handle the case when type argument is used in the method return type (make it not-null)
class J {
var notNullSet: Set<String> = emptySet()
var notNullCollection: Collection<String> = emptyList()
var nullableCollection: Collection<String> = emptyList()
var notNullList: List<String> = listOf()
var nullableList: List<String?> = listOf()
fun foo() {
for (s in notNullSet) {
println(s.length)
}
for (s in notNullCollection) {
println(s.length)
}
for (s in nullableCollection) {
if (s != null) {
println(s.length)
}
}
takeNotNullCollection(emptyList())
takeNotNullCollection(returnNotNullCollection())
}
private fun takeNotNullCollection(strings: Collection<String>) {
}
fun returnNotNullCollection(): Collection<String> {
return emptyList()
}
}

View File

@@ -0,0 +1,32 @@
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
class MethodCallArgument {
void test() {
takesStrings(strings(), strings());
}
ArrayList<@NotNull String> strings() {
return new ArrayList<>();
}
private void takesStrings(ArrayList<String> strings1, ArrayList<String> strings2) {
}
}
class MethodCallArgumentReverse {
void test() {
takesStrings(strings1(), strings2());
}
ArrayList<String> strings1() {
return new ArrayList<>();
}
ArrayList<String> strings2() {
return new ArrayList<>();
}
private void takesStrings(ArrayList<@NotNull String> strings1, ArrayList<@NotNull String> strings2) {
}
}

View File

@@ -0,0 +1,29 @@
internal class MethodCallArgument {
fun test() {
takesStrings(strings(), strings())
}
fun strings(): ArrayList<String> {
return ArrayList<String>()
}
private fun takesStrings(strings1: ArrayList<String>?, strings2: ArrayList<String>?) {
}
}
internal class MethodCallArgumentReverse {
fun test() {
takesStrings(strings1(), strings2())
}
fun strings1(): ArrayList<String> {
return ArrayList<String>()
}
fun strings2(): ArrayList<String> {
return ArrayList<String>()
}
private fun takesStrings(strings1: ArrayList<String>?, strings2: ArrayList<String>?) {
}
}

View File

@@ -0,0 +1,29 @@
internal class MethodCallArgument {
fun test() {
takesStrings(strings(), strings())
}
fun strings(): ArrayList<String> {
return ArrayList()
}
private fun takesStrings(strings1: ArrayList<String>, strings2: ArrayList<String>) {
}
}
internal class MethodCallArgumentReverse {
fun test() {
takesStrings(strings1(), strings2())
}
fun strings1(): ArrayList<String> {
return ArrayList()
}
fun strings2(): ArrayList<String> {
return ArrayList()
}
private fun takesStrings(strings1: ArrayList<String>, strings2: ArrayList<String>) {
}
}

View File

@@ -0,0 +1,22 @@
public class C {
public String[] stringsField = new String[]{"Hello", "World"};
public void field() {
for (String s : stringsField) {
System.out.println(s.length());
}
}
public void param(String[] strings) {
for (String s : strings) {
System.out.println(s.length());
}
}
public void local() {
String[] stringsLocal = new String[]{"Hello", "World"};
for (String s : stringsLocal) {
System.out.println(s.length());
}
}
}

View File

@@ -0,0 +1,24 @@
// ERROR: Initializer type mismatch: expected 'kotlin.Array<kotlin.String?>', actual 'kotlin.Array<kotlin.String>'.
// ERROR: Type mismatch: inferred type is 'kotlin.String?', but 'kotlin.String' was expected.
class C {
var stringsField: Array<String> = arrayOf<String>("Hello", "World")
fun field() {
for (s in stringsField) {
println(s.length)
}
}
fun param(strings: Array<String>) {
for (s in strings) {
println(s.length)
}
}
fun local() {
val stringsLocal: Array<String?> = arrayOf<String>("Hello", "World")
for (s in stringsLocal) {
println(s!!.length)
}
}
}

View File

@@ -0,0 +1,22 @@
class C {
var stringsField: Array<String> = arrayOf("Hello", "World")
fun field() {
for (s in stringsField) {
println(s.length)
}
}
fun param(strings: Array<String>) {
for (s in strings) {
println(s.length)
}
}
fun local() {
val stringsLocal = arrayOf("Hello", "World")
for (s in stringsLocal) {
println(s.length)
}
}
}

View File

@@ -0,0 +1,93 @@
import java.util.*;
class TestCollection {
public Collection<String> stringsField = new ArrayList<>();
public void field() {
for (String s : stringsField) {
System.out.println(s.length());
}
}
public void param(Collection<String> strings) {
for (String s : strings) {
System.out.println(s.length());
}
}
public void local() {
Collection<String> stringsLocal = new ArrayList<>();
for (String s : stringsLocal) {
System.out.println(s.length());
}
}
}
class TestIterable {
public Iterable<String> stringsField = new ArrayList<>();
public void field() {
for (String s : stringsField) {
System.out.println(s.length());
}
}
public void param(Iterable<String> strings) {
for (String s : strings) {
System.out.println(s.length());
}
}
public void local() {
Iterable<String> stringsLocal = new ArrayList<>();
for (String s : stringsLocal) {
System.out.println(s.length());
}
}
}
class TestList {
public List<String> stringsField = new ArrayList<>();
public void field() {
for (String s : stringsField) {
System.out.println(s.length());
}
}
public void param(List<String> strings) {
for (String s : strings) {
System.out.println(s.length());
}
}
public void local() {
List<String> stringsLocal = new ArrayList<>();
for (String s : stringsLocal) {
System.out.println(s.length());
}
}
}
class TestSet {
public Set<String> stringsField = new HashSet<>();
public void field() {
for (String s : stringsField) {
System.out.println(s.length());
}
}
public void param(Set<String> strings) {
for (String s : strings) {
System.out.println(s.length());
}
}
public void local() {
Set<String> stringsLocal = new HashSet<>();
for (String s : stringsLocal) {
System.out.println(s.length());
}
}
}

View File

@@ -0,0 +1,91 @@
internal class TestCollection {
var stringsField: Collection<String> = ArrayList()
fun field() {
for (s in stringsField) {
println(s.length)
}
}
fun param(strings: Collection<String>) {
for (s in strings) {
println(s.length)
}
}
fun local() {
val stringsLocal: Collection<String> = ArrayList()
for (s in stringsLocal) {
println(s.length)
}
}
}
internal class TestIterable {
var stringsField: Iterable<String> = ArrayList()
fun field() {
for (s in stringsField) {
println(s.length)
}
}
fun param(strings: Iterable<String>) {
for (s in strings) {
println(s.length)
}
}
fun local() {
val stringsLocal: Iterable<String> = ArrayList()
for (s in stringsLocal) {
println(s.length)
}
}
}
internal class TestList {
var stringsField: List<String> = ArrayList()
fun field() {
for (s in stringsField) {
println(s.length)
}
}
fun param(strings: List<String>) {
for (s in strings) {
println(s.length)
}
}
fun local() {
val stringsLocal: List<String> = ArrayList()
for (s in stringsLocal) {
println(s.length)
}
}
}
internal class TestSet {
var stringsField: Set<String> = HashSet()
fun field() {
for (s in stringsField) {
println(s.length)
}
}
fun param(strings: Set<String>) {
for (s in strings) {
println(s.length)
}
}
fun local() {
val stringsLocal: Set<String> = HashSet()
for (s in stringsLocal) {
println(s.length)
}
}
}

View File

@@ -0,0 +1,91 @@
internal class TestCollection {
var stringsField: MutableCollection<String> = ArrayList<String>()
fun field() {
for (s in stringsField) {
println(s.length)
}
}
fun param(strings: MutableCollection<String>) {
for (s in strings) {
println(s.length)
}
}
fun local() {
val stringsLocal: MutableCollection<String> = ArrayList<String>()
for (s in stringsLocal) {
println(s.length)
}
}
}
internal class TestIterable {
var stringsField: Iterable<String> = ArrayList<String>()
fun field() {
for (s in stringsField) {
println(s.length)
}
}
fun param(strings: Iterable<String>) {
for (s in strings) {
println(s.length)
}
}
fun local() {
val stringsLocal: Iterable<String> = ArrayList<String>()
for (s in stringsLocal) {
println(s.length)
}
}
}
internal class TestList {
var stringsField: MutableList<String> = ArrayList<String>()
fun field() {
for (s in stringsField) {
println(s.length)
}
}
fun param(strings: MutableList<String>) {
for (s in strings) {
println(s.length)
}
}
fun local() {
val stringsLocal: MutableList<String> = ArrayList<String>()
for (s in stringsLocal) {
println(s.length)
}
}
}
internal class TestSet {
var stringsField: MutableSet<String> = HashSet<String>()
fun field() {
for (s in stringsField) {
println(s.length)
}
}
fun param(strings: MutableSet<String>) {
for (s in strings) {
println(s.length)
}
}
fun local() {
val stringsLocal: MutableSet<String> = HashSet<String>()
for (s in stringsLocal) {
println(s.length)
}
}
}

View File

@@ -0,0 +1,13 @@
import java.util.*;
class TestMethodCall {
public void test() {
for (String s : returnStrings()) {
System.out.println(s.length());
}
}
private Iterable<String> returnStrings() {
return new ArrayList<>();
}
}

View File

@@ -0,0 +1,11 @@
internal class TestMethodCall {
fun test() {
for (s in returnStrings()) {
println(s.length)
}
}
private fun returnStrings(): Iterable<String> {
return ArrayList<String>()
}
}

View File

@@ -0,0 +1,11 @@
internal class TestMethodCall {
fun test() {
for (s in returnStrings()) {
println(s.length)
}
}
private fun returnStrings(): Iterable<String> {
return ArrayList()
}
}

View File

@@ -0,0 +1,21 @@
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
class TypeArgumentOnly {
private void notNull(ArrayList<@NotNull String> strings) {
foo(strings);
}
private void foo(ArrayList<String> strings) {
}
}
class DeeplyNotNull {
private void notNull(@NotNull ArrayList<@NotNull String> strings) {
foo(strings);
}
// top-level ArrayList can still be nullable
private void foo(ArrayList<String> strings) {
}
}

View File

@@ -0,0 +1,18 @@
internal class TypeArgumentOnly {
private fun notNull(strings: ArrayList<String>) {
foo(strings)
}
private fun foo(strings: ArrayList<String>) {
}
}
internal class DeeplyNotNull {
private fun notNull(strings: ArrayList<String>) {
foo(strings)
}
// top-level ArrayList can still be nullable
private fun foo(strings: ArrayList<String>) {
}
}

View File

@@ -0,0 +1,18 @@
internal class TypeArgumentOnly {
private fun notNull(strings: ArrayList<String>?) {
foo(strings)
}
private fun foo(strings: ArrayList<String>?) {
}
}
internal class DeeplyNotNull {
private fun notNull(strings: ArrayList<String>) {
foo(strings)
}
// top-level ArrayList can still be nullable
private fun foo(strings: ArrayList<String>?) {
}
}

View File

@@ -0,0 +1,3 @@
internal class K {
fun <T : Any> f(topic: Topic<T>) {}
}

View File

@@ -0,0 +1,11 @@
class J {
Topic<String> topic = new Topic<>();
void test(K k) {
k.f(topic);
k.<String>f(topic);
}
}
class Topic<T> {
}

View File

@@ -0,0 +1,10 @@
internal class J {
var topic: Topic<String> = Topic()
fun test(k: K) {
k.f(topic)
k.f(topic)
}
}
internal class Topic<T>

View File

@@ -0,0 +1,12 @@
// ERROR: Argument type mismatch: actual type is 'Topic<kotlin.String?>', but 'Topic<T>' was expected.
// ERROR: Argument type mismatch: actual type is 'Topic<kotlin.String?>', but 'Topic<T>' was expected.
internal class J {
var topic: Topic<String?> = Topic<String?>()
fun test(k: K) {
k.f<String>(topic)
k.f<String>(topic)
}
}
internal class Topic<T>

View File

@@ -0,0 +1,5 @@
class K<T : Any> {
}
class K2<T1, T2 : Any> {
}

View File

@@ -0,0 +1,12 @@
public class J {
private static final K<String> ka = new K<String>();
private static final K<String> kb = new K<>();
private static final K<Integer> kc = new K<>();
private static final K<Object> kd = new K<Object>();
private static final K2<String, String> k2a = new K2<String, String>();
private static final K2<String, String> k2b = new K2<>();
private static final K2<String, Integer> k2c = new K2<>();
private static final K2<Integer, String> k2d = new K2<>();
private static final K2<Object, Object> k2e = new K2<Object, Object>();
}

View File

@@ -0,0 +1,12 @@
object J {
private val ka = K<String>()
private val kb = K<String>()
private val kc = K<Int>()
private val kd = K<Any>()
private val k2a = K2<String, String>()
private val k2b = K2<String, String>()
private val k2c = K2<String, Int>()
private val k2d = K2<Int, String>()
private val k2e = K2<Any, Any>()
}

View File

@@ -0,0 +1,12 @@
object J {
private val ka = K<String>()
private val kb = K<String>()
private val kc = K<Int>()
private val kd = K<Any>()
private val k2a = K2<String?, String>()
private val k2b = K2<String?, String>()
private val k2c = K2<String?, Int>()
private val k2d = K2<Int?, String>()
private val k2e = K2<Any?, Any>()
}

View File

@@ -0,0 +1,15 @@
public class C {
public void foo(String[] strings) {
bar(strings);
}
public void bar(String[] strings) {
baz(strings);
}
public void baz(String[] strings) {
for (String s : strings) {
System.out.println(s.length());
}
}
}

View File

@@ -0,0 +1,15 @@
class C {
fun foo(strings: Array<String>) {
bar(strings)
}
fun bar(strings: Array<String>) {
baz(strings)
}
fun baz(strings: Array<String>) {
for (s in strings) {
println(s.length)
}
}
}

View File

@@ -0,0 +1,16 @@
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
public class C {
private final HashMap<String, String> field = new HashMap<>();
public void foo() {
new D(field);
}
}
class D {
public D(HashMap<@NotNull String, @NotNull String> param) {
}
}

View File

@@ -0,0 +1,9 @@
class C {
private val field = HashMap<String, String>()
fun foo() {
D(field)
}
}
internal class D(param: HashMap<String, String>?)

View File

@@ -0,0 +1,15 @@
import java.util.*;
class J {
public ArrayList<String> stringsField = new ArrayList<>();
public void test() {
for (String s : stringsField) {
System.out.println(s.length());
}
}
private ArrayList<String> returnStrings() {
return stringsField;
}
}

View File

@@ -0,0 +1,13 @@
internal class J {
var stringsField: ArrayList<String> = ArrayList<String>()
fun test() {
for (s in stringsField) {
println(s.length)
}
}
private fun returnStrings(): ArrayList<String> {
return stringsField
}
}

View File

@@ -0,0 +1,13 @@
internal class J {
var stringsField: ArrayList<String> = ArrayList()
fun test() {
for (s in stringsField) {
println(s.length)
}
}
private fun returnStrings(): ArrayList<String> {
return stringsField
}
}

View File

@@ -0,0 +1,20 @@
import java.util.*;
// NOTE: wrong error message is KT-69090
class J {
private final ArrayList<String> strings = new ArrayList<>();
public void report(String s) {
strings.add(s);
}
public ArrayList<String> returnStrings() {
return strings; // update return expression type from method return type
}
public void test() {
for (String s : returnStrings()) {
System.out.println(s.length());
}
}
}

View File

@@ -0,0 +1,19 @@
// ERROR: Cannot access 'fun add(e: String!, elementData: (Array<Any!>..Array<out Any!>?), s: Int): Unit': it is private in 'java/util/ArrayList'.
// NOTE: wrong error message is KT-69090
internal class J {
private val strings = ArrayList<String>()
fun report(s: String?) {
strings.add(s)
}
fun returnStrings(): ArrayList<String> {
return strings // update return expression type from method return type
}
fun test() {
for (s in returnStrings()) {
println(s.length)
}
}
}

View File

@@ -0,0 +1,18 @@
// NOTE: wrong error message is KT-69090
internal class J {
private val strings = ArrayList<String>()
fun report(s: String) {
strings.add(s)
}
fun returnStrings(): ArrayList<String> {
return strings // update return expression type from method return type
}
fun test() {
for (s in returnStrings()) {
println(s.length)
}
}
}

View File

@@ -0,0 +1,14 @@
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
public class C {
private final HashMap<String, String> field = new HashMap<>();
public void foo() {
baz(field);
}
public void baz(HashMap<@NotNull String, @NotNull String> param) {
}
}

View File

@@ -0,0 +1,10 @@
class C {
private val field = HashMap<String, String>()
fun foo() {
baz(field)
}
fun baz(param: HashMap<String, String>?) {
}
}

View File

@@ -0,0 +1,12 @@
import java.util.ArrayList;
public class J {
private void test(ArrayList<String> strings) {
ArrayList<String> strings1 = strings;
ArrayList<String> strings2 = strings1;
for (String s : strings2) {
System.out.println(s.hashCode());
}
}
}

View File

@@ -0,0 +1,10 @@
class J {
private fun test(strings: ArrayList<String>) {
val strings1 = strings
val strings2 = strings1
for (s in strings2) {
println(s.hashCode())
}
}
}

View File

@@ -0,0 +1,9 @@
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
class ReturnMethodCallExpr {
@NotNull String test(ArrayList<String> param) {
return param.get(0);
}
}

View File

@@ -0,0 +1,5 @@
internal class ReturnMethodCallExpr {
fun test(param: ArrayList<String?>): String {
return param.get(0)!!
}
}

View File

@@ -0,0 +1,5 @@
internal class ReturnMethodCallExpr {
fun test(param: ArrayList<String>): String {
return param[0]
}
}

View File

@@ -0,0 +1,12 @@
import java.util.List;
import java.util.Map;
public class J {
void foo(List<String> list, Map<String, String> map) {
String s1 = list.get(0);
String s2 = map.get("");
s1.length(); // not-null assertion
s2.length(); // not-null assertion
}
}

View File

@@ -0,0 +1,9 @@
class J {
fun foo(list: List<String>, map: Map<String?, String?>) {
val s1 = list[0]
val s2 = map[""]
s1.length // not-null assertion
s2!!.length // not-null assertion
}
}

View File

@@ -0,0 +1,9 @@
class J {
fun foo(list: MutableList<String>, map: MutableMap<String?, String>) {
val s1 = list.get(0)
val s2: String = map.get("")!!
s1.length // not-null assertion
s2.length // not-null assertion
}
}

View File

@@ -0,0 +1,22 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public class J {
public static <T> ArrayList<@Nullable T> nullableList() {
return new ArrayList<>();
}
public static <T> ArrayList<@NotNull T> notNullList() {
return new ArrayList<>();
}
public static <T> ArrayList<T> unknownList() {
return new ArrayList<>();
}
public static <T> ArrayList<String> unrelatedList() {
return new ArrayList<>();
}
}

View File

@@ -0,0 +1,32 @@
import java.util.ArrayList;
public class Foo {
void test() {
ArrayList<String> nullableList = J.nullableList();
ArrayList<String> notNullList = J.notNullList();
ArrayList<String> unknownListNullable = J.unknownList();
ArrayList<String> unknownListNotNull = J.unknownList();
ArrayList<String> unrelatedListNotNull = J.unrelatedList();
ArrayList<String> unrelatedList2 = J.unrelatedList();
for (String s : notNullList) {
System.out.println(s.length());
}
for (String s : unknownListNotNull) {
System.out.println(s.length());
}
for (String s : unknownListNullable) {
if (s != null) {
System.out.println(s.length());
}
}
for (String s : unrelatedListNotNull) {
System.out.println(s.length());
}
}
}

View File

@@ -0,0 +1,30 @@
class Foo {
fun test() {
val nullableList = J.nullableList<String?>()
val notNullList = J.notNullList<String?>()
val unknownListNullable = J.unknownList<String?>()
val unknownListNotNull = J.unknownList<String?>()
val unrelatedListNotNull = J.unrelatedList<Any?>()
val unrelatedList2 = J.unrelatedList<Any?>()
for (s in notNullList) {
println(s.length)
}
for (s in unknownListNotNull) {
println(s.length)
}
for (s in unknownListNullable) {
if (s != null) {
println(s.length)
}
}
for (s in unrelatedListNotNull) {
println(s.length)
}
}
}

View File

@@ -0,0 +1,30 @@
class Foo {
fun test() {
val nullableList = J.nullableList<String>()
val notNullList = J.notNullList<String>()
val unknownListNullable = J.unknownList<String>()
val unknownListNotNull = J.unknownList<String>()
val unrelatedListNotNull = J.unrelatedList<Any>()
val unrelatedList2 = J.unrelatedList<Any>()
for (s in notNullList) {
println(s.length)
}
for (s in unknownListNotNull) {
println(s.length)
}
for (s in unknownListNullable) {
if (s != null) {
println(s.length)
}
}
for (s in unrelatedListNotNull) {
println(s.length)
}
}
}

View File

@@ -0,0 +1,15 @@
import java.util.*;
class TestWildcard {
public ArrayList<? extends Number> extendsNumber = new ArrayList<>();
public ArrayList<? super Number> superNumber = new ArrayList<>();
public void test() {
for (Number n : extendsNumber) {
System.out.println(n.hashCode());
}
for (Object o : superNumber) {
System.out.println(o.hashCode());
}
}
}

View File

@@ -0,0 +1,13 @@
internal class TestWildcard {
var extendsNumber: ArrayList<out Number> = ArrayList<Number>()
var superNumber: ArrayList<in Number> = ArrayList<Number>()
fun test() {
for (n in extendsNumber) {
println(n.hashCode())
}
for (o in superNumber) {
println(o.hashCode())
}
}
}

View File

@@ -0,0 +1,13 @@
internal class TestWildcard {
var extendsNumber: ArrayList<out Number> = ArrayList()
var superNumber: ArrayList<in Number> = ArrayList()
fun test() {
for (n in extendsNumber) {
println(n.hashCode())
}
for (o in superNumber) {
println(o.hashCode())
}
}
}

View File

@@ -0,0 +1,8 @@
package demo
internal class Test {
fun test(vararg args: Any?) {
var args = args
args = arrayOf<Int?>(1, 2, 3)
}
}

View File

@@ -3,6 +3,6 @@ package demo
internal class Test {
fun test(vararg args: Any?) {
var args = args
args = arrayOf<Int?>(1, 2, 3)
args = arrayOf<Int>(1, 2, 3)
}
}
}