diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaPsiUtil.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaPsiUtil.java index d9d748f20b33..03f72fff5e71 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaPsiUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaPsiUtil.java @@ -1,10 +1,7 @@ // Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.codeInspection.dataFlow; -import com.intellij.codeInsight.AnnotationUtil; -import com.intellij.codeInsight.Nullability; -import com.intellij.codeInsight.NullabilityAnnotationInfo; -import com.intellij.codeInsight.NullableNotNullManager; +import com.intellij.codeInsight.*; import com.intellij.codeInsight.daemon.impl.analysis.JavaGenericsUtil; import com.intellij.codeInspection.dataFlow.interpreter.RunnerResult; import com.intellij.codeInspection.dataFlow.interpreter.StandardDataFlowInterpreter; @@ -242,69 +239,23 @@ public final class DfaPsiUtil { public static @NotNull Nullability getTypeNullability(@Nullable PsiType type) { - NullabilityAnnotationInfo info = getTypeNullabilityInfo(type); - return info == null ? Nullability.UNKNOWN : info.getNullability(); + if (type == null) return Nullability.UNKNOWN; + return type.getNullability().nullability(); } public static @Nullable NullabilityAnnotationInfo getTypeNullabilityInfo(@Nullable PsiType type) { if (type == null || type instanceof PsiPrimitiveType) return null; - Ref result = Ref.create(null); - boolean local = type instanceof PsiClassType classType && - classType.getPsiContext() instanceof PsiJavaCodeReferenceElement ref && - ref.getParent() instanceof PsiTypeElement typeElement && - typeElement.getParent() instanceof PsiLocalVariable; - InheritanceUtil.processSuperTypes(type, true, eachType -> { - result.set(getTypeOwnNullability(eachType, local)); - return result.get() == null && - (!(type instanceof PsiClassType) || PsiUtil.resolveClassInClassTypeOnly(type) instanceof PsiTypeParameter); - }); - return result.get(); - } - - private static @Nullable NullabilityAnnotationInfo getTypeOwnNullability(@NotNull PsiType eachType, boolean local) { - NullabilityAnnotationInfo info = getNullabilityFromAnnotations(eachType.getAnnotations()); - if (info != null) return info; - if (eachType instanceof PsiClassType classType && !local) { - PsiElement context = classType.getPsiContext(); - if (context != null) { - NullableNotNullManager manager = NullableNotNullManager.getInstance(context.getProject()); - if (context instanceof PsiJavaCodeReferenceElement ref && ref.getParent() instanceof PsiTypeElement typeElement) { - // Due to the absence of nullability type inference, the annotation that presents directly at context - // could be removed from eachType. In this case, using context annotation would be wrong, so we just return null - info = getNullabilityFromAnnotations(typeElement.getApplicableAnnotations()); - } - NullabilityAnnotationInfo typeUseNullability = manager.findDefaultTypeUseNullability(context); - if (typeUseNullability != null) { - if (info != null && info.getNullability() != typeUseNullability.getNullability()) { - return null; - } - return typeUseNullability; - } - PsiClass declaration = PsiUtil.resolveClassInClassTypeOnly(classType); - if (declaration instanceof PsiTypeParameter typeParameter && typeParameter.getExtendsList().getReferenceElements().length == 0) { - // If there's no bound, we assume an implicit `extends Object` bound, which is subject to default annotation if any. - typeUseNullability = manager.findDefaultTypeUseNullability(declaration); - if (info != null && typeUseNullability != null && info.getNullability() != typeUseNullability.getNullability()) { - return null; - } - return typeUseNullability; - } - } + TypeNullability nullability = type.getNullability(); + NullabilitySource source = nullability.source(); + if (source instanceof NullabilitySource.ExtendsBound extendsBound) { + source = extendsBound.boundSource(); } - return null; - } - - private static @Nullable NullabilityAnnotationInfo getNullabilityFromAnnotations(PsiAnnotation[] annotations) { - for (PsiAnnotation annotation : annotations) { - String qualifiedName = annotation.getQualifiedName(); - NullableNotNullManager nnn = NullableNotNullManager.getInstance(annotation.getProject()); - Optional optionalNullability = nnn.getAnnotationNullability(qualifiedName); - if (optionalNullability.isPresent()) { - Nullability nullability = optionalNullability.get(); - if (nullability == Nullability.NULLABLE && shouldIgnoreAnnotation(annotation)) continue; - return new NullabilityAnnotationInfo(annotation, nullability, false); - } + if (source instanceof NullabilitySource.ExplicitAnnotation ea) { + return new NullabilityAnnotationInfo(ea.annotation(), nullability.nullability(), false); + } + if (source instanceof NullabilitySource.ContainerAnnotation ca) { + return new NullabilityAnnotationInfo(ca.annotation(), nullability.nullability(), true); } return null; } @@ -332,9 +283,14 @@ public final class DfaPsiUtil { PsiParameter parameter = sam.getParameterList().getParameter(index); if (parameter != null) { PsiType parameterType = type.resolveGenerics().getSubstitutor().substitute(parameter.getType()); - NullabilityAnnotationInfo info = getTypeNullabilityInfo(GenericsUtil.eliminateWildcards(parameterType, false, true)); - if (info != null) { - return info.getNullability(); + if (parameterType instanceof PsiWildcardType wildcardType) { + parameterType = wildcardType.getBound(); + } + if (parameterType != null) { + TypeNullability typeNullability = parameterType.getNullability(); + if (!typeNullability.equals(TypeNullability.UNKNOWN)) { + return typeNullability.nullability(); + } } return getElementNullability(null, parameter); } diff --git a/java/java-psi-api/src/com/intellij/codeInsight/TypeNullability.java b/java/java-psi-api/src/com/intellij/codeInsight/TypeNullability.java index e92246cbe517..638bfdfeca29 100644 --- a/java/java-psi-api/src/com/intellij/codeInsight/TypeNullability.java +++ b/java/java-psi-api/src/com/intellij/codeInsight/TypeNullability.java @@ -58,7 +58,8 @@ public final class TypeNullability { * @see NullabilitySource.ExtendsBound */ public @NotNull TypeNullability inherited() { - return new TypeNullability(myNullability, mySource.inherited()); + NullabilitySource inherited = mySource.inherited(); + return inherited.equals(mySource) ? this : new TypeNullability(myNullability, inherited); } public @NotNull TypeNullability instantiatedWith(@NotNull TypeNullability nullability) { @@ -77,6 +78,34 @@ public final class TypeNullability { return this; } + public @NotNull TypeNullability join(@NotNull TypeNullability other) { + if (this.nullability() == other.nullability()) { + if (this.source().equals(other.source())) return this; + return new TypeNullability(this.nullability(), NullabilitySource.multiSource(Arrays.asList(this.source(), other.source()))); + } + if (this.nullability() == Nullability.NULLABLE) { + return this; + } + if (other.nullability() == Nullability.NULLABLE) { + return other; + } + return this.nullability() == Nullability.UNKNOWN ? this : other; + } + + public @NotNull TypeNullability meet(@NotNull TypeNullability other) { + if (this.nullability() == other.nullability()) { + if (this.source().equals(other.source())) return this; + return new TypeNullability(Nullability.NOT_NULL, NullabilitySource.multiSource(Arrays.asList(this.source(), other.source()))); + } + if (this.nullability() == Nullability.NOT_NULL) { + return this; + } + if (other.nullability() == Nullability.NOT_NULL) { + return other; + } + return this.nullability() == Nullability.NULLABLE ? this : other; + } + /** * @param collection type nullabilities to intersect * @return the intersection of the type nullabilities in the collection diff --git a/java/java-psi-api/src/com/intellij/psi/GenericsUtil.java b/java/java-psi-api/src/com/intellij/psi/GenericsUtil.java index d84bfa703308..1f7765157019 100644 --- a/java/java-psi-api/src/com/intellij/psi/GenericsUtil.java +++ b/java/java-psi-api/src/com/intellij/psi/GenericsUtil.java @@ -1,6 +1,7 @@ // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.psi; +import com.intellij.codeInsight.TypeNullability; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Couple; @@ -32,8 +33,8 @@ public final class GenericsUtil { public static @Nullable PsiType getLeastUpperBound(PsiType type1, PsiType type2, PsiManager manager) { if (TypeConversionUtil.isPrimitiveAndNotNull(type1) || TypeConversionUtil.isPrimitiveAndNotNull(type2)) return null; - if (TypeConversionUtil.isNullType(type1)) return type2; - if (TypeConversionUtil.isNullType(type2)) return type1; + if (TypeConversionUtil.isNullType(type1)) return type2.withNullability(TypeNullability.NULLABLE_MANDATED); + if (TypeConversionUtil.isNullType(type2)) return type1.withNullability(TypeNullability.NULLABLE_MANDATED); if (Comparing.equal(type1, type2)) return type1; return getLeastUpperBound(type1, type2, new LinkedHashSet<>(), manager); } @@ -97,7 +98,8 @@ public final class GenericsUtil { PsiClass[] supers = getLeastUpperClasses(aClass, bClass); if (supers.length == 0) { - return PsiType.getJavaLangObject(manager, type1.getResolveScope()); + return PsiType.getJavaLangObject(manager, type1.getResolveScope()) + .withNullability(type1.getNullability().join(type2.getNullability())); } final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(manager.getProject()); diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/PsiSubstitutorImpl.java b/java/java-psi-impl/src/com/intellij/psi/impl/PsiSubstitutorImpl.java index 7f15053ed98b..df0a897e585e 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/PsiSubstitutorImpl.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/PsiSubstitutorImpl.java @@ -217,7 +217,7 @@ public final class PsiSubstitutorImpl implements PsiSubstitutor { } PsiSubstitutor resultSubstitutor = processClass(aClass, resolveResult.getSubstitutor()); return new PsiImmediateClassType(aClass, resultSubstitutor, classType.getLanguageLevel(), - classType.getAnnotationProvider(), classType.getPsiContext(), classType.getNullability()); + classType.getAnnotationProvider(), classType.getPsiContext()); } private @NotNull PsiSubstitutor processClass(@NotNull PsiClass resolve, @NotNull PsiSubstitutor originalSubstitutor) { diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/PsiImmediateClassType.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/PsiImmediateClassType.java index 883785267bf2..35bab873808e 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/PsiImmediateClassType.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/PsiImmediateClassType.java @@ -23,6 +23,7 @@ import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.util.PsiUtil; import com.intellij.psi.util.PsiUtilCore; import com.intellij.util.IncorrectOperationException; +import com.intellij.util.JavaTypeNullabilityUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -31,7 +32,7 @@ public class PsiImmediateClassType extends PsiClassType.Stub { private final PsiSubstitutor mySubstitutor; private final PsiManager myManager; private final @Nullable PsiElement myPsiContext; - private final @NotNull TypeNullability myNullability; + private final @Nullable TypeNullability myNullability; private String myCanonicalText; private String myCanonicalTextAnnotated; private String myPresentableText; @@ -113,15 +114,15 @@ public class PsiImmediateClassType extends PsiClassType.Stub { @Nullable LanguageLevel level, @NotNull TypeAnnotationProvider provider, @Nullable PsiElement context) { - this(aClass, substitutor, level, provider, context, TypeNullability.UNKNOWN); + this(aClass, substitutor, level, provider, context, null); } - public PsiImmediateClassType(@NotNull PsiClass aClass, - @NotNull PsiSubstitutor substitutor, - @Nullable LanguageLevel level, - @NotNull TypeAnnotationProvider provider, - @Nullable PsiElement context, - @NotNull TypeNullability nullability) { + PsiImmediateClassType(@NotNull PsiClass aClass, + @NotNull PsiSubstitutor substitutor, + @Nullable LanguageLevel level, + @NotNull TypeAnnotationProvider provider, + @Nullable PsiElement context, + @Nullable TypeNullability nullability) { super(level, provider); myClass = aClass; myManager = aClass.getManager(); @@ -148,7 +149,8 @@ public class PsiImmediateClassType extends PsiClassType.Stub { @Override public @NotNull TypeNullability getNullability() { - return myNullability; + if (myNullability != null) return myNullability; + return JavaTypeNullabilityUtil.getTypeNullability(this); } @Override diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/InferenceSession.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/InferenceSession.java index 9393a339fa20..dc6386ea9616 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/InferenceSession.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/InferenceSession.java @@ -548,7 +548,7 @@ public class InferenceSession { PsiSubstitutor substitutor = PsiSubstitutor.EMPTY; for (InferenceVariable variable : variables) { final PsiType equalsBound = getEqualsBound(variable, substitutor); - if (equalsBound != null && !PsiTypes.nullType().equals(equalsBound)) { + if (!PsiTypes.nullType().equals(equalsBound)) { substitutor = substitutor.put(variable.getParameter(), equalsBound); } } @@ -1173,7 +1173,7 @@ public class InferenceSession { } final PsiType upperBound = getUpperBound(var, substitutor); PsiType type; - if (eqBound != PsiTypes.nullType() && (myErased || eqBound != null)) { + if (eqBound != PsiTypes.nullType()) { PsiClass aClass = PsiUtil.resolveClassInClassTypeOnly(eqBound); if (aClass instanceof PsiTypeParameter && TypeConversionUtil.isFreshVariable((PsiTypeParameter)aClass)) { PsiCapturedWildcardType capturedWildcard = var.getUserData(ORIGINAL_CAPTURE); @@ -1328,36 +1328,36 @@ public class InferenceSession { StringUtil.join(var.getBounds(upperBound), typePresentation, ", ")); } - private PsiType getLowerBound(InferenceVariable var, PsiSubstitutor substitutor) { + private @NotNull PsiType getLowerBound(InferenceVariable var, PsiSubstitutor substitutor) { return composeBound(var, InferenceBound.LOWER, pair -> GenericsUtil.getLeastUpperBound(pair.first, pair.second, myManager), substitutor); } - private PsiType getUpperBound(InferenceVariable var, PsiSubstitutor substitutor) { + private @NotNull PsiType getUpperBound(InferenceVariable var, PsiSubstitutor substitutor) { return composeBound(var, InferenceBound.UPPER, UPPER_BOUND_FUNCTION, substitutor); } - private PsiType getEqualsBound(InferenceVariable var, PsiSubstitutor substitutor) { + private @NotNull PsiType getEqualsBound(InferenceVariable var, PsiSubstitutor substitutor) { return composeBound(var, InferenceBound.EQ, pair -> !Comparing.equal(pair.first, pair.second) ? null : pair.first, substitutor); } - private PsiType composeBound(InferenceVariable variable, - InferenceBound boundType, - Function, ? extends PsiType> fun, - PsiSubstitutor substitutor) { + private @NotNull PsiType composeBound(InferenceVariable variable, + InferenceBound boundType, + Function, ? extends PsiType> fun, + PsiSubstitutor substitutor) { return composeBound(variable, boundType, fun, substitutor, false); } - private PsiType composeBound(InferenceVariable variable, - InferenceBound boundType, - Function, ? extends PsiType> fun, - PsiSubstitutor substitutor, - boolean includeNonProperBounds) { + private @NotNull PsiType composeBound(InferenceVariable variable, + InferenceBound boundType, + Function, ? extends PsiType> fun, + PsiSubstitutor substitutor, + boolean includeNonProperBounds) { final List bounds = variable.getBounds(boundType); - PsiType lub = PsiTypes.nullType(); + PsiType lub = null; for (PsiType bound : bounds) { bound = substituteNonProperBound(bound, substitutor); if (includeNonProperBounds || isProperType(bound)) { - if (lub == PsiTypes.nullType()) { + if (lub == null) { lub = bound; } else { @@ -1369,7 +1369,7 @@ public class InferenceSession { } } } - return lub; + return lub == null ? PsiTypes.nullType() : lub; } public PsiManager getManager() { @@ -1951,14 +1951,6 @@ public class InferenceSession { return myInferenceSessionContainer; } - public PsiType startWithFreshVars(PsiType type) { - PsiSubstitutor s = PsiSubstitutor.EMPTY; - for (InferenceVariable variable : myInferenceVariables) { - s = s.put(variable, JavaPsiFacade.getElementFactory(myManager.getProject()).createType(variable.getParameter())); - } - return s.substitute(type); - } - public static PsiClass findParameterizationOfTheSameGenericClass(List upperBounds, Processor> processor) { for (int i = 0; i < upperBounds.size(); i++) { diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/InferenceVariable.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/InferenceVariable.java index 62a0d5ca03b0..0b686521d185 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/InferenceVariable.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/InferenceVariable.java @@ -1,6 +1,7 @@ // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.psi.impl.source.resolve.graphInference; +import com.intellij.codeInsight.TypeNullability; import com.intellij.psi.*; import com.intellij.psi.impl.light.LightTypeParameter; import com.intellij.psi.util.PsiUtil; @@ -66,12 +67,26 @@ public class InferenceVariable extends LightTypeParameter { classType = PsiTypes.nullType(); } - if (incorporationPhase == null || !bounds.contains(classType)) { + int oldBound = bounds.indexOf(classType); + if (incorporationPhase == null || oldBound == -1) { bounds.add(classType); if (incorporationPhase != null) { incorporationPhase.addBound(this, classType, inferenceBound); } return true; + } else { + PsiType oldBoundType = bounds.get(oldBound); + TypeNullability nullability1 = oldBoundType.getNullability(); + TypeNullability nullability2 = classType.getNullability(); + if (!nullability1.equals(nullability2)) { + TypeNullability nullability = inferenceBound == InferenceBound.LOWER ? + nullability1.join(nullability2) : + nullability1.meet(nullability2); + PsiType result = oldBoundType.withNullability(nullability); + bounds.set(oldBound, result); + incorporationPhase.addBound(this, result, inferenceBound); + return true; + } } return false; } diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/constraints/ConstraintUtil.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/constraints/ConstraintUtil.java new file mode 100644 index 000000000000..ebdd5595857a --- /dev/null +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/constraints/ConstraintUtil.java @@ -0,0 +1,16 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.psi.impl.source.resolve.graphInference.constraints; + +import com.intellij.psi.PsiType; +import org.jetbrains.annotations.Nullable; + +final class ConstraintUtil { + static boolean typesEqual(@Nullable PsiType t, @Nullable PsiType t1) { + if (t == null || t1 == null) return t == null && t1 == null; + return t.equals(t1) && t.getNullability().equals(t1.getNullability()); + } + + static int typeHashCode(@Nullable PsiType t) { + return t == null ? 0 : 31 * (31 + t.getNullability().hashCode()) + t.hashCode(); + } +} diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/constraints/ExpressionCompatibilityConstraint.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/constraints/ExpressionCompatibilityConstraint.java index 29cd0064e522..2bd292543cc8 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/constraints/ExpressionCompatibilityConstraint.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/constraints/ExpressionCompatibilityConstraint.java @@ -63,6 +63,9 @@ public class ExpressionCompatibilityConstraint extends InputOutputConstraintForm constraints.add(new TypeCompatibilityConstraint(myT, exprType)); } + if (myT instanceof PsiClassType && exprType == PsiTypes.nullType()) { + constraints.add(new StrictSubtypingConstraint(myT, exprType)); + } return true; } if (myExpression instanceof PsiParenthesizedExpression) { diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/constraints/StrictSubtypingConstraint.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/constraints/StrictSubtypingConstraint.java index 41d5cfe18c56..22974d6e4693 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/constraints/StrictSubtypingConstraint.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/constraints/StrictSubtypingConstraint.java @@ -57,7 +57,16 @@ public class StrictSubtypingConstraint implements ConstraintFormula { } if (PsiTypes.nullType().equals(myT) || myT == null) return false; - if (PsiTypes.nullType().equals(myS) || myS == null || myT.equalsToText(CommonClassNames.JAVA_LANG_OBJECT)) return true; + if (PsiTypes.nullType().equals(myS)) { + InferenceVariable inferenceVariable = session.getInferenceVariable(myT); + if (inferenceVariable != null) { + InferenceVariable.addBound(myT, myS, InferenceBound.LOWER, session); + } + return true; + } + else if (myS == null || myT.equalsToText(CommonClassNames.JAVA_LANG_OBJECT)) { + return true; + } if (PsiTypes.voidType().equals(myS) ^ PsiTypes.voidType().equals(myT)) return false; @@ -195,17 +204,13 @@ public class StrictSubtypingConstraint implements ConstraintFormula { if (o == null || getClass() != o.getClass()) return false; StrictSubtypingConstraint that = (StrictSubtypingConstraint)o; - - if (myS != null ? !myS.equals(that.myS) : that.myS != null) return false; - if (myT != null ? !myT.equals(that.myT) : that.myT != null) return false; - - return true; + return ConstraintUtil.typesEqual(myS, that.myS) && ConstraintUtil.typesEqual(myT, that.myT); } @Override public int hashCode() { - int result = myS != null ? myS.hashCode() : 0; - result = 31 * result + (myT != null ? myT.hashCode() : 0); + int result = ConstraintUtil.typeHashCode(myT); + result = 31 * result + ConstraintUtil.typeHashCode(myS); return result; } diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/constraints/SubtypingConstraint.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/constraints/SubtypingConstraint.java index 754d237d65e8..ca034ccb32ed 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/constraints/SubtypingConstraint.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/constraints/SubtypingConstraint.java @@ -47,17 +47,13 @@ public class SubtypingConstraint implements ConstraintFormula { if (o == null || getClass() != o.getClass()) return false; SubtypingConstraint that = (SubtypingConstraint)o; - - if (myS != null ? !myS.equals(that.myS) : that.myS != null) return false; - if (myT != null ? !myT.equals(that.myT) : that.myT != null) return false; - - return true; + return ConstraintUtil.typesEqual(myS, that.myS) && ConstraintUtil.typesEqual(myT, that.myT); } @Override public int hashCode() { - int result = myS != null ? myS.hashCode() : 0; - result = 31 * result + (myT != null ? myT.hashCode() : 0); + int result = ConstraintUtil.typeHashCode(myT); + result = 31 * result + ConstraintUtil.typeHashCode(myS); return result; } diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/constraints/TypeCompatibilityConstraint.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/constraints/TypeCompatibilityConstraint.java index fa37ebd89c49..7247917054f6 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/constraints/TypeCompatibilityConstraint.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/constraints/TypeCompatibilityConstraint.java @@ -32,8 +32,8 @@ public class TypeCompatibilityConstraint implements ConstraintFormula { private PsiType myS; public TypeCompatibilityConstraint(@NotNull PsiType t, @NotNull PsiType s) { - myT = t.annotate(TypeAnnotationProvider.EMPTY); - myS = s.annotate(TypeAnnotationProvider.EMPTY); + myT = t; + myS = s; } @Override @@ -131,17 +131,13 @@ public class TypeCompatibilityConstraint implements ConstraintFormula { if (o == null || getClass() != o.getClass()) return false; TypeCompatibilityConstraint that = (TypeCompatibilityConstraint)o; - - if (!myS.equals(that.myS)) return false; - if (!myT.equals(that.myT)) return false; - - return true; + return ConstraintUtil.typesEqual(myS, that.myS) && ConstraintUtil.typesEqual(myT, that.myT); } @Override public int hashCode() { - int result = myT.hashCode(); - result = 31 * result + myS.hashCode(); + int result = ConstraintUtil.typeHashCode(myT); + result = 31 * result + ConstraintUtil.typeHashCode(myS); return result; } diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/constraints/TypeEqualityConstraint.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/constraints/TypeEqualityConstraint.java index 0421b3e28315..8432dc0544da 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/constraints/TypeEqualityConstraint.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/constraints/TypeEqualityConstraint.java @@ -139,17 +139,13 @@ public class TypeEqualityConstraint implements ConstraintFormula { if (o == null || getClass() != o.getClass()) return false; TypeEqualityConstraint that = (TypeEqualityConstraint)o; - - if (myS != null ? !myS.equals(that.myS) : that.myS != null) return false; - if (myT != null ? !myT.equals(that.myT) : that.myT != null) return false; - - return true; + return ConstraintUtil.typesEqual(myS, that.myS) && ConstraintUtil.typesEqual(myT, that.myT); } @Override public int hashCode() { - int result = myT != null ? myT.hashCode() : 0; - result = 31 * result + (myS != null ? myS.hashCode() : 0); + int result = ConstraintUtil.typeHashCode(myT); + result = 31 * result + ConstraintUtil.typeHashCode(myS); return result; } diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiConditionalExpressionImpl.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiConditionalExpressionImpl.java index 2232804cfd45..eba1569c9a97 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiConditionalExpressionImpl.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiConditionalExpressionImpl.java @@ -51,7 +51,9 @@ public class PsiConditionalExpressionImpl extends ExpressionPsiElement implement PsiExpression expr2 = getElseExpression(); PsiType type1 = expr1 == null ? null : expr1.getType(); PsiType type2 = expr2 == null ? null : expr2.getType(); - if (Objects.equals(type1, type2)) return type1; + if (Objects.equals(type1, type2)) { + return type1 == null ? null : type1.withNullability(type1.getNullability().join(type2.getNullability())); + } if (PsiUtil.isLanguageLevel8OrHigher(this) && PsiPolyExpressionUtil.isPolyExpression(this)) { diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/AssertJAssert.java b/java/java-tests/testData/inspection/dataFlow/fixture/AssertJAssert.java index 684db7381fbc..2242ba20a969 100644 --- a/java/java-tests/testData/inspection/dataFlow/fixture/AssertJAssert.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/AssertJAssert.java @@ -94,6 +94,12 @@ class Descriptable { @NullMarked class MarkedAsNull { void test() { + Person person = new Person("user-name", null); + + Assertions.assertThat(person.userType()).isNull(); Assertions.assertThat((Object)null).isNull(); } + + public enum UserType { TYPE_1, TYPE_2 } + public record Person (String name, @Nullable UserType userType) {} } \ No newline at end of file diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/JSpecifyReturnFromGenericFunctions.java b/java/java-tests/testData/inspection/dataFlow/fixture/JSpecifyReturnFromGenericFunctions.java new file mode 100644 index 000000000000..11e1a2b2d619 --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/JSpecifyReturnFromGenericFunctions.java @@ -0,0 +1,45 @@ +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +import java.util.function.Supplier; + +@NullMarked +class Main { + + public static void main(String[] args) { + + // (1) No warning expected: Expression 'getNullableObject()' might evaluate to null but is returned by the method declared as @NullMarked + fNullableBound(() -> getNullableObject()); + + // (2) No warning expected: Function may return null, but it's not allowed here + fNullableBound(Main::getNullableObject); + + // No null warnings, as expected (this is the current workaround, to specify T explicitly) + Main.<@Nullable Object>fNullableBound(() -> getNullableObject()); + Main.<@Nullable Object>fNullableBound(Main::getNullableObject); + + // Expected warning: Expression 'getNullableObject()' might evaluate to null but is returned by the method declared as @NullMarked + fNonNullBound(() -> getNullableObject()); + + // Expected warning: Function may return null, but it's not allowed here + fNonNullBound(Main::getNullableObject); + + // (3) NICE-TO-HAVE, it would be nice to have a warning that a @Nullable type for T is not allowed + Main.<@Nullable Object>fNonNullBound(() -> getNullableObject()); + Main.<@Nullable Object>fNonNullBound(Main::getNullableObject); + + } + + static T fNullableBound(Supplier supplier){ + return supplier.get(); + } + + static T fNonNullBound(Supplier supplier){ + return supplier.get(); + } + + static @Nullable Object getNullableObject() { + return null; + } + +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/PassthroughGenericParameter.java b/java/java-tests/testData/inspection/dataFlow/fixture/PassthroughGenericParameter.java new file mode 100644 index 000000000000..466e4dc0521f --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/PassthroughGenericParameter.java @@ -0,0 +1,21 @@ +import org.jetbrains.annotations.NotNull; +import java.util.function.Supplier; + +class TestTest { + public void test() { + @NotNull String string = reSupplier(this::supplyString); + + onlyNonNull(string); + } + + public void onlyNonNull(@NotNull String string) { + } + + public @NotNull String supplyString() { + return "test"; + } + + public T reSupplier(@NotNull Supplier supplier) { + return supplier.get(); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/RequireNonNullMethodRef.java b/java/java-tests/testData/inspection/dataFlow/fixture/RequireNonNullMethodRef.java index b15573a4f477..8714b7941c7f 100644 --- a/java/java-tests/testData/inspection/dataFlow/fixture/RequireNonNullMethodRef.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/RequireNonNullMethodRef.java @@ -15,7 +15,7 @@ class Main { Stream.of("test3") .map(Main::testNullableStaticMethod) - .map(Main::requireNonNull); + .map(Main::requireNonNull); } static T requireNonNull(T obj) { diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection21Test.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection21Test.java index 7c7c2fccd993..9ff0b9a59f3b 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection21Test.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection21Test.java @@ -192,4 +192,14 @@ public class DataFlowInspection21Test extends DataFlowInspectionTestCase { public void testObjectUtilsNullMethods() { doTest(); } + + public void testJSpecifyReturnFromGenericFunctions() { + addJSpecifyNullMarked(myFixture); + setupTypeUseAnnotations("org.jspecify.annotations", myFixture); + doTest(); + } + + public void testPassthroughGenericParameter() { + doTestWith((dfi, cvi) -> dfi.TREAT_UNKNOWN_MEMBERS_AS_NULLABLE = true); + } } \ No newline at end of file