From 2a97fb17ba50ee443aa245c203ba7fc39bc182df Mon Sep 17 00:00:00 2001 From: Dmitry Petrov Date: Tue, 28 Mar 2017 14:40:35 +0300 Subject: [PATCH] Fix source information mapping in PsiSourceManager Add tests for source information mapping KT-17108 source information corrupted on PSI -> IR transformation --- .../kotlin/psi2ir/PsiSourceManager.kt | 2 +- .../augmentedAssignmentWithExpression.kt | 23 +++++++ .../augmentedAssignmentWithExpression.txt | 33 ++++++++++ compiler/testData/ir/sourceRanges/kt17108.kt | 27 ++++++++ compiler/testData/ir/sourceRanges/kt17108.txt | 7 ++ .../kotlin/ir/AbstractIrGeneratorTestCase.kt | 13 ++-- .../ir/AbstractIrSourceRangesTestCase.kt | 64 +++++++++++++++++++ .../ir/IrSourceRangesTestCaseGenerated.java | 50 +++++++++++++++ .../kotlin/generators/tests/GenerateTests.kt | 5 ++ 9 files changed, 218 insertions(+), 6 deletions(-) create mode 100644 compiler/testData/ir/sourceRanges/augmentedAssignmentWithExpression.kt create mode 100644 compiler/testData/ir/sourceRanges/augmentedAssignmentWithExpression.txt create mode 100644 compiler/testData/ir/sourceRanges/kt17108.kt create mode 100644 compiler/testData/ir/sourceRanges/kt17108.txt create mode 100644 compiler/tests/org/jetbrains/kotlin/ir/AbstractIrSourceRangesTestCase.kt create mode 100644 compiler/tests/org/jetbrains/kotlin/ir/IrSourceRangesTestCaseGenerated.java diff --git a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/PsiSourceManager.kt b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/PsiSourceManager.kt index 09cc8510707..a5f0d854f53 100644 --- a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/PsiSourceManager.kt +++ b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/PsiSourceManager.kt @@ -42,7 +42,7 @@ class PsiSourceManager : SourceManager { override fun getLineNumber(offset: Int): Int { val index = lineStartOffsets.binarySearch(offset) - return if (index >= 0) index else -index - 1 + return if (index >= 0) index else -index - 2 } override fun getColumnNumber(offset: Int): Int { diff --git a/compiler/testData/ir/sourceRanges/augmentedAssignmentWithExpression.kt b/compiler/testData/ir/sourceRanges/augmentedAssignmentWithExpression.kt new file mode 100644 index 00000000000..6bf6ad8457b --- /dev/null +++ b/compiler/testData/ir/sourceRanges/augmentedAssignmentWithExpression.kt @@ -0,0 +1,23 @@ +package test + +class Host { + operator fun plusAssign(x: Int) {} + + fun test1() { + this += 1 + } +} + +fun foo() = Host() + +fun Host.test2() { + this += 1 +} + +fun test3() { + foo() += 1 +} + +fun test4(a: () -> Host) { + a() += 1 +} diff --git a/compiler/testData/ir/sourceRanges/augmentedAssignmentWithExpression.txt b/compiler/testData/ir/sourceRanges/augmentedAssignmentWithExpression.txt new file mode 100644 index 00000000000..738ae815b3f --- /dev/null +++ b/compiler/testData/ir/sourceRanges/augmentedAssignmentWithExpression.txt @@ -0,0 +1,33 @@ +@0:0..23:0 FILE /augmentedAssignmentWithExpression.kt + @2:0..8:1 CLASS CLASS Host + @2:0..8:1 CONSTRUCTOR public constructor Host() + @2:0..8:1 BLOCK_BODY + @2:0..8:1 DELEGATING_CONSTRUCTOR_CALL 'constructor Any()' + @2:0..8:1 INSTANCE_INITIALIZER_CALL classDescriptor='Host' + @3:4..38 FUN public final operator fun plusAssign(x: kotlin.Int): kotlin.Unit + @3:36..38 BLOCK_BODY + @5:4..7:5 FUN public final fun test1(): kotlin.Unit + @5:16..7:5 BLOCK_BODY + @6:8..17 CALL 'plusAssign(Int): Unit' type=kotlin.Unit origin=PLUSEQ + @6:8..12 GET_VAR '' type=test.Host origin=null + @6:16..17 CONST Int type=kotlin.Int value='1' + @10:0..18 FUN public fun foo(): test.Host + @10:12..18 BLOCK_BODY + @10:12..18 RETURN type=kotlin.Nothing from='foo(): Host' + @10:12..18 CALL 'constructor Host()' type=test.Host origin=null + @12:0..14:1 FUN public fun test.Host.test2(): kotlin.Unit + @12:17..14:1 BLOCK_BODY + @13:4..13 CALL 'plusAssign(Int): Unit' type=kotlin.Unit origin=PLUSEQ + @13:4..8 GET_VAR '' type=test.Host origin=null + @13:12..13 CONST Int type=kotlin.Int value='1' + @16:0..18:1 FUN public fun test3(): kotlin.Unit + @16:12..18:1 BLOCK_BODY + @17:4..14 CALL 'plusAssign(Int): Unit' type=kotlin.Unit origin=PLUSEQ + @17:4..9 CALL 'foo(): Host' type=test.Host origin=null + @17:13..14 CONST Int type=kotlin.Int value='1' + @20:0..22:1 FUN public fun test4(a: () -> test.Host): kotlin.Unit + @20:25..22:1 BLOCK_BODY + @21:4..12 CALL 'plusAssign(Int): Unit' type=kotlin.Unit origin=PLUSEQ + @21:4..7 CALL 'invoke(): Host' type=test.Host origin=INVOKE + @21:4..5 GET_VAR 'value-parameter a: () -> Host' type=() -> test.Host origin=VARIABLE_AS_FUNCTION + @21:11..12 CONST Int type=kotlin.Int value='1' diff --git a/compiler/testData/ir/sourceRanges/kt17108.kt b/compiler/testData/ir/sourceRanges/kt17108.kt new file mode 100644 index 00000000000..ded86b805e8 --- /dev/null +++ b/compiler/testData/ir/sourceRanges/kt17108.kt @@ -0,0 +1,27 @@ +/** +* Represents a value which is either `true` or `false`. On the JVM, non-nullable values of this type are +* represented as values of the primitive type `boolean`. +*/ +public interface Boolean { + /** + * Returns the inverse of this boolean. + */ + public operator fun not(): Boolean + + /** + * Performs a logical `and` operation between this Boolean and the [other] one. + */ + infix fun and(other: Boolean): Boolean + + /** + * Performs a logical `or` operation between this Boolean and the [other] one. + */ + infix fun or(other: Boolean): Boolean + + /** + * Performs a logical `xor` operation between this Boolean and the [other] one. + */ + infix fun xor(other: Boolean): Boolean + + fun compareTo(other: Boolean): Int +} \ No newline at end of file diff --git a/compiler/testData/ir/sourceRanges/kt17108.txt b/compiler/testData/ir/sourceRanges/kt17108.txt new file mode 100644 index 00000000000..c2bf1bc950b --- /dev/null +++ b/compiler/testData/ir/sourceRanges/kt17108.txt @@ -0,0 +1,7 @@ +@0:0..26:1 FILE /kt17108.kt + @0:0..26:1 CLASS INTERFACE Boolean + @5:4..8:38 FUN public abstract operator fun not(): Boolean + @10:4..13:42 FUN public abstract infix fun and(other: Boolean): Boolean + @15:4..18:41 FUN public abstract infix fun or(other: Boolean): Boolean + @20:4..23:42 FUN public abstract infix fun xor(other: Boolean): Boolean + @25:4..38 FUN public abstract fun compareTo(other: Boolean): kotlin.Int diff --git a/compiler/tests/org/jetbrains/kotlin/ir/AbstractIrGeneratorTestCase.kt b/compiler/tests/org/jetbrains/kotlin/ir/AbstractIrGeneratorTestCase.kt index cda099a2e81..bc43df153b8 100644 --- a/compiler/tests/org/jetbrains/kotlin/ir/AbstractIrGeneratorTestCase.kt +++ b/compiler/tests/org/jetbrains/kotlin/ir/AbstractIrGeneratorTestCase.kt @@ -83,7 +83,7 @@ abstract class AbstractIrGeneratorTestCase : CodegenTestCase() { protected fun generateIrModule(ignoreErrors: Boolean = false): IrModuleFragment { assert(myFiles != null) { "myFiles not initialized" } assert(myEnvironment != null) { "myEnvironment not initialized" } - return generateIrModule(myFiles.psiFiles, myEnvironment, ignoreErrors) + return generateIrModule(myFiles.psiFiles, myEnvironment, Psi2IrTranslator(Psi2IrConfiguration(ignoreErrors))) } protected fun generateIrFilesAsSingleModule(testFiles: List, ignoreErrors: Boolean = false): Map { @@ -109,17 +109,20 @@ abstract class AbstractIrGeneratorTestCase : CodegenTestCase() { return textFile } - fun generateIrModule(ktFiles: List, environment: KotlinCoreEnvironment, ignoreErrors: Boolean = false): IrModuleFragment { + fun generateIrModule(ktFiles: List, environment: KotlinCoreEnvironment, psi2ir: Psi2IrTranslator): IrModuleFragment { val analysisResult = JvmResolveUtil.analyze(ktFiles, environment) - if (!ignoreErrors) { + if (!psi2ir.configuration.ignoreErrors) { analysisResult.throwIfError() AnalyzingUtils.throwExceptionOnErrors(analysisResult.bindingContext) } - return generateIrModule(ktFiles, analysisResult.moduleDescriptor, analysisResult.bindingContext, ignoreErrors) + return generateIrModule(ktFiles, analysisResult.moduleDescriptor, analysisResult.bindingContext, psi2ir) } fun generateIrModule(ktFiles: List, moduleDescriptor: ModuleDescriptor, bindingContext: BindingContext, ignoreErrors: Boolean = false) = - Psi2IrTranslator(Psi2IrConfiguration(ignoreErrors)).generateModule(moduleDescriptor, ktFiles, bindingContext) + generateIrModule(ktFiles, moduleDescriptor, bindingContext, Psi2IrTranslator(Psi2IrConfiguration(ignoreErrors))) + + fun generateIrModule(ktFiles: List, moduleDescriptor: ModuleDescriptor, bindingContext: BindingContext, psi2ir: Psi2IrTranslator) = + psi2ir.generateModule(moduleDescriptor, ktFiles, bindingContext) } } diff --git a/compiler/tests/org/jetbrains/kotlin/ir/AbstractIrSourceRangesTestCase.kt b/compiler/tests/org/jetbrains/kotlin/ir/AbstractIrSourceRangesTestCase.kt new file mode 100644 index 00000000000..792d4c9e620 --- /dev/null +++ b/compiler/tests/org/jetbrains/kotlin/ir/AbstractIrSourceRangesTestCase.kt @@ -0,0 +1,64 @@ +/* + * Copyright 2010-2017 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.ir.util.RenderIrElementVisitor +import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid +import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid +import org.jetbrains.kotlin.ir.visitors.acceptVoid +import org.jetbrains.kotlin.test.KotlinTestUtils +import org.jetbrains.kotlin.utils.Printer +import java.io.File + +abstract class AbstractIrSourceRangesTestCase : AbstractIrGeneratorTestCase() { + override fun doTest(wholeFile: File, testFiles: List) { + val dir = wholeFile.parentFile + val testFileToIrFile = generateIrFilesAsSingleModule(testFiles) + for ((testFile, irFile) in testFileToIrFile) { + val irFileDump = irFile.dumpWithSourceLocations(irFile.fileEntry) + val expectedSourceLocations = File(dir, testFile.name.replace(".kt", ".txt")) + KotlinTestUtils.assertEqualsToFile(expectedSourceLocations, irFileDump) + } + } + + private fun IrElement.dumpWithSourceLocations(fileEntry: SourceManager.FileEntry): String = + StringBuilder().also { + acceptVoid(DumpSourceLocations(it, fileEntry)) + }.toString() + + private class DumpSourceLocations( + out: Appendable, + val fileEntry: SourceManager.FileEntry + ) : IrElementVisitorVoid { + val printer = Printer(out, " ") + val elementRenderer = RenderIrElementVisitor() + + override fun visitElement(element: IrElement) { + val sourceRangeInfo = fileEntry.getSourceRangeInfo(element.startOffset, element.endOffset) + printer.println("@${sourceRangeInfo.render()} ${element.accept(elementRenderer, null)}") + printer.pushIndent() + element.acceptChildrenVoid(this) + printer.popIndent() + } + + private fun SourceRangeInfo.render() = + if (startLineNumber == endLineNumber) + "$startLineNumber:$startColumnNumber..$endColumnNumber" + else + "$startLineNumber:$startColumnNumber..$endLineNumber:$endColumnNumber" + } +} diff --git a/compiler/tests/org/jetbrains/kotlin/ir/IrSourceRangesTestCaseGenerated.java b/compiler/tests/org/jetbrains/kotlin/ir/IrSourceRangesTestCaseGenerated.java new file mode 100644 index 00000000000..6223f2a7389 --- /dev/null +++ b/compiler/tests/org/jetbrains/kotlin/ir/IrSourceRangesTestCaseGenerated.java @@ -0,0 +1,50 @@ +/* + * Copyright 2010-2017 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.TargetBackend; +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/sourceRanges") +@TestDataPath("$PROJECT_ROOT") +@RunWith(JUnit3RunnerWithInners.class) +public class IrSourceRangesTestCaseGenerated extends AbstractIrSourceRangesTestCase { + public void testAllFilesPresentInSourceRanges() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/ir/sourceRanges"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("augmentedAssignmentWithExpression.kt") + public void testAugmentedAssignmentWithExpression() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/ir/sourceRanges/augmentedAssignmentWithExpression.kt"); + doTest(fileName); + } + + @TestMetadata("kt17108.kt") + public void testKt17108() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/ir/sourceRanges/kt17108.kt"); + doTest(fileName); + } +} diff --git a/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt b/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt index d66df71f881..875e5549a37 100755 --- a/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt +++ b/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt @@ -127,6 +127,7 @@ 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.AbstractIrCfgTestCase +import org.jetbrains.kotlin.ir.AbstractIrSourceRangesTestCase import org.jetbrains.kotlin.ir.AbstractIrTextTestCase import org.jetbrains.kotlin.j2k.AbstractJavaToKotlinConverterForWebDemoTest import org.jetbrains.kotlin.j2k.AbstractJavaToKotlinConverterMultiFileTest @@ -276,6 +277,10 @@ fun main(args: Array) { model("ir/irCfg") } + testClass { + model("ir/sourceRanges") + } + testClass { model("codegen/bytecodeListing") }