Switch to UAST 1.3
am: 8b707232af
Change-Id: I159ac4b0dccdd45f1f60ab7a9529c2beaa2581c9
diff --git a/build.gradle b/build.gradle
index eedd62a..24126e6 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,6 +1,6 @@
buildscript {
- ext.gradle_version = '3.2.1'
- ext.studio_version = '26.2.1'
+ ext.gradle_version = '3.4.0-beta01'
+ ext.studio_version = '26.4.0-beta01'
ext.kotlin_version = '1.3.11'
repositories {
google()
diff --git a/src/main/java/com/android/tools/metalava/ComparisonVisitor.kt b/src/main/java/com/android/tools/metalava/ComparisonVisitor.kt
index 77d3dc0..6b703c8 100644
--- a/src/main/java/com/android/tools/metalava/ComparisonVisitor.kt
+++ b/src/main/java/com/android/tools/metalava/ComparisonVisitor.kt
@@ -333,7 +333,17 @@
val simpleType2 = parameter2.type().toCanonicalType()
delta = simpleType1.compareTo(simpleType2)
if (delta != 0) {
- break
+ // Special case: Kotlin coroutines
+ if (simpleType1.startsWith("kotlin.coroutines.") && simpleType2.startsWith("kotlin.coroutines.")) {
+ val t1 = simpleType1.removePrefix("kotlin.coroutines.").removePrefix("experimental.")
+ val t2 = simpleType2.removePrefix("kotlin.coroutines.").removePrefix("experimental.")
+ delta = t1.compareTo(t2)
+ if (delta != 0) {
+ break
+ }
+ } else {
+ break
+ }
}
}
}
diff --git a/src/main/java/com/android/tools/metalava/CompatibilityCheck.kt b/src/main/java/com/android/tools/metalava/CompatibilityCheck.kt
index 78aebe1..94ac90b 100644
--- a/src/main/java/com/android/tools/metalava/CompatibilityCheck.kt
+++ b/src/main/java/com/android/tools/metalava/CompatibilityCheck.kt
@@ -510,7 +510,7 @@
}
}
- if (new.modifiers.isInline() && new.isKotlin()) {
+ if (new.modifiers.isInline()) {
val oldTypes = old.typeParameterList().typeParameters()
val newTypes = new.typeParameterList().typeParameters()
for (i in 0 until oldTypes.size) {
diff --git a/src/main/java/com/android/tools/metalava/Constants.kt b/src/main/java/com/android/tools/metalava/Constants.kt
index 4e0a3c7..88cd3a7 100644
--- a/src/main/java/com/android/tools/metalava/Constants.kt
+++ b/src/main/java/com/android/tools/metalava/Constants.kt
@@ -32,6 +32,9 @@
const val ANDROID_REQUIRES_PERMISSION = "android.annotation.RequiresPermission"
const val RECENTLY_NULLABLE = "androidx.annotation.RecentlyNullable"
const val RECENTLY_NONNULL = "androidx.annotation.RecentlyNonNull"
+const val ANDROIDX_VISIBLE_FOR_TESTING = "androidx.annotation.VisibleForTesting"
+const val ANDROID_SUPPORT_VISIBLE_FOR_TESTING = "android.support.annotation.VisibleForTesting"
+const val ATTR_OTHERWISE = "otherwise"
const val ENV_VAR_METALAVA_TESTS_RUNNING = "METALAVA_TESTS_RUNNING"
const val ENV_VAR_METALAVA_DUMP_ARGV = "METALAVA_DUMP_ARGV"
diff --git a/src/main/java/com/android/tools/metalava/DocAnalyzer.kt b/src/main/java/com/android/tools/metalava/DocAnalyzer.kt
index 44eed3b..b831a5b 100644
--- a/src/main/java/com/android/tools/metalava/DocAnalyzer.kt
+++ b/src/main/java/com/android/tools/metalava/DocAnalyzer.kt
@@ -224,7 +224,8 @@
}
private fun handleKotlinDeprecation(annotation: AnnotationItem, item: Item) {
- val text = annotation.findAttribute(ATTR_VALUE)?.value?.value()?.toString() ?: return
+ val text = (annotation.findAttribute("message") ?: annotation.findAttribute(ATTR_VALUE))
+ ?.value?.value()?.toString() ?: return
if (text.isBlank() || item.documentation.contains(text)) {
return
}
diff --git a/src/main/java/com/android/tools/metalava/ExtractAnnotations.kt b/src/main/java/com/android/tools/metalava/ExtractAnnotations.kt
index d69b1af..9076f31 100644
--- a/src/main/java/com/android/tools/metalava/ExtractAnnotations.kt
+++ b/src/main/java/com/android/tools/metalava/ExtractAnnotations.kt
@@ -34,6 +34,7 @@
import com.android.tools.metalava.model.psi.PsiAnnotationItem
import com.android.tools.metalava.model.psi.PsiClassItem
import com.android.tools.metalava.model.psi.PsiMethodItem
+import com.android.tools.metalava.model.psi.UAnnotationItem
import com.android.tools.metalava.model.visitors.ApiVisitor
import com.google.common.xml.XmlEscapers
import com.intellij.psi.JavaRecursiveElementVisitor
@@ -234,26 +235,33 @@
)
}
- if (typeDefAnnotation is PsiAnnotationItem && typeDefClass is PsiClassItem) {
- val result = AnnotationHolder(
- typeDefClass, typeDefAnnotation,
- annotationLookup.findRealAnnotation(
- typeDefAnnotation.psiAnnotation,
- typeDefClass.psiClass,
- null
+ val result =
+ if (typeDefAnnotation is PsiAnnotationItem && typeDefClass is PsiClassItem) {
+ AnnotationHolder(
+ typeDefClass, typeDefAnnotation,
+ annotationLookup.findRealAnnotation(
+ typeDefAnnotation.psiAnnotation,
+ typeDefClass.psiClass,
+ null
+ )
)
- )
- classToAnnotationHolder[className] = result
- addItem(item, result)
-
- if (item is PsiMethodItem && result.uAnnotation != null &&
- !reporter.isSuppressed(Errors.RETURNING_UNEXPECTED_CONSTANT)
- ) {
- verifyReturnedConstants(item, result.uAnnotation, result, className)
+ } else if (typeDefAnnotation is UAnnotationItem && typeDefClass is PsiClassItem) {
+ AnnotationHolder(
+ typeDefClass, typeDefAnnotation, typeDefAnnotation.uAnnotation
+ )
+ } else {
+ continue
}
- continue
+ classToAnnotationHolder[className] = result
+ addItem(item, result)
+
+ if (item is PsiMethodItem && result.uAnnotation != null &&
+ !reporter.isSuppressed(Errors.RETURNING_UNEXPECTED_CONSTANT)
+ ) {
+ verifyReturnedConstants(item, result.uAnnotation, result, className)
}
+ continue
}
}
}
@@ -469,11 +477,12 @@
) {
val annotationItem = annotationHolder.annotationItem
val uAnnotation = annotationHolder.uAnnotation
- ?: if (annotationItem is PsiAnnotationItem) {
- // Imported annotation
- JavaUAnnotation.wrap(annotationItem.psiAnnotation)
- } else {
- return
+ ?: when (annotationItem) {
+ is UAnnotationItem -> annotationItem.uAnnotation
+ is PsiAnnotationItem ->
+ // Imported annotation
+ JavaUAnnotation.wrap(annotationItem.psiAnnotation)
+ else -> return
}
val qualifiedName = annotationItem.qualifiedName()
diff --git a/src/main/java/com/android/tools/metalava/KotlinInteropChecks.kt b/src/main/java/com/android/tools/metalava/KotlinInteropChecks.kt
index a904af5..278d1c7 100644
--- a/src/main/java/com/android/tools/metalava/KotlinInteropChecks.kt
+++ b/src/main/java/com/android/tools/metalava/KotlinInteropChecks.kt
@@ -95,10 +95,8 @@
if (checked) {
val annotation = method.modifiers.findAnnotation("kotlin.jvm.Throws")
if (annotation != null) {
- val attribute =
- annotation.findAttribute("exceptionClasses") ?: annotation.findAttribute("value")
- ?: annotation.attributes().firstOrNull()
- if (attribute != null) {
+ // There can be multiple values
+ for (attribute in annotation.attributes()) {
for (v in attribute.leafValues()) {
val source = v.toSource()
if (source.endsWith(exception.simpleName() + "::class")) {
diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiAnnotationItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiAnnotationItem.kt
index e8c6acb..decece4 100644
--- a/src/main/java/com/android/tools/metalava/model/psi/PsiAnnotationItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/psi/PsiAnnotationItem.kt
@@ -284,7 +284,7 @@
override val value: Any?
get() {
if (psiValue is PsiLiteral) {
- return psiValue.value
+ return psiValue.value ?: psiValue.text.removeSurrounding("\"")
}
val value = ConstantEvaluator.evaluate(null, psiValue)
@@ -292,7 +292,7 @@
return value
}
- return psiValue.text
+ return psiValue.text ?: psiValue.text.removeSurrounding("\"")
}
override fun value(): Any? = value
diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiClassItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiClassItem.kt
index 564e15c..4a5ba9d 100644
--- a/src/main/java/com/android/tools/metalava/model/psi/PsiClassItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/psi/PsiClassItem.kt
@@ -420,6 +420,8 @@
)
}
+ val isKotlin = isKotlin(psiClass)
+
val constructors: MutableList<PsiConstructorItem> = ArrayList(5)
for (psiMethod in psiMethods) {
if (psiMethod.isPrivate() || psiMethod.isPackagePrivate()) {
@@ -468,7 +470,7 @@
item.fields = fields
item.properties = emptyList()
- if (isKotlin(psiClass)) {
+ if (isKotlin) {
// Try to initialize the Kotlin properties
val properties = mutableListOf<PsiPropertyItem>()
for (method in psiMethods) {
diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiMethodItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiMethodItem.kt
index f856c91..c337332 100644
--- a/src/main/java/com/android/tools/metalava/model/psi/PsiMethodItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/psi/PsiMethodItem.kt
@@ -357,9 +357,16 @@
// methods with super methods also consider this method non-final.)
modifiers.setFinal(false)
}
- val parameters = psiMethod.parameterList.parameters.mapIndexed { index, parameter ->
- PsiParameterItem.create(codebase, parameter, index)
- }
+ val parameters =
+ if (psiMethod is UMethod) {
+ psiMethod.uastParameters.mapIndexed { index, parameter ->
+ PsiParameterItem.create(codebase, parameter, index)
+ }
+ } else {
+ psiMethod.parameterList.parameters.mapIndexed { index, parameter ->
+ PsiParameterItem.create(codebase, parameter, index)
+ }
+ }
val returnType = codebase.getType(psiMethod.returnType!!)
val method = PsiMethodItem(
codebase = codebase,
diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiModifierItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiModifierItem.kt
index 3a11295..5ac50e5 100644
--- a/src/main/java/com/android/tools/metalava/model/psi/PsiModifierItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/psi/PsiModifierItem.kt
@@ -16,6 +16,9 @@
package com.android.tools.metalava.model.psi
+import com.android.tools.metalava.ANDROIDX_VISIBLE_FOR_TESTING
+import com.android.tools.metalava.ANDROID_SUPPORT_VISIBLE_FOR_TESTING
+import com.android.tools.metalava.ATTR_OTHERWISE
import com.android.tools.metalava.model.AnnotationItem
import com.android.tools.metalava.model.Codebase
import com.android.tools.metalava.model.DefaultModifierList
@@ -24,12 +27,18 @@
import com.intellij.psi.PsiDocCommentOwner
import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiModifier
+import com.intellij.psi.PsiModifierList
import com.intellij.psi.PsiModifierListOwner
import com.intellij.psi.PsiReferenceExpression
+import com.intellij.psi.PsiPrimitiveType
import org.jetbrains.kotlin.asJava.elements.KtLightModifierList
+import org.jetbrains.kotlin.asJava.elements.KtLightNullabilityAnnotation
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtNamedFunction
+import org.jetbrains.uast.UAnnotated
import org.jetbrains.uast.UMethod
+import org.jetbrains.uast.UVariable
+import org.jetbrains.uast.kotlin.KotlinNullabilityUAnnotation
class PsiModifierItem(
codebase: Codebase,
@@ -38,8 +47,12 @@
) : DefaultModifierList(codebase, flags, annotations), ModifierList, MutableModifierList {
companion object {
fun create(codebase: PsiBasedCodebase, element: PsiModifierListOwner, documentation: String?): PsiModifierItem {
- val modifiers = create(codebase, element)
-
+ val modifiers =
+ if (element is UAnnotated) {
+ create(codebase, element, element)
+ } else {
+ create(codebase, element)
+ }
if (documentation?.contains("@deprecated") == true ||
// Check for @Deprecated annotation
((element as? PsiDocCommentOwner)?.isDeprecated == true)
@@ -50,9 +63,7 @@
return modifiers
}
- private fun create(codebase: PsiBasedCodebase, element: PsiModifierListOwner): PsiModifierItem {
- val modifierList = element.modifierList ?: return PsiModifierItem(codebase)
-
+ private fun computeFlag(element: PsiModifierListOwner, modifierList: PsiModifierList): Int {
var flags = 0
if (modifierList.hasModifierProperty(PsiModifier.PUBLIC)) {
flags = flags or PUBLIC
@@ -139,6 +150,14 @@
}
}
+ return flags
+ }
+
+ private fun create(codebase: PsiBasedCodebase, element: PsiModifierListOwner): PsiModifierItem {
+ val modifierList = element.modifierList ?: return PsiModifierItem(codebase)
+
+ var flags = computeFlag(element, modifierList)
+
val psiAnnotations = modifierList.annotations
return if (psiAnnotations.isEmpty()) {
PsiModifierItem(codebase, flags)
@@ -146,22 +165,16 @@
val annotations: MutableList<AnnotationItem> =
psiAnnotations.map {
val qualifiedName = it.qualifiedName
- // TODO: com.android.internal.annotations.VisibleForTesting?
- if (qualifiedName == "androidx.annotation.VisibleForTesting" ||
- qualifiedName == "android.support.annotation.VisibleForTesting") {
- val otherwise = it.findAttributeValue("otherwise")
+ // Consider also supporting com.android.internal.annotations.VisibleForTesting?
+ if (qualifiedName == ANDROIDX_VISIBLE_FOR_TESTING ||
+ qualifiedName == ANDROID_SUPPORT_VISIBLE_FOR_TESTING) {
+ val otherwise = it.findAttributeValue(ATTR_OTHERWISE)
val ref = when {
otherwise is PsiReferenceExpression -> otherwise.referenceName ?: ""
otherwise != null -> otherwise.text
else -> ""
}
- if (ref.endsWith("PROTECTED")) {
- flags = (flags and PUBLIC.inv() and PRIVATE.inv() and INTERNAL.inv()) or PROTECTED
- } else if (ref.endsWith("PACKAGE_PRIVATE")) {
- flags = (flags and PUBLIC.inv() and PRIVATE.inv() and INTERNAL.inv() and PROTECTED.inv())
- } else if (ref.endsWith("PRIVATE") || ref.endsWith("NONE")) {
- flags = (flags and PUBLIC.inv() and PROTECTED.inv() and INTERNAL.inv()) or PRIVATE
- }
+ flags = getVisibilityFlag(ref, flags)
}
PsiAnnotationItem.create(codebase, it, qualifiedName)
@@ -170,6 +183,74 @@
}
}
+ private fun create(
+ codebase: PsiBasedCodebase,
+ element: PsiModifierListOwner,
+ annotated: UAnnotated
+ ): PsiModifierItem {
+ val modifierList = element.modifierList ?: return PsiModifierItem(codebase)
+ var flags = computeFlag(element, modifierList)
+ val uAnnotations = annotated.annotations
+
+ return if (uAnnotations.isEmpty()) {
+ val psiAnnotations = modifierList.annotations
+ if (!psiAnnotations.isEmpty()) {
+ val annotations: MutableList<AnnotationItem> =
+ psiAnnotations.map { PsiAnnotationItem.create(codebase, it) }.toMutableList()
+ PsiModifierItem(codebase, flags, annotations)
+ } else {
+ PsiModifierItem(codebase, flags)
+ }
+ } else {
+ val isPrimitiveVariable = element is UVariable && element.type is PsiPrimitiveType
+
+ val annotations: MutableList<AnnotationItem> = uAnnotations
+ // Uast sometimes puts nullability annotations on primitives!?
+ .filter { !isPrimitiveVariable || it !is KotlinNullabilityUAnnotation }
+ .map {
+
+ val qualifiedName = it.qualifiedName
+ if (qualifiedName == ANDROIDX_VISIBLE_FOR_TESTING ||
+ qualifiedName == ANDROID_SUPPORT_VISIBLE_FOR_TESTING) {
+ val otherwise = it.findAttributeValue(ATTR_OTHERWISE)
+ val ref = when {
+ otherwise is PsiReferenceExpression -> otherwise.referenceName ?: ""
+ otherwise != null -> otherwise.asSourceString()
+ else -> ""
+ }
+ flags = getVisibilityFlag(ref, flags)
+ }
+
+ UAnnotationItem.create(codebase, it, qualifiedName)
+ }.toMutableList()
+
+ if (!isPrimitiveVariable) {
+ val psiAnnotations = modifierList.annotations
+ if (psiAnnotations.isNotEmpty() && annotations.none { it.isNullnessAnnotation() }) {
+ val ktNullAnnotation = psiAnnotations.firstOrNull { it is KtLightNullabilityAnnotation }
+ ktNullAnnotation?.let {
+ annotations.add(PsiAnnotationItem.create(codebase, it))
+ }
+ }
+ }
+
+ PsiModifierItem(codebase, flags, annotations)
+ }
+ }
+
+ /** Modifies the modifier flags based on the VisibleForTesting otherwise constants */
+ private fun getVisibilityFlag(ref: String, flags: Int): Int {
+ return if (ref.endsWith("PROTECTED")) {
+ (flags and PUBLIC.inv() and PRIVATE.inv() and INTERNAL.inv()) or PROTECTED
+ } else if (ref.endsWith("PACKAGE_PRIVATE")) {
+ (flags and PUBLIC.inv() and PRIVATE.inv() and INTERNAL.inv() and PROTECTED.inv())
+ } else if (ref.endsWith("PRIVATE") || ref.endsWith("NONE")) {
+ (flags and PUBLIC.inv() and PROTECTED.inv() and INTERNAL.inv()) or PRIVATE
+ } else {
+ flags
+ }
+ }
+
fun create(codebase: PsiBasedCodebase, original: PsiModifierItem): PsiModifierItem {
val originalAnnotations = original.annotations ?: return PsiModifierItem(codebase, original.flags)
val copy: MutableList<AnnotationItem> = ArrayList(originalAnnotations.size)
diff --git a/src/main/java/com/android/tools/metalava/model/psi/UAnnotationItem.kt b/src/main/java/com/android/tools/metalava/model/psi/UAnnotationItem.kt
new file mode 100644
index 0000000..0d0266e
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/psi/UAnnotationItem.kt
@@ -0,0 +1,326 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.metalava.model.psi
+
+import com.android.SdkConstants
+import com.android.tools.lint.detector.api.ConstantEvaluator
+import com.android.tools.metalava.model.AnnotationArrayAttributeValue
+import com.android.tools.metalava.model.AnnotationAttribute
+import com.android.tools.metalava.model.AnnotationAttributeValue
+import com.android.tools.metalava.model.AnnotationItem
+import com.android.tools.metalava.model.AnnotationSingleAttributeValue
+import com.android.tools.metalava.model.AnnotationTarget
+import com.android.tools.metalava.model.ClassItem
+import com.android.tools.metalava.model.DefaultAnnotationItem
+import com.android.tools.metalava.model.Item
+import com.intellij.psi.PsiClass
+import com.intellij.psi.PsiExpression
+import com.intellij.psi.PsiField
+import com.intellij.psi.PsiLiteral
+import com.intellij.psi.PsiMethod
+import com.intellij.psi.impl.JavaConstantExpressionEvaluator
+import org.jetbrains.kotlin.asJava.elements.KtLightNullabilityAnnotation
+import org.jetbrains.uast.UAnnotation
+import org.jetbrains.uast.UBinaryExpression
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UExpression
+import org.jetbrains.uast.ULiteralExpression
+import org.jetbrains.uast.UReferenceExpression
+import org.jetbrains.uast.util.isArrayInitializer
+
+class UAnnotationItem private constructor(
+ override val codebase: PsiBasedCodebase,
+ val uAnnotation: UAnnotation,
+ private val originalName: String?
+) : DefaultAnnotationItem(codebase) {
+ private val qualifiedName = AnnotationItem.mapName(codebase, originalName)
+
+ private var attributes: List<AnnotationAttribute>? = null
+
+ override fun originalName(): String? = originalName
+
+ override fun toString(): String = toSource()
+
+ override fun toSource(target: AnnotationTarget): String {
+ val sb = StringBuilder(60)
+ appendAnnotation(codebase, sb, uAnnotation, originalName, target)
+ return sb.toString()
+ }
+
+ override fun resolve(): ClassItem? {
+ return codebase.findClass(originalName ?: return null)
+ }
+
+ override fun isNonNull(): Boolean {
+ if (uAnnotation.javaPsi is KtLightNullabilityAnnotation &&
+ originalName == ""
+ ) {
+ // Hack/workaround: some UAST annotation nodes do not provide qualified name :=(
+ return true
+ }
+ return super.isNonNull()
+ }
+
+ override fun qualifiedName() = qualifiedName
+
+ override fun attributes(): List<AnnotationAttribute> {
+ if (attributes == null) {
+ val uAttributes = uAnnotation.attributeValues
+ attributes = if (uAttributes.isEmpty()) {
+ emptyList()
+ } else {
+ val list = mutableListOf<AnnotationAttribute>()
+ for (parameter in uAttributes) {
+ list.add(
+ UAnnotationAttribute(
+ codebase,
+ parameter.name ?: SdkConstants.ATTR_VALUE, parameter.expression
+ )
+ )
+ }
+ list
+ }
+ }
+
+ return attributes!!
+ }
+
+ companion object {
+ fun create(codebase: PsiBasedCodebase, uAnnotation: UAnnotation, qualifiedName: String? = uAnnotation.qualifiedName): UAnnotationItem {
+ return UAnnotationItem(codebase, uAnnotation, qualifiedName)
+ }
+
+ fun create(codebase: PsiBasedCodebase, original: UAnnotationItem): UAnnotationItem {
+ return UAnnotationItem(codebase, original.uAnnotation, original.originalName)
+ }
+
+ private fun appendAnnotation(
+ codebase: PsiBasedCodebase,
+ sb: StringBuilder,
+ uAnnotation: UAnnotation,
+ originalName: String?,
+ target: AnnotationTarget
+ ) {
+ val qualifiedName = AnnotationItem.mapName(codebase, originalName, null, target) ?: return
+
+ val attributes = uAnnotation.attributeValues
+ if (attributes.isEmpty()) {
+ sb.append("@$qualifiedName")
+ return
+ }
+
+ sb.append("@")
+ sb.append(qualifiedName)
+ sb.append("(")
+ if (attributes.size == 1 && (attributes[0].name == null || attributes[0].name == SdkConstants.ATTR_VALUE)) {
+ // Special case: omit "value" if it's the only attribute
+ appendValue(codebase, sb, attributes[0].expression, target)
+ } else {
+ var first = true
+ for (attribute in attributes) {
+ if (first) {
+ first = false
+ } else {
+ sb.append(", ")
+ }
+ sb.append(attribute.name ?: SdkConstants.ATTR_VALUE)
+ sb.append('=')
+ appendValue(codebase, sb, attribute.expression, target)
+ }
+ }
+ sb.append(")")
+ }
+
+ private fun appendValue(
+ codebase: PsiBasedCodebase,
+ sb: StringBuilder,
+ value: UExpression?,
+ target: AnnotationTarget
+ ) {
+ // Compute annotation string -- we don't just use value.text here
+ // because that may not use fully qualified names, e.g. the source may say
+ // @RequiresPermission(Manifest.permission.ACCESS_COARSE_LOCATION)
+ // and we want to compute
+ // @android.support.annotation.RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
+ when (value) {
+ null -> sb.append("null")
+ is ULiteralExpression -> sb.append(CodePrinter.constantToSource(value.value))
+ is UReferenceExpression -> {
+ val resolved = value.resolve()
+ when (resolved) {
+ is PsiField -> {
+ val containing = resolved.containingClass
+ if (containing != null) {
+ // If it's a field reference, see if it looks like the field is hidden; if
+ // so, inline the value
+ val cls = codebase.findOrCreateClass(containing)
+ val initializer = resolved.initializer
+ if (initializer != null) {
+ val fieldItem = cls.findField(resolved.name)
+ if (fieldItem == null || fieldItem.isHiddenOrRemoved()) {
+ // Use the literal value instead
+ val source = getConstantSource(initializer)
+ if (source != null) {
+ sb.append(source)
+ return
+ }
+ }
+ }
+ containing.qualifiedName?.let {
+ sb.append(it).append('.')
+ }
+ }
+
+ sb.append(resolved.name)
+ }
+ is PsiClass -> resolved.qualifiedName?.let { sb.append(it) }
+ else -> {
+ sb.append(value.sourcePsi?.text ?: value.asSourceString())
+ }
+ }
+ }
+ is UBinaryExpression -> {
+ appendValue(codebase, sb, value.leftOperand, target)
+ sb.append(' ')
+ sb.append(value.operator.text)
+ sb.append(' ')
+ appendValue(codebase, sb, value.rightOperand, target)
+ }
+ is UCallExpression -> {
+ if (value.isArrayInitializer()) {
+ sb.append('{')
+ var first = true
+ for (initializer in value.valueArguments) {
+ if (first) {
+ first = false
+ } else {
+ sb.append(", ")
+ }
+ appendValue(codebase, sb, initializer, target)
+ }
+ sb.append('}')
+ } else {
+ println("todo")
+ }
+ }
+ is UAnnotation -> {
+ appendAnnotation(codebase, sb, value, value.qualifiedName, target)
+ }
+ else -> {
+ val source = getConstantSource(value)
+ if (source != null) {
+ sb.append(source)
+ return
+ }
+ sb.append(value.sourcePsi?.text ?: value.asSourceString())
+ }
+ }
+ }
+
+ private fun getConstantSource(value: UExpression): String? {
+ val constant = value.evaluate()
+ return CodePrinter.constantToExpression(constant)
+ }
+
+ private fun getConstantSource(value: PsiExpression): String? {
+ val constant = JavaConstantExpressionEvaluator.computeConstantExpression(value, false)
+ return CodePrinter.constantToExpression(constant)
+ }
+ }
+}
+
+class UAnnotationAttribute(
+ codebase: PsiBasedCodebase,
+ override val name: String,
+ psiValue: UExpression
+) : AnnotationAttribute {
+ override val value: AnnotationAttributeValue = UAnnotationValue.create(
+ codebase, psiValue
+ )
+}
+
+abstract class UAnnotationValue : AnnotationAttributeValue {
+ companion object {
+ fun create(codebase: PsiBasedCodebase, value: UExpression): UAnnotationValue {
+ return if (value.isArrayInitializer()) {
+ UAnnotationArrayAttributeValue(codebase, value as UCallExpression)
+ } else {
+ UAnnotationSingleAttributeValue(codebase, value)
+ }
+ }
+ }
+
+ override fun toString(): String = toSource()
+}
+
+class UAnnotationSingleAttributeValue(
+ private val codebase: PsiBasedCodebase,
+ private val psiValue: UExpression
+) : UAnnotationValue(), AnnotationSingleAttributeValue {
+ override val valueSource: String = getText(psiValue)
+ override val value: Any?
+ get() {
+ if (psiValue is ULiteralExpression) {
+ val value = psiValue.value
+ if (value != null) {
+ return value
+ } else if (psiValue.isNull) {
+ return null
+ }
+ }
+ if (psiValue is PsiLiteral) {
+ return psiValue.value ?: getText(psiValue).removeSurrounding("\"")
+ }
+
+ val value = ConstantEvaluator.evaluate(null, psiValue)
+ if (value != null) {
+ return value
+ }
+
+ return getText(psiValue).removeSurrounding("\"")
+ }
+
+ override fun value(): Any? = value
+
+ override fun toSource(): String = getText(psiValue)
+
+ override fun resolve(): Item? {
+ if (psiValue is UReferenceExpression) {
+ val resolved = psiValue.resolve()
+ when (resolved) {
+ is PsiField -> return codebase.findField(resolved)
+ is PsiClass -> return codebase.findOrCreateClass(resolved)
+ is PsiMethod -> return codebase.findMethod(resolved)
+ }
+ }
+ return null
+ }
+}
+
+class UAnnotationArrayAttributeValue(codebase: PsiBasedCodebase, private val value: UCallExpression) :
+ UAnnotationValue(), AnnotationArrayAttributeValue {
+ override val values = value.valueArguments.map {
+ create(codebase, it)
+ }.toList()
+
+ override fun toSource(): String = getText(value)
+}
+
+private fun getText(element: UElement): String {
+ return element.sourcePsi?.text ?: element.asSourceString()
+}
\ No newline at end of file
diff --git a/src/main/resources/version.properties b/src/main/resources/version.properties
index 233e17e..0e3b7f6 100644
--- a/src/main/resources/version.properties
+++ b/src/main/resources/version.properties
@@ -2,4 +2,4 @@
# Version definition
# This file is read by gradle build scripts, but also packaged with metalava
# as a resource for the Version classes to read.
-metalavaVersion=1.2.5
+metalavaVersion=1.2.6
diff --git a/src/test/java/com/android/tools/metalava/ApiFileTest.kt b/src/test/java/com/android/tools/metalava/ApiFileTest.kt
index 962f9c6..61bf801 100644
--- a/src/test/java/com/android/tools/metalava/ApiFileTest.kt
+++ b/src/test/java/com/android/tools/metalava/ApiFileTest.kt
@@ -285,7 +285,7 @@
package androidx.core.util {
public final class TestKt {
ctor public TestKt();
- method public static inline <K, V> android.util.LruCache<K,V> lruCache(int maxSize, kotlin.jvm.functions.Function2<? super K,? super V,java.lang.Integer> sizeOf = { _, _ -> 1 }, kotlin.jvm.functions.Function1<? super K,? extends V> create = { (V)null }, kotlin.jvm.functions.Function4<? super java.lang.Boolean,? super K,? super V,? super V,kotlin.Unit> onEntryRemoved = { _, _, _, _ -> });
+ method public static inline <K, V> android.util.LruCache<K,V> lruCache(int maxSize, kotlin.jvm.functions.Function2<? super K,? super V,java.lang.Integer> sizeOf = { _, _ -> 1 }, kotlin.jvm.functions.Function1<? super K,? extends V> create = { (java.lang.Object)null }, kotlin.jvm.functions.Function4<? super java.lang.Boolean,? super K,? super V,? super V,kotlin.Unit> onEntryRemoved = { _, _, _, _ -> });
}
}
""",
@@ -457,10 +457,10 @@
package test.pkg {
public final class TestKt {
ctor public TestKt();
- method public static inline <T> void a(T t);
- method public static inline <reified T> void b(T t);
- method public static inline <reified T> void e(T t);
- method public static inline <reified T> void f(T, T t);
+ method public static inline <T> void a(@Nullable T t);
+ method public static inline <reified T> void b(@Nullable T t);
+ method public static inline <reified T> void e(@Nullable T t);
+ method public static inline <reified T> void f(@Nullable T, @Nullable T t);
}
}
""",
@@ -484,7 +484,7 @@
package test.pkg {
public final class TestKt {
ctor public TestKt();
- method public static suspend inline Object hello(kotlin.coroutines.experimental.Continuation<? super kotlin.Unit> p);
+ method public static suspend inline Object hello(@NonNull kotlin.coroutines.Continuation<? super kotlin.Unit> p);
}
}
""",
@@ -3212,8 +3212,8 @@
import androidx.annotation.VisibleForTesting;
@SuppressWarnings({"ClassNameDiffersFromFileName", "WeakerAccess"})
- public class ProductionCode {
- private ProductionCode() { }
+ public class ProductionCodeJava {
+ private ProductionCodeJava() { }
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
public void shouldBeProtected() {
@@ -3242,7 +3242,7 @@
package test.pkg
import androidx.annotation.VisibleForTesting
- open class ProductionCode2 private constructor() {
+ open class ProductionCodeKotlin private constructor() {
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
fun shouldBeProtected() {
@@ -3270,10 +3270,10 @@
),
api = """
package test.pkg {
- public class ProductionCode {
+ public class ProductionCodeJava {
method protected void shouldBeProtected();
}
- public class ProductionCode2 {
+ public class ProductionCodeKotlin {
method protected final void shouldBeProtected();
}
}
@@ -3331,4 +3331,4 @@
)
)
}
-}
\ No newline at end of file
+}
diff --git a/src/test/java/com/android/tools/metalava/ApiLintTest.kt b/src/test/java/com/android/tools/metalava/ApiLintTest.kt
index 87d9a74..bcb59a8 100644
--- a/src/test/java/com/android/tools/metalava/ApiLintTest.kt
+++ b/src/test/java/com/android/tools/metalava/ApiLintTest.kt
@@ -1456,7 +1456,7 @@
import java.io.InputStream;
public class CheckFiles {
- public MyClass(Context context, File file) {
+ public CheckFiles(Context context, File file) {
}
public void ok(int i, File file) { }
public void ok(int i, InputStream stream) { }
@@ -1487,7 +1487,7 @@
import java.io.InputStream;
public class CheckFiles {
- public MyClass(Context context, File file) {
+ public CheckFiles(Context context, File file) {
}
public void ok(int i, File file) { }
public void ok(int i, InputStream stream) { }
diff --git a/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt b/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt
index a457fcd..819bd2b 100644
--- a/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt
+++ b/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt
@@ -285,6 +285,32 @@
}
@Test
+ fun `Kotlin Coroutines`() {
+ check(
+ warnings = "",
+ compatibilityMode = false,
+ inputKotlinStyleNulls = true,
+ outputKotlinStyleNulls = true,
+ checkCompatibilityApi = """
+ package test.pkg {
+ public final class TestKt {
+ ctor public TestKt();
+ method public static suspend inline java.lang.Object hello(kotlin.coroutines.experimental.Continuation<? super kotlin.Unit>);
+ }
+ }
+ """,
+ signatureSource = """
+ package test.pkg {
+ public final class TestKt {
+ ctor public TestKt();
+ method public static suspend inline Object hello(@NonNull kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+ }
+ }
+ """
+ )
+ }
+
+ @Test
fun `Add flag new methods but not overrides from platform`() {
check(
warnings = """
diff --git a/src/test/java/com/android/tools/metalava/DriverTest.kt b/src/test/java/com/android/tools/metalava/DriverTest.kt
index 780c067..5d521dc 100644
--- a/src/test/java/com/android/tools/metalava/DriverTest.kt
+++ b/src/test/java/com/android/tools/metalava/DriverTest.kt
@@ -131,6 +131,9 @@
}
}
+ // This is here to make sure we don't have any unexpected random println's
+ // in the source that are left behind after debugging and ends up polluting
+ // the production output
class OutputForbiddenWriter(private val stream: String) : ByteArrayOutputStream() {
override fun write(b: ByteArray?, off: Int, len: Int) {
fail("Unexpected write directly to $stream")
diff --git a/src/test/java/com/android/tools/metalava/KotlinInteropChecksTest.kt b/src/test/java/com/android/tools/metalava/KotlinInteropChecksTest.kt
index f3efebc..037100f 100644
--- a/src/test/java/com/android/tools/metalava/KotlinInteropChecksTest.kt
+++ b/src/test/java/com/android/tools/metalava/KotlinInteropChecksTest.kt
@@ -91,6 +91,7 @@
extraArguments = arrayOf(ARG_CHECK_KOTLIN_INTEROP),
warnings = """
src/test/pkg/Foo.kt:7: warning: Companion object constants like INTEGER_ONE should be marked @JvmField for Java interoperability; see https://android.github.io/kotlin-guides/interop.html#companion-constants [MissingJvmstatic]
+ src/test/pkg/Foo.kt:10: warning: Companion object constants like WRONG2 should be using @JvmField, not @JvmStatic; see https://android.github.io/kotlin-guides/interop.html#companion-constants [MissingJvmstatic]
src/test/pkg/Foo.kt:13: warning: Companion object methods like missing should be marked @JvmStatic for Java interoperability; see https://android.github.io/kotlin-guides/interop.html#companion-functions [MissingJvmstatic]
""",
sourceFiles = *arrayOf(
@@ -105,7 +106,7 @@
const val INTEGER_ONE = 1
var BIG_INTEGER_ONE = BigInteger.ONE
@JvmStatic val WRONG = 2 // not yet flagged
- @JvmStatic @JvmField val WRONG2 = 2 // not yet flagged
+ @JvmStatic @JvmField val WRONG2 = 2
@JvmField val ok3 = 3
fun missing() { }