[java-inspections] IDEA-381019 Invalid local variable type inference in JSpecify mode with generic functions

GitOrigin-RevId: 4a99f16a371b9ad909500aadc14031ebcf547149
This commit is contained in:
Mikhail Pyltsin
2025-10-28 19:20:57 +00:00
committed by intellij-monorepo-bot
parent bcf0ff8249
commit 581215e7e9
6 changed files with 78 additions and 2 deletions
@@ -266,7 +266,7 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection
}
else if (type != null) {
PsiElement context = type instanceof PsiClassType classType ? classType.getPsiContext() : annotation;
if (context != null) {
if (context != null && !(PsiUtil.isJavaToken(context, JavaTokenType.VAR_KEYWORD))) {
checkRedundantInContainerScope(annotation, manager.findDefaultTypeUseNullability(context), nullability);
}
}
@@ -255,6 +255,7 @@ public abstract class PsiClassType extends PsiType implements JvmReferenceType {
/**
* If class-type is created from the explicit reference in the code returns that reference.
* If type is created from `var` keyword, return this keyword
* @return reference which the type is created from. Returns null if not applicable.
*/
@ApiStatus.Experimental
@@ -71,7 +71,8 @@ public final class JavaTypeNullabilityUtil {
private static boolean isLocal(PsiClassType classType) {
PsiElement context = classType.getPsiContext();
return context instanceof PsiJavaCodeReferenceElement &&
//PsiKeyword is used for `var` case
return (context instanceof PsiJavaCodeReferenceElement || PsiUtil.isJavaToken(context, JavaTokenType.VAR_KEYWORD)) &&
context.getParent() instanceof PsiTypeElement &&
context.getParent().getParent() instanceof PsiLocalVariable;
}
@@ -89,6 +89,15 @@ public class PsiTypeElementImpl extends CompositePsiElement implements PsiTypeEl
else if (PsiUtil.isJavaToken(child, JavaTokenType.VAR_KEYWORD)) {
assert type == null : this;
type = inferVarType(parent);
if (type instanceof PsiClassType) {
PsiClassType psiClassType = (PsiClassType)type;
PsiClassType.ClassResolveResult result = psiClassType.resolveGenerics();
PsiClass element = result.getElement();
if (element != null) {
type = new PsiImmediateClassType(element, result.getSubstitutor(), psiClassType.getLanguageLevel(),
psiClassType.getAnnotationProvider(), child);
}
}
}
else if (child instanceof PsiJavaCodeReferenceElement) {
assert type == null : this;
@@ -0,0 +1,59 @@
import org.jspecify.annotations.*;
import java.util.List;
@NullMarked
class Scratch {
void exact(ExactFactory factory) {
var created = factory.create();
System.out.println("Created: " + created);
System.out.println("Created: " + created.<warning descr="Method invocation 'hashCode' may produce 'NullPointerException'">hashCode</warning>());
}
<T> void exact(GenericFactory<T> factory) {
var created = factory.create();
System.out.println("Created: " + created);
System.out.println("Created: " + created.<warning descr="Method invocation 'hashCode' may produce 'NullPointerException'">hashCode</warning>());
}
<T> void exact(GenericFactoryList<T> factory) {
for (var o : factory.createList()) {
if (o.<warning descr="Method invocation 'hashCode' may produce 'NullPointerException'">hashCode</warning>() == 1) {
System.out.println("null");
}
System.out.println("Created: " + o);
}
}
<T> void polyNullNotNull(PolyNullGenericFactory<T> factory) {
var created = factory.create();
System.out.println("Created: " + created);
System.out.println("Created: " + created.hashCode());
}
<T extends @Nullable Object> void polyNullNullable(PolyNullGenericFactory<T> factory) {
var created = factory.create();
System.out.println("Created: " + created);
System.out.println("Created: " + created.<warning descr="Method invocation 'hashCode' may produce 'NullPointerException'">hashCode</warning>());
}
@FunctionalInterface
interface ExactFactory {
@Nullable String create();
}
@FunctionalInterface
interface GenericFactory<T> {
@Nullable T create();
}
@FunctionalInterface
interface GenericFactoryList<T> {
List<@Nullable T> createList();
}
@FunctionalInterface
interface PolyNullGenericFactory<T extends @Nullable Object> {
T create();
}
}
@@ -163,6 +163,12 @@ public class DataFlowInspection21Test extends DataFlowInspectionTestCase {
doTest();
}
public void testJSpecifyLocalWithGenericsWithVar() {
addJSpecifyNullMarked(myFixture);
setupTypeUseAnnotations("org.jspecify.annotations", myFixture);
doTest();
}
public void testJSpecifyCallExplicitTypeParameters() {
addJSpecifyNullMarked(myFixture);
setupTypeUseAnnotations("org.jspecify.annotations", myFixture);