K2: Refine how JDK members are mapped to built-in classes

Previously, the semantic was more-or-less correct for most of the cases
but some corner one, like `sort` in MutableList didn't work properly.

Namely, `sort` should be marked there in a way to forbid to call it
everywhere beside super-calls.
Also, overriding it should be allowed.

Mostly, the logic was re-written to K2 model from K1-related
JvmBuiltInsCustomizer.

^KT-57694 In progress
^KT-57269 Fixed
This commit is contained in:
Denis.Zharkov
2023-04-13 11:50:54 +02:00
committed by Space Team
parent 161fe1d2ca
commit 2e5b783cc6
59 changed files with 1225 additions and 595 deletions
@@ -12,18 +12,10 @@ import org.jetbrains.kotlin.fir.declarations.FirRegularClass
import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
import org.jetbrains.kotlin.fir.declarations.utils.classId
import org.jetbrains.kotlin.fir.resolve.ScopeSession
import org.jetbrains.kotlin.fir.resolve.createSubstitutionForScope
import org.jetbrains.kotlin.fir.resolve.defaultType
import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider
import org.jetbrains.kotlin.fir.resolve.scopeSessionKey
import org.jetbrains.kotlin.fir.resolve.substitution.substitutorByMap
import org.jetbrains.kotlin.fir.scopes.FirContainingNamesAwareScope
import org.jetbrains.kotlin.fir.scopes.FirTypeScope
import org.jetbrains.kotlin.fir.scopes.impl.FirClassSubstitutionScope
import org.jetbrains.kotlin.fir.scopes.jvm.JvmMappedScope
import org.jetbrains.kotlin.fir.scopes.platformClassMapper
import org.jetbrains.kotlin.fir.scopes.unsubstitutedScope
import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol
fun wrapScopeWithJvmMapped(
klass: FirClass,
@@ -32,6 +24,7 @@ fun wrapScopeWithJvmMapped(
scopeSession: ScopeSession,
memberRequiredPhase: FirResolvePhase?,
): FirContainingNamesAwareScope {
if (klass !is FirRegularClass) return declaredMemberScope
val classId = klass.classId
val kotlinUnsafeFqName = classId.asSingleFqName().toUnsafe()
val javaClassId = JavaToKotlinClassMap.mapKotlinToJava(kotlinUnsafeFqName)
@@ -41,59 +34,21 @@ fun wrapScopeWithJvmMapped(
?: return declaredMemberScope
val preparedSignatures = JvmMappedScope.prepareSignatures(javaClass, JavaToKotlinClassMap.isMutable(kotlinUnsafeFqName))
return if (preparedSignatures.isNotEmpty()) {
javaClass.unsubstitutedScope(
val javaClassUseSiteScope = javaClass.unsubstitutedScope(
useSiteSession,
scopeSession,
withForcedTypeCalculator = false,
memberRequiredPhase = memberRequiredPhase,
).let { javaClassUseSiteScope ->
val jvmMappedScope = JvmMappedScope(
useSiteSession,
klass,
javaClass,
declaredMemberScope,
javaClassUseSiteScope,
preparedSignatures
)
if (klass !is FirRegularClass) {
jvmMappedScope
} else {
// We should substitute Java type parameters with base Kotlin type parameters to match overrides properly
// It's necessary for MutableMap, which has *two* JavaMappedScope inside (one for itself and another for base Map)
wrapSubstitutionScopeIfNeed(
useSiteSession, jvmMappedScope, klass, scopeSession,
derivedClass = klass,
)
}
}
)
JvmMappedScope(
useSiteSession,
klass,
javaClass,
declaredMemberScope,
javaClassUseSiteScope,
preparedSignatures,
)
} else {
declaredMemberScope
}
}
private fun wrapSubstitutionScopeIfNeed(
session: FirSession,
useSiteMemberScope: FirTypeScope,
declaration: FirClass,
builder: ScopeSession,
derivedClass: FirRegularClass
): FirTypeScope {
if (declaration.typeParameters.isEmpty()) return useSiteMemberScope
return builder.getOrBuild(declaration.symbol, PLATFORM_TYPE_PARAMETERS_SUBSTITUTION_SCOPE_KEY) {
val platformClass = session.platformClassMapper.getCorrespondingPlatformClass(declaration) ?: return@getOrBuild useSiteMemberScope
// This kind of substitution is necessary when method which is mapped from Java (e.g. Java Map.forEach)
// is called on an external type, like MyMap<String, String>,
// to determine parameter types properly (e.g. String, String instead of K, V)
val platformTypeParameters = platformClass.typeParameters
val platformSubstitution = createSubstitutionForScope(platformTypeParameters, declaration.defaultType(), session)
val substitutor = substitutorByMap(platformSubstitution, session)
FirClassSubstitutionScope(
session, useSiteMemberScope, PLATFORM_TYPE_PARAMETERS_SUBSTITUTION_SCOPE_KEY, substitutor,
dispatchReceiverTypeForSubstitutedMembers = derivedClass.defaultType(),
skipPrivateMembers = true,
derivedClassLookupTag = derivedClass.symbol.toLookupTag()
)
}
}
private val PLATFORM_TYPE_PARAMETERS_SUBSTITUTION_SCOPE_KEY = scopeSessionKey<FirClassSymbol<*>, FirTypeScope>()
@@ -5,36 +5,50 @@
package org.jetbrains.kotlin.fir.scopes.jvm
import org.jetbrains.kotlin.builtins.jvm.JavaToKotlinClassMap
import org.jetbrains.kotlin.builtins.jvm.JvmBuiltInsSignatures
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.declarations.FirClass
import org.jetbrains.kotlin.fir.declarations.FirConstructor
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
import org.jetbrains.kotlin.fir.declarations.constructors
import org.jetbrains.kotlin.fir.containingClassLookupTag
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.utils.classId
import org.jetbrains.kotlin.fir.declarations.utils.isFinal
import org.jetbrains.kotlin.fir.dispatchReceiverClassLookupTagOrNull
import org.jetbrains.kotlin.fir.isSubstitutionOrIntersectionOverride
import org.jetbrains.kotlin.fir.resolve.defaultType
import org.jetbrains.kotlin.fir.resolve.lookupSuperTypes
import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutorByMap
import org.jetbrains.kotlin.fir.scopes.FirContainingNamesAwareScope
import org.jetbrains.kotlin.fir.scopes.FirTypeScope
import org.jetbrains.kotlin.fir.scopes.ProcessorAction
import org.jetbrains.kotlin.fir.scopes.*
import org.jetbrains.kotlin.fir.scopes.impl.FirFakeOverrideGenerator
import org.jetbrains.kotlin.fir.scopes.impl.FirStandardOverrideChecker
import org.jetbrains.kotlin.fir.scopes.impl.buildSubstitutorForOverridesCheck
import org.jetbrains.kotlin.fir.scopes.impl.declaredMemberScope
import org.jetbrains.kotlin.fir.symbols.ConeTypeParameterLookupTag
import org.jetbrains.kotlin.fir.symbols.impl.*
import org.jetbrains.kotlin.fir.types.coneType
import org.jetbrains.kotlin.fir.types.impl.ConeTypeParameterTypeImpl
import org.jetbrains.kotlin.load.java.BuiltinSpecialProperties
import org.jetbrains.kotlin.load.java.SpecialGenericSignatures
import org.jetbrains.kotlin.load.java.getPropertyNamesCandidatesByAccessorName
import org.jetbrains.kotlin.load.kotlin.SignatureBuildingComponents
import org.jetbrains.kotlin.name.CallableId
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.Name
/**
* @param firKotlinClass Kotlin version of built-in class mapped to some JDK class (e.g. kotlin.collections.List)
* @param firJavaClass JDK version of some built-in class (e.g. java.util.List)
* @param declaredMemberScope basic/common declared scope (without any additional members) of a Kotlin version
* @param javaMappedClassUseSiteScope use-site scope of JDK class
*/
class JvmMappedScope(
private val session: FirSession,
private val firKotlinClass: FirClass,
firJavaClass: FirRegularClass,
private val firKotlinClass: FirRegularClass,
private val firJavaClass: FirRegularClass,
private val declaredMemberScope: FirContainingNamesAwareScope,
private val javaMappedClassUseSiteScope: FirTypeScope,
private val signatures: Signatures
private val signatures: Signatures,
) : FirTypeScope() {
private val functionsCache = mutableMapOf<FirNamedFunctionSymbol, FirNamedFunctionSymbol>()
@@ -42,43 +56,145 @@ class JvmMappedScope(
private val overrideChecker = FirStandardOverrideChecker(session)
private val substitutor = ConeSubstitutorByMap(
firJavaClass.typeParameters.zip(firKotlinClass.typeParameters).associate { (javaParameter, kotlinParameter) ->
javaParameter.symbol to ConeTypeParameterTypeImpl(ConeTypeParameterLookupTag(kotlinParameter.symbol), isNullable = false)
},
session
)
private val substitutor = createMappingSubstitutor(firJavaClass, firKotlinClass, session)
private val kotlinDispatchReceiverType = firKotlinClass.defaultType()
override fun processFunctionsByName(name: Name, processor: (FirNamedFunctionSymbol) -> Unit) {
val visibleMethods = signatures.visibleMethodSignaturesByName[name]
?: return declaredMemberScope.processFunctionsByName(name, processor)
private val declaredScopeOfMutableVersion = JavaToKotlinClassMap.readOnlyToMutable(firKotlinClass.classId)?.let {
session.symbolProvider.getClassLikeSymbolByClassId(it) as? FirClassSymbol
}?.let {
session.declaredMemberScope(it, memberRequiredPhase = null)
}
private val isMutableContainer = JavaToKotlinClassMap.isMutable(firKotlinClass.classId)
private val allJavaMappedSuperClassIds: List<ClassId> by lazy {
buildList {
add(firJavaClass.classId)
lookupSuperTypes(firJavaClass.symbol, lookupInterfaces = true, deep = true, session).mapTo(this) { superType ->
val originalClassId = superType.lookupTag.classId
JavaToKotlinClassMap.mapKotlinToJava(originalClassId.asSingleFqName().toUnsafe()) ?: originalClassId
}
}
}
override fun processFunctionsByName(name: Name, processor: (FirNamedFunctionSymbol) -> Unit) {
val declared = mutableListOf<FirNamedFunctionSymbol>()
declaredMemberScope.processFunctionsByName(name) { symbol ->
declared += symbol
processor(symbol)
}
val declaredSignatures by lazy {
declared.mapTo(mutableSetOf()) { it.fir.computeJvmDescriptor() }
}
javaMappedClassUseSiteScope.processFunctionsByName(name) { symbol ->
val newSymbol = getOrCreateSubstitutedCopy(symbol)
val jvmSignature = newSymbol.fir.computeJvmDescriptor()
if (jvmSignature in visibleMethods && jvmSignature !in declaredSignatures) {
processor(newSymbol)
val declaredSignatures: Set<String> by lazy {
buildSet {
declared.mapTo(this) { it.fir.computeJvmDescriptor() }
declaredScopeOfMutableVersion?.processFunctionsByName(name) {
add(it.fir.computeJvmDescriptor())
}
}
}
javaMappedClassUseSiteScope.processFunctionsByName(name) processor@{ symbol ->
if (!symbol.isDeclaredInMappedJavaClass() || !(symbol.fir.status as FirResolvedDeclarationStatus).visibility.isPublicAPI) {
return@processor
}
val jvmDescriptor = symbol.fir.computeJvmDescriptor()
// We don't need adding what is already declared
if (jvmDescriptor in declaredSignatures) return@processor
// That condition means that the member is already declared in the built-in class, but has a non-trivially mapped JVM descriptor
if (isRenamedJdkMethod(jvmDescriptor) || symbol.isOverrideOfKotlinBuiltinPropertyGetter()) return@processor
// If it's java.lang.List.contains(Object) it being loaded as contains(E) and treated as an override
// of kotlin.collections.Collection.contains(E), thus we're not loading it as an additional JDK member
if (isOverrideOfKotlinDeclaredFunction(symbol)) return@processor
if (isMutabilityViolation(symbol, jvmDescriptor)) return@processor
val jdkMemberStatus = getJdkMethodStatus(jvmDescriptor)
if (jdkMemberStatus == JDKMemberStatus.DROP) return@processor
// hidden methods in final class can't be overridden or called with 'super'
if (jdkMemberStatus == JDKMemberStatus.HIDDEN && firKotlinClass.isFinal) return@processor
val newSymbol = getOrCreateSubstitutedCopy(symbol, jdkMemberStatus)
processor(newSymbol)
}
}
private fun isOverrideOfKotlinDeclaredFunction(symbol: FirNamedFunctionSymbol) =
javaMappedClassUseSiteScope.anyOverriddenOf(symbol, ::isDeclaredInBuiltinClass)
private fun isMutabilityViolation(symbol: FirNamedFunctionSymbol, jvmDescriptor: String): Boolean {
val signature = SignatureBuildingComponents.signature(firJavaClass.classId, jvmDescriptor)
val isAmongMutableSignatures = signature in JvmBuiltInsSignatures.MUTABLE_METHOD_SIGNATURES
// If the method belongs to MUTABLE_METHOD_SIGNATURES, but the class is a read-only collection we shouldn't add it.
// For example, we don't want j.u.Collection.removeIf would got to read-only kotlin.collections.Collection
// But if the method is not among MUTABLE_METHOD_SIGNATURES, but the class is a mutable version, we skip it too,
// because it has already been added to the read-only version from which we inherit it.
// For example, we don't need regular j.u.Collection.stream was duplicated in MutableCollection
// as it's already present in the read-only version.
if (isAmongMutableSignatures != isMutableContainer) return true
return javaMappedClassUseSiteScope.anyOverriddenOf(symbol) {
!it.isSubstitutionOrIntersectionOverride && it.containingClassLookupTag()?.classId?.let(JavaToKotlinClassMap::isMutable) == true
}
}
private fun FirNamedFunctionSymbol.isOverrideOfKotlinBuiltinPropertyGetter(): Boolean {
val fqName = firJavaClass.classId.asSingleFqName().child(name)
if (valueParameterSymbols.isEmpty()) {
if (fqName in BuiltinSpecialProperties.GETTER_FQ_NAMES) return true
if (getPropertyNamesCandidatesByAccessorName(name).any(::isTherePropertyWithNameInKotlinClass)) return true
}
return false
}
// j/l/Number.intValue(), j/u/Collection.remove(I), etc.
private fun isRenamedJdkMethod(jvmDescriptor: String): Boolean {
val signature = SignatureBuildingComponents.signature(firJavaClass.classId, jvmDescriptor)
return signature in SpecialGenericSignatures.JVM_SIGNATURES_FOR_RENAMED_BUILT_INS
}
private fun isTherePropertyWithNameInKotlinClass(name: Name): Boolean {
if (name !in declaredMemberScope.getCallableNames()) return false
return declaredMemberScope.getProperties(name).isNotEmpty()
}
// Mostly, what this function checks is if the member was serialized to built-ins, but not loaded from JDK.
// Currently, we use FirDeclarationOrigin.Library for all deserialized members, including built-in ones.
// Another implementation might be `it.origin != FirDeclarationOrigin.Enhancement`, but that shouldn't really matter.
private fun isDeclaredInBuiltinClass(it: FirNamedFunctionSymbol) =
it.origin == FirDeclarationOrigin.Library
private fun FirNamedFunctionSymbol.isDeclaredInMappedJavaClass(): Boolean {
return !fir.isSubstitutionOrIntersectionOverride && fir.dispatchReceiverClassLookupTagOrNull() == firJavaClass.symbol.toLookupTag()
}
private fun getJdkMethodStatus(jvmDescriptor: String): JDKMemberStatus {
for (classId in allJavaMappedSuperClassIds) {
when (SignatureBuildingComponents.signature(classId, jvmDescriptor)) {
in JvmBuiltInsSignatures.HIDDEN_METHOD_SIGNATURES -> return JDKMemberStatus.HIDDEN
in JvmBuiltInsSignatures.VISIBLE_METHOD_SIGNATURES -> return JDKMemberStatus.VISIBLE
in JvmBuiltInsSignatures.DROP_LIST_METHOD_SIGNATURES -> return JDKMemberStatus.DROP
}
}
// For unknown methods, we use HIDDEN policy by default
return JDKMemberStatus.HIDDEN
}
private enum class JDKMemberStatus {
HIDDEN, VISIBLE, DROP
}
override fun processPropertiesByName(name: Name, processor: (FirVariableSymbol<*>) -> Unit) {
declaredMemberScope.processPropertiesByName(name, processor)
}
private fun getOrCreateSubstitutedCopy(symbol: FirNamedFunctionSymbol): FirNamedFunctionSymbol {
private fun getOrCreateSubstitutedCopy(symbol: FirNamedFunctionSymbol, jdkMemberStatus: JDKMemberStatus): FirNamedFunctionSymbol {
return functionsCache.getOrPut(symbol) {
val oldFunction = symbol.fir
val newSymbol = FirNamedFunctionSymbol(CallableId(firKotlinClass.classId, symbol.callableId.callableName))
@@ -90,8 +206,12 @@ class JvmMappedScope(
symbol.fir.origin,
newDispatchReceiverType = kotlinDispatchReceiverType,
newParameterTypes = oldFunction.valueParameters.map { substitutor.substituteOrSelf(it.returnTypeRef.coneType) },
newReturnType = substitutor.substituteOrSelf(oldFunction.returnTypeRef.coneType)
)
newReturnType = substitutor.substituteOrSelf(oldFunction.returnTypeRef.coneType),
).apply {
if (jdkMemberStatus == JDKMemberStatus.HIDDEN) {
isHiddenEverywhereBesideSuperCalls = true
}
}
newSymbol
}
}
@@ -175,7 +295,8 @@ class JvmMappedScope(
}
override fun getCallableNames(): Set<Name> {
return declaredMemberScope.getCallableNames() + signatures.visibleMethodSignaturesByName.keys
// It's ok to return a super set of actually available member names
return declaredMemberScope.getCallableNames() + javaMappedClassUseSiteScope.getCallableNames()
}
override fun getClassifierNames(): Set<Name> {
@@ -243,6 +364,21 @@ class JvmMappedScope(
return Signatures(visibleMethodsByName, hiddenConstructors)
}
/**
* For fromClass=A<T1, T2>, toClass=B<F1, F1> classes
* @returns {T1 -> F1, T2 -> F2} substitution
*/
private fun createMappingSubstitutor(fromClass: FirRegularClass, toClass: FirRegularClass, session: FirSession): ConeSubstitutor =
ConeSubstitutorByMap(
fromClass.typeParameters.zip(toClass.typeParameters).associate { (fromTypeParameter, toTypeParameter) ->
fromTypeParameter.symbol to ConeTypeParameterTypeImpl(
ConeTypeParameterLookupTag(toTypeParameter.symbol),
isNullable = false
)
},
session
)
}
override fun toString(): String {