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.scopes.processClassifiersByNameWithAction
|
||||||
import org.jetbrains.kotlin.fir.service
|
import org.jetbrains.kotlin.fir.service
|
||||||
import org.jetbrains.kotlin.fir.symbols.*
|
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.symbols.impl.FirClassSymbol
|
||||||
import org.jetbrains.kotlin.fir.types.*
|
import org.jetbrains.kotlin.fir.types.*
|
||||||
import org.jetbrains.kotlin.name.Name
|
import org.jetbrains.kotlin.name.Name
|
||||||
@@ -140,12 +141,12 @@ class MemberScopeTowerLevel(
|
|||||||
|
|
||||||
private fun <T : ConeSymbol> processMembers(
|
private fun <T : ConeSymbol> processMembers(
|
||||||
output: TowerScopeLevel.TowerScopeLevelProcessor<T>,
|
output: TowerScopeLevel.TowerScopeLevelProcessor<T>,
|
||||||
takeMembers: FirScope.(processor: (T) -> ProcessorAction) -> ProcessorAction
|
processScopeMembers: FirScope.(processor: (T) -> ProcessorAction) -> ProcessorAction
|
||||||
): ProcessorAction {
|
): ProcessorAction {
|
||||||
val scope = dispatchReceiver.type.scope(session) ?: return ProcessorAction.NEXT
|
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))
|
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(
|
override fun <T : ConeSymbol> processElementsByName(
|
||||||
@@ -163,6 +164,8 @@ class MemberScopeTowerLevel(
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun ConeCallableSymbol.hasExtensionReceiver(): Boolean = (this as? FirCallableSymbol)?.fir?.receiverTypeRef != null
|
||||||
|
|
||||||
class ScopeTowerLevel(
|
class ScopeTowerLevel(
|
||||||
val session: FirSession,
|
val session: FirSession,
|
||||||
val scope: FirScope
|
val scope: FirScope
|
||||||
@@ -175,12 +178,26 @@ class ScopeTowerLevel(
|
|||||||
): ProcessorAction {
|
): ProcessorAction {
|
||||||
return when (token) {
|
return when (token) {
|
||||||
|
|
||||||
TowerScopeLevel.Token.Properties -> scope.processPropertiesByName(name) { processor.consumeCandidate(it as T, null) }
|
TowerScopeLevel.Token.Properties -> scope.processPropertiesByName(name) { candidate ->
|
||||||
TowerScopeLevel.Token.Functions -> scope.processFunctionsByName(name) { processor.consumeCandidate(it as T, null) }
|
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) {
|
TowerScopeLevel.Token.Objects -> scope.processClassifiersByNameWithAction(name, FirPosition.OTHER) {
|
||||||
processor.consumeCandidate(
|
processor.consumeCandidate(
|
||||||
it as T,
|
it as T,
|
||||||
null
|
boundDispatchReceiver = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -319,8 +336,8 @@ class ExplicitReceiverTowerDataConsumer<T : ConeSymbol>(
|
|||||||
MemberScopeTowerLevel(session, explicitReceiver).processElementsByName(
|
MemberScopeTowerLevel(session, explicitReceiver).processElementsByName(
|
||||||
token,
|
token,
|
||||||
name,
|
name,
|
||||||
null,
|
extensionReceiver = null,
|
||||||
object : TowerScopeLevel.TowerScopeLevelProcessor<T> {
|
processor = object : TowerScopeLevel.TowerScopeLevelProcessor<T> {
|
||||||
override fun consumeCandidate(symbol: T, boundDispatchReceiver: ReceiverValueWithPossibleTypes?): ProcessorAction {
|
override fun consumeCandidate(symbol: T, boundDispatchReceiver: ReceiverValueWithPossibleTypes?): ProcessorAction {
|
||||||
resultCollector.consumeCandidate(
|
resultCollector.consumeCandidate(
|
||||||
group,
|
group,
|
||||||
@@ -339,8 +356,8 @@ class ExplicitReceiverTowerDataConsumer<T : ConeSymbol>(
|
|||||||
towerScopeLevel.processElementsByName(
|
towerScopeLevel.processElementsByName(
|
||||||
token,
|
token,
|
||||||
name,
|
name,
|
||||||
explicitReceiver,
|
extensionReceiver = explicitReceiver,
|
||||||
object : TowerScopeLevel.TowerScopeLevelProcessor<T> {
|
processor = object : TowerScopeLevel.TowerScopeLevelProcessor<T> {
|
||||||
override fun consumeCandidate(symbol: T, boundDispatchReceiver: ReceiverValueWithPossibleTypes?): ProcessorAction {
|
override fun consumeCandidate(symbol: T, boundDispatchReceiver: ReceiverValueWithPossibleTypes?): ProcessorAction {
|
||||||
resultCollector.consumeCandidate(
|
resultCollector.consumeCandidate(
|
||||||
group,
|
group,
|
||||||
|
|||||||
@@ -7,7 +7,10 @@ package org.jetbrains.kotlin.fir.resolve.calls
|
|||||||
|
|
||||||
import org.jetbrains.kotlin.fir.declarations.FirFunction
|
import org.jetbrains.kotlin.fir.declarations.FirFunction
|
||||||
import org.jetbrains.kotlin.fir.resolve.transformers.firUnsafe
|
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.fir.symbols.impl.FirFunctionSymbol
|
||||||
|
import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind
|
||||||
|
import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind.*
|
||||||
import java.lang.IllegalStateException
|
import java.lang.IllegalStateException
|
||||||
|
|
||||||
|
|
||||||
@@ -17,6 +20,39 @@ abstract class ResolutionStage {
|
|||||||
|
|
||||||
abstract class CheckerStage : 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() {
|
internal object MapArguments : ResolutionStage() {
|
||||||
override fun check(candidate: Candidate, sink: CheckerSink, callInfo: CallInfo) {
|
override fun check(candidate: Candidate, sink: CheckerSink, callInfo: CallInfo) {
|
||||||
val symbol = candidate.symbol as? FirFunctionSymbol ?: return sink.reportApplicability(CandidateApplicability.HIDDEN)
|
val symbol = candidate.symbol as? FirFunctionSymbol ?: return sink.reportApplicability(CandidateApplicability.HIDDEN)
|
||||||
@@ -47,9 +83,13 @@ internal object CheckArguments : CheckerStage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
internal fun functionCallResolutionSequence() =
|
internal fun functionCallResolutionSequence() = listOf(
|
||||||
listOf<ResolutionStage>(MapArguments, CreateFreshTypeVariableSubstitutorStage, CheckArguments)
|
CheckExplicitReceiverConsistency, CheckReceivers.Dispatch, CheckReceivers.Extension,
|
||||||
|
MapArguments, CreateFreshTypeVariableSubstitutorStage, CheckArguments
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
internal fun qualifiedAccessResolutionSequence() =
|
internal fun qualifiedAccessResolutionSequence() = listOf(
|
||||||
listOf<ResolutionStage>(CreateFreshTypeVariableSubstitutorStage)
|
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()
|
val o = Owner()
|
||||||
o.foo()
|
o.foo()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun err() {
|
||||||
|
foo()
|
||||||
|
this.foo()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,11 @@ FILE: simple.kt
|
|||||||
R|<local>/o|.R|/Owner.foo|()
|
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");
|
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")
|
@TestMetadata("simple.kt")
|
||||||
public void testSimple() throws Exception {
|
public void testSimple() throws Exception {
|
||||||
runTest("compiler/fir/resolve/testData/resolve/expresssions/simple.kt");
|
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);
|
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")
|
@TestMetadata("simple.kt")
|
||||||
public void testSimple() throws Exception {
|
public void testSimple() throws Exception {
|
||||||
runTest("compiler/fir/resolve/testData/resolve/nested/simple.kt");
|
runTest("compiler/fir/resolve/testData/resolve/nested/simple.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user