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:
committed by
Space Team
parent
161fe1d2ca
commit
2e5b783cc6
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user