merge AnnotationChangesTracker functionality into DifferentiateStrategy; support Kotlin Deprecation annotations (IJPL-179400)

Merge-request: IJ-MR-156114
Merged-by: Eugene Zhuravlev <eugene.zhuravlev@jetbrains.com>

GitOrigin-RevId: ec163f659592bdcde8e73017f77365ff2d0e75f5
This commit is contained in:
Eugene Zhuravlev
2025-02-26 18:30:32 +00:00
committed by intellij-monorepo-bot
parent 5f29a404dc
commit 8a0c4b43a8
31 changed files with 536 additions and 273 deletions
@@ -1,2 +0,0 @@
# Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
org.jetbrains.jps.dependency.kotlin.NullabilityAnnotationChangesTracker
@@ -15,7 +15,7 @@ public final class GeneralJvmDifferentiateStrategy implements DifferentiateStrat
private static final Logger LOG = Logger.getInstance("#org.jetbrains.jps.dependency.java.GeneralJvmDifferentiateStrategy");
private static final Iterable<JvmDifferentiateStrategy> ourExtensions = collect(
ServiceLoader.load(JvmDifferentiateStrategy.class, GeneralJvmDifferentiateStrategy.class.getClassLoader()),
ServiceLoader.load(JvmDifferentiateStrategy.class),
new SmartList<>()
);
@@ -17,11 +17,6 @@ import static org.jetbrains.jps.javac.Iterators.*;
public final class JavaDifferentiateStrategy extends JvmDifferentiateStrategyImpl {
private static final Iterable<AnnotationChangesTracker> ourAnnotationChangeTrackers = collect(
ServiceLoader.load(AnnotationChangesTracker.class, JavaDifferentiateStrategy.class.getClassLoader()),
new SmartList<>()
);
@Override
public boolean isIncremental(DifferentiateContext context, Node<?, ?> affectedNode) {
if (affectedNode instanceof JvmClass && ((JvmClass)affectedNode).getFlags().isGenerated()) {
@@ -295,32 +290,7 @@ public final class JavaDifferentiateStrategy extends JvmDifferentiateStrategyImp
}
}
Difference.Specifier<ElementAnnotation, ElementAnnotation.Diff> annotationsDiff = classDiff.annotations();
if (!annotationsDiff.unchanged()) {
EnumSet<AnnotationChangesTracker.Recompile> toRecompile = EnumSet.noneOf(AnnotationChangesTracker.Recompile.class);
for (AnnotationChangesTracker tracker : ourAnnotationChangeTrackers) {
if (toRecompile.containsAll(AnnotationChangesTracker.RECOMPILE_ALL)) {
break;
}
Set<AnnotationChangesTracker.Recompile> result = tracker.classAnnotationsChanged(changedClass, annotationsDiff);
if (result.contains(AnnotationChangesTracker.Recompile.USAGES)) {
debug("Extension ", tracker.getClass().getName(), " requested class usages recompilation because of changes in annotations list --- adding class usage to affected usages");
}
if (result.contains(AnnotationChangesTracker.Recompile.SUBCLASSES)) {
debug("Extension ", tracker.getClass().getName(), " requested subclasses recompilation because of changes in annotations list --- adding subclasses to affected usages");
}
toRecompile.addAll(result);
}
boolean affectUsages = toRecompile.contains(AnnotationChangesTracker.Recompile.USAGES);
if (affectUsages) {
context.affectUsage(new ClassUsage(changedClass.getReferenceID()));
}
if (toRecompile.contains(AnnotationChangesTracker.Recompile.SUBCLASSES)) {
affectSubclasses(context, future, changedClass.getReferenceID(), affectUsages);
}
}
return true;
return super.processChangedClass(context, change, future, present);
}
@Override
@@ -425,38 +395,6 @@ public final class JavaDifferentiateStrategy extends JvmDifferentiateStrategyImp
}
}
}
Difference.Specifier<ElementAnnotation, ElementAnnotation.Diff> annotationsDiff = diff.annotations();
Difference.Specifier<ParamAnnotation, ParamAnnotation.Diff> paramAnnotationsDiff = diff.paramAnnotations();
if (!annotationsDiff.unchanged() || !paramAnnotationsDiff.unchanged()) {
EnumSet<AnnotationChangesTracker.Recompile> toRecompile = EnumSet.noneOf(AnnotationChangesTracker.Recompile.class);
for (AnnotationChangesTracker tracker : ourAnnotationChangeTrackers) {
if (toRecompile.containsAll(AnnotationChangesTracker.RECOMPILE_ALL)) {
break;
}
Set<AnnotationChangesTracker.Recompile> result = tracker.methodAnnotationsChanged(changedMethod, annotationsDiff, paramAnnotationsDiff);
if (result.contains(AnnotationChangesTracker.Recompile.USAGES)) {
debug("Extension ", tracker.getClass().getName(), " requested recompilation because of changes in annotations list --- affecting method usages");
}
if (result.contains(AnnotationChangesTracker.Recompile.SUBCLASSES)) {
debug("Extension ", tracker.getClass().getName(), " requested recompilation because of changes in method annotations or method parameter annotations list --- affecting subclasses");
}
toRecompile.addAll(result);
}
if (toRecompile.contains(AnnotationChangesTracker.Recompile.USAGES)) {
affectMemberUsages(context, changedClass.getReferenceID(), changedMethod, propagated);
if (changedMethod.isAbstract() || toRecompile.contains(AnnotationChangesTracker.Recompile.SUBCLASSES)) {
for (Pair<JvmClass, JvmMethod> pair : recurse(Pair.create(changedClass, changedMethod), p -> p.second.isOverridable()? future.getOverridingMethods(p.first, p.second, p.second::isSameByJavaRules) : Collections.emptyList(), false)) {
JvmNodeReferenceID clsId = pair.first.getReferenceID();
JvmMethod meth = pair.getSecond();
affectMemberUsages(context, clsId, meth, future.collectSubclassesWithoutMethod(clsId, meth));
}
}
}
if (toRecompile.contains(AnnotationChangesTracker.Recompile.SUBCLASSES)) {
affectSubclasses(context, future, changedClass.getReferenceID(), false);
}
}
}
Iterable<Difference.Change<JvmMethod, JvmMethod.Diff>> moreAccessible = collect(filter(methodChanges, ch -> ch.getDiff().accessExpanded()), new SmartList<>());
@@ -493,7 +431,7 @@ public final class JavaDifferentiateStrategy extends JvmDifferentiateStrategyImp
}
debug("End of changed methods processing");
return true;
return super.processChangedMethods(context, clsChange, methodChanges, future, present);
}
@Override
@@ -843,31 +781,7 @@ public final class JavaDifferentiateStrategy extends JvmDifferentiateStrategyImp
}
}
Difference.Specifier<ElementAnnotation, ElementAnnotation.Diff> annotationsDiff = diff.annotations();
if (!annotationsDiff.unchanged()) {
EnumSet<AnnotationChangesTracker.Recompile> toRecompile = EnumSet.noneOf(AnnotationChangesTracker.Recompile.class);
for (AnnotationChangesTracker tracker : ourAnnotationChangeTrackers) {
if (toRecompile.containsAll(AnnotationChangesTracker.RECOMPILE_ALL)) {
break;
}
Set<AnnotationChangesTracker.Recompile> result = tracker.fieldAnnotationsChanged(changedField, annotationsDiff);
if (result.contains(AnnotationChangesTracker.Recompile.USAGES)) {
debug("Extension ", tracker.getClass().getName(), " requested recompilation because of changes in annotations list --- affecting field usages");
}
if (result.contains(AnnotationChangesTracker.Recompile.SUBCLASSES)) {
debug("Extension ", tracker.getClass().getName(), " requested recompilation because of changes in field annotations list --- affecting subclasses");
}
toRecompile.addAll(result);
}
if (toRecompile.contains(AnnotationChangesTracker.Recompile.USAGES)) {
affectMemberUsages(context, changedClass.getReferenceID(), changedField, propagated);
}
if (toRecompile.contains(AnnotationChangesTracker.Recompile.SUBCLASSES)) {
affectSubclasses(context, future, changedClass.getReferenceID(), false);
}
}
return true;
return super.processChangedField(context, clsChange, fieldChange, future, present);
}
@Override
@@ -32,10 +32,7 @@ import java.util.function.Supplier;
public final class JvmClassNodeBuilder extends ClassVisitor implements NodeBuilder {
private static final Iterable<AnnotationChangesTracker> ourAnnotationChangeTrackers = Iterators.collect(
ServiceLoader.load(AnnotationChangesTracker.class, JvmClassNodeBuilder.class.getClassLoader()),
new SmartList<>()
);
private static final Iterable<JvmDifferentiateStrategy> ourDifferentiateStrategies = Iterators.collect(ServiceLoader.load(JvmDifferentiateStrategy.class), new SmartList<>());
private static final Logger LOG = Logger.getInstance(JvmClassNodeBuilder.class);
public static final String LAMBDA_FACTORY_CLASS = "java/lang/invoke/LambdaMetafactory";
@@ -969,8 +966,8 @@ public final class JvmClassNodeBuilder extends ClassVisitor implements NodeBuild
}
private static boolean isAnnotationTracked(TypeRepr.ClassType annotationType) {
for (AnnotationChangesTracker tracker : ourAnnotationChangeTrackers) {
if (tracker.isAnnotationTracked(annotationType)) {
for (JvmDifferentiateStrategy strategy : ourDifferentiateStrategies) {
if (strategy.isAnnotationTracked(annotationType)) {
return true;
}
}
@@ -1,6 +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 org.jetbrains.jps.dependency.java;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.dependency.DifferentiateContext;
import org.jetbrains.jps.dependency.Node;
import org.jetbrains.jps.dependency.diff.Difference;
@@ -11,6 +12,15 @@ import org.jetbrains.jps.dependency.diff.Difference;
*/
public interface JvmDifferentiateStrategy {
/**
* @param annotationType the annotation class type to check. DependencyGraph will parse and store only those annotations,
* if there exists at least one registered AnnotationTracker that can track annotations of this type
* @return true if this AnnotationTracker can track annotations of this type, false otherwise.
*/
default boolean isAnnotationTracked(@NotNull TypeRepr.ClassType annotationType) {
return false;
}
default boolean isIncremental(DifferentiateContext context, Node<?, ?> affectedNode) {
return true;
}
@@ -46,39 +56,47 @@ public interface JvmDifferentiateStrategy {
if (!processChangedClass(context, change, future, present)) {
return false;
}
JvmClass changedClass = change.getPast();
Difference.Specifier<JvmMethod, JvmMethod.Diff> methodsDiff = change.getDiff().methods();
if (!methodsDiff.unchanged()) {
if (!processRemovedMethods(context, change, methodsDiff.removed(), future, present)) {
return false;
}
if (!processAddedMethods(context, change, methodsDiff.added(), future, present)) {
return false;
}
if (!processChangedMethods(context, change, methodsDiff.changed(), future, present)) {
return false;
}
}
Difference.Specifier<JvmField, JvmField.Diff> fieldsDiff = change.getDiff().fields();
if (!fieldsDiff.unchanged()) {
if (!processRemovedFields(context, change, fieldsDiff.removed(), future, present)) {
return false;
}
if (!processAddedFields(context, change, fieldsDiff.added(), future, present)) {
return false;
}
if (!processChangedFields(context, change, fieldsDiff.changed(), future, present)) {
return false;
}
}
}
return true;
}
default boolean processChangedClass(DifferentiateContext context, Difference.Change<JvmClass, JvmClass.Diff> change, Utils future, Utils present) {
Difference.Specifier<JvmMethod, JvmMethod.Diff> methodsDiff = change.getDiff().methods();
if (!methodsDiff.unchanged()) {
if (!processRemovedMethods(context, change, methodsDiff.removed(), future, present)) {
return false;
}
if (!processAddedMethods(context, change, methodsDiff.added(), future, present)) {
return false;
}
if (!processChangedMethods(context, change, methodsDiff.changed(), future, present)) {
return false;
}
}
Difference.Specifier<JvmField, JvmField.Diff> fieldsDiff = change.getDiff().fields();
if (!fieldsDiff.unchanged()) {
if (!processRemovedFields(context, change, fieldsDiff.removed(), future, present)) {
return false;
}
if (!processAddedFields(context, change, fieldsDiff.added(), future, present)) {
return false;
}
if (!processChangedFields(context, change, fieldsDiff.changed(), future, present)) {
return false;
}
}
Difference.Specifier<ElementAnnotation, ElementAnnotation.Diff> annotationDiff = change.getDiff().annotations();
if (!annotationDiff.unchanged()) {
if (!processClassAnnotations(context, change, annotationDiff, future, present)) {
return false;
}
}
return true;
}
default boolean processClassAnnotations(DifferentiateContext context, Difference.Change<JvmClass, JvmClass.Diff> change, Difference.Specifier<ElementAnnotation, ElementAnnotation.Diff> annotationDiff, Utils future, Utils present) {
return true;
}
@@ -118,6 +136,18 @@ public interface JvmDifferentiateStrategy {
}
default boolean processChangedMethod(DifferentiateContext context, Difference.Change<JvmClass, JvmClass.Diff> clsChange, Difference.Change<JvmMethod, JvmMethod.Diff> methodChange, Utils future, Utils present) {
JvmMethod.Diff diff = methodChange.getDiff();
Difference.Specifier<ElementAnnotation, ElementAnnotation.Diff> annotationsDiff = diff.annotations();
Difference.Specifier<ParamAnnotation, ParamAnnotation.Diff> paramAnnotationsDiff = diff.paramAnnotations();
if (!annotationsDiff.unchanged() || !paramAnnotationsDiff.unchanged()) {
if (!processMethodAnnotations(context, clsChange, methodChange, annotationsDiff, paramAnnotationsDiff, future, present)) {
return false;
}
}
return true;
}
default boolean processMethodAnnotations(DifferentiateContext context, Difference.Change<JvmClass, JvmClass.Diff> clsChange, Difference.Change<JvmMethod, JvmMethod.Diff> methodChange, Difference.Specifier<ElementAnnotation, ElementAnnotation.Diff> annotationsDiff, Difference.Specifier<ParamAnnotation, ParamAnnotation.Diff> paramAnnotationsDiff, Utils future, Utils present){
return true;
}
@@ -157,6 +187,16 @@ public interface JvmDifferentiateStrategy {
}
default boolean processChangedField(DifferentiateContext context, Difference.Change<JvmClass, JvmClass.Diff> clsChange, Difference.Change<JvmField, JvmField.Diff> fieldChange, Utils future, Utils present) {
Difference.Specifier<ElementAnnotation, ElementAnnotation.Diff> annotationDiff = fieldChange.getDiff().annotations();
if (!annotationDiff.unchanged()) {
if (!processFieldAnnotations(context, clsChange, fieldChange, annotationDiff, future, present)) {
return false;
}
}
return true;
}
default boolean processFieldAnnotations(DifferentiateContext context, Difference.Change<JvmClass, JvmClass.Diff> clsChange, Difference.Change<JvmField, JvmField.Diff> fieldChange, Difference.Specifier<ElementAnnotation, ElementAnnotation.Diff> annotationDiff, Utils future, Utils present) {
return true;
}
@@ -1,10 +1,14 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jps.dependency.java;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Pair;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.dependency.*;
import org.jetbrains.jps.dependency.diff.Difference;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -12,11 +16,81 @@ import java.util.function.Predicate;
import static org.jetbrains.jps.javac.Iterators.*;
/**
* This class provides some common utilities for strategy implementations
* This class provides implementation common to all jvm strategies
*/
public abstract class JvmDifferentiateStrategyImpl implements JvmDifferentiateStrategy {
public abstract class JvmDifferentiateStrategyImpl implements JvmDifferentiateStrategy{
private static final Logger LOG = Logger.getInstance("#org.jetbrains.jps.dependency.java.JvmDifferentiateStrategyImpl");
protected enum AnnotationAffectionKind {
added, removed, changed
}
protected final <T extends AnnotationInstance, D extends AnnotationInstance.Diff<T>> boolean isAffectedByAnnotations(
Proto element, Difference.Specifier<T, D> annotationsDiff, Set<AnnotationAffectionKind> affectionKinds, Predicate<? super TypeRepr.ClassType> annotationSelector
) {
return !element.isPrivate() && find(getAffectedAnnotations(annotationsDiff, affectionKinds), annotationSelector::test) != null;
}
protected final <T extends AnnotationInstance, D extends AnnotationInstance.Diff<T>> Iterable<TypeRepr.ClassType> getAffectedAnnotations(
Difference.Specifier<T, D> annotationsDiff, Set<AnnotationAffectionKind> affectionKinds
) {
Iterable<? extends T> added = affectionKinds.contains(AnnotationAffectionKind.added)? annotationsDiff.added() : List.of();
Iterable<? extends T> removed = affectionKinds.contains(AnnotationAffectionKind.removed)? annotationsDiff.removed() : List.of();
Iterable<? extends T> changed = affectionKinds.contains(AnnotationAffectionKind.changed)? map(annotationsDiff.changed(), Difference.Change::getPast) : List.of();
return map(flat(List.of(added, removed, changed)), AnnotationInstance::getAnnotationClass);
}
protected enum AnnotationAffectionScope {
/**
* If present in the returned result set, the usages of the annotated program element (class, field, method) will be affected.
* it means that files where this program element is references, will be marked for recompilation
*/
usages,
/**
* If present in the returned result set, the subclasses of the annotated class will be affected.
* If returned for an annotated field/method, the subclasses of the class containing this field/method will be affected.
*/
subclasses
}
protected void affectClassAnnotationUsages(DifferentiateContext context, Set<AnnotationAffectionScope> toRecompile, Difference.Change<JvmClass, JvmClass.Diff> change, Utils future, Utils present) {
JvmClass changedClass = change.getPast();
boolean affectUsages = toRecompile.contains(AnnotationAffectionScope.usages);
if (affectUsages) {
context.affectUsage(new ClassUsage(changedClass.getReferenceID()));
}
if (toRecompile.contains(AnnotationAffectionScope.subclasses)) {
affectSubclasses(context, future, changedClass.getReferenceID(), affectUsages);
}
}
protected void affectFieldAnnotationUsages(DifferentiateContext context, Set<AnnotationAffectionScope> toRecompile, Difference.Change<JvmClass, JvmClass.Diff> clsChange, JvmField changedField, Utils future, Utils present) {
JvmClass changedClass = clsChange.getPast();
if (toRecompile.contains(AnnotationAffectionScope.usages)) {
affectMemberUsages(context, changedClass.getReferenceID(), changedField, future.collectSubclassesWithoutField(changedClass.getReferenceID(), changedField));
}
if (toRecompile.contains(AnnotationAffectionScope.subclasses)) {
affectSubclasses(context, future, changedClass.getReferenceID(), false);
}
}
protected void affectMethodAnnotationUsages(DifferentiateContext context, Set<AnnotationAffectionScope> toRecompile, Difference.Change<JvmClass, JvmClass.Diff> clsChange, JvmMethod changedMethod, Utils future, Utils present) {
JvmClass changedClass = clsChange.getPast();
if (toRecompile.contains(AnnotationAffectionScope.usages)) {
affectMemberUsages(context, changedClass.getReferenceID(), changedMethod, future.collectSubclassesWithoutMethod(changedClass.getReferenceID(), changedMethod));
if (changedMethod.isAbstract() || toRecompile.contains(AnnotationAffectionScope.subclasses)) {
for (Pair<JvmClass, JvmMethod> pair : recurse(Pair.create(changedClass, changedMethod), p -> p.second.isOverridable()? future.getOverridingMethods(p.first, p.second, p.second::isSameByJavaRules) : Collections.emptyList(), false)) {
JvmNodeReferenceID clsId = pair.first.getReferenceID();
JvmMethod meth = pair.getSecond();
affectMemberUsages(context, clsId, meth, future.collectSubclassesWithoutMethod(clsId, meth));
}
}
}
if (toRecompile.contains(AnnotationAffectionScope.subclasses)) {
affectSubclasses(context, future, changedClass.getReferenceID(), false);
}
}
protected void affectMemberUsages(DifferentiateContext context, JvmNodeReferenceID clsId, ProtoMember member, Iterable<JvmNodeReferenceID> propagated) {
affectMemberUsages(context, clsId, member, propagated, null);
}
@@ -23,6 +23,82 @@ import static org.jetbrains.jps.javac.Iterators.*;
*/
public final class KotlinJvmDifferentiateStrategy extends JvmDifferentiateStrategyImpl {
private static final TypeRepr.ClassType JVM_OVERLOADS_ANNOTATION = new TypeRepr.ClassType("kotlin/jvm/JvmOverloads");
private static final Set<TypeRepr.ClassType> ourDeprecationAnnotations = Set.of(
new TypeRepr.ClassType("kotlin/Deprecated"),
new TypeRepr.ClassType("kotlin/DeprecatedSinceKotlin")
);
private static final Set<String> ourNullabilityAnnotations = Set.of(
"org/jetbrains/annotations/Nullable",
"androidx/annotation/Nullable",
"android/support/annotation/Nullable",
"android/annotation/Nullable",
"com/android/annotations/Nullable",
"org/eclipse/jdt/annotation/Nullable",
"org/checkerframework/checker/nullness/qual/Nullable",
"javax/annotation/Nullable",
"javax/annotation/CheckForNull",
"edu/umd/cs/findbugs/annotations/CheckForNull",
"edu/umd/cs/findbugs/annotations/Nullable",
"edu/umd/cs/findbugs/annotations/PossiblyNull",
"io/reactivex/annotations/Nullable",
"io/reactivex/rxjava3/annotations/Nullable",
"javax/annotation/Nonnull",
"org/jetbrains/annotations/NotNull",
"edu/umd/cs/findbugs/annotations/NonNull",
"androidx/annotation/NonNull",
"android/support/annotation/NonNull",
"android/annotation/NonNull",
"com/android/annotations/NonNull",
"org/eclipse/jdt/annotation/NonNull",
"org/checkerframework/checker/nullness/qual/NonNull",
"lombok/NonNull",
"io/reactivex/annotations/NonNull",
"io/reactivex/rxjava3/annotations/NonNull"
);
@Override
public boolean isAnnotationTracked(TypeRepr.@NotNull ClassType annotationType) {
return ourNullabilityAnnotations.contains(annotationType.getJvmName()) || ourDeprecationAnnotations.contains(annotationType);
}
@Override
public boolean processClassAnnotations(DifferentiateContext context, Difference.Change<JvmClass, JvmClass.Diff> change, Difference.Specifier<ElementAnnotation, ElementAnnotation.Diff> annotationDiff, Utils future, Utils present) {
JvmClass changedClass = change.getPast();
if (isAffectedByAnnotations(changedClass, annotationDiff, EnumSet.of(AnnotationAffectionKind.added, AnnotationAffectionKind.changed), ourDeprecationAnnotations::contains)) {
debug("Deprecation annotations changed for ", changedClass.getName(), " --- affecting class usages");
affectClassAnnotationUsages(context, EnumSet.of(AnnotationAffectionScope.usages), change, future, present);
}
return super.processClassAnnotations(context, change, annotationDiff, future, present);
}
@Override
public boolean processFieldAnnotations(DifferentiateContext context, Difference.Change<JvmClass, JvmClass.Diff> clsChange, Difference.Change<JvmField, JvmField.Diff> fieldChange, Difference.Specifier<ElementAnnotation, ElementAnnotation.Diff> annotationDiff, Utils future, Utils present) {
JvmField changedField = fieldChange.getPast();
if (
isAffectedByAnnotations(changedField, annotationDiff, EnumSet.of(AnnotationAffectionKind.added, AnnotationAffectionKind.removed), t -> ourNullabilityAnnotations.contains(t.getJvmName())) ||
isAffectedByAnnotations(changedField, annotationDiff, EnumSet.of(AnnotationAffectionKind.added, AnnotationAffectionKind.changed), ourDeprecationAnnotations::contains)
) {
debug("Nullability or Deprecation annotations changed for field ", changedField, " --- affecting field usages");
affectFieldAnnotationUsages(context, EnumSet.of(AnnotationAffectionScope.usages), clsChange, changedField, future, present);
}
return super.processFieldAnnotations(context, clsChange, fieldChange, annotationDiff, future, present);
}
@Override
public boolean processMethodAnnotations(DifferentiateContext context, Difference.Change<JvmClass, JvmClass.Diff> clsChange, Difference.Change<JvmMethod, JvmMethod.Diff> methodChange, Difference.Specifier<ElementAnnotation, ElementAnnotation.Diff> annotationsDiff, Difference.Specifier<ParamAnnotation, ParamAnnotation.Diff> paramAnnotationsDiff, Utils future, Utils present) {
JvmMethod changedMethod = methodChange.getPast();
if (
isAffectedByAnnotations(changedMethod, annotationsDiff, EnumSet.of(AnnotationAffectionKind.added, AnnotationAffectionKind.removed), t -> ourNullabilityAnnotations.contains(t.getJvmName())) ||
isAffectedByAnnotations(changedMethod, paramAnnotationsDiff, EnumSet.of(AnnotationAffectionKind.added, AnnotationAffectionKind.removed), t -> ourNullabilityAnnotations.contains(t.getJvmName())) ||
isAffectedByAnnotations(changedMethod, annotationsDiff, EnumSet.of(AnnotationAffectionKind.added, AnnotationAffectionKind.changed), ourDeprecationAnnotations::contains)
) {
debug("Nullability annotations/parameter annotations or Deprecation annotations changed for method ", changedMethod, " --- affecting method usages");
EnumSet<AnnotationAffectionScope> affection = changedMethod.isFinal()? EnumSet.of(AnnotationAffectionScope.usages) : EnumSet.of(AnnotationAffectionScope.usages, AnnotationAffectionScope.subclasses);
affectMethodAnnotationUsages(context, affection, clsChange, changedMethod, future, present);
}
return super.processMethodAnnotations(context, clsChange, methodChange, annotationsDiff, paramAnnotationsDiff, future, present);
}
@Override
public boolean processAddedClasses(DifferentiateContext context, Iterable<JvmClass> addedClasses, Utils future, Utils present) {
@@ -351,7 +427,7 @@ public final class KotlinJvmDifferentiateStrategy extends JvmDifferentiateStrate
}
return true;
return super.processChangedClass(context, change, future, present);
}
@Override
@@ -384,7 +460,7 @@ public final class KotlinJvmDifferentiateStrategy extends JvmDifferentiateStrate
debug("Function was inlineable, or has become inlineable or a body of inline method has changed; affecting method usages ", name);
affectMemberLookupUsages(context, changedClass, name, future);
}
return true;
return super.processChangedMethod(context, clsChange, methodChange, future, present);
}
@Override
@@ -422,7 +498,7 @@ public final class KotlinJvmDifferentiateStrategy extends JvmDifferentiateStrate
}
}
return true;
return super.processChangedField(context, clsChange, fieldChange, future, present);
}
@Override
@@ -474,6 +550,23 @@ public final class KotlinJvmDifferentiateStrategy extends JvmDifferentiateStrate
return true;
}
@Override
protected void affectMethodAnnotationUsages(DifferentiateContext context, Set<AnnotationAffectionScope> toRecompile, Difference.Change<JvmClass, JvmClass.Diff> clsChange, JvmMethod changedMethod, Utils future, Utils present) {
super.affectMethodAnnotationUsages(context, toRecompile, clsChange, changedMethod, future, present);
if (toRecompile.contains(AnnotationAffectionScope.usages)) {
JvmClass changedClass = clsChange.getPast();
affectMemberLookupUsages(context, changedClass, KJvmUtils.getMethodKotlinName(changedClass, changedMethod), present);
}
}
@Override
protected void affectClassAnnotationUsages(DifferentiateContext context, Set<AnnotationAffectionScope> toRecompile, Difference.Change<JvmClass, JvmClass.Diff> change, Utils future, Utils present) {
super.affectClassAnnotationUsages(context, toRecompile, change, future, present);
if (toRecompile.contains(AnnotationAffectionScope.usages)) {
affectClassLookupUsages(context, change.getPast());
}
}
private void affectConflictingCallExpressions(DifferentiateContext context, JvmClass cls, JvmMethod clsMethod, Utils utils, @Nullable Predicate<Node<?, ?>> constraint) {
if (clsMethod.isPrivate() || clsMethod.isStaticInitializer()) {
return;
@@ -626,7 +719,7 @@ public final class KotlinJvmDifferentiateStrategy extends JvmDifferentiateStrate
private static Iterable<JvmMethod> withJvmOverloads(JvmClass cls, JvmMethod method) {
return unique(flat(
asIterable(method),
filter(cls.getMethods(), m -> Objects.equals(m.getName(), method.getName()) && Objects.equals(m.getType(), method.getType()) && find(m.getAnnotations(), a -> JVM_OVERLOADS_ANNOTATION.equals(a.getAnnotationClass())) != null)
filter(cls.getMethods(), m -> Objects.equals(m.getName(), method.getName()) && Objects.equals(m.getType(), method.getType()) && contains(map(m.getAnnotations(), AnnotationInstance::getAnnotationClass), JVM_OVERLOADS_ANNOTATION))
));
}
@@ -1,66 +0,0 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jps.dependency.kotlin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.dependency.diff.Difference;
import org.jetbrains.jps.dependency.java.*;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import static org.jetbrains.jps.javac.Iterators.*;
public final class NullabilityAnnotationChangesTracker implements AnnotationChangesTracker {
private static final Set<String> ourTrackedAnnotations = Set.of(
"org/jetbrains/annotations/Nullable",
"androidx/annotation/Nullable",
"android/support/annotation/Nullable",
"android/annotation/Nullable",
"com/android/annotations/Nullable",
"org/eclipse/jdt/annotation/Nullable",
"org/checkerframework/checker/nullness/qual/Nullable",
"javax/annotation/Nullable",
"javax/annotation/CheckForNull",
"edu/umd/cs/findbugs/annotations/CheckForNull",
"edu/umd/cs/findbugs/annotations/Nullable",
"edu/umd/cs/findbugs/annotations/PossiblyNull",
"io/reactivex/annotations/Nullable",
"io/reactivex/rxjava3/annotations/Nullable",
"javax/annotation/Nonnull",
"org/jetbrains/annotations/NotNull",
"edu/umd/cs/findbugs/annotations/NonNull",
"androidx/annotation/NonNull",
"android/support/annotation/NonNull",
"android/annotation/NonNull",
"com/android/annotations/NonNull",
"org/eclipse/jdt/annotation/NonNull",
"org/checkerframework/checker/nullness/qual/NonNull",
"lombok/NonNull",
"io/reactivex/annotations/NonNull",
"io/reactivex/rxjava3/annotations/NonNull"
);
@Override
public boolean isAnnotationTracked(@NotNull TypeRepr.ClassType annotationType) {
return ourTrackedAnnotations.contains(annotationType.getJvmName());
}
@Override
public @NotNull Set<Recompile> methodAnnotationsChanged(JvmMethod method, Difference.Specifier<ElementAnnotation, ElementAnnotation.Diff> annotationsDiff, Difference.Specifier<ParamAnnotation, ParamAnnotation.Diff> paramAnnotationsDiff) {
if (isAffected(map(flat(List.of(annotationsDiff.added(), annotationsDiff.removed(), paramAnnotationsDiff.added(), paramAnnotationsDiff.removed())), AnnotationInstance::getAnnotationClass))) {
return method.isFinal()? EnumSet.of(Recompile.USAGES) : EnumSet.of(Recompile.USAGES, Recompile.SUBCLASSES);
}
return RECOMPILE_NONE;
}
@Override
public @NotNull Set<Recompile> fieldAnnotationsChanged(JvmField field, Difference.Specifier<ElementAnnotation, ElementAnnotation.Diff> annotationsDiff) {
return isAffected(map(flat(annotationsDiff.added(), annotationsDiff.removed()), AnnotationInstance::getAnnotationClass))? EnumSet.of(Recompile.USAGES) : RECOMPILE_NONE;
}
private static boolean isAffected(Iterable<TypeRepr.ClassType> addedOrRemoved) {
return !isEmpty(filter(addedOrRemoved, t -> ourTrackedAnnotations.contains(t.getJvmName())));
}
}
@@ -1,2 +0,0 @@
# Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
org.jetbrains.jps.dependency.java.MockAnnotationsChangeTracker
@@ -0,0 +1,2 @@
# Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
org.jetbrains.jps.dependency.java.TestJvmDifferentiateStrategy
@@ -1,72 +0,0 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jps.dependency.java;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.builders.java.dependencyView.MockAnnotation;
import org.jetbrains.jps.builders.java.dependencyView.MockHierarchyAnnotation;
import org.jetbrains.jps.dependency.diff.Difference;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import static org.jetbrains.jps.javac.Iterators.*;
public final class MockAnnotationsChangeTracker implements AnnotationChangesTracker {
private static final String ANOTATION_NAME = MockAnnotation.class.getName().replace('.', '/');
private static final String HIERARCHY_ANOTATION_NAME = MockHierarchyAnnotation.class.getName().replace('.', '/');
private static final Set<String> KOTLIN_TESTS_ANOTATION_NAMES = Set.of("foo/Ann", "Ann");
@Override
public boolean isAnnotationTracked(@NotNull TypeRepr.ClassType annotationType) {
String typeName = annotationType.getJvmName();
return KOTLIN_TESTS_ANOTATION_NAMES.contains(typeName) || Objects.equals(ANOTATION_NAME, typeName) || Objects.equals(HIERARCHY_ANOTATION_NAME, typeName);
}
@Override
public @NotNull Set<Recompile> methodAnnotationsChanged(JvmMethod method, Difference.Specifier<ElementAnnotation, ElementAnnotation.Diff> annotationsDiff, Difference.Specifier<ParamAnnotation, ParamAnnotation.Diff> paramAnnotationsDiff) {
//return RECOMPILE_NONE;
return handleChanges(
map(flat(List.of(annotationsDiff.added(), annotationsDiff.removed(), map(annotationsDiff.changed(), Difference.Change::getPast), paramAnnotationsDiff.added(), paramAnnotationsDiff.removed(), map(paramAnnotationsDiff.changed(), Difference.Change::getPast))), AnnotationInstance::getAnnotationClass)
);
}
@Override
public @NotNull Set<Recompile> fieldAnnotationsChanged(JvmField field, Difference.Specifier<ElementAnnotation, ElementAnnotation.Diff> annotationsDiff) {
//return RECOMPILE_NONE;
return handleChanges(
map(flat(List.of(annotationsDiff.added(), annotationsDiff.removed(), map(annotationsDiff.changed(), Difference.Change::getPast))), AnnotationInstance::getAnnotationClass)
);
}
@Override
public @NotNull Set<Recompile> classAnnotationsChanged(JvmClass aClass, Difference.Specifier<ElementAnnotation, ElementAnnotation.Diff> annotationsDiff) {
//return RECOMPILE_NONE;
return handleChanges(
map(flat(List.of(annotationsDiff.added(), annotationsDiff.removed(), map(annotationsDiff.changed(), Difference.Change::getPast))), AnnotationInstance::getAnnotationClass)
);
}
@NotNull
public Set<Recompile> handleChanges(Iterable<TypeRepr.ClassType> changes) {
final Set<Recompile> result = EnumSet.noneOf(Recompile.class);
if (containsAnnotation(ANOTATION_NAME, changes)) {
result.add(Recompile.USAGES);
}
if (containsAnnotation(HIERARCHY_ANOTATION_NAME, changes)) {
result.add(Recompile.SUBCLASSES);
}
if (containsAnnotation(KOTLIN_TESTS_ANOTATION_NAMES, changes)) {
result.addAll(RECOMPILE_ALL);
}
return result;
}
private static boolean containsAnnotation(@NotNull String annotationName, Iterable<TypeRepr.ClassType> classes) {
return containsAnnotation(Set.of(annotationName), classes);
}
private static boolean containsAnnotation(Set<String> annotationNames, Iterable<TypeRepr.ClassType> classes) {
return !isEmpty(filter(classes, type -> annotationNames.contains(type.getJvmName())));
}
}
@@ -0,0 +1,75 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jps.dependency.java;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.builders.java.dependencyView.MockAnnotation;
import org.jetbrains.jps.builders.java.dependencyView.MockHierarchyAnnotation;
import org.jetbrains.jps.dependency.DifferentiateContext;
import org.jetbrains.jps.dependency.diff.Difference;
import org.jetbrains.jps.javac.Iterators;
import java.util.EnumSet;
import java.util.Objects;
import java.util.Set;
/**
* The main purpose of this class is to register the annotation tracker for mock and test annotations used in tests
*/
public class TestJvmDifferentiateStrategy extends JvmDifferentiateStrategyImpl {
private static final String ANOTATION_NAME = MockAnnotation.class.getName().replace('.', '/');
private static final String HIERARCHY_ANOTATION_NAME = MockHierarchyAnnotation.class.getName().replace('.', '/');
private static final Set<String> KOTLIN_TESTS_ANOTATION_NAMES = Set.of("foo/Ann", "Ann");
@Override
public boolean isAnnotationTracked(@NotNull TypeRepr.ClassType annotationType) {
String typeName = annotationType.getJvmName();
return KOTLIN_TESTS_ANOTATION_NAMES.contains(typeName) || Objects.equals(ANOTATION_NAME, typeName) || Objects.equals(HIERARCHY_ANOTATION_NAME, typeName);
}
@Override
public boolean processClassAnnotations(DifferentiateContext context, Difference.Change<JvmClass, JvmClass.Diff> change, Difference.Specifier<ElementAnnotation, ElementAnnotation.Diff> annotationDiff, Utils future, Utils present) {
Set<AnnotationAffectionScope> affectionScope = getAffectionScope(getAffectedAnnotations(annotationDiff, EnumSet.of(AnnotationAffectionKind.added, AnnotationAffectionKind.removed, AnnotationAffectionKind.changed)));
if (!affectionScope.isEmpty()) {
affectClassAnnotationUsages(context, affectionScope, change, future, present);
}
return super.processClassAnnotations(context, change, annotationDiff, future, present);
}
@Override
public boolean processFieldAnnotations(DifferentiateContext context, Difference.Change<JvmClass, JvmClass.Diff> clsChange, Difference.Change<JvmField, JvmField.Diff> fieldChange, Difference.Specifier<ElementAnnotation, ElementAnnotation.Diff> annotationDiff, Utils future, Utils present) {
Set<AnnotationAffectionScope> affectionScope = getAffectionScope(getAffectedAnnotations(annotationDiff, EnumSet.of(AnnotationAffectionKind.added, AnnotationAffectionKind.removed, AnnotationAffectionKind.changed)));
if (!affectionScope.isEmpty()) {
affectFieldAnnotationUsages(context, affectionScope, clsChange, fieldChange.getPast(), future, present);
}
return super.processFieldAnnotations(context, clsChange, fieldChange, annotationDiff, future, present);
}
@Override
public boolean processMethodAnnotations(DifferentiateContext context, Difference.Change<JvmClass, JvmClass.Diff> clsChange, Difference.Change<JvmMethod, JvmMethod.Diff> methodChange, Difference.Specifier<ElementAnnotation, ElementAnnotation.Diff> annotationsDiff, Difference.Specifier<ParamAnnotation, ParamAnnotation.Diff> paramAnnotationsDiff, Utils future, Utils present) {
EnumSet<AnnotationAffectionKind> affectionKinds = EnumSet.of(AnnotationAffectionKind.added, AnnotationAffectionKind.removed, AnnotationAffectionKind.changed);
Set<AnnotationAffectionScope> affectionScope = getAffectionScope(
Iterators.flat(getAffectedAnnotations(annotationsDiff, affectionKinds), getAffectedAnnotations(paramAnnotationsDiff, affectionKinds))
);
if (!affectionScope.isEmpty()) {
affectMethodAnnotationUsages(context, affectionScope, clsChange, methodChange.getPast(), future, present);
}
return super.processMethodAnnotations(context, clsChange, methodChange, annotationsDiff, paramAnnotationsDiff, future, present);
}
private static Set<AnnotationAffectionScope> getAffectionScope(Iterable<TypeRepr.ClassType> trackedAnnotations) {
Set<AnnotationAffectionScope> result = EnumSet.noneOf(AnnotationAffectionScope.class);
for (TypeRepr.ClassType annotation : trackedAnnotations) {
if (ANOTATION_NAME.equals(annotation.getJvmName())) {
result.add(AnnotationAffectionScope.usages);
}
else if (HIERARCHY_ANOTATION_NAME.equals(annotation.getJvmName())) {
result.add(AnnotationAffectionScope.subclasses);
}
else if (KOTLIN_TESTS_ANOTATION_NAMES.contains(annotation.getJvmName())) {
result.addAll(EnumSet.of(AnnotationAffectionScope.usages, AnnotationAffectionScope.subclasses));
}
}
return result;
}
}
@@ -0,0 +1,22 @@
================ Step #1 =================
Cleaning output files:
out/production/module/META-INF/module.kotlin_module
out/production/module/test/Util.class
End of files
Compiling files:
src/util.kt
End of files
Exit code: OK
------------------------------------------
Cleaning output files:
out/production/module/META-INF/module.kotlin_module
out/production/module/test/Client.class
End of files
Compiling files:
src/client.kt
End of files
Exit code: ABORT
------------------------------------------
COMPILATION FAILED
'fun foo(param: String): Int' is deprecated. message.
@@ -0,0 +1,7 @@
package test
class Client {
fun test(u: Util) {
val a = u.foo("param")
}
}
@@ -0,0 +1,6 @@
package test
class Util {
@Deprecated("message")
fun foo(param : String) = 10
}
@@ -0,0 +1,6 @@
package test
class Util {
@Deprecated("message", level = DeprecationLevel.ERROR)
fun foo(param : String) = 10
}
@@ -0,0 +1,22 @@
================ Step #1 =================
Cleaning output files:
out/production/module/META-INF/module.kotlin_module
out/production/module/test/Util.class
End of files
Compiling files:
src/util.kt
End of files
Exit code: OK
------------------------------------------
Cleaning output files:
out/production/module/META-INF/module.kotlin_module
out/production/module/test/Client.class
End of files
Compiling files:
src/client.kt
End of files
Exit code: ABORT
------------------------------------------
COMPILATION FAILED
'var foo: Int' is deprecated. message.
@@ -0,0 +1,7 @@
package test
class Client {
fun test(u: Util) {
val a = u.foo
}
}
@@ -0,0 +1,8 @@
package test
class Util {
@Deprecated("message")
var foo = 10
get
set
}
@@ -0,0 +1,8 @@
package test
class Util {
@Deprecated("message", level = DeprecationLevel.ERROR)
var foo = 10
get
set
}
@@ -0,0 +1,22 @@
================ Step #1 =================
Cleaning output files:
out/production/module/META-INF/module.kotlin_module
out/production/module/test/Util.class
End of files
Compiling files:
src/util.kt
End of files
Exit code: OK
------------------------------------------
Cleaning output files:
out/production/module/META-INF/module.kotlin_module
out/production/module/test/ClientGet.class
End of files
Compiling files:
src/clientGet.kt
End of files
Exit code: ABORT
------------------------------------------
COMPILATION FAILED
'var foo: Int' is deprecated. message.
@@ -0,0 +1,7 @@
package test
class ClientGet {
fun test(u: Util) {
val a = u.foo
}
}
@@ -0,0 +1,7 @@
package test
class ClientSet {
fun test(u: Util) {
u.foo = 10
}
}
@@ -0,0 +1,8 @@
package test
class Util {
var foo = 10
@Deprecated("message")
get
set
}
@@ -0,0 +1,8 @@
package test
class Util {
var foo = 10
@Deprecated("message", level = DeprecationLevel.ERROR)
get
set
}
@@ -0,0 +1,22 @@
================ Step #1 =================
Cleaning output files:
out/production/module/META-INF/module.kotlin_module
out/production/module/test/Util.class
End of files
Compiling files:
src/util.kt
End of files
Exit code: OK
------------------------------------------
Cleaning output files:
out/production/module/META-INF/module.kotlin_module
out/production/module/test/ClientSet.class
End of files
Compiling files:
src/clientSet.kt
End of files
Exit code: ABORT
------------------------------------------
COMPILATION FAILED
'var foo: Int' is deprecated. message.
@@ -0,0 +1,7 @@
package test
class ClientGet {
fun test(u: Util) {
val a = u.foo
}
}
@@ -0,0 +1,7 @@
package test
class ClientSet {
fun test(u: Util) {
u.foo = 10
}
}
@@ -0,0 +1,8 @@
package test
class Util {
var foo = 10
get
@Deprecated("message")
set
}
@@ -0,0 +1,8 @@
package test
class Util {
var foo = 10
get
@Deprecated("message", level = DeprecationLevel.ERROR)
set
}
@@ -105,6 +105,26 @@ public class IncrementalK2JvmJpsTestGenerated extends AbstractIncrementalK2JvmJp
runTest("pureKotlin/annotations/");
}
@TestMetadata("deprecateFunction")
public void testDeprecateFunction() throws Exception {
runTest("pureKotlin/deprecateFunction/");
}
@TestMetadata("deprecateProperty")
public void testDeprecateProperty() throws Exception {
runTest("pureKotlin/deprecateProperty/");
}
@TestMetadata("deprecatePropertyGetter")
public void testDeprecatePropertyGetter() throws Exception {
runTest("pureKotlin/deprecatePropertyGetter/");
}
@TestMetadata("deprecatePropertySetter")
public void testDeprecatePropertySetter() throws Exception {
runTest("pureKotlin/deprecatePropertySetter/");
}
@TestMetadata("anonymousObjectChanged")
public void testAnonymousObjectChanged() throws Exception {
runTest("pureKotlin/anonymousObjectChanged/");