AbstractClosureAnnotator for IR.

This commit is contained in:
Dmitry Petrov
2016-09-05 15:54:04 +03:00
committed by Dmitry Petrov
parent ceeccfa1b7
commit ec2cfd2389
17 changed files with 387 additions and 19 deletions
+1
View File
@@ -14,6 +14,7 @@
<module fileurl="file://$PROJECT_DIR$/ant/ant.iml" filepath="$PROJECT_DIR$/ant/ant.iml" />
<module fileurl="file://$PROJECT_DIR$/compiler/backend/backend.iml" filepath="$PROJECT_DIR$/compiler/backend/backend.iml" group="compiler/java" />
<module fileurl="file://$PROJECT_DIR$/compiler/backend-common/backend-common.iml" filepath="$PROJECT_DIR$/compiler/backend-common/backend-common.iml" group="compiler" />
<module fileurl="file://$PROJECT_DIR$/compiler/ir/backend.common/backend.common.iml" filepath="$PROJECT_DIR$/compiler/ir/backend.common/backend.common.iml" group="compiler/ir" />
<module fileurl="file://$PROJECT_DIR$/compiler/ir/backend.jvm/backend.jvm.iml" filepath="$PROJECT_DIR$/compiler/ir/backend.jvm/backend.jvm.iml" group="compiler/ir" />
<module fileurl="file://$PROJECT_DIR$/build-common/build-common.iml" filepath="$PROJECT_DIR$/build-common/build-common.iml" />
<module fileurl="file://$PROJECT_DIR$/core/builtins/builtins.iml" filepath="$PROJECT_DIR$/core/builtins/builtins.iml" group="core" />
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="util" />
<orderEntry type="module" module-name="ir.tree" />
<orderEntry type="module" module-name="descriptors" />
</component>
</module>
@@ -0,0 +1,148 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.xbackend.common
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrGeneralFunction
import org.jetbrains.kotlin.ir.declarations.IrLocalPropertyAccessor
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
import org.jetbrains.kotlin.resolve.DescriptorUtils
import java.util.*
class Closure(
val capturedThisReferences: List<ClassDescriptor>,
val capturedReceiverParameters: List<ReceiverParameterDescriptor>,
val capturedVariables: List<VariableDescriptor>
)
abstract class AbstractClosureAnnotator : IrElementVisitorVoid {
protected abstract fun recordFunctionClosure(functionDescriptor: FunctionDescriptor, closure: Closure)
protected abstract fun recordClassClosure(classDescriptor: ClassDescriptor, closure: Closure)
private class ClosureBuilder(val owner: DeclarationDescriptor) {
val capturedThisReferences = mutableSetOf<ClassDescriptor>()
val capturedReceiverParameters = mutableSetOf<ReceiverParameterDescriptor>()
val capturedVariables = mutableSetOf<VariableDescriptor>()
fun buildClosure() = Closure(
capturedThisReferences.toList(),
capturedReceiverParameters.toList(),
capturedVariables.toList()
)
fun addNested(closure: Closure) {
fillInCapturedThisReferences(closure)
fillInNestedClosure(capturedReceiverParameters, closure.capturedReceiverParameters)
fillInNestedClosure(capturedVariables, closure.capturedVariables)
}
private fun fillInCapturedThisReferences(closure: Closure) {
if (owner is ClassDescriptor) {
closure.capturedThisReferences.filterTo(capturedThisReferences) { it != owner }
}
else if (owner is CallableMemberDescriptor && owner.dispatchReceiverParameter != null) {
val ownerClass = owner.containingDeclaration as? ClassDescriptor
closure.capturedThisReferences.filterTo(capturedThisReferences) { it != ownerClass }
}
}
private fun <T : CallableDescriptor> fillInNestedClosure(destination: MutableSet<T>, nested: List<T>) {
nested.filterTo(destination) {
it.containingDeclaration != owner
}
}
fun addCapturedThis(classDescriptor: ClassDescriptor) {
if (owner is ClassDescriptor && owner != classDescriptor) {
capturedThisReferences.add(classDescriptor)
}
else if (owner is CallableMemberDescriptor && owner.containingDeclaration != classDescriptor) {
capturedThisReferences.add(classDescriptor)
}
}
}
private val closuresStack = ArrayDeque<ClosureBuilder>()
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
}
override fun visitClass(declaration: IrClass) {
val classDescriptor = declaration.descriptor
val closureBuilder = ClosureBuilder(classDescriptor)
closuresStack.push(closureBuilder)
declaration.acceptChildrenVoid(this)
closuresStack.pop()
val closure = closureBuilder.buildClosure()
if (DescriptorUtils.isLocal(classDescriptor)) {
recordClassClosure(classDescriptor, closure)
}
closuresStack.peek()?.addNested(closure)
}
override fun visitGeneralFunction(declaration: IrGeneralFunction) {
val functionDescriptor = declaration.descriptor
val closureBuilder = ClosureBuilder(functionDescriptor)
closuresStack.push(closureBuilder)
declaration.acceptChildrenVoid(this)
closuresStack.pop()
val closure = closureBuilder.buildClosure()
if (DescriptorUtils.isLocal(functionDescriptor)) {
recordFunctionClosure(functionDescriptor, closure)
}
closuresStack.peek()?.addNested(closure)
}
override fun visitLocalPropertyAccessor(declaration: IrLocalPropertyAccessor) {
// Local property accessors are created for delegated local properties and have no closure.
}
override fun visitThisReference(expression: IrThisReference) {
closuresStack.peek().addCapturedThis(expression.classDescriptor)
}
override fun visitVariableAccess(expression: IrVariableAccessExpression) {
val closureBuilder = closuresStack.peek()
val variableDescriptor = expression.descriptor
if (variableDescriptor.containingDeclaration != closureBuilder.owner) {
closureBuilder.capturedVariables.add(variableDescriptor)
}
expression.acceptChildrenVoid(this)
}
override fun visitGetExtensionReceiver(expression: IrGetExtensionReceiver) {
val closureBuilder = closuresStack.peek()
val receiverDescriptor = expression.descriptor
if (receiverDescriptor.containingDeclaration != closureBuilder.owner) {
closureBuilder.capturedReceiverParameters.add(receiverDescriptor)
}
}
}
+1
View File
@@ -15,5 +15,6 @@
<orderEntry type="module" module-name="ir.tree" scope="TEST" />
<orderEntry type="module" module-name="ir.psi2ir" scope="TEST" />
<orderEntry type="library" scope="TEST" name="junit-4.12" level="project" />
<orderEntry type="module" module-name="backend.common" scope="TEST" />
</component>
</module>
@@ -0,0 +1,83 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.ir
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
import org.jetbrains.kotlin.test.KotlinTestUtils
import org.jetbrains.kotlin.xbackend.common.AbstractClosureAnnotator
import org.jetbrains.kotlin.xbackend.common.Closure
import java.io.File
import java.io.PrintWriter
import java.io.StringWriter
abstract class AbstractClosureAnnotatorTestCase : AbstractIrGeneratorTestCase() {
override fun doTest(wholeFile: File, testFiles: List<TestFile>) {
val dir = wholeFile.parentFile
val ignoreErrors = shouldIgnoreErrors(wholeFile)
for ((testFile, irFile) in generateIrFilesAsSingleModule(testFiles, ignoreErrors)) {
doTestIrFileAgainstExpectations(dir, testFile, irFile)
}
}
private fun doTestIrFileAgainstExpectations(dir: File, testFile: TestFile, irFile: IrFile) {
val expectedFile = File(dir, testFile.name.replace(".kt", ".closure"))
val actualClosures = renderClosures(irFile)
KotlinTestUtils.assertEqualsToFile(expectedFile, actualClosures)
}
private fun renderClosures(irFile: IrFile): String {
val actualStringWriter = StringWriter()
val actualOut = PrintWriter(actualStringWriter)
irFile.acceptChildrenVoid(object : AbstractClosureAnnotator() {
override fun recordClassClosure(classDescriptor: ClassDescriptor, closure: Closure) {
actualOut.println("Closure for class ${classDescriptor.name}:")
printClosure(closure)
actualOut.println()
}
override fun recordFunctionClosure(functionDescriptor: FunctionDescriptor, closure: Closure) {
if (functionDescriptor is ConstructorDescriptor) {
actualOut.println("Closure for constructor ${functionDescriptor.containingDeclaration.name}:")
}
else {
actualOut.println("Closure for function ${functionDescriptor.name}:")
}
printClosure(closure)
actualOut.println()
}
private fun printClosure(closure: Closure) {
closure.capturedThisReferences.forEach {
actualOut.println(" 'this' for ${it.name}")
}
closure.capturedReceiverParameters.forEach {
actualOut.println(" receiver for ${it.containingDeclaration.name}")
}
closure.capturedVariables.forEach {
actualOut.println(" variable ${it.name}")
}
}
})
return actualStringWriter.toString()
}
}
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.ir
import junit.framework.TestCase
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.codegen.CodegenTestCase
@@ -28,6 +29,8 @@ import org.jetbrains.kotlin.test.ConfigurationKind
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.jetbrains.kotlin.test.KotlinTestUtils.getAnnotationsJar
import java.io.File
import java.io.FileWriter
import java.io.PrintWriter
import java.util.*
abstract class AbstractIrGeneratorTestCase : CodegenTestCase() {
@@ -86,6 +89,24 @@ abstract class AbstractIrGeneratorTestCase : CodegenTestCase() {
val ktFiles = testFiles.filter { it.name.endsWith(".kt") }
return ktFiles.zip(irModule.files).toMap()
}
companion object {
private val IGNORE_ERRORS_PATTERN = Regex("""// !IGNORE_ERRORS""")
internal fun shouldIgnoreErrors(wholeFile: File): Boolean =
IGNORE_ERRORS_PATTERN.containsMatchIn(wholeFile.readText())
internal fun createExpectedTextFile(testFile: TestFile, dir: File, fileName: String): File {
val textFile = File(dir, fileName)
if (!textFile.exists()) {
TestCase.assertTrue("Can't create an expected text containingFile: ${textFile.absolutePath}", textFile.createNewFile())
PrintWriter(FileWriter(textFile)).use {
it.println("$fileName: new expected text containingFile for ${testFile.name}")
}
}
return textFile
}
}
}
@@ -75,10 +75,6 @@ abstract class AbstractIrTextTestCase : AbstractIrGeneratorTestCase() {
companion object {
private val EXPECTED_OCCURRENCES_PATTERN = Regex("""^\s*//\s*(\d+)\s*(.*)$""")
private val IR_FILE_TXT_PATTERN = Regex("""// IR_FILE: (.*)$""")
private val IGNORE_ERRORS_PATTERN = Regex("""// !IGNORE_ERRORS""")
internal fun shouldIgnoreErrors(wholeFile: File): Boolean =
IGNORE_ERRORS_PATTERN.containsMatchIn(wholeFile.readText())
internal fun parseExpectations(dir: File, testFile: TestFile): Expectations {
val regexps = ArrayList<RegexpInText>()
@@ -102,17 +98,6 @@ abstract class AbstractIrTextTestCase : AbstractIrGeneratorTestCase() {
return Expectations(regexps, treeFiles)
}
internal fun createExpectedTextFile(testFile: TestFile, dir: File, fileName: String): File {
val textFile = File(dir, fileName)
if (!textFile.exists()) {
TestCase.assertTrue("Can't create an IR expected text containingFile: ${textFile.absolutePath}", textFile.createNewFile())
PrintWriter(FileWriter(textFile)).use {
it.println("$fileName: new IR expected text containingFile for ${testFile.name}")
}
}
return textFile
}
}
}
@@ -60,8 +60,9 @@ interface IrElementVisitor<out R, in D> {
fun visitSingletonReference(expression: IrGetSingletonValue, data: D) = visitDeclarationReference(expression, data)
fun visitGetObjectValue(expression: IrGetObjectValue, data: D) = visitSingletonReference(expression, data)
fun visitGetEnumValue(expression: IrGetEnumValue, data: D) = visitSingletonReference(expression, data)
fun visitGetVariable(expression: IrGetVariable, data: D) = visitDeclarationReference(expression, data)
fun visitSetVariable(expression: IrSetVariable, data: D) = visitDeclarationReference(expression, data)
fun visitVariableAccess(expression: IrVariableAccessExpression, data: D) = visitDeclarationReference(expression, data)
fun visitGetVariable(expression: IrGetVariable, data: D) = visitVariableAccess(expression, data)
fun visitSetVariable(expression: IrSetVariable, data: D) = visitVariableAccess(expression, data)
fun visitBackingFieldReference(expression: IrBackingFieldExpression, data: D) = visitDeclarationReference(expression, data)
fun visitGetBackingField(expression: IrGetBackingField, data: D) = visitBackingFieldReference(expression, data)
fun visitSetBackingField(expression: IrSetBackingField, data: D) = visitBackingFieldReference(expression, data)
@@ -123,10 +123,13 @@ interface IrElementVisitorVoid : IrElementVisitor<Unit, Nothing?> {
fun visitGetEnumValue(expression: IrGetEnumValue) = visitSingletonReference(expression)
override fun visitGetEnumValue(expression: IrGetEnumValue, data: Nothing?) = visitGetEnumValue(expression)
fun visitGetVariable(expression: IrGetVariable) = visitDeclarationReference(expression)
fun visitVariableAccess(expression: IrVariableAccessExpression) = visitDeclarationReference(expression)
override fun visitVariableAccess(expression: IrVariableAccessExpression, data: Nothing?) = visitVariableAccess(expression)
fun visitGetVariable(expression: IrGetVariable) = visitVariableAccess(expression)
override fun visitGetVariable(expression: IrGetVariable, data: Nothing?) = visitGetVariable(expression)
fun visitSetVariable(expression: IrSetVariable) = visitDeclarationReference(expression)
fun visitSetVariable(expression: IrSetVariable) = visitVariableAccess(expression)
override fun visitSetVariable(expression: IrSetVariable, data: Nothing?) = visitSetVariable(expression)
fun visitBackingFieldReference(expression: IrBackingFieldExpression) = visitDeclarationReference(expression)
@@ -0,0 +1,7 @@
Closure for function test2:
receiver for outer
variable y
Closure for function test1:
receiver for outer
@@ -0,0 +1,7 @@
fun String.outer(x: Int) {
fun test1(x: Int, y: Int) {
fun test2(x: Int) = this + x + y
test2(y).length + x + y
}
test1(x, x * x)
}
@@ -0,0 +1,15 @@
Closure for constructor <no name provided>:
Closure for function test1:
'this' for C
Closure for function <anonymous>:
'this' for C
'this' for <no name provided>
Closure for function test2:
'this' for C
Closure for class <no name provided>:
'this' for C
@@ -0,0 +1,6 @@
class C {
fun foo() = object {
fun test1() = this@C.toString() + this.toString()
fun test2() = { this@C.toString() + this.toString() }
}
}
@@ -0,0 +1,6 @@
Closure for constructor <no name provided>:
Closure for function test6:
Closure for class <no name provided>:
@@ -0,0 +1,10 @@
fun Int.test1(x: Int) = this + x
class Test2(val x: Int) {
fun test3() = x
fun Int.test4() = this + x
}
val test5 = object {
fun Int.test6(x: Int) = this + x
}
@@ -0,0 +1,55 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.ir;
import com.intellij.testFramework.TestDataPath;
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.TestMetadata;
import org.junit.runner.RunWith;
import java.io.File;
import java.util.regex.Pattern;
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@TestMetadata("compiler/testData/ir/closureAnnotator")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public class ClosureAnnotatorTestCaseGenerated extends AbstractClosureAnnotatorTestCase {
public void testAllFilesPresentInClosureAnnotator() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/ir/closureAnnotator"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("localFunctions.kt")
public void testLocalFunctions() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/ir/closureAnnotator/localFunctions.kt");
doTest(fileName);
}
@TestMetadata("multipleThisReferences.kt")
public void testMultipleThisReferences() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/ir/closureAnnotator/multipleThisReferences.kt");
doTest(fileName);
}
@TestMetadata("topLevelDeclarations.kt")
public void testTopLevelDeclarations() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/ir/closureAnnotator/topLevelDeclarations.kt");
doTest(fileName);
}
}
@@ -121,6 +121,7 @@ import org.jetbrains.kotlin.idea.stubs.AbstractMultiFileHighlightingTest
import org.jetbrains.kotlin.idea.stubs.AbstractResolveByStubTest
import org.jetbrains.kotlin.idea.stubs.AbstractStubBuilderTest
import org.jetbrains.kotlin.integration.AbstractAntTaskTest
import org.jetbrains.kotlin.ir.AbstractClosureAnnotatorTestCase
import org.jetbrains.kotlin.ir.AbstractIrTextTestCase
import org.jetbrains.kotlin.ir.AbstractPsi2IrAcceptanceTestCase
import org.jetbrains.kotlin.j2k.AbstractJavaToKotlinConverterForWebDemoTest
@@ -249,6 +250,10 @@ fun main(args: Array<String>) {
model("codegen/box")
}
testClass<AbstractClosureAnnotatorTestCase> {
model("ir/closureAnnotator")
}
testClass<AbstractBytecodeListingTest>() {
model("codegen/bytecodeListing")
}