diff --git a/src/main/java/com/android/tools/metalava/AnnotationsMerger.kt b/src/main/java/com/android/tools/metalava/AnnotationsMerger.kt index 795d99eba61947220d871b1c242803b38232e8b0..76d430d61207d99750541ffda78e42a2b6bfdd72 100644 --- a/src/main/java/com/android/tools/metalava/AnnotationsMerger.kt +++ b/src/main/java/com/android/tools/metalava/AnnotationsMerger.kt @@ -56,11 +56,16 @@ import com.android.tools.metalava.model.ClassItem import com.android.tools.metalava.model.Codebase import com.android.tools.metalava.model.DefaultAnnotationItem import com.android.tools.metalava.model.DefaultAnnotationValue +import com.android.tools.metalava.model.FieldItem import com.android.tools.metalava.model.Item import com.android.tools.metalava.model.MethodItem +import com.android.tools.metalava.model.ModifierList +import com.android.tools.metalava.model.ParameterItem +import com.android.tools.metalava.model.TypeItem import com.android.tools.metalava.model.parseDocument import com.android.tools.metalava.model.psi.PsiAnnotationItem import com.android.tools.metalava.model.psi.PsiBasedCodebase +import com.android.tools.metalava.model.psi.PsiTypeItem import com.android.tools.metalava.model.visitors.ApiVisitor import com.google.common.io.ByteStreams import com.google.common.io.Closeables @@ -240,32 +245,70 @@ class AnnotationsMerger( override fun compare(old: Item, new: Item) { val newModifiers = new.modifiers for (annotation in old.modifiers.annotations()) { - var addAnnotation = false - if (annotation.isNullnessAnnotation()) { - if (!newModifiers.hasNullnessInfo()) { - addAnnotation = true - } - } else { - // TODO: Check for other incompatibilities than nullness? - val qualifiedName = annotation.qualifiedName() ?: continue - if (newModifiers.findAnnotation(qualifiedName) == null) { - addAnnotation = true - } + mergeAnnotation(annotation, newModifiers, new) + } + } + + private fun mergeAnnotation( + annotation: AnnotationItem, + newModifiers: ModifierList, + new: Item + ) { + var addAnnotation = false + if (annotation.isNullnessAnnotation()) { + if (!newModifiers.hasNullnessInfo()) { + addAnnotation = true } + } else { + // TODO: Check for other incompatibilities than nullness? + val qualifiedName = annotation.qualifiedName() ?: return + if (newModifiers.findAnnotation(qualifiedName) == null) { + addAnnotation = true + } + } - if (addAnnotation) { - // Don't map annotation names - this would turn newly non null back into non null - new.mutableModifiers().addAnnotation( - new.codebase.createAnnotation( - annotation.toSource(), - new, - mapName = false - ) + if (addAnnotation) { + // Don't map annotation names - this would turn newly non null back into non null + new.mutableModifiers().addAnnotation( + new.codebase.createAnnotation( + annotation.toSource(), + new, + mapName = false ) + ) + } + } + + override fun compare(old: ParameterItem, new: ParameterItem) { + mergeTypeAnnotations(old.type(), new) + } + + override fun compare(old: FieldItem, new: FieldItem) { + mergeTypeAnnotations(old.type(), new) + } + + override fun compare(old: MethodItem, new: MethodItem) { + mergeTypeAnnotations(old.returnType(), new) + } + + // Merge in type annotations + private fun mergeTypeAnnotations( + typeItem: TypeItem?, + new: Item + ) { + typeItem ?: return + val type = (typeItem as? PsiTypeItem)?.psiType ?: return + val typeAnnotations = type.annotations + if (typeAnnotations.isNotEmpty()) { + for (annotation in typeAnnotations) { + val codebase = new.codebase as PsiBasedCodebase + val annotationItem = PsiAnnotationItem.create(codebase, annotation) + mergeAnnotation(annotationItem, new.modifiers, new) } } } } + CodebaseComparator().compare( visitor, externalCodebase, codebase, ApiPredicate() ) diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiTypeItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiTypeItem.kt index 306770a675fbde386b4780bf60a18384a403973f..507731483f1b5c997a3185b01b54593792aaef5d 100644 --- a/src/main/java/com/android/tools/metalava/model/psi/PsiTypeItem.kt +++ b/src/main/java/com/android/tools/metalava/model/psi/PsiTypeItem.kt @@ -56,7 +56,7 @@ import com.intellij.psi.util.TypeConversionUtil import org.jetbrains.kotlin.asJava.elements.KtLightTypeParameter /** Represents a type backed by PSI */ -class PsiTypeItem private constructor(private val codebase: PsiBasedCodebase, private val psiType: PsiType) : TypeItem { +class PsiTypeItem private constructor(private val codebase: PsiBasedCodebase, val psiType: PsiType) : TypeItem { private var toString: String? = null private var toAnnotatedString: String? = null private var toInnerAnnotatedString: String? = null diff --git a/src/test/java/com/android/tools/metalava/AnnotationsMergerTest.kt b/src/test/java/com/android/tools/metalava/AnnotationsMergerTest.kt index cc7eb68c3c8845c98f32e019078176867b691b9d..f1d6a12b9ae8bbc1251567ad8d921c18c9986c15 100644 --- a/src/test/java/com/android/tools/metalava/AnnotationsMergerTest.kt +++ b/src/test/java/com/android/tools/metalava/AnnotationsMergerTest.kt @@ -211,24 +211,70 @@ class AnnotationsMergerTest : DriverTest() { } @Test - fun `Merge inclusion annotations from Java stub files`() { + fun `Merge type use qualifier annotations from Java stub files`() { + // See b/123223339 check( - warnings = "src/test/pkg/Example.java:6: error: @test.annotation.Show APIs must also be marked @hide: method test.pkg.Example.cShown() [UnhiddenSystemApi]", sourceFiles = *arrayOf( java( """ package test.pkg; - public interface Example { - void aNotAnnotated(); - void bHidden(); - void cShown(); + public class Test { + private Test() { } + public void foo(Object... args) { } } + """ + ), + libcoreNonNullSource, + libcoreNullableSource + ), + compatibilityMode = false, + outputKotlinStyleNulls = false, + omitCommonPackages = false, + mergeJavaStubAnnotations = """ + package test.pkg; - public interface HiddenExample { - void method(); + public class Test { + public void foo(java.lang.@libcore.util.Nullable Object @libcore.util.NonNull ... args) { throw new RuntimeException("Stub!"); } } - """ + """, + api = """ + package test.pkg { + public class Test { + method public void foo(@androidx.annotation.NonNull java.lang.Object...); + } + } + """, + extraArguments = arrayOf(ARG_HIDE_PACKAGE, "libcore.util") + ) + } + + @Test + fun `Merge inclusion annotations from Java stub files`() { + check( + warnings = "src/test/pkg/Example.annotated.java:6: error: @test.annotation.Show APIs must also be marked @hide: method test.pkg.Example.cShown() [UnhiddenSystemApi]", + sourceFiles = *arrayOf( + java( + "src/test/pkg/Example.annotated.java", + """ + package test.pkg; + + public interface Example { + void aNotAnnotated(); + void bHidden(); + void cShown(); + } + """ + ), + java( + "src/test/pkg/HiddenExample.annotated.java", + """ + package test.pkg; + + public interface HiddenExample { + void method(); + } + """ ) ), compatibilityMode = false, @@ -267,14 +313,15 @@ class AnnotationsMergerTest : DriverTest() { check( sourceFiles = *arrayOf( java( + "src/test/pkg/Example.annotated.java", """ - package test.pkg; + package test.pkg; - public interface Example { - void aNotAnnotated(); - void bShown(); - } - """ + public interface Example { + void aNotAnnotated(); + void bShown(); + } + """ ) ), compatibilityMode = false, diff --git a/src/test/java/com/android/tools/metalava/DriverTest.kt b/src/test/java/com/android/tools/metalava/DriverTest.kt index 533e58f8d1690d6cdf2d644e490fb8139499bdae..e54df8105f49f68edb3208ffcce5903ae69be7c4 100644 --- a/src/test/java/com/android/tools/metalava/DriverTest.kt +++ b/src/test/java/com/android/tools/metalava/DriverTest.kt @@ -1902,6 +1902,10 @@ abstract class DriverTest { } } + fun java(to: String, @Language("JAVA") source: String): LintDetectorTest.TestFile { + return TestFiles.java(to, source.trimIndent()) + } + fun java(@Language("JAVA") source: String): LintDetectorTest.TestFile { return TestFiles.java(source.trimIndent()) }