KspHasModifiers.kt
/*
* Copyright 2020 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 androidx.room.compiler.processing.ksp
import androidx.room.compiler.processing.XHasModifiers
import com.google.devtools.ksp.getVisibility
import com.google.devtools.ksp.isAbstract
import com.google.devtools.ksp.isOpen
import com.google.devtools.ksp.isPrivate
import com.google.devtools.ksp.isProtected
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSDeclaration
import com.google.devtools.ksp.symbol.KSFunctionDeclaration
import com.google.devtools.ksp.symbol.KSPropertyAccessor
import com.google.devtools.ksp.symbol.KSPropertyDeclaration
import com.google.devtools.ksp.symbol.KSValueParameter
import com.google.devtools.ksp.symbol.Modifier
import com.google.devtools.ksp.symbol.Origin
import com.google.devtools.ksp.symbol.Visibility
/**
* Implementation of [XHasModifiers] for ksp declarations.
*/
sealed class KspHasModifiers(
protected val declaration: KSDeclaration
) : XHasModifiers {
override fun isPublic(): Boolean {
// internals are public from java but KSP's declaration.isPublic excludes them.
return declaration.getVisibility() == Visibility.INTERNAL ||
declaration.getVisibility() == Visibility.PUBLIC
}
override fun isInternal(): Boolean {
return declaration.getVisibility() == Visibility.INTERNAL
}
override fun isProtected(): Boolean {
return declaration.isProtected()
}
override fun isAbstract(): Boolean {
return declaration.modifiers.contains(Modifier.ABSTRACT) ||
when (declaration) {
is KSPropertyDeclaration -> declaration.isAbstract()
is KSClassDeclaration -> declaration.isAbstract()
is KSFunctionDeclaration -> declaration.isAbstract
else -> false
}
}
override fun isKtPrivate(): Boolean {
return isPrivate()
}
override fun isPrivate(): Boolean {
return declaration.isPrivate()
}
override fun isStatic(): Boolean {
return declaration.isStatic()
}
override fun isTransient(): Boolean {
return declaration.isTransient()
}
override fun isFinal(): Boolean {
return when (declaration) {
// To align with Javac, a final property means its backing field is immutable, instead
// of the property being non-overridable. A property can have a final backing field
// while being open for overrides (e.g. `open val x = ...`), and it can also have a
// non-final backing field while being closed for overrides (e.g. `var x = ...`).
is KSPropertyDeclaration -> {
if (declaration.origin == Origin.JAVA) {
// Fallback to modifiers check as
// KSPropertyDeclarationJavaImpl.isMutable is always true:
// https://github.com/google/ksp/issues/1153
declaration.modifiers.contains(Modifier.FINAL)
} else {
// Here we consider all vals are final, and all vars are not final, even
// lateinit vars.
!declaration.isMutable
}
}
// Although parameters in Kotlin are vals in source, they're not marked
// final in bytecode.
is KSValueParameter -> {
false
}
// For classes and functions, final means not open (overridable).
else -> {
!declaration.isOpen()
}
}
}
private class Declaration(declaration: KSDeclaration) : KspHasModifiers(declaration)
private class ClassDeclaration(declaration: KSDeclaration) : KspHasModifiers(declaration) {
override fun isStatic(): Boolean {
if (declaration.isStatic()) {
return true
}
// inner classes in kotlin are static by default unless they have inner modifier.
// for .class files, there is currently a bug:
// https://github.com/google/ksp/pull/232 and once it is fixed, inner modifier will
// be reported for .class files as well.
if (declaration.origin != Origin.JAVA &&
declaration.parentDeclaration is KSClassDeclaration // nested class
) {
return !declaration.modifiers.contains(Modifier.INNER)
}
return false
}
}
private class PropertyField(
val propertyDeclaration: KSPropertyDeclaration
) : KspHasModifiers(propertyDeclaration) {
private val acceptDeclarationModifiers by lazy {
// Deciding whether we should read modifiers from a KSPropertyDeclaration is not very
// straightforward. (jvmField == true -> read modifiers from declaration)
// When origin is java, always read.
// When origin is kotlin, read if it has @JvmField annotation
// When origin is .class, it depends whether the property was originally a kotlin code
// or java code.
// Unfortunately, we don't have a way of checking it as KotlinMetadata annotation is not
// visible via KSP. We approximate it by checking if it is delegated or not.
when (declaration.origin) {
Origin.JAVA, Origin.JAVA_LIB -> true
Origin.KOTLIN, Origin.KOTLIN_LIB -> declaration.hasJvmFieldAnnotation()
else -> false
}
}
override fun isPublic(): Boolean {
return if (acceptDeclarationModifiers) {
super.isPublic()
} else {
// The visibility of the field will be the same as the visibility of
// lateinit property setter:
// https://kotlinlang.org/docs/java-to-kotlin-interop.html#instance-fields
isLateinit &&
(setterModifiers?.contains(Modifier.PUBLIC) == true ||
setterModifiers?.contains(Modifier.INTERNAL) == true)
}
}
override fun isProtected(): Boolean {
return if (acceptDeclarationModifiers) {
super.isProtected()
} else {
isLateinit && setterModifiers?.contains(Modifier.PROTECTED) == true
}
}
override fun isPrivate(): Boolean {
return if (acceptDeclarationModifiers) {
super.isPrivate()
} else {
if (isLateinit) {
setterModifiers?.contains(Modifier.PRIVATE) == true
} else {
true
}
}
}
private val isLateinit: Boolean by lazy {
propertyDeclaration.modifiers.contains(Modifier.LATEINIT)
}
private val setterModifiers: Set<Modifier>? by lazy {
propertyDeclaration.setter?.modifiers
}
}
/**
* Handles accessor visibility when there is an accessor declared in code.
* We cannot simply merge modifiers of the property and the accessor as the visibility rules
* of the declaration is more complicated than just looking at modifiers.
*/
private class PropertyFieldAccessor(
private val accessor: KSPropertyAccessor
) : KspHasModifiers(accessor.receiver) {
override fun isPublic(): Boolean {
return accessor.modifiers.contains(Modifier.PUBLIC) ||
(!isPrivate() && !isProtected() && super.isPublic())
}
override fun isProtected(): Boolean {
return accessor.modifiers.contains(Modifier.PROTECTED) ||
(!isPrivate() && super.isProtected())
}
override fun isPrivate(): Boolean {
return accessor.modifiers.contains(Modifier.PRIVATE) ||
super.isPrivate()
}
override fun isFinal(): Boolean {
// super.isFinal() is for the property, not the accessor
return accessor.modifiers.contains(Modifier.FINAL)
}
}
companion object {
fun createForSyntheticAccessor(
property: KSPropertyDeclaration,
accessor: KSPropertyAccessor?
): XHasModifiers {
if (accessor != null) {
return PropertyFieldAccessor(accessor)
}
return Declaration(property)
}
fun create(owner: KSPropertyDeclaration): XHasModifiers {
return PropertyField(owner)
}
fun create(owner: KSFunctionDeclaration): XHasModifiers {
return Declaration(owner)
}
fun create(owner: KSClassDeclaration): XHasModifiers {
return ClassDeclaration(owner)
}
}
}