Support loading Java records

^KT-43677 In Progress
This commit is contained in:
Denis.Zharkov
2020-11-23 16:02:26 +03:00
parent f25b7672a7
commit 513f7177ca
45 changed files with 688 additions and 61 deletions
@@ -85,11 +85,13 @@ interface JavaClass : JavaClassifier, JavaTypeParameterListOwner, JavaModifierLi
val isInterface: Boolean
val isAnnotationType: Boolean
val isEnum: Boolean
val isRecord: Boolean
val lightClassOriginKind: LightClassOriginKind?
val methods: Collection<JavaMethod>
val fields: Collection<JavaField>
val constructors: Collection<JavaConstructor>
val recordComponents: Collection<JavaRecordComponent>
fun hasDefaultConstructor(): Boolean
}
@@ -133,6 +135,11 @@ interface JavaValueParameter : JavaAnnotationOwner {
val isVararg: Boolean
}
interface JavaRecordComponent : JavaMember {
val type: JavaType
val isVararg: Boolean
}
interface JavaTypeParameter : JavaClassifier {
val upperBounds: Collection<JavaClassifierType>
}
@@ -22,10 +22,7 @@ import org.jetbrains.kotlin.descriptors.ClassDescriptor;
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor;
import org.jetbrains.kotlin.descriptors.PropertyDescriptor;
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor;
import org.jetbrains.kotlin.load.java.structure.JavaClass;
import org.jetbrains.kotlin.load.java.structure.JavaElement;
import org.jetbrains.kotlin.load.java.structure.JavaField;
import org.jetbrains.kotlin.load.java.structure.JavaMethod;
import org.jetbrains.kotlin.load.java.structure.*;
import org.jetbrains.kotlin.name.FqName;
public interface JavaResolverCache {
@@ -37,7 +34,7 @@ public interface JavaResolverCache {
}
@Override
public void recordMethod(@NotNull JavaMethod method, @NotNull SimpleFunctionDescriptor descriptor) {
public void recordMethod(@NotNull JavaMember member, @NotNull SimpleFunctionDescriptor descriptor) {
}
@Override
@@ -56,7 +53,8 @@ public interface JavaResolverCache {
@Nullable
ClassDescriptor getClassResolvedFromSource(@NotNull FqName fqName);
void recordMethod(@NotNull JavaMethod method, @NotNull SimpleFunctionDescriptor descriptor);
// It's a JavaMethod or JavaRecordComponent
void recordMethod(@NotNull JavaMember member, @NotNull SimpleFunctionDescriptor descriptor);
void recordConstructor(@NotNull JavaElement element, @NotNull ConstructorDescriptor descriptor);
@@ -19,4 +19,5 @@ package org.jetbrains.kotlin.load.java.descriptors;
import org.jetbrains.kotlin.descriptors.ClassDescriptor;
public interface JavaClassDescriptor extends ClassDescriptor {
boolean isRecord();
}
@@ -60,6 +60,7 @@ public class JavaMethodDescriptor extends SimpleFunctionDescriptorImpl implement
}
private ParameterNamesStatus parameterNamesStatus = null;
private final boolean isForRecordComponent;
protected JavaMethodDescriptor(
@NotNull DeclarationDescriptor containingDeclaration,
@@ -67,9 +68,11 @@ public class JavaMethodDescriptor extends SimpleFunctionDescriptorImpl implement
@NotNull Annotations annotations,
@NotNull Name name,
@NotNull Kind kind,
@NotNull SourceElement source
@NotNull SourceElement source,
boolean isForRecordComponent
) {
super(containingDeclaration, original, annotations, name, kind, source);
this.isForRecordComponent = isForRecordComponent;
}
@NotNull
@@ -77,9 +80,10 @@ public class JavaMethodDescriptor extends SimpleFunctionDescriptorImpl implement
@NotNull DeclarationDescriptor containingDeclaration,
@NotNull Annotations annotations,
@NotNull Name name,
@NotNull SourceElement source
@NotNull SourceElement source,
boolean isForRecordComponent
) {
return new JavaMethodDescriptor(containingDeclaration, null, annotations, name, Kind.DECLARATION, source);
return new JavaMethodDescriptor(containingDeclaration, null, annotations, name, Kind.DECLARATION, source, isForRecordComponent);
}
@NotNull
@@ -118,6 +122,10 @@ public class JavaMethodDescriptor extends SimpleFunctionDescriptorImpl implement
this.parameterNamesStatus = ParameterNamesStatus.get(hasStableParameterNames, hasSynthesizedParameterNames);
}
public boolean isForRecordComponent() {
return isForRecordComponent;
}
@NotNull
@Override
protected JavaMethodDescriptor createSubstitutedCopy(
@@ -134,7 +142,8 @@ public class JavaMethodDescriptor extends SimpleFunctionDescriptorImpl implement
annotations,
newName != null ? newName : getName(),
kind,
source
source,
isForRecordComponent
);
result.setParameterNamesStatus(hasStableParameterNames(), hasSynthesizedParameterNames());
return result;
@@ -26,12 +26,18 @@ interface DeclaredMemberIndex {
fun findFieldByName(name: Name): JavaField?
fun getFieldNames(): Set<Name>
fun getRecordComponentNames(): Set<Name>
fun findRecordComponentByName(name: Name): JavaRecordComponent?
object Empty : DeclaredMemberIndex {
override fun findMethodsByName(name: Name) = listOf<JavaMethod>()
override fun getMethodNames() = emptySet<Name>()
override fun findFieldByName(name: Name): JavaField? = null
override fun getFieldNames() = emptySet<Name>()
override fun getRecordComponentNames(): Set<Name> = emptySet()
override fun findRecordComponentByName(name: Name): JavaRecordComponent? = null
}
}
@@ -45,11 +51,15 @@ open class ClassDeclaredMemberIndex(
private val methods = jClass.methods.asSequence().filter(methodFilter).groupBy { m -> m.name }
private val fields = jClass.fields.asSequence().filter(memberFilter).associateBy { m -> m.name }
private val components = jClass.recordComponents.filter(memberFilter).associateBy { it.name }
override fun findMethodsByName(name: Name): Collection<JavaMethod> = methods[name] ?: listOf()
override fun getMethodNames(): Set<Name> = jClass.methods.asSequence().filter(methodFilter).mapTo(mutableSetOf(), JavaMethod::name)
override fun findFieldByName(name: Name): JavaField? = fields[name]
override fun getFieldNames(): Set<Name> = jClass.fields.asSequence().filter(memberFilter).mapTo(mutableSetOf(), JavaField::name)
override fun getRecordComponentNames(): Set<Name> = components.keys
override fun findRecordComponentByName(name: Name): JavaRecordComponent? = components[name]
}
@@ -84,6 +84,8 @@ class LazyJavaClassDescriptor(
override fun getKind() = kind
override fun getModality() = modality
override fun isRecord(): Boolean = jClass.isRecord
// To workaround a problem with Scala compatibility (KT-9700),
// we consider private visibility of a Java top level class as package private
// Shortly: Scala plugin introduces special kind of "private in package" classes
@@ -39,10 +39,7 @@ import org.jetbrains.kotlin.load.java.lazy.LazyJavaResolverContext
import org.jetbrains.kotlin.load.java.lazy.childForMethod
import org.jetbrains.kotlin.load.java.lazy.resolveAnnotations
import org.jetbrains.kotlin.load.java.lazy.types.toAttributes
import org.jetbrains.kotlin.load.java.structure.JavaArrayType
import org.jetbrains.kotlin.load.java.structure.JavaClass
import org.jetbrains.kotlin.load.java.structure.JavaConstructor
import org.jetbrains.kotlin.load.java.structure.JavaMethod
import org.jetbrains.kotlin.load.java.structure.*
import org.jetbrains.kotlin.load.kotlin.computeJvmDescriptor
import org.jetbrains.kotlin.name.FqNameUnsafe
import org.jetbrains.kotlin.name.Name
@@ -79,23 +76,79 @@ class LazyJavaClassMemberScope(
it.memberScope.getFunctionNames()
}.apply {
addAll(declaredMemberIndex().getMethodNames())
addAll(declaredMemberIndex().getRecordComponentNames())
addAll(computeClassNames(kindFilter, nameFilter))
}
internal val constructors = c.storageManager.createLazyValue {
val constructors = jClass.constructors
val result = ArrayList<JavaClassConstructorDescriptor>(constructors.size)
val result = ArrayList<ClassConstructorDescriptor>(constructors.size)
for (constructor in constructors) {
val descriptor = resolveConstructor(constructor)
result.add(descriptor)
}
if (jClass.isRecord) {
val defaultConstructor = createDefaultRecordConstructor()
val jvmDescriptor = defaultConstructor.computeJvmDescriptor(withReturnType = false)
if (result.none { it.computeJvmDescriptor(withReturnType = false) == jvmDescriptor }) {
result.add(defaultConstructor)
c.components.javaResolverCache.recordConstructor(jClass, defaultConstructor)
}
}
c.components.signatureEnhancement.enhanceSignatures(
c,
result.ifEmpty { listOfNotNull(createDefaultConstructor()) }
).toList()
}
private fun createDefaultRecordConstructor(): ClassConstructorDescriptor {
val classDescriptor = ownerDescriptor
val constructorDescriptor = JavaClassConstructorDescriptor.createJavaConstructor(
classDescriptor, Annotations.EMPTY, /* isPrimary = */ true, c.components.sourceElementFactory.source(jClass)
)
val valueParameters = createRecordConstructorParameters(constructorDescriptor)
constructorDescriptor.setHasSynthesizedParameterNames(false)
constructorDescriptor.initialize(valueParameters, getConstructorVisibility(classDescriptor))
constructorDescriptor.setHasStableParameterNames(false)
constructorDescriptor.returnType = classDescriptor.defaultType
return constructorDescriptor
}
private fun createRecordConstructorParameters(constructor: ClassConstructorDescriptorImpl): List<ValueParameterDescriptor> {
val components = jClass.recordComponents
val result = ArrayList<ValueParameterDescriptor>(components.size)
val attr = TypeUsage.COMMON.toAttributes(isForAnnotationParameter = false)
for ((index, component) in components.withIndex()) {
val parameterType = c.typeResolver.transformJavaType(component.type, attr)
val varargElementType =
if (component.isVararg) c.components.module.builtIns.getArrayElementType(parameterType) else null
result.add(
ValueParameterDescriptorImpl(
constructor,
null,
index,
Annotations.EMPTY,
component.name,
parameterType,
/* deeclaresDefaultValue = */false,
/* isCrossinline = */ false,
/* isNoinline = */ false,
varargElementType,
c.components.sourceElementFactory.source(component)
)
)
}
return result
}
override fun JavaMethodDescriptor.isVisibleAsFunction(): Boolean {
if (jClass.isAnnotationType) return false
return isVisibleAsFunctionInCurrentClass(this)
@@ -439,6 +492,42 @@ class LazyJavaClassMemberScope(
}
}
override fun computeImplicitlyDeclaredFunctions(result: MutableCollection<SimpleFunctionDescriptor>, name: Name) {
if (jClass.isRecord && declaredMemberIndex().findRecordComponentByName(name) != null && result.none { it.valueParameters.isEmpty() }) {
result.add(resolveRecordComponentToFunctionDescriptor(declaredMemberIndex().findRecordComponentByName(name)!!))
}
}
private fun resolveRecordComponentToFunctionDescriptor(recordComponent: JavaRecordComponent): JavaMethodDescriptor {
val annotations = c.resolveAnnotations(recordComponent)
val functionDescriptorImpl = JavaMethodDescriptor.createJavaMethod(
ownerDescriptor, annotations, recordComponent.name, c.components.sourceElementFactory.source(recordComponent), true
)
val returnTypeAttrs = TypeUsage.COMMON.toAttributes(isForAnnotationParameter = false)
val returnType = c.typeResolver.transformJavaType(recordComponent.type, returnTypeAttrs)
functionDescriptorImpl.initialize(
null,
getDispatchReceiverParameter(),
emptyList(),
emptyList(),
returnType,
// Those functions are generated as open in bytecode
// Actually, it should not be important because the class is final anyway, but leaving them open is convenient for consistency
Modality.convertFromFlags(abstract = false, open = true),
DescriptorVisibilities.PUBLIC,
null,
)
functionDescriptorImpl.setParameterNamesStatus(false, false)
c.components.javaResolverCache.recordMethod(recordComponent, functionDescriptorImpl)
return functionDescriptorImpl
}
override fun computeNonDeclaredProperties(name: Name, result: MutableCollection<PropertyDescriptor>) {
if (jClass.isAnnotationType) {
computeAnnotationProperties(name, result)
@@ -82,6 +82,10 @@ abstract class LazyJavaScope(
// Fake overrides, values()/valueOf(), etc.
protected abstract fun computeNonDeclaredFunctions(result: MutableCollection<SimpleFunctionDescriptor>, name: Name)
// It has a similar semantics to computeNonDeclaredFunctions, but it's being called just once per class
// While computeNonDeclaredFunctions is being called once per scope instance (once per KotlinTypeRefiner)
protected open fun computeImplicitlyDeclaredFunctions(result: MutableCollection<SimpleFunctionDescriptor>, name: Name) {}
protected abstract fun getDispatchReceiverParameter(): ReceiverParameterDescriptor?
private val declaredFunctions: MemoizedFunctionToNotNull<Name, Collection<SimpleFunctionDescriptor>> =
@@ -98,6 +102,8 @@ abstract class LazyJavaScope(
result.add(descriptor)
}
computeImplicitlyDeclaredFunctions(result, name)
result
}
@@ -154,7 +160,8 @@ abstract class LazyJavaScope(
protected fun resolveMethodToFunctionDescriptor(method: JavaMethod): JavaMethodDescriptor {
val annotations = c.resolveAnnotations(method)
val functionDescriptorImpl = JavaMethodDescriptor.createJavaMethod(
ownerDescriptor, annotations, method.name, c.components.sourceElementFactory.source(method)
ownerDescriptor, annotations, method.name, c.components.sourceElementFactory.source(method),
declaredMemberIndex().findRecordComponentByName(method.name) != null && method.valueParameters.isEmpty()
)
val c = c.childForMethod(functionDescriptorImpl, method)
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.descriptors.runtime.structure
import org.jetbrains.kotlin.load.java.structure.JavaClass
import org.jetbrains.kotlin.load.java.structure.JavaClassifierType
import org.jetbrains.kotlin.load.java.structure.JavaRecordComponent
import org.jetbrains.kotlin.load.java.structure.LightClassOriginKind
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
@@ -96,6 +97,9 @@ class ReflectJavaClass(
.map(::ReflectJavaConstructor)
.toList()
override val recordComponents: Collection<JavaRecordComponent>
get() = emptyList()
override fun hasDefaultConstructor() = false // any default constructor is returned by constructors
override val lightClassOriginKind: LightClassOriginKind?
@@ -114,6 +118,10 @@ class ReflectJavaClass(
override val isEnum: Boolean
get() = klass.isEnum
// TODO: Support reflect
override val isRecord: Boolean
get() = false
override fun equals(other: Any?) = other is ReflectJavaClass && klass == other.klass
override fun hashCode() = klass.hashCode()
@@ -125,7 +125,7 @@ abstract class AbstractJvmRuntimeDescriptorLoaderTest : TestCaseWithTmpdir() {
},
""
)
LoadDescriptorUtil.compileJavaWithAnnotationsJar(sources, tmpdir)
LoadDescriptorUtil.compileJavaWithAnnotationsJar(sources, tmpdir, emptyList(), null)
}
fileName.endsWith(".kt") -> {
val environment = KotlinTestUtils.createEnvironmentWithJdkAndNullabilityAnnotationsFromIdea(