Cone conflict resolver: extract JVM equivalent call filtering to a separate class

This commit is contained in:
Mikhail Glukhikh
2019-11-12 10:30:36 +03:00
parent 96c5003a13
commit ac13a8c8b2
6 changed files with 276 additions and 195 deletions
@@ -7,7 +7,6 @@ package org.jetbrains.kotlin.fir
import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.fir.declarations.FirMemberDeclaration
import org.jetbrains.kotlin.fir.diagnostics.FirSimpleDiagnostic
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.expressions.impl.FirExpressionStub
import org.jetbrains.kotlin.fir.expressions.impl.FirResolvedQualifierImpl
@@ -19,6 +18,7 @@ import org.jetbrains.kotlin.fir.references.impl.FirResolvedNamedReferenceImpl
import org.jetbrains.kotlin.fir.references.impl.FirSimpleNamedReference
import org.jetbrains.kotlin.fir.resolve.*
import org.jetbrains.kotlin.fir.resolve.calls.*
import org.jetbrains.kotlin.fir.resolve.calls.jvm.ConeEquivalentCallConflictResolver
import org.jetbrains.kotlin.fir.resolve.diagnostics.FirAmbiguityError
import org.jetbrains.kotlin.fir.resolve.diagnostics.FirInapplicableCandidateError
import org.jetbrains.kotlin.fir.resolve.diagnostics.FirUnresolvedNameError
@@ -61,7 +61,11 @@ class FirCallResolver(
topLevelScopes = topLevelScopes.asReversed(),
localScopes = localScopes.asReversed()
)
private val conflictResolver = ConeOverloadConflictResolver(TypeSpecificityComparator.NONE, inferenceComponents)
private val conflictResolver = ConeCompositeConflictResolver(
ConeOverloadConflictResolver(TypeSpecificityComparator.NONE, inferenceComponents),
ConeEquivalentCallConflictResolver(TypeSpecificityComparator.NONE, inferenceComponents)
)
fun resolveCallAndSelectCandidate(functionCall: FirFunctionCall, expectedTypeRef: FirTypeRef?, file: FirFile): FirFunctionCall {
qualifiedResolver.reset()
@@ -0,0 +1,139 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.fir.resolve.calls
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.fir.types.arrayElementType
import org.jetbrains.kotlin.fir.types.coneTypeUnsafe
import org.jetbrains.kotlin.resolve.OverloadabilitySpecificityCallbacks
import org.jetbrains.kotlin.resolve.calls.results.FlatSignature
import org.jetbrains.kotlin.resolve.calls.results.SimpleConstraintSystem
import org.jetbrains.kotlin.resolve.calls.results.TypeSpecificityComparator
import org.jetbrains.kotlin.resolve.calls.results.isSignatureNotLessSpecific
abstract class AbstractConeCallConflictResolver(
private val specificityComparator: TypeSpecificityComparator,
protected val inferenceComponents: InferenceComponents
) : ConeCallConflictResolver {
protected fun Collection<Candidate>.setIfOneOrEmpty(): Set<Candidate>? = when (size) {
0 -> emptySet()
1 -> setOf(single())
else -> null
}
/**
* Returns `true` if [call1] is definitely more or equally specific [call2],
* `false` otherwise.
*/
protected fun compareCallsByUsedArguments(
call1: FlatSignature<Candidate>,
call2: FlatSignature<Candidate>,
discriminateGenerics: Boolean
): Boolean {
if (discriminateGenerics) {
val isGeneric1 = call1.isGeneric
val isGeneric2 = call2.isGeneric
// generic loses to non-generic
if (isGeneric1 && !isGeneric2) return false
if (!isGeneric1 && isGeneric2) return true
// two generics are non-comparable
if (isGeneric1 && isGeneric2) return false
}
if (!call1.isExpect && call2.isExpect) return true
if (call1.isExpect && !call2.isExpect) return false
return createEmptyConstraintSystem().isSignatureNotLessSpecific(
call1,
call2,
OverloadabilitySpecificityCallbacks,
specificityComparator
)
}
protected fun createFlatSignature(call: Candidate): FlatSignature<Candidate> {
return when (val declaration = call.symbol.fir) {
is FirSimpleFunction -> createFlatSignature(call, declaration)
is FirConstructor -> createFlatSignature(call, declaration)
is FirVariable<*> -> createFlatSignature(call, declaration)
is FirClass<*> -> createFlatSignature(call, declaration)
else -> error("Not supported: $declaration")
}
}
protected fun createFlatSignature(call: Candidate, variable: FirVariable<*>): FlatSignature<Candidate> {
return FlatSignature(
call,
(variable as? FirProperty)?.typeParameters?.map { it.symbol }.orEmpty(),
listOfNotNull<ConeKotlinType>(variable.receiverTypeRef?.coneTypeUnsafe()),
variable.receiverTypeRef != null,
false,
0,
(variable as? FirProperty)?.isExpect == true,
false // TODO
)
}
protected fun createFlatSignature(call: Candidate, constructor: FirConstructor): FlatSignature<Candidate> {
return FlatSignature(
call,
constructor.typeParameters.map { it.symbol },
computeParameterTypes(call, constructor),
//constructor.receiverTypeRef != null,
false,
constructor.valueParameters.any { it.isVararg },
constructor.valueParameters.count { it.defaultValue != null },
constructor.isExpect,
false // TODO
)
}
protected fun createFlatSignature(call: Candidate, function: FirSimpleFunction): FlatSignature<Candidate> {
return FlatSignature(
call,
function.typeParameters.map { it.symbol },
computeParameterTypes(call, function),
function.receiverTypeRef != null,
function.valueParameters.any { it.isVararg },
function.valueParameters.count { it.defaultValue != null },
function.isExpect,
false // TODO
)
}
private fun FirValueParameter.argumentType(): ConeKotlinType {
val type = returnTypeRef.coneTypeUnsafe<ConeKotlinType>()
if (isVararg) return type.arrayElementType(inferenceComponents.session)!!
return type
}
private fun computeParameterTypes(
call: Candidate,
function: FirFunction<*>
): List<ConeKotlinType> {
return (call.resultingTypeForCallableReference?.typeArguments?.map { it as ConeKotlinType }
?: (listOfNotNull(function.receiverTypeRef?.coneTypeUnsafe()) +
call.argumentMapping?.map { it.value.argumentType() }.orEmpty()))
}
private fun createFlatSignature(call: Candidate, klass: FirClass<*>): FlatSignature<Candidate> {
return FlatSignature(
call,
(klass as? FirRegularClass)?.typeParameters?.map { it.symbol }.orEmpty(),
valueParameterTypes = emptyList(),
hasExtensionReceiver = false,
hasVarargs = false,
numDefaults = 0,
isExpect = (klass as? FirRegularClass)?.isExpect == true,
isSyntheticMember = false
)
}
private fun createEmptyConstraintSystem(): SimpleConstraintSystem {
return ConeSimpleConstraintSystemImpl(inferenceComponents.createConstraintSystem())
}
}
@@ -0,0 +1,18 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.fir.resolve.calls
interface ConeCallConflictResolver {
fun chooseMaximallySpecificCandidates(
candidates: Collection<Candidate>,
discriminateGenerics: Boolean
): Set<Candidate> = chooseMaximallySpecificCandidates(candidates.toSet(), discriminateGenerics)
fun chooseMaximallySpecificCandidates(
candidates: Set<Candidate>,
discriminateGenerics: Boolean
): Set<Candidate>
}
@@ -0,0 +1,21 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.fir.resolve.calls
class ConeCompositeConflictResolver(
private vararg val conflictResolvers: AbstractConeCallConflictResolver
) : ConeCallConflictResolver {
override fun chooseMaximallySpecificCandidates(candidates: Set<Candidate>, discriminateGenerics: Boolean): Set<Candidate> {
if (candidates.size <= 1) return candidates
var currentCandidates = candidates
var index = 0
while (currentCandidates.size > 1 && index < conflictResolvers.size) {
val conflictResolver = conflictResolvers[index++]
currentCandidates = conflictResolver.chooseMaximallySpecificCandidates(candidates, discriminateGenerics)
}
return currentCandidates
}
}
@@ -5,19 +5,15 @@
package org.jetbrains.kotlin.fir.resolve.calls
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.render
import org.jetbrains.kotlin.fir.resolve.substitution.substitutorByMap
import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.fir.types.arrayElementType
import org.jetbrains.kotlin.fir.types.coneTypeUnsafe
import org.jetbrains.kotlin.resolve.OverloadabilitySpecificityCallbacks
import org.jetbrains.kotlin.resolve.calls.inference.model.NewConstraintSystemImpl
import org.jetbrains.kotlin.resolve.calls.inference.model.SimpleConstraintSystemConstraintPosition
import org.jetbrains.kotlin.resolve.calls.results.FlatSignature
import org.jetbrains.kotlin.resolve.calls.results.SimpleConstraintSystem
import org.jetbrains.kotlin.resolve.calls.results.TypeSpecificityComparator
import org.jetbrains.kotlin.resolve.calls.results.isSignatureNotLessSpecific
import org.jetbrains.kotlin.types.model.KotlinTypeMarker
import org.jetbrains.kotlin.types.model.TypeParameterMarker
import org.jetbrains.kotlin.types.model.TypeSubstitutorMarker
@@ -25,154 +21,21 @@ import org.jetbrains.kotlin.types.model.TypeSystemInferenceExtensionContext
import org.jetbrains.kotlin.utils.addToStdlib.cast
class ConeOverloadConflictResolver(
val specificityComparator: TypeSpecificityComparator,
val inferenceComponents: InferenceComponents
specificityComparator: TypeSpecificityComparator,
inferenceComponents: InferenceComponents
) : AbstractConeCallConflictResolver(specificityComparator, inferenceComponents) {
) {
fun chooseMaximallySpecificCandidates(
candidates: Collection<Candidate>,
//checkArgumentsMode: CheckArgumentTypesMode,
override fun chooseMaximallySpecificCandidates(
candidates: Set<Candidate>,
discriminateGenerics: Boolean
): Set<Candidate> {
candidates.setIfOneOrEmpty()?.let { return it }
val candidatesSet = filterOutEquivalentCalls(candidates)
findMaximallySpecificCall(candidatesSet, false)?.let { return setOf(it) }
findMaximallySpecificCall(candidates, false)?.let { return setOf(it) }
if (discriminateGenerics) {
findMaximallySpecificCall(candidatesSet, true)?.let { return setOf(it) }
findMaximallySpecificCall(candidates, true)?.let { return setOf(it) }
}
return candidatesSet
}
private fun filterOutEquivalentCalls(candidates: Collection<Candidate>): Set<Candidate> {
val result = mutableSetOf<Candidate>()
outerLoop@ for (myCandidate in candidates) {
val me = myCandidate.symbol.fir
if (me is FirCallableMemberDeclaration<*> && me.symbol.callableId.className == null) {
for (otherCandidate in result) {
val other = otherCandidate.symbol.fir
if (other is FirCallableMemberDeclaration<*> && other.symbol.callableId.className == null) {
if (areEquivalentTopLevelCallables(me, myCandidate, other, otherCandidate)) {
continue@outerLoop
}
}
}
}
result += myCandidate
}
return result
}
private fun areEquivalentTopLevelCallables(
first: FirCallableMemberDeclaration<*>,
firstCandidate: Candidate,
second: FirCallableMemberDeclaration<*>,
secondCandidate: Candidate
): Boolean {
if (first.symbol.callableId != second.symbol.callableId) return false
if (first.isExpect != second.isExpect) return false
if (first.receiverTypeRef?.coneTypeUnsafe<ConeKotlinType>() != second.receiverTypeRef?.coneTypeUnsafe()) {
return false
}
val firstSignature = createFlatSignature(firstCandidate, first)
val secondSignature = createFlatSignature(secondCandidate, second)
return compareCallsByUsedArguments(firstSignature, secondSignature, false) &&
compareCallsByUsedArguments(secondSignature, firstSignature, false)
}
private fun createFlatSignature(call: Candidate): FlatSignature<Candidate> {
return when (val declaration = call.symbol.fir) {
is FirSimpleFunction -> createFlatSignature(call, declaration)
is FirConstructor -> createFlatSignature(call, declaration)
is FirVariable<*> -> createFlatSignature(call, declaration)
is FirClass<*> -> createFlatSignature(call, declaration)
else -> error("Not supported: $declaration")
}
}
private fun createFlatSignature(call: Candidate, declaration: FirCallableMemberDeclaration<*>): FlatSignature<Candidate> {
return when (declaration) {
is FirSimpleFunction -> createFlatSignature(call, declaration)
is FirConstructor -> createFlatSignature(call, declaration)
is FirVariable<*> -> createFlatSignature(call, declaration as FirVariable<*>)
else -> error("Not supported: $declaration")
}
}
private fun createFlatSignature(call: Candidate, constructor: FirConstructor): FlatSignature<Candidate> {
return FlatSignature(
call,
constructor.typeParameters.map { it.symbol },
computeParameterTypes(call, constructor),
//constructor.receiverTypeRef != null,
false,
constructor.valueParameters.any { it.isVararg },
constructor.valueParameters.count { it.defaultValue != null },
constructor.isExpect,
false // TODO
)
}
private fun FirValueParameter.argumentType(): ConeKotlinType {
val type = returnTypeRef.coneTypeUnsafe<ConeKotlinType>()
if (isVararg) return type.arrayElementType(inferenceComponents.session)!!
return type
}
private fun createFlatSignature(call: Candidate, function: FirSimpleFunction): FlatSignature<Candidate> {
return FlatSignature(
call,
function.typeParameters.map { it.symbol },
computeParameterTypes(call, function),
function.receiverTypeRef != null,
function.valueParameters.any { it.isVararg },
function.valueParameters.count { it.defaultValue != null },
function.isExpect,
false // TODO
)
}
private fun computeParameterTypes(
call: Candidate,
function: FirFunction<*>
): List<ConeKotlinType> {
return (call.resultingTypeForCallableReference?.typeArguments?.map { it as ConeKotlinType }
?: (listOfNotNull<ConeKotlinType>(function.receiverTypeRef?.coneTypeUnsafe()) +
call.argumentMapping?.map { it.value.argumentType() }.orEmpty()))
}
private fun createFlatSignature(call: Candidate, variable: FirVariable<*>): FlatSignature<Candidate> {
return FlatSignature(
call,
(variable as? FirProperty)?.typeParameters?.map { it.symbol }.orEmpty(),
listOfNotNull<ConeKotlinType>(variable.receiverTypeRef?.coneTypeUnsafe()),
variable.receiverTypeRef != null,
false,
0,
(variable as? FirProperty)?.isExpect == true,
false // TODO
)
}
private fun createFlatSignature(call: Candidate, klass: FirClass<*>): FlatSignature<Candidate> {
return FlatSignature(
call,
(klass as? FirRegularClass)?.typeParameters?.map { it.symbol }.orEmpty(),
valueParameterTypes = emptyList(),
hasExtensionReceiver = false,
hasVarargs = false,
numDefaults = 0,
isExpect = (klass as? FirRegularClass)?.isExpect == true,
isSyntheticMember = false
)
}
private fun createEmptyConstraintSystem(): SimpleConstraintSystem {
return ConeSimpleConstraintSystemImpl(inferenceComponents.createConstraintSystem())
return candidates
}
private fun findMaximallySpecificCall(
@@ -180,11 +43,9 @@ class ConeOverloadConflictResolver(
discriminateGenerics: Boolean//,
//isDebuggerContext: Boolean
): Candidate? {
val filteredCandidates = candidates//uniquifyCandidatesSet(candidates)
if (candidates.size <= 1) return candidates.singleOrNull()
if (filteredCandidates.size <= 1) return filteredCandidates.singleOrNull()
val conflictingCandidates = filteredCandidates.map { candidateCall ->
val conflictingCandidates = candidates.map { candidateCall ->
createFlatSignature(candidateCall)
}
@@ -208,7 +69,7 @@ class ConeOverloadConflictResolver(
}
}
if (result == null) return null
if (any { it != result && isNotWorse(it, result!!) }) {
if (any { it != result && isNotWorse(it, result) }) {
return null
}
return result
@@ -235,38 +96,6 @@ class ConeOverloadConflictResolver(
)
}
/**
* Returns `true` if [call1] is definitely more or equally specific [call2],
* `false` otherwise.
*/
private fun compareCallsByUsedArguments(
call1: FlatSignature<Candidate>,
call2: FlatSignature<Candidate>,
discriminateGenerics: Boolean
): Boolean {
if (discriminateGenerics) {
val isGeneric1 = call1.isGeneric
val isGeneric2 = call2.isGeneric
// generic loses to non-generic
if (isGeneric1 && !isGeneric2) return false
if (!isGeneric1 && isGeneric2) return true
// two generics are non-comparable
if (isGeneric1 && isGeneric2) return false
}
if (!call1.isExpect && call2.isExpect) return true
if (call1.isExpect && !call2.isExpect) return false
return createEmptyConstraintSystem().isSignatureNotLessSpecific(
call1,
call2,
OverloadabilitySpecificityCallbacks,
specificityComparator
)
}
private fun isOfNotLessSpecificShape(
call1: FlatSignature<Candidate>,
call2: FlatSignature<Candidate>
@@ -282,12 +111,6 @@ class ConeOverloadConflictResolver(
return true
}
private fun <C> Collection<C>.setIfOneOrEmpty(): Set<C>? = when (size) {
0 -> emptySet()
1 -> setOf(single())
else -> null
}
}
object NoSubstitutor : TypeSubstitutorMarker
@@ -295,19 +118,21 @@ object NoSubstitutor : TypeSubstitutorMarker
class ConeSimpleConstraintSystemImpl(val system: NewConstraintSystemImpl) : SimpleConstraintSystem {
override fun registerTypeVariables(typeParameters: Collection<TypeParameterMarker>): TypeSubstitutorMarker = with(context) {
val csBuilder = system.getBuilder()
val substitutionMap = typeParameters.associate {
val substitutionMap = typeParameters.associateWith {
require(it is FirTypeParameterSymbol)
val variable = TypeParameterBasedTypeVariable(it)
csBuilder.registerVariable(variable)
it to variable.defaultType
variable.defaultType
}
val substitutor = substitutorByMap(substitutionMap.cast())
for (typeParameter in typeParameters) {
require(typeParameter is FirTypeParameterSymbol)
for (upperBound in typeParameter.fir.bounds) {
addSubtypeConstraint(substitutionMap[typeParameter]!!, substitutor.substituteOrSelf(upperBound.coneTypeUnsafe()))
addSubtypeConstraint(
substitutionMap[typeParameter] ?: error("No ${typeParameter.fir.render()} in substitution map"),
substitutor.substituteOrSelf(upperBound.coneTypeUnsafe())
)
}
}
return substitutor
@@ -0,0 +1,74 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.fir.resolve.calls.jvm
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.resolve.calls.Candidate
import org.jetbrains.kotlin.fir.resolve.calls.AbstractConeCallConflictResolver
import org.jetbrains.kotlin.fir.resolve.calls.InferenceComponents
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.fir.types.coneTypeUnsafe
import org.jetbrains.kotlin.resolve.calls.results.FlatSignature
import org.jetbrains.kotlin.resolve.calls.results.TypeSpecificityComparator
// This conflict resolver filters JVM equivalent top-level functions
// like emptyArray() from intrinsics and built-ins
class ConeEquivalentCallConflictResolver(
specificityComparator: TypeSpecificityComparator,
inferenceComponents: InferenceComponents
) : AbstractConeCallConflictResolver(specificityComparator, inferenceComponents) {
override fun chooseMaximallySpecificCandidates(
candidates: Set<Candidate>,
discriminateGenerics: Boolean
): Set<Candidate> {
return filterOutEquivalentCalls(candidates)
}
private fun filterOutEquivalentCalls(candidates: Collection<Candidate>): Set<Candidate> {
val result = mutableSetOf<Candidate>()
outerLoop@ for (myCandidate in candidates) {
val me = myCandidate.symbol.fir
if (me is FirCallableMemberDeclaration<*> && me.symbol.callableId.className == null) {
for (otherCandidate in result) {
val other = otherCandidate.symbol.fir
if (other is FirCallableMemberDeclaration<*> && other.symbol.callableId.className == null) {
if (areEquivalentTopLevelCallables(me, myCandidate, other, otherCandidate)) {
continue@outerLoop
}
}
}
}
result += myCandidate
}
return result
}
private fun areEquivalentTopLevelCallables(
first: FirCallableMemberDeclaration<*>,
firstCandidate: Candidate,
second: FirCallableMemberDeclaration<*>,
secondCandidate: Candidate
): Boolean {
if (first.symbol.callableId != second.symbol.callableId) return false
if (first.isExpect != second.isExpect) return false
if (first.receiverTypeRef?.coneTypeUnsafe<ConeKotlinType>() != second.receiverTypeRef?.coneTypeUnsafe()) {
return false
}
val firstSignature = createFlatSignature(firstCandidate, first)
val secondSignature = createFlatSignature(secondCandidate, second)
return compareCallsByUsedArguments(firstSignature, secondSignature, false) &&
compareCallsByUsedArguments(secondSignature, firstSignature, false)
}
private fun createFlatSignature(call: Candidate, declaration: FirCallableMemberDeclaration<*>): FlatSignature<Candidate> {
return when (declaration) {
is FirSimpleFunction -> createFlatSignature(call, declaration)
is FirConstructor -> createFlatSignature(call, declaration)
is FirVariable<*> -> createFlatSignature(call, declaration as FirVariable<*>)
else -> error("Not supported: $declaration")
}
}
}