From 202e992ae3e9c4d86a95965ea49e86ae03f977e2 Mon Sep 17 00:00:00 2001 From: Dmitry Petrov Date: Thu, 17 Jan 2019 13:45:28 +0300 Subject: [PATCH] psi2ir: handle 'this' as reference to a super companion object In super class constructor arguments, 'this' can be resolved as a reference to a companion object of a superclass. This breaks an assumption in psi2ir that 'this' can only refer to some receiver from the current scope. If 'this' refers to an 'object' (including 'companion obejct'), and we are not inside the corresponding class scope, then 'this' represents a reference to a singleton instance "by name" (represented as IrGetObjectValue). --- .../generators/ArgumentsGenerationUtils.kt | 10 +- .../psi2ir/generators/StatementGenerator.kt | 45 +++++++-- .../anonymousObjectInExtension.kt | 2 - .../thisReferenceBeforeClassDeclared.kt | 10 ++ .../thisReferenceBeforeClassDeclared.txt | 95 +++++++++++++++++++ .../kotlin/ir/IrTextTestCaseGenerated.java | 5 + 6 files changed, 148 insertions(+), 19 deletions(-) create mode 100644 compiler/testData/ir/irText/expressions/thisReferenceBeforeClassDeclared.kt create mode 100644 compiler/testData/ir/irText/expressions/thisReferenceBeforeClassDeclared.txt diff --git a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/ArgumentsGenerationUtils.kt b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/ArgumentsGenerationUtils.kt index bf7f96a7d11..448b0112ea9 100644 --- a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/ArgumentsGenerationUtils.kt +++ b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/ArgumentsGenerationUtils.kt @@ -134,14 +134,10 @@ private fun StatementGenerator.shouldGenerateReceiverAsSingletonReference(receiv } private fun StatementGenerator.generateThisOrSuperReceiver(receiver: ReceiverValue, classDescriptor: ClassDescriptor): IrExpression { - val expressionReceiver = - receiver as? ExpressionReceiver ?: throw AssertionError("'this' or 'super' receiver should be an expression receiver") + val expressionReceiver = receiver as? ExpressionReceiver + ?: throw AssertionError("'this' or 'super' receiver should be an expression receiver") val ktReceiver = expressionReceiver.expression - return IrGetValueImpl( - ktReceiver.startOffsetSkippingComments, ktReceiver.endOffset, - expressionReceiver.type.toIrType(), - context.symbolTable.referenceValueParameter(classDescriptor.thisAsReceiverParameter) - ) + return generateThisReceiver(ktReceiver.startOffsetSkippingComments, ktReceiver.endOffset, expressionReceiver.type, classDescriptor) } fun StatementGenerator.generateBackingFieldReceiver( diff --git a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/StatementGenerator.kt b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/StatementGenerator.kt index 5e3e14e830f..7b2aa1ad6bc 100644 --- a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/StatementGenerator.kt +++ b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/StatementGenerator.kt @@ -38,6 +38,7 @@ import org.jetbrains.kotlin.psi2ir.intermediate.createTemporaryVariableInBlock import org.jetbrains.kotlin.psi2ir.intermediate.setExplicitReceiverValue import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContextUtils +import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall import org.jetbrains.kotlin.resolve.constants.CompileTimeConstant @@ -364,28 +365,52 @@ class StatementGenerator( override fun visitSafeQualifiedExpression(expression: KtSafeQualifiedExpression, data: Nothing?): IrStatement = expression.selectorExpression!!.accept(this, data) + private fun isInsideClass(classDescriptor: ClassDescriptor): Boolean { + var scopeDescriptor: DeclarationDescriptor? = scopeOwner + while (scopeDescriptor != null) { + if (scopeDescriptor == classDescriptor) return true + scopeDescriptor = scopeDescriptor.containingDeclaration + } + return false + } + + fun generateThisReceiver(startOffset: Int, endOffset: Int, kotlinType: KotlinType, classDescriptor: ClassDescriptor): IrExpression { + val thisAsReceiverParameter = classDescriptor.thisAsReceiverParameter + val thisType = kotlinType.toIrType() + + return if (DescriptorUtils.isObject(classDescriptor) && !isInsideClass(classDescriptor)) { + IrGetObjectValueImpl( + startOffset, endOffset, + thisType, + context.symbolTable.referenceClass(classDescriptor) + ) + } else { + IrGetValueImpl( + startOffset, endOffset, + thisType, + context.symbolTable.referenceValueParameter(thisAsReceiverParameter) + ) + } + } + override fun visitThisExpression(expression: KtThisExpression, data: Nothing?): IrExpression { val referenceTarget = getOrFail(BindingContext.REFERENCE_TARGET, expression.instanceReference) { "No reference target for this" } + val startOffset = expression.startOffsetSkippingComments + val endOffset = expression.endOffset return when (referenceTarget) { - is ClassDescriptor -> { - val thisAsReceiverParameter = referenceTarget.thisAsReceiverParameter - val thisType = thisAsReceiverParameter.type.toIrType() - IrGetValueImpl( - expression.startOffsetSkippingComments, expression.endOffset, - thisType, - context.symbolTable.referenceValueParameter(thisAsReceiverParameter) - ) - } + is ClassDescriptor -> + generateThisReceiver(startOffset, endOffset, referenceTarget.thisAsReceiverParameter.type, referenceTarget) is CallableDescriptor -> { val extensionReceiver = referenceTarget.extensionReceiverParameter ?: TODO("No extension receiver: $referenceTarget") val extensionReceiverType = extensionReceiver.type.toIrType() IrGetValueImpl( - expression.startOffsetSkippingComments, expression.endOffset, + startOffset, endOffset, extensionReceiverType, context.symbolTable.referenceValueParameter(extensionReceiver) ) } + else -> error("Expected this or receiver: $referenceTarget") } diff --git a/compiler/testData/codegen/box/localClasses/anonymousObjectInExtension.kt b/compiler/testData/codegen/box/localClasses/anonymousObjectInExtension.kt index 0eb1d3217a4..74124f89a58 100644 --- a/compiler/testData/codegen/box/localClasses/anonymousObjectInExtension.kt +++ b/compiler/testData/codegen/box/localClasses/anonymousObjectInExtension.kt @@ -1,5 +1,3 @@ -// IGNORE_BACKEND: JVM_IR, JS_IR - fun WithCompanion.test(): String { object : WithCompanion(this) {} return "OK" diff --git a/compiler/testData/ir/irText/expressions/thisReferenceBeforeClassDeclared.kt b/compiler/testData/ir/irText/expressions/thisReferenceBeforeClassDeclared.kt new file mode 100644 index 00000000000..9ecd279c30f --- /dev/null +++ b/compiler/testData/ir/irText/expressions/thisReferenceBeforeClassDeclared.kt @@ -0,0 +1,10 @@ +fun WithCompanion.test() { + val test1 = object : WithCompanion(this) {} + val test2 = object : WithCompanion(this.foo()) {} +} + +open class WithCompanion(a: WithCompanion.Companion) { + companion object { + fun foo(): WithCompanion.Companion = this + } +} \ No newline at end of file diff --git a/compiler/testData/ir/irText/expressions/thisReferenceBeforeClassDeclared.txt b/compiler/testData/ir/irText/expressions/thisReferenceBeforeClassDeclared.txt new file mode 100644 index 00000000000..d10e7efb373 --- /dev/null +++ b/compiler/testData/ir/irText/expressions/thisReferenceBeforeClassDeclared.txt @@ -0,0 +1,95 @@ +FILE fqName: fileName:/thisReferenceBeforeClassDeclared.kt + FUN name:test visibility:public modality:FINAL <> ($receiver:WithCompanion) returnType:kotlin.Unit flags: + $receiver: VALUE_PARAMETER name: type:WithCompanion flags: + BLOCK_BODY + VAR name:test1 type:test. flags:val + BLOCK type=test. origin=OBJECT_LITERAL + CLASS CLASS name: modality:FINAL visibility:local flags: superTypes:[WithCompanion] + $this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:test. flags: + CONSTRUCTOR visibility:public <> () returnType:test. flags:primary + BLOCK_BODY + DELEGATING_CONSTRUCTOR_CALL 'constructor WithCompanion(WithCompanion.Companion)' + a: GET_OBJECT 'companion object of WithCompanion' type=WithCompanion.Companion + INSTANCE_INITIALIZER_CALL classDescriptor='' + FUN FAKE_OVERRIDE name:equals visibility:public modality:OPEN <> ($this:kotlin.Any, other:kotlin.Any?) returnType:kotlin.Boolean flags: + overridden: + FUN FAKE_OVERRIDE name:equals visibility:public modality:OPEN <> ($this:kotlin.Any, other:kotlin.Any?) returnType:kotlin.Boolean flags: + $this: VALUE_PARAMETER name: type:kotlin.Any flags: + VALUE_PARAMETER name:other index:0 type:kotlin.Any? flags: + FUN FAKE_OVERRIDE name:hashCode visibility:public modality:OPEN <> ($this:kotlin.Any) returnType:kotlin.Int flags: + overridden: + FUN FAKE_OVERRIDE name:hashCode visibility:public modality:OPEN <> ($this:kotlin.Any) returnType:kotlin.Int flags: + $this: VALUE_PARAMETER name: type:kotlin.Any flags: + FUN FAKE_OVERRIDE name:toString visibility:public modality:OPEN <> ($this:kotlin.Any) returnType:kotlin.String flags: + overridden: + FUN FAKE_OVERRIDE name:toString visibility:public modality:OPEN <> ($this:kotlin.Any) returnType:kotlin.String flags: + $this: VALUE_PARAMETER name: type:kotlin.Any flags: + CALL 'constructor ()' type=test. origin=OBJECT_LITERAL + VAR name:test2 type:test. flags:val + BLOCK type=test. origin=OBJECT_LITERAL + CLASS CLASS name: modality:FINAL visibility:local flags: superTypes:[WithCompanion] + $this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:test. flags: + CONSTRUCTOR visibility:public <> () returnType:test. flags:primary + BLOCK_BODY + DELEGATING_CONSTRUCTOR_CALL 'constructor WithCompanion(WithCompanion.Companion)' + a: CALL 'foo(): WithCompanion.Companion' type=WithCompanion.Companion origin=null + $this: GET_OBJECT 'companion object of WithCompanion' type=WithCompanion.Companion + INSTANCE_INITIALIZER_CALL classDescriptor='' + FUN FAKE_OVERRIDE name:equals visibility:public modality:OPEN <> ($this:kotlin.Any, other:kotlin.Any?) returnType:kotlin.Boolean flags: + overridden: + FUN FAKE_OVERRIDE name:equals visibility:public modality:OPEN <> ($this:kotlin.Any, other:kotlin.Any?) returnType:kotlin.Boolean flags: + $this: VALUE_PARAMETER name: type:kotlin.Any flags: + VALUE_PARAMETER name:other index:0 type:kotlin.Any? flags: + FUN FAKE_OVERRIDE name:hashCode visibility:public modality:OPEN <> ($this:kotlin.Any) returnType:kotlin.Int flags: + overridden: + FUN FAKE_OVERRIDE name:hashCode visibility:public modality:OPEN <> ($this:kotlin.Any) returnType:kotlin.Int flags: + $this: VALUE_PARAMETER name: type:kotlin.Any flags: + FUN FAKE_OVERRIDE name:toString visibility:public modality:OPEN <> ($this:kotlin.Any) returnType:kotlin.String flags: + overridden: + FUN FAKE_OVERRIDE name:toString visibility:public modality:OPEN <> ($this:kotlin.Any) returnType:kotlin.String flags: + $this: VALUE_PARAMETER name: type:kotlin.Any flags: + CALL 'constructor ()' type=test. origin=OBJECT_LITERAL + CLASS CLASS name:WithCompanion modality:OPEN visibility:public flags: superTypes:[kotlin.Any] + $this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:WithCompanion flags: + CONSTRUCTOR visibility:public <> (a:WithCompanion.Companion) returnType:WithCompanion flags:primary + VALUE_PARAMETER name:a index:0 type:WithCompanion.Companion flags: + BLOCK_BODY + DELEGATING_CONSTRUCTOR_CALL 'constructor Any()' + INSTANCE_INITIALIZER_CALL classDescriptor='WithCompanion' + CLASS OBJECT name:Companion modality:FINAL visibility:public flags:companion superTypes:[kotlin.Any] + $this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:WithCompanion.Companion flags: + CONSTRUCTOR visibility:private <> () returnType:WithCompanion.Companion flags:primary + BLOCK_BODY + DELEGATING_CONSTRUCTOR_CALL 'constructor Any()' + INSTANCE_INITIALIZER_CALL classDescriptor='companion object of WithCompanion' + FUN name:foo visibility:public modality:FINAL <> ($this:WithCompanion.Companion) returnType:WithCompanion.Companion flags: + $this: VALUE_PARAMETER name: type:WithCompanion.Companion flags: + BLOCK_BODY + RETURN type=kotlin.Nothing from='foo(): WithCompanion.Companion' + GET_VAR 'this@Companion: Companion' type=WithCompanion.Companion origin=null + FUN FAKE_OVERRIDE name:equals visibility:public modality:OPEN <> ($this:kotlin.Any, other:kotlin.Any?) returnType:kotlin.Boolean flags: + overridden: + FUN IR_EXTERNAL_DECLARATION_STUB name:equals visibility:public modality:OPEN <> ($this:kotlin.Any, other:kotlin.Any?) returnType:kotlin.Boolean flags: + $this: VALUE_PARAMETER name: type:kotlin.Any flags: + VALUE_PARAMETER name:other index:0 type:kotlin.Any? flags: + FUN FAKE_OVERRIDE name:hashCode visibility:public modality:OPEN <> ($this:kotlin.Any) returnType:kotlin.Int flags: + overridden: + FUN IR_EXTERNAL_DECLARATION_STUB name:hashCode visibility:public modality:OPEN <> ($this:kotlin.Any) returnType:kotlin.Int flags: + $this: VALUE_PARAMETER name: type:kotlin.Any flags: + FUN FAKE_OVERRIDE name:toString visibility:public modality:OPEN <> ($this:kotlin.Any) returnType:kotlin.String flags: + overridden: + FUN IR_EXTERNAL_DECLARATION_STUB name:toString visibility:public modality:OPEN <> ($this:kotlin.Any) returnType:kotlin.String flags: + $this: VALUE_PARAMETER name: type:kotlin.Any flags: + FUN FAKE_OVERRIDE name:equals visibility:public modality:OPEN <> ($this:kotlin.Any, other:kotlin.Any?) returnType:kotlin.Boolean flags: + overridden: + FUN IR_EXTERNAL_DECLARATION_STUB name:equals visibility:public modality:OPEN <> ($this:kotlin.Any, other:kotlin.Any?) returnType:kotlin.Boolean flags: + $this: VALUE_PARAMETER name: type:kotlin.Any flags: + VALUE_PARAMETER name:other index:0 type:kotlin.Any? flags: + FUN FAKE_OVERRIDE name:hashCode visibility:public modality:OPEN <> ($this:kotlin.Any) returnType:kotlin.Int flags: + overridden: + FUN IR_EXTERNAL_DECLARATION_STUB name:hashCode visibility:public modality:OPEN <> ($this:kotlin.Any) returnType:kotlin.Int flags: + $this: VALUE_PARAMETER name: type:kotlin.Any flags: + FUN FAKE_OVERRIDE name:toString visibility:public modality:OPEN <> ($this:kotlin.Any) returnType:kotlin.String flags: + overridden: + FUN IR_EXTERNAL_DECLARATION_STUB name:toString visibility:public modality:OPEN <> ($this:kotlin.Any) returnType:kotlin.String flags: + $this: VALUE_PARAMETER name: type:kotlin.Any flags: diff --git a/compiler/tests/org/jetbrains/kotlin/ir/IrTextTestCaseGenerated.java b/compiler/tests/org/jetbrains/kotlin/ir/IrTextTestCaseGenerated.java index eeda514383d..bbe0d0cb24c 100644 --- a/compiler/tests/org/jetbrains/kotlin/ir/IrTextTestCaseGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/ir/IrTextTestCaseGenerated.java @@ -1132,6 +1132,11 @@ public class IrTextTestCaseGenerated extends AbstractIrTextTestCase { runTest("compiler/testData/ir/irText/expressions/thisOfGenericOuterClass.kt"); } + @TestMetadata("thisReferenceBeforeClassDeclared.kt") + public void testThisReferenceBeforeClassDeclared() throws Exception { + runTest("compiler/testData/ir/irText/expressions/thisReferenceBeforeClassDeclared.kt"); + } + @TestMetadata("throw.kt") public void testThrow() throws Exception { runTest("compiler/testData/ir/irText/expressions/throw.kt");