FIR resolve: add receiver consistency checking
NB: looks like checker itself may be not needed, but it's important to check extension receiver presence during candidate collection
This commit is contained in:
@@ -22,6 +22,7 @@ import org.jetbrains.kotlin.fir.scopes.ProcessorAction
|
||||
import org.jetbrains.kotlin.fir.scopes.processClassifiersByNameWithAction
|
||||
import org.jetbrains.kotlin.fir.service
|
||||
import org.jetbrains.kotlin.fir.symbols.*
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol
|
||||
import org.jetbrains.kotlin.fir.types.*
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
@@ -140,12 +141,12 @@ class MemberScopeTowerLevel(
|
||||
|
||||
private fun <T : ConeSymbol> processMembers(
|
||||
output: TowerScopeLevel.TowerScopeLevelProcessor<T>,
|
||||
takeMembers: FirScope.(processor: (T) -> ProcessorAction) -> ProcessorAction
|
||||
processScopeMembers: FirScope.(processor: (T) -> ProcessorAction) -> ProcessorAction
|
||||
): ProcessorAction {
|
||||
val scope = dispatchReceiver.type.scope(session) ?: return ProcessorAction.NEXT
|
||||
if (scope.takeMembers { output.consumeCandidate(it, dispatchReceiver) }.stop()) return ProcessorAction.STOP
|
||||
if (scope.processScopeMembers { output.consumeCandidate(it, dispatchReceiver) }.stop()) return ProcessorAction.STOP
|
||||
val withSynthetic = FirSyntheticPropertiesScope(session, scope, ReturnTypeCalculatorWithJump(session))
|
||||
return withSynthetic.takeMembers { output.consumeCandidate(it, dispatchReceiver) }
|
||||
return withSynthetic.processScopeMembers { output.consumeCandidate(it, dispatchReceiver) }
|
||||
}
|
||||
|
||||
override fun <T : ConeSymbol> processElementsByName(
|
||||
@@ -163,6 +164,8 @@ class MemberScopeTowerLevel(
|
||||
|
||||
}
|
||||
|
||||
private fun ConeCallableSymbol.hasExtensionReceiver(): Boolean = (this as? FirCallableSymbol)?.fir?.receiverTypeRef != null
|
||||
|
||||
class ScopeTowerLevel(
|
||||
val session: FirSession,
|
||||
val scope: FirScope
|
||||
@@ -175,12 +178,26 @@ class ScopeTowerLevel(
|
||||
): ProcessorAction {
|
||||
return when (token) {
|
||||
|
||||
TowerScopeLevel.Token.Properties -> scope.processPropertiesByName(name) { processor.consumeCandidate(it as T, null) }
|
||||
TowerScopeLevel.Token.Functions -> scope.processFunctionsByName(name) { processor.consumeCandidate(it as T, null) }
|
||||
TowerScopeLevel.Token.Properties -> scope.processPropertiesByName(name) { candidate ->
|
||||
val candidateHasExtensionReceiver = candidate.hasExtensionReceiver()
|
||||
if (candidateHasExtensionReceiver == (extensionReceiver != null)) {
|
||||
processor.consumeCandidate(candidate as T, boundDispatchReceiver = null)
|
||||
} else {
|
||||
ProcessorAction.NEXT
|
||||
}
|
||||
}
|
||||
TowerScopeLevel.Token.Functions -> scope.processFunctionsByName(name) { candidate ->
|
||||
val candidateHasExtensionReceiver = candidate.hasExtensionReceiver()
|
||||
if (candidateHasExtensionReceiver == (extensionReceiver != null)) {
|
||||
processor.consumeCandidate(candidate as T, boundDispatchReceiver = null)
|
||||
} else {
|
||||
ProcessorAction.NEXT
|
||||
}
|
||||
}
|
||||
TowerScopeLevel.Token.Objects -> scope.processClassifiersByNameWithAction(name, FirPosition.OTHER) {
|
||||
processor.consumeCandidate(
|
||||
it as T,
|
||||
null
|
||||
boundDispatchReceiver = null
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -319,8 +336,8 @@ class ExplicitReceiverTowerDataConsumer<T : ConeSymbol>(
|
||||
MemberScopeTowerLevel(session, explicitReceiver).processElementsByName(
|
||||
token,
|
||||
name,
|
||||
null,
|
||||
object : TowerScopeLevel.TowerScopeLevelProcessor<T> {
|
||||
extensionReceiver = null,
|
||||
processor = object : TowerScopeLevel.TowerScopeLevelProcessor<T> {
|
||||
override fun consumeCandidate(symbol: T, boundDispatchReceiver: ReceiverValueWithPossibleTypes?): ProcessorAction {
|
||||
resultCollector.consumeCandidate(
|
||||
group,
|
||||
@@ -339,8 +356,8 @@ class ExplicitReceiverTowerDataConsumer<T : ConeSymbol>(
|
||||
towerScopeLevel.processElementsByName(
|
||||
token,
|
||||
name,
|
||||
explicitReceiver,
|
||||
object : TowerScopeLevel.TowerScopeLevelProcessor<T> {
|
||||
extensionReceiver = explicitReceiver,
|
||||
processor = object : TowerScopeLevel.TowerScopeLevelProcessor<T> {
|
||||
override fun consumeCandidate(symbol: T, boundDispatchReceiver: ReceiverValueWithPossibleTypes?): ProcessorAction {
|
||||
resultCollector.consumeCandidate(
|
||||
group,
|
||||
|
||||
@@ -7,7 +7,10 @@ package org.jetbrains.kotlin.fir.resolve.calls
|
||||
|
||||
import org.jetbrains.kotlin.fir.declarations.FirFunction
|
||||
import org.jetbrains.kotlin.fir.resolve.transformers.firUnsafe
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol
|
||||
import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind
|
||||
import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind.*
|
||||
import java.lang.IllegalStateException
|
||||
|
||||
|
||||
@@ -17,6 +20,39 @@ abstract class ResolutionStage {
|
||||
|
||||
abstract class CheckerStage : ResolutionStage()
|
||||
|
||||
internal object CheckExplicitReceiverConsistency : ResolutionStage() {
|
||||
override fun check(candidate: Candidate, sink: CheckerSink, callInfo: CallInfo) {
|
||||
val receiverKind = candidate.receiverKind
|
||||
val explicitReceiver = callInfo.explicitReceiver
|
||||
// TODO: add invoke cases
|
||||
when (receiverKind) {
|
||||
NO_EXPLICIT_RECEIVER -> {
|
||||
if (explicitReceiver != null) return sink.reportApplicability(CandidateApplicability.WRONG_RECEIVER)
|
||||
}
|
||||
EXTENSION_RECEIVER, DISPATCH_RECEIVER -> {
|
||||
if (explicitReceiver == null) return sink.reportApplicability(CandidateApplicability.WRONG_RECEIVER)
|
||||
}
|
||||
BOTH_RECEIVERS -> {
|
||||
if (explicitReceiver == null) return sink.reportApplicability(CandidateApplicability.WRONG_RECEIVER)
|
||||
// Here we should also check additional invoke receiver
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal sealed class CheckReceivers : ResolutionStage() {
|
||||
object Dispatch : CheckReceivers()
|
||||
|
||||
object Extension : CheckReceivers()
|
||||
|
||||
override fun check(candidate: Candidate, sink: CheckerSink, callInfo: CallInfo) {
|
||||
val callableSymbol = candidate.symbol as? FirCallableSymbol ?: return
|
||||
val callableId = callableSymbol.callableId
|
||||
val callable = callableSymbol.fir
|
||||
}
|
||||
}
|
||||
|
||||
internal object MapArguments : ResolutionStage() {
|
||||
override fun check(candidate: Candidate, sink: CheckerSink, callInfo: CallInfo) {
|
||||
val symbol = candidate.symbol as? FirFunctionSymbol ?: return sink.reportApplicability(CandidateApplicability.HIDDEN)
|
||||
@@ -47,9 +83,13 @@ internal object CheckArguments : CheckerStage() {
|
||||
}
|
||||
|
||||
|
||||
internal fun functionCallResolutionSequence() =
|
||||
listOf<ResolutionStage>(MapArguments, CreateFreshTypeVariableSubstitutorStage, CheckArguments)
|
||||
internal fun functionCallResolutionSequence() = listOf(
|
||||
CheckExplicitReceiverConsistency, CheckReceivers.Dispatch, CheckReceivers.Extension,
|
||||
MapArguments, CreateFreshTypeVariableSubstitutorStage, CheckArguments
|
||||
)
|
||||
|
||||
|
||||
internal fun qualifiedAccessResolutionSequence() =
|
||||
listOf<ResolutionStage>(CreateFreshTypeVariableSubstitutorStage)
|
||||
internal fun qualifiedAccessResolutionSequence() = listOf(
|
||||
CheckExplicitReceiverConsistency, CheckReceivers.Dispatch, CheckReceivers.Extension,
|
||||
CreateFreshTypeVariableSubstitutorStage
|
||||
)
|
||||
@@ -15,5 +15,5 @@ FILE: localImplicitBodies.kt
|
||||
|
||||
}
|
||||
|
||||
lval g: <ERROR TYPE REF: Unresolved name: abc> = R|<local>/x|.R|/sss|()
|
||||
lval g: <ERROR TYPE REF: Unresolved name: sss> = R|<local>/x|.<Unresolved name: sss>#()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
fun foo() {}
|
||||
|
||||
class C {
|
||||
fun bar() {}
|
||||
fun err() {}
|
||||
|
||||
class Nested {
|
||||
fun test() {
|
||||
err()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun test() {
|
||||
val c = C()
|
||||
foo()
|
||||
c.bar()
|
||||
|
||||
val err = C()
|
||||
err.foo()
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
FILE: receiverConsistency.kt
|
||||
public final fun foo(): R|kotlin/Unit| {
|
||||
}
|
||||
public final class C : R|kotlin/Any| {
|
||||
public constructor(): R|C| {
|
||||
super<R|kotlin/Any|>()
|
||||
}
|
||||
|
||||
public final fun bar(): R|kotlin/Unit| {
|
||||
}
|
||||
|
||||
public final fun err(): R|kotlin/Unit| {
|
||||
}
|
||||
|
||||
public final class Nested : R|kotlin/Any| {
|
||||
public constructor(): R|C.Nested| {
|
||||
super<R|kotlin/Any|>()
|
||||
}
|
||||
|
||||
public final fun test(): R|kotlin/Unit| {
|
||||
R|/C.err|()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
public final fun test(): R|kotlin/Unit| {
|
||||
lval c: R|C| = R|/C.C|()
|
||||
R|/foo|()
|
||||
R|<local>/c|.R|/C.bar|()
|
||||
lval err: R|C| = R|/C.C|()
|
||||
R|<local>/err|.<Unresolved name: foo>#()
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
class Owner {
|
||||
|
||||
fun foo() {
|
||||
bar()
|
||||
this.bar()
|
||||
}
|
||||
|
||||
fun bar() {
|
||||
val i = Inner()
|
||||
i.baz()
|
||||
}
|
||||
|
||||
fun err() {}
|
||||
|
||||
inner class Inner {
|
||||
fun baz() {
|
||||
gau()
|
||||
this.gau()
|
||||
}
|
||||
|
||||
fun gau() {
|
||||
val o = Owner()
|
||||
o.foo()
|
||||
foo()
|
||||
this@Owner.foo()
|
||||
this.err()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun test() {
|
||||
val o = Owner()
|
||||
o.foo()
|
||||
val err = Owner.Inner()
|
||||
err.baz()
|
||||
val i = o.Inner()
|
||||
i.gau()
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
FILE: inner.kt
|
||||
public final class Owner : R|kotlin/Any| {
|
||||
public constructor(): R|Owner| {
|
||||
super<R|kotlin/Any|>()
|
||||
}
|
||||
|
||||
public final fun foo(): R|kotlin/Unit| {
|
||||
R|/Owner.bar|()
|
||||
this#.R|/Owner.bar|()
|
||||
}
|
||||
|
||||
public final fun bar(): R|kotlin/Unit| {
|
||||
lval i: R|Owner.Inner| = R|/Owner.Inner.Inner|()
|
||||
R|<local>/i|.R|/Owner.Inner.baz|()
|
||||
}
|
||||
|
||||
public final fun err(): R|kotlin/Unit| {
|
||||
}
|
||||
|
||||
public final inner class Inner : R|kotlin/Any| {
|
||||
public constructor(): R|Owner.Inner| {
|
||||
super<R|kotlin/Any|>()
|
||||
}
|
||||
|
||||
public final fun baz(): R|kotlin/Unit| {
|
||||
R|/Owner.Inner.gau|()
|
||||
this#.R|/Owner.Inner.gau|()
|
||||
}
|
||||
|
||||
public final fun gau(): R|kotlin/Unit| {
|
||||
lval o: R|Owner| = R|/Owner.Owner|()
|
||||
R|<local>/o|.R|/Owner.foo|()
|
||||
R|/Owner.foo|()
|
||||
this@Owner.R|/Owner.foo|()
|
||||
this#.<Unresolved name: err>#()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
public final fun test(): R|kotlin/Unit| {
|
||||
lval o: R|Owner| = R|/Owner.Owner|()
|
||||
R|<local>/o|.R|/Owner.foo|()
|
||||
lval err: R|Owner.Inner| = R|/Owner|.R|/Owner.Inner.Inner|()
|
||||
R|<local>/err|.R|/Owner.Inner.baz|()
|
||||
lval i: R|Owner.Inner| = R|<local>/o|.R|/Owner.Inner.Inner|()
|
||||
R|<local>/i|.R|/Owner.Inner.gau|()
|
||||
}
|
||||
@@ -20,6 +20,11 @@ class Owner {
|
||||
val o = Owner()
|
||||
o.foo()
|
||||
}
|
||||
|
||||
fun err() {
|
||||
foo()
|
||||
this.foo()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,11 @@ FILE: simple.kt
|
||||
R|<local>/o|.R|/Owner.foo|()
|
||||
}
|
||||
|
||||
public final fun err(): R|kotlin/Unit| {
|
||||
R|/Owner.foo|()
|
||||
this#.<Unresolved name: foo>#()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+10
@@ -227,6 +227,11 @@ public class FirResolveTestCaseGenerated extends AbstractFirResolveTestCase {
|
||||
runTest("compiler/fir/resolve/testData/resolve/expresssions/objects.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("receiverConsistency.kt")
|
||||
public void testReceiverConsistency() throws Exception {
|
||||
runTest("compiler/fir/resolve/testData/resolve/expresssions/receiverConsistency.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("simple.kt")
|
||||
public void testSimple() throws Exception {
|
||||
runTest("compiler/fir/resolve/testData/resolve/expresssions/simple.kt");
|
||||
@@ -432,6 +437,11 @@ public class FirResolveTestCaseGenerated extends AbstractFirResolveTestCase {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/fir/resolve/testData/resolve/nested"), Pattern.compile("^([^.]+)\\.kt$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("inner.kt")
|
||||
public void testInner() throws Exception {
|
||||
runTest("compiler/fir/resolve/testData/resolve/nested/inner.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("simple.kt")
|
||||
public void testSimple() throws Exception {
|
||||
runTest("compiler/fir/resolve/testData/resolve/nested/simple.kt");
|
||||
|
||||
Reference in New Issue
Block a user