JS property accessors inlining (KT-13456)

This commit is contained in:
Anton Bannykh
2016-10-28 16:28:21 +03:00
parent 56ef09c09c
commit 0dc9121a91
24 changed files with 719 additions and 28 deletions
+162
View File
@@ -0,0 +1,162 @@
// PROPERTY_NOT_USED: p1
// PROPERTY_NOT_READ_FROM: p2
// PROPERTY_NOT_WRITTEN_TO: p3
// CHECK_NOT_CALLED: get_p4
// CHECK_NOT_CALLED: set_p4
// CHECK_NOT_CALLED: get_p5
// CHECK_NOT_CALLED: set_p6
// PROPERTY_NOT_USED: p7
// PROPERTY_NOT_READ_FROM: p8
// PROPERTY_NOT_WRITTEN_TO: p9
// CHECK_NOT_CALLED: get_p10_s8ev3o$
// CHECK_NOT_CALLED: set_p10_rksjo2$
// CHECK_NOT_CALLED: get_p11_s8ev3o$
// CHECK_NOT_CALLED: set_p12_rksjo2$
// CHECK_NOT_CALLED: get_p13
// CHECK_NOT_CALLED: set_p13
// CHECK_NOT_CALLED: get_p14
// CHECK_NOT_CALLED: set_p15
// FILE: 1.kt
package test
var a = 0
inline var p1: Int
get() = a + 10000
set(v: Int) {
a = v + 100
}
var p2: Int
inline get() = a + 20000
set(v: Int) {
a = v + 200
}
var p3: Int
get() = a + 30000
inline set(v: Int) {
a = v + 300
}
inline var Int.p4: Int
get() = this * 100 + a + 40000
set(v: Int) {
a = this + v + 400
}
var Int.p5: Int
inline get() = this * 100 + a + 50000
set(v: Int) {
a = this + v + 500
}
var Int.p6: Int
get() = this * 100 + a + 60000
inline set(v: Int) {
a = this + v + 600
}
class A {
inline var p7: Int
get() = a + 70000
set(v: Int) {
a = v + 700
}
var p8: Int
inline get() = a + 80000
set(v: Int) {
a = v + 800
}
var p9: Int
get() = a + 90000
inline set(v: Int) {
a = v + 900
}
inline var Int.p10: Int
get() = this * 100 + a + 100000
set(v: Int) {
a = this + v + 1000
}
var Int.p11: Int
inline get() = this * 100 + a + 110000
set(v: Int) {
a = this + v + 1100
}
var Int.p12: Int
get() = this * 100 + a + 120000
inline set(v: Int) {
a = this + v + 1200
}
}
inline var A.p13: Int
get() = a + 130000
set(v: Int) {
a = v + 1300
}
var A.p14: Int
inline get() = a + 140000
set(v: Int) {
a = v + 1400
}
var A.p15: Int
get() = a + 150000
inline set(v: Int) {
a = v + 1500
}
// FILE: 2.kt
import test.*
fun box(): String {
p1 = 1
if (p1 != 10101) return "test1: $p1"
p2 = 2
if (p2 != 20202) return "test2: $p2"
p3 = 3
if (p3 != 30303) return "test3: $p3"
4000000.p4 = 4
if (4000000.p4 != 404040404) return "test4: ${4000000.p4}"
5000000.p5 = 5
if (5000000.p5 != 505050505) return "test5: ${5000000.p5}"
6000000.p6 = 6
if (6000000.p6 != 606060606) return "test6: ${6000000.p6}"
val a = A()
a.p7 = 7
if (a.p7 != 70707) return "test7: ${a.p7}"
a.p8 = 8
if (a.p8 != 80808) return "test8: ${a.p8}"
a.p9 = 9
if (a.p9 != 90909) return "test9: ${a.p9}"
with (a) {
10000000.p10 = 10
if (10000000.p10 != 1010101010) return "test10: ${10000000.p10}"
11000000.p11 = 11
if (11000000.p11 != 1111111111) return "test11: ${11000000.p11}"
12000000.p12 = 12
if (12000000.p12 != 1212121212) return "test12: ${12000000.p12}"
}
a.p13 = 13
if (a.p13 != 131313) return "test13: ${a.p13}"
a.p14 = 14
if (a.p14 != 141414) return "test14: ${a.p14}"
a.p15 = 15
if (a.p15 != 151515) return "test15: ${a.p15}"
return "OK"
}
+3 -2
View File
@@ -1,9 +1,10 @@
// WITH_RUNTIME
// WITH_REFLECT
// FILE: 1.kt
package test
inline val <reified T: Any> T.value: String
get() = T::class.java.simpleName
get() = T::class.simpleName!!
// FILE: 2.kt
import test.*
@@ -11,5 +12,5 @@ import test.*
class OK
fun box(): String {
return OK().value
return OK().value ?: "fail"
}
+2 -1
View File
@@ -1,11 +1,12 @@
// WITH_RUNTIME
// WITH_REFLECT
// FILE: 1.kt
package test
var bvalue: String = ""
inline var <reified T : Any> T.value: String
get() = T::class.java.simpleName + bvalue
get() = T::class.simpleName!! + bvalue
set(p: String) {
bvalue = p
}
@@ -1733,6 +1733,12 @@ public class BlackBoxInlineCodegenTestGenerated extends AbstractBlackBoxInlineCo
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxInline/property"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("property.kt")
public void testProperty() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/property/property.kt");
doTest(fileName);
}
@TestMetadata("reifiedVal.kt")
public void testReifiedVal() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/property/reifiedVal.kt");
@@ -1733,6 +1733,12 @@ public class CompileKotlinAgainstInlineKotlinTestGenerated extends AbstractCompi
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxInline/property"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("property.kt")
public void testProperty() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/property/property.kt");
doTest(fileName);
}
@TestMetadata("reifiedVal.kt")
public void testReifiedVal() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/property/reifiedVal.kt");
@@ -133,6 +133,7 @@ import org.jetbrains.kotlin.jps.incremental.AbstractProtoComparisonTest
import org.jetbrains.kotlin.js.test.semantics.AbstractBoxJsTest
import org.jetbrains.kotlin.js.test.semantics.AbstractJsCodegenBoxTest
import org.jetbrains.kotlin.js.test.semantics.AbstractNonLocalReturnsTest
import org.jetbrains.kotlin.js.test.semantics.AbstractPropertyAccessorsInlineTests
import org.jetbrains.kotlin.jvm.compiler.*
import org.jetbrains.kotlin.jvm.runtime.AbstractJvm8RuntimeDescriptorLoaderTest
import org.jetbrains.kotlin.jvm.runtime.AbstractJvmRuntimeDescriptorLoaderTest
@@ -1188,6 +1189,10 @@ fun main(args: Array<String>) {
testClass<AbstractNonLocalReturnsTest> {
model("codegen/boxInline/nonLocalReturns/", targetBackend = TargetBackend.JS)
}
testClass<AbstractPropertyAccessorsInlineTests>() {
model("codegen/boxInline/property/", targetBackend = TargetBackend.JS)
}
}
}
@@ -33,6 +33,12 @@ var JsInvocation.descriptor: CallableDescriptor? by MetadataProperty(default = n
var JsInvocation.psiElement: PsiElement? by MetadataProperty(default = null)
var JsNameRef.inlineStrategy: InlineStrategy? by MetadataProperty(default = null)
var JsNameRef.descriptor: CallableDescriptor? by MetadataProperty(default = null)
var JsNameRef.psiElement: PsiElement? by MetadataProperty(default = null)
var JsFunction.isLocal: Boolean by MetadataProperty(default = false)
var JsParameter.hasDefaultValue: Boolean by MetadataProperty(default = false)
@@ -23,6 +23,8 @@ import kotlin.jvm.functions.Function1;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.descriptors.CallableDescriptor;
import org.jetbrains.kotlin.descriptors.PropertyGetterDescriptor;
import org.jetbrains.kotlin.descriptors.PropertySetterDescriptor;
import org.jetbrains.kotlin.diagnostics.DiagnosticSink;
import org.jetbrains.kotlin.diagnostics.Errors;
import org.jetbrains.kotlin.js.inline.clean.FunctionPostProcessor;
@@ -43,6 +45,7 @@ import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.flattenStatemen
public class JsInliner extends JsVisitorWithContextImpl {
private final Map<JsName, JsFunction> functions;
private final Map<String, JsFunction> accessors;
private final Stack<JsInliningContext> inliningContexts = new Stack<JsInliningContext>();
private final Set<JsFunction> processedFunctions = CollectionUtilsKt.IdentitySet();
private final Set<JsFunction> inProcessFunctions = CollectionUtilsKt.IdentitySet();
@@ -65,7 +68,8 @@ public class JsInliner extends JsVisitorWithContextImpl {
public static JsProgram process(@NotNull TranslationContext context) {
JsProgram program = context.program();
Map<JsName, JsFunction> functions = CollectUtilsKt.collectNamedFunctions(program);
JsInliner inliner = new JsInliner(functions, new FunctionReader(context), context.bindingTrace());
Map<String, JsFunction> accessors = CollectUtilsKt.collectAccessors(program);
JsInliner inliner = new JsInliner(functions, accessors, new FunctionReader(context), context.bindingTrace());
inliner.accept(program);
RemoveUnusedFunctionDefinitionsKt.removeUnusedFunctionDefinitions(program, functions);
return program;
@@ -73,14 +77,85 @@ public class JsInliner extends JsVisitorWithContextImpl {
private JsInliner(
@NotNull Map<JsName, JsFunction> functions,
@NotNull Map<String, JsFunction> accessors,
@NotNull FunctionReader functionReader,
@NotNull DiagnosticSink trace
) {
this.functions = functions;
this.accessors = accessors;
this.functionReader = functionReader;
this.trace = trace;
}
@Override
public boolean visit(@NotNull JsNameRef x, @NotNull JsContext ctx) {
JsInvocation dummy = tryCreatePropertyGetterInvocation(x);
if (dummy != null) {
return visit(dummy, ctx);
}
return super.visit(x, ctx);
}
@Override
public void endVisit(@NotNull JsNameRef x, @NotNull JsContext ctx) {
JsInvocation dummy = tryCreatePropertyGetterInvocation(x);
if (dummy != null) {
endVisit(dummy, ctx);
}
super.visit(x, ctx);
}
@Override
public boolean visit(@NotNull JsBinaryOperation x, @NotNull JsContext ctx) {
JsInvocation dummy = tryCreatePropertySetterInvocation(x);
if (dummy != null) {
return visit(dummy, ctx);
}
return super.visit(x, ctx);
}
@Override
public void endVisit(@NotNull JsBinaryOperation x, @NotNull JsContext ctx) {
JsInvocation dummy = tryCreatePropertySetterInvocation(x);
if (dummy != null) {
// Prevent FunctionInlineMutator from creating a variable for the result (there is none, because assignment is a statement)
// TODO is there a better way to achieve this?
getInliningContext().getStatementContext().replaceMe(new JsExpressionStatement(dummy));
endVisit(dummy, ctx);
}
super.visit(x, ctx);
}
@Nullable
private static JsInvocation tryCreatePropertyGetterInvocation(@NotNull JsNameRef x) {
if (MetadataProperties.getInlineStrategy(x) != null && MetadataProperties.getDescriptor(x) instanceof PropertyGetterDescriptor) {
JsInvocation dummyInvocation = new JsInvocation(x);
copyInlineMetadata(x, dummyInvocation);
return dummyInvocation;
}
return null;
}
@Nullable
private static JsInvocation tryCreatePropertySetterInvocation(@NotNull JsBinaryOperation x) {
if (!x.getOperator().isAssignment() || !(x.getArg1() instanceof JsNameRef)) return null;
JsNameRef name = (JsNameRef) x.getArg1();
if (MetadataProperties.getInlineStrategy(name) != null &&
MetadataProperties.getDescriptor(name) instanceof PropertySetterDescriptor) {
JsInvocation dummyInvocation = new JsInvocation(name, x.getArg2());
copyInlineMetadata(name, dummyInvocation);
return dummyInvocation;
}
return null;
}
private static void copyInlineMetadata(@NotNull JsNameRef from, @NotNull JsInvocation to) {
MetadataProperties.setInlineStrategy(to, MetadataProperties.getInlineStrategy(from));
MetadataProperties.setDescriptor(to, MetadataProperties.getDescriptor(from));
MetadataProperties.setPsiElement(to, MetadataProperties.getPsiElement(from));
}
@Override
public boolean visit(@NotNull JsFunction function, @NotNull JsContext context) {
inliningContexts.push(new JsInliningContext(function));
@@ -248,6 +323,12 @@ public class JsInliner extends JsVisitorWithContextImpl {
protected JsFunction lookUpStaticFunction(@Nullable JsName functionName) {
return functions.get(functionName);
}
@Nullable
@Override
protected JsFunction lookUpStaticFunctionByTag(@NotNull String functionTag) {
return accessors.get(functionTag);
}
};
}
@@ -20,6 +20,7 @@ import com.google.dart.compiler.backend.js.ast.*
import com.google.dart.compiler.backend.js.ast.metadata.descriptor
import org.jetbrains.kotlin.js.inline.FunctionReader
import org.jetbrains.kotlin.js.inline.util.*
import org.jetbrains.kotlin.js.translate.context.Namer
abstract class FunctionContext(
private val function: JsFunction,
@@ -27,6 +28,8 @@ abstract class FunctionContext(
) {
protected abstract fun lookUpStaticFunction(functionName: JsName?): JsFunction?
protected abstract fun lookUpStaticFunctionByTag(functionTag: String): JsFunction?
fun getFunctionDefinition(call: JsInvocation): JsFunction {
return getFunctionDefinitionImpl(call)!!
}
@@ -71,7 +74,10 @@ abstract class FunctionContext(
*/
private fun getFunctionDefinitionImpl(call: JsInvocation): JsFunction? {
val descriptor = call.descriptor
if (descriptor != null && descriptor in functionReader) return functionReader[descriptor]
if (descriptor != null) {
if (descriptor in functionReader) return functionReader[descriptor]
lookUpStaticFunctionByTag(Namer.getFunctionTag(descriptor))?.let { return it }
}
/** remove ending `()` */
val callQualifier: JsExpression = if (isCallInvocation(call)) {
@@ -84,7 +90,9 @@ abstract class FunctionContext(
/** process cases 2, 3 */
val qualifier = callQualifier.transitiveStaticRef
return when (qualifier) {
is JsInvocation -> lookUpStaticFunction(getSimpleName(qualifier)!!)?.let { if (isFunctionCreator(it)) it else null }
is JsInvocation -> {
lookUpStaticFunction(getSimpleName(qualifier)!!)?.let { if (isFunctionCreator(it)) it else null }
}
is JsNameRef -> lookUpStaticFunction(qualifier.name)
is JsFunction -> qualifier
else -> null
@@ -22,6 +22,7 @@ import com.google.dart.compiler.backend.js.ast.metadata.staticRef
import org.jetbrains.kotlin.js.inline.util.collectors.InstanceCollector
import org.jetbrains.kotlin.js.translate.expression.InlineMetadata
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
import org.jetbrains.kotlin.js.translate.utils.name
import java.util.*
fun collectFunctionReferencesInside(scope: JsNode): List<JsName> =
@@ -179,6 +180,21 @@ fun collectNamedFunctionsAndMetadata(scope: JsNode): Map<JsName, Pair<JsFunction
return namedFunctions
}
fun collectAccessors(scope: JsNode): Map<String, JsFunction> {
val accessors = hashMapOf<String, JsFunction>()
scope.accept(object : RecursiveJsVisitor() {
override fun visitInvocation(invocation: JsInvocation) {
InlineMetadata.decompose(invocation)?.let {
accessors[it.tag.value] = it.function
}
super.visitInvocation(invocation)
}
})
return accessors
}
fun <T : JsNode> collectInstances(klass: Class<T>, scope: JsNode): List<T> {
return with(InstanceCollector(klass, visitNestedDeclarations = false)) {
accept(scope)
@@ -4505,6 +4505,12 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest {
doTest(fileName);
}
@TestMetadata("property.kt")
public void testProperty() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/inlineMultiModule/property.kt");
doTest(fileName);
}
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/inlineMultiModule/simple.kt");
@@ -0,0 +1,68 @@
/*
* 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.js.test.semantics;
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/codegen/boxInline/property")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public class PropertyAccessorsInlineTestsGenerated extends AbstractPropertyAccessorsInlineTests {
public void testAllFilesPresentInProperty() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxInline/property"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JS, true);
}
@TestMetadata("property.kt")
public void testProperty() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/property/property.kt");
doTest(fileName);
}
@TestMetadata("reifiedVal.kt")
public void testReifiedVal() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/property/reifiedVal.kt");
doTest(fileName);
}
@TestMetadata("reifiedVar.kt")
public void testReifiedVar() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/property/reifiedVar.kt");
doTest(fileName);
}
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/property/simple.kt");
doTest(fileName);
}
@TestMetadata("simpleExtension.kt")
public void testSimpleExtension() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/property/simpleExtension.kt");
doTest(fileName);
}
}
@@ -29,6 +29,8 @@ abstract class BorrowedInlineTest(relativePath: String) : BasicBoxTest(
abstract class AbstractNonLocalReturnsTest : BorrowedInlineTest("nonLocalReturns/")
abstract class AbstractPropertyAccessorsInlineTests : BorrowedInlineTest("property/")
abstract class AbstractBoxJsTest() : BasicBoxTest(
BasicBoxTest.TEST_DATA_DIR_PATH + "box/",
BasicBoxTest.TEST_DATA_DIR_PATH + "out/box/"
@@ -53,6 +53,27 @@ public class DirectiveTestUtils {
}
};
private static final DirectiveHandler PROPERTY_NOT_USED = new DirectiveHandler("PROPERTY_NOT_USED") {
@Override
void processEntry(@NotNull JsNode ast, @NotNull ArgumentsHelper arguments) throws Exception {
checkPropertyNotUsed(ast, arguments.getFirst(), false, false);
}
};
private static final DirectiveHandler PROPERTY_NOT_READ_FROM = new DirectiveHandler("PROPERTY_NOT_READ_FROM") {
@Override
void processEntry(@NotNull JsNode ast, @NotNull ArgumentsHelper arguments) throws Exception {
checkPropertyNotUsed(ast, arguments.getFirst(), false, true);
}
};
private static final DirectiveHandler PROPERTY_NOT_WRITTEN_TO = new DirectiveHandler("PROPERTY_NOT_WRITTEN_TO") {
@Override
void processEntry(@NotNull JsNode ast, @NotNull ArgumentsHelper arguments) throws Exception {
checkPropertyNotUsed(ast, arguments.getFirst(), true, false);
}
};
private static final DirectiveHandler FUNCTION_CALLED_IN_SCOPE = new DirectiveHandler("CHECK_CALLED_IN_SCOPE") {
@Override
void processEntry(@NotNull JsNode ast, @NotNull ArgumentsHelper arguments) throws Exception {
@@ -232,6 +253,9 @@ public class DirectiveTestUtils {
private static final List<DirectiveHandler> DIRECTIVE_HANDLERS = Arrays.asList(
FUNCTION_CONTAINS_NO_CALLS,
FUNCTION_NOT_CALLED,
PROPERTY_NOT_USED,
PROPERTY_NOT_READ_FROM,
PROPERTY_NOT_WRITTEN_TO,
FUNCTION_CALLED_IN_SCOPE,
FUNCTION_NOT_CALLED_IN_SCOPE,
FUNCTIONS_HAVE_SAME_LINES,
@@ -261,6 +285,17 @@ public class DirectiveTestUtils {
assertEquals(errorMessage, 0, callsCount);
}
public static void checkPropertyNotUsed(JsNode node, String propertyName, boolean isGetAllowed, boolean isSetAllowed) throws Exception {
PropertyReferenceCollector counter = PropertyReferenceCollector.Companion.collect(node);
if (!isGetAllowed) {
assertFalse("inline property getter for `" + propertyName + "` is called", counter.hasUnqualifiedReads(propertyName));
}
if (!isSetAllowed) {
assertFalse("inline property setter for `" + propertyName + "` is called", counter.hasUnqualifiedWrites(propertyName));
}
}
public static void checkFunctionNotCalled(JsNode node, String functionName) throws Exception {
CallCounter counter = CallCounter.countCalls(node);
int functionCalledCount = counter.getQualifiedCallsCount(functionName);
@@ -0,0 +1,57 @@
/*
* 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.js.test.utils
import com.google.dart.compiler.backend.js.ast.*
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
class PropertyReferenceCollector : RecursiveJsVisitor() {
private val identReadSet = hashSetOf<String>()
private val identWriteSet = hashSetOf<String>()
fun hasUnqualifiedReads(expectedIdent: String) = expectedIdent in identReadSet
fun hasUnqualifiedWrites(expectedIdent: String) = expectedIdent in identWriteSet
override fun visitNameRef(nameRef: JsNameRef) {
super.visitNameRef(nameRef)
identReadSet.add(nameRef.ident)
}
override fun visitBinaryExpression(x: JsBinaryOperation) {
var assignmentToProperty = false
JsAstUtils.decomposeAssignment(x)?.let { (left, right) ->
(left as? JsNameRef)?.let { nameRef ->
assignmentToProperty = true
identWriteSet.add(nameRef.ident)
nameRef.qualifier?.accept(this)
right.accept(this)
}
}
if (!assignmentToProperty) {
super.visitBinaryExpression(x)
}
}
companion object {
fun collect(node: JsNode): PropertyReferenceCollector {
val visitor = PropertyReferenceCollector()
node.accept(visitor)
return visitor
}
}
}
@@ -28,6 +28,7 @@ import org.jetbrains.kotlin.js.translate.context.Namer
import org.jetbrains.kotlin.js.translate.context.Namer.getCapturedVarAccessor
import org.jetbrains.kotlin.js.translate.reference.ReferenceTranslator
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
import org.jetbrains.kotlin.js.translate.reference.buildReifiedTypeArgs
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils.pureFqn
import org.jetbrains.kotlin.js.translate.utils.JsDescriptorUtils
import org.jetbrains.kotlin.js.translate.utils.TranslationUtils
@@ -120,7 +121,8 @@ object DefaultVariableAccessCase : VariableAccessCase() {
override fun VariableAccessInfo.extensionReceiver(): JsExpression {
val functionRef = ReferenceTranslator.translateAsValueReference(getAccessDescriptorIfNeeded(), context)
return JsInvocation(functionRef, extensionReceiver!!, *additionalArguments.toTypedArray())
val reifiedTypeArguments = resolvedCall.typeArguments.buildReifiedTypeArgs(context)
return JsInvocation(functionRef, reifiedTypeArguments + listOf(extensionReceiver!!) + additionalArguments)
}
override fun VariableAccessInfo.bothReceivers(): JsExpression {
@@ -135,7 +135,7 @@ public final class FunctionTranslator extends AbstractTranslator {
List<JsParameter> jsParameters = new SmartList<JsParameter>();
Map<DeclarationDescriptor, JsExpression> aliases = new HashMap<DeclarationDescriptor, JsExpression>();
for (TypeParameterDescriptor type : descriptor.getTypeParameters()) {
for (TypeParameterDescriptor type : getTypeParameters(descriptor)) {
if (type.isReified()) {
JsName paramNameForType = context().getNameForDescriptor(type);
jsParameters.add(new JsParameter(paramNameForType));
@@ -158,6 +158,13 @@ public final class FunctionTranslator extends AbstractTranslator {
return jsParameters;
}
private static List<TypeParameterDescriptor> getTypeParameters(FunctionDescriptor functionDescriptor) {
if (functionDescriptor instanceof PropertyAccessorDescriptor) {
return ((PropertyAccessorDescriptor) functionDescriptor).getCorrespondingProperty().getTypeParameters();
}
return functionDescriptor.getTypeParameters();
}
public static void addParameters(List<JsParameter> list, FunctionDescriptor descriptor, TranslationContext context) {
for (ValueParameterDescriptor valueParameter : descriptor.getValueParameters()) {
JsParameter jsParameter = new JsParameter(context.getNameForDescriptor(valueParameter));
@@ -19,8 +19,6 @@ package org.jetbrains.kotlin.js.translate.expression
import com.google.dart.compiler.backend.js.ast.*
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.js.translate.context.Namer
import org.jetbrains.kotlin.js.translate.context.TranslationContext
import org.jetbrains.kotlin.js.translate.utils.JsDescriptorUtils.*
private val METADATA_PROPERTIES_COUNT = 2
@@ -185,7 +185,7 @@ class CallArgumentTranslator private constructor(
if (CallExpressionTranslator.shouldBeInlined(callDescriptor)) {
val typeArgs = resolvedCall.typeArguments
return typeArgs.addReifiedTypeArgsTo(result, context)
return result.copy(reifiedArguments = typeArgs.buildReifiedTypeArgs(context))
}
return result
@@ -330,10 +330,9 @@ class CallArgumentTranslator private constructor(
}
private fun Map<TypeParameterDescriptor, KotlinType>.addReifiedTypeArgsTo(
info: CallArgumentTranslator.ArgumentsInfo,
public fun Map<TypeParameterDescriptor, KotlinType>.buildReifiedTypeArgs(
context: TranslationContext
): CallArgumentTranslator.ArgumentsInfo {
): List<JsExpression> {
val reifiedTypeArguments = SmartList<JsExpression>()
val patternTranslator = PatternTranslator.newInstance(context)
@@ -349,5 +348,5 @@ private fun Map<TypeParameterDescriptor, KotlinType>.addReifiedTypeArgsTo(
reifiedTypeArguments.add(isCheckCallable)
}
return info.copy(reifiedArguments = reifiedTypeArguments)
return reifiedTypeArguments
}
@@ -72,7 +72,7 @@ public final class CallExpressionTranslator extends AbstractCallExpressionTransl
}
public static boolean shouldBeInlined(@NotNull CallableDescriptor descriptor) {
if (descriptor instanceof SimpleFunctionDescriptor) {
if (descriptor instanceof SimpleFunctionDescriptor || descriptor instanceof PropertyAccessorDescriptor) {
return InlineUtil.isInline(descriptor);
}
@@ -16,10 +16,12 @@
package org.jetbrains.kotlin.js.translate.reference;
import com.google.dart.compiler.backend.js.ast.JsBinaryOperation;
import com.google.dart.compiler.backend.js.ast.JsExpression;
import com.google.dart.compiler.backend.js.ast.JsNameRef;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.descriptors.VariableDescriptor;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.js.translate.callTranslator.CallTranslator;
import org.jetbrains.kotlin.js.translate.context.TranslationContext;
import org.jetbrains.kotlin.js.translate.general.AbstractTranslator;
@@ -27,6 +29,9 @@ import org.jetbrains.kotlin.psi.KtReferenceExpression;
import org.jetbrains.kotlin.resolve.calls.callUtil.CallUtilKt;
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall;
import org.jetbrains.kotlin.resolve.inline.InlineUtil;
import static org.jetbrains.kotlin.js.translate.utils.InlineUtils.setInlineCallMetadata;
public class VariableAccessTranslator extends AbstractTranslator implements AccessTranslator {
public static VariableAccessTranslator newInstance(
@@ -39,19 +44,23 @@ public class VariableAccessTranslator extends AbstractTranslator implements Acce
resolvedCall = ((VariableAsFunctionResolvedCall) resolvedCall).getVariableCall();
}
assert resolvedCall.getResultingDescriptor() instanceof VariableDescriptor;
return new VariableAccessTranslator(context, (ResolvedCall<? extends VariableDescriptor>) resolvedCall, receiver);
return new VariableAccessTranslator(context, referenceExpression, (ResolvedCall<? extends VariableDescriptor>) resolvedCall,
receiver);
}
private final ResolvedCall<? extends VariableDescriptor> resolvedCall;
private final KtReferenceExpression referenceExpression;
private final JsExpression receiver;
private VariableAccessTranslator(
@NotNull TranslationContext context,
@NotNull KtReferenceExpression referenceExpression,
@NotNull ResolvedCall<? extends VariableDescriptor> resolvedCall,
@Nullable JsExpression receiver
) {
super(context);
this.referenceExpression = referenceExpression;
this.receiver = receiver;
this.resolvedCall = resolvedCall;
}
@@ -59,29 +68,56 @@ public class VariableAccessTranslator extends AbstractTranslator implements Acce
@NotNull
@Override
public JsExpression translateAsGet() {
return CallTranslator.INSTANCE.translateGet(context(), resolvedCall, receiver);
JsExpression e = CallTranslator.INSTANCE.translateGet(context(), resolvedCall, receiver);
CallableDescriptor original = resolvedCall.getResultingDescriptor().getOriginal();
if (original instanceof PropertyDescriptor) {
PropertyGetterDescriptor getter = ((PropertyDescriptor) original).getGetter();
if (InlineUtil.isInline(getter)) {
if (e instanceof JsNameRef) {
// Get was translated as a name reference
setInlineCallMetadata((JsNameRef) e, referenceExpression, getter);
} else {
setInlineCallMetadata(e, referenceExpression, getter, context());
}
}
}
return e;
}
@NotNull
@Override
public JsExpression translateAsSet(@NotNull JsExpression setTo) {
return CallTranslator.INSTANCE.translateSet(context(), resolvedCall, setTo, receiver);
JsExpression e = CallTranslator.INSTANCE.translateSet(context(), resolvedCall, setTo, receiver);
CallableDescriptor original = resolvedCall.getResultingDescriptor().getOriginal();
if (original instanceof PropertyDescriptor) {
PropertySetterDescriptor setter = ((PropertyDescriptor)original).getSetter();
if (InlineUtil.isInline(setter)) {
if (e instanceof JsBinaryOperation && ((JsBinaryOperation) e).getOperator().isAssignment()) {
// Set was translated as an assignment
setInlineCallMetadata((JsNameRef) (((JsBinaryOperation) e).getArg1()), referenceExpression, setter);
} else {
setInlineCallMetadata(e, referenceExpression, setter, context());
}
}
}
return e;
}
@NotNull
@Override
public AccessTranslator getCached() {
JsExpression cachedReceiver = receiver != null ? context().cacheExpressionIfNeeded(receiver) : null;
return new CachedVariableAccessTranslator(context(), resolvedCall, cachedReceiver);
return new CachedVariableAccessTranslator(context(), referenceExpression, resolvedCall, cachedReceiver);
}
private static class CachedVariableAccessTranslator extends VariableAccessTranslator implements AccessTranslator {
public CachedVariableAccessTranslator(
@NotNull TranslationContext context,
@NotNull KtReferenceExpression referenceExpression,
@NotNull ResolvedCall<? extends VariableDescriptor> resolvedCall,
@Nullable JsExpression cachedReceiver
) {
super(context, resolvedCall, cachedReceiver);
super(context, referenceExpression, resolvedCall, cachedReceiver);
}
@NotNull
@@ -29,6 +29,7 @@ import org.jetbrains.kotlin.js.translate.callTranslator.CallTranslator;
import org.jetbrains.kotlin.js.translate.context.Namer;
import org.jetbrains.kotlin.js.translate.context.TemporaryConstVariable;
import org.jetbrains.kotlin.js.translate.context.TranslationContext;
import org.jetbrains.kotlin.js.translate.expression.InlineMetadata;
import org.jetbrains.kotlin.js.translate.general.Translation;
import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.psi.psiUtil.PsiUtilsKt;
@@ -37,6 +38,7 @@ import org.jetbrains.kotlin.resolve.DescriptorUtils;
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
import org.jetbrains.kotlin.resolve.calls.model.ResolvedValueArgument;
import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver;
import org.jetbrains.kotlin.resolve.inline.InlineUtil;
import org.jetbrains.kotlin.types.KotlinType;
import java.util.ArrayList;
@@ -58,15 +60,21 @@ public final class TranslationUtils {
public static JsPropertyInitializer translateFunctionAsEcma5PropertyDescriptor(@NotNull JsFunction function,
@NotNull FunctionDescriptor descriptor,
@NotNull TranslationContext context) {
JsExpression functionExpression = function;
if (InlineUtil.isInline(descriptor)) {
InlineMetadata metadata = InlineMetadata.compose(function, descriptor);
functionExpression = metadata.getFunctionWithMetadata();
}
if (DescriptorUtils.isExtension(descriptor) ||
descriptor instanceof PropertyAccessorDescriptor &&
shouldAccessViaFunctions(((PropertyAccessorDescriptor) descriptor).getCorrespondingProperty())
) {
return translateExtensionFunctionAsEcma5DataDescriptor(function, descriptor, context);
return translateExtensionFunctionAsEcma5DataDescriptor(functionExpression, descriptor, context);
}
else {
JsStringLiteral getOrSet = context.program().getStringLiteral(getAccessorFunctionName(descriptor));
return new JsPropertyInitializer(getOrSet, function);
return new JsPropertyInitializer(getOrSet, functionExpression);
}
}
@@ -82,9 +90,9 @@ public final class TranslationUtils {
}
@NotNull
private static JsPropertyInitializer translateExtensionFunctionAsEcma5DataDescriptor(@NotNull JsFunction function,
private static JsPropertyInitializer translateExtensionFunctionAsEcma5DataDescriptor(@NotNull JsExpression functionExpression,
@NotNull FunctionDescriptor descriptor, @NotNull TranslationContext context) {
JsObjectLiteral meta = createDataDescriptor(function, ModalityKt.isOverridable(descriptor), false);
JsObjectLiteral meta = createDataDescriptor(functionExpression, ModalityKt.isOverridable(descriptor), false);
return new JsPropertyInitializer(context.getNameForDescriptor(descriptor).makeRef(), meta);
}
@@ -44,10 +44,9 @@ import org.jetbrains.kotlin.resolve.inline.InlineStrategy
fun setInlineCallMetadata(
expression: JsExpression,
psiElement: KtExpression,
resolvedCall: ResolvedCall<*>,
descriptor: CallableDescriptor,
context: TranslationContext
) {
val descriptor = PsiUtils.getFunctionDescriptor(resolvedCall)
assert(CallExpressionTranslator.shouldBeInlined(descriptor)) {
"Expected descriptor of callable, that should be inlined, but got: $descriptor"
}
@@ -69,6 +68,23 @@ fun setInlineCallMetadata(
visitor.accept(expression)
}
fun setInlineCallMetadata(
expression: JsExpression,
psiElement: KtExpression,
resolvedCall: ResolvedCall<*>,
context: TranslationContext
) = setInlineCallMetadata(expression, psiElement, PsiUtils.getFunctionDescriptor(resolvedCall), context)
fun setInlineCallMetadata(
nameRef: JsNameRef,
psiElement: KtExpression,
descriptor: CallableDescriptor
) {
nameRef.descriptor = descriptor
nameRef.inlineStrategy = InlineStrategy.IN_PLACE
nameRef.psiElement = psiElement
}
fun TranslationContext.aliasedName(descriptor: CallableDescriptor): JsName {
val alias = getAliasForDescriptor(descriptor)
val aliasName = (alias as? JsNameRef)?.name
@@ -0,0 +1,165 @@
// MODULE: lib
// FILE: lib.kt
package test
var a = 0
inline var p1: Int
get() = a + 10000
set(v) {
a = v + 100
}
var p2: Int
inline get() = a + 20000
set(v) {
a = v + 200
}
var p3: Int
get() = a + 30000
inline set(v) {
a = v + 300
}
inline var Int.p4: Int
get() = this * 100 + a + 40000
set(v) {
a = this + v + 400
}
var Int.p5: Int
inline get() = this * 100 + a + 50000
set(v) {
a = this + v + 500
}
var Int.p6: Int
get() = this * 100 + a + 60000
inline set(v) {
a = this + v + 600
}
class A {
inline var p7: Int
get() = a + 70000
set(v) {
a = v + 700
}
var p8: Int
inline get() = a + 80000
set(v) {
a = v + 800
}
var p9: Int
get() = a + 90000
inline set(v) {
a = v + 900
}
inline var Int.p10: Int
get() = this * 100 + a + 100000
set(v) {
a = this + v + 1000
}
var Int.p11: Int
inline get() = this * 100 + a + 110000
set(v) {
a = this + v + 1100
}
var Int.p12: Int
get() = this * 100 + a + 120000
inline set(v) {
a = this + v + 1200
}
}
inline var A.p13: Int
get() = a + 130000
set(v) {
a = v + 1300
}
var A.p14: Int
inline get() = a + 140000
set(v) {
a = v + 1400
}
var A.p15: Int
get() = a + 150000
inline set(v) {
a = v + 1500
}
// MODULE: main(lib)
// FILE: main.kt
// PROPERTY_NOT_USED: p1
// PROPERTY_NOT_READ_FROM: p2
// PROPERTY_NOT_WRITTEN_TO: p3
// CHECK_NOT_CALLED: imported$get_p4
// CHECK_NOT_CALLED: imported$set_p4
// CHECK_NOT_CALLED: imported$get_p5
// CHECK_NOT_CALLED: imported$set_p6
// PROPERTY_NOT_USED: p7
// PROPERTY_NOT_READ_FROM: p8
// PROPERTY_NOT_WRITTEN_TO: p9
// CHECK_NOT_CALLED: imported$A$get_A$p10
// CHECK_NOT_CALLED: imported$A$set_A$p10
// CHECK_NOT_CALLED: imported$A$get_A$p11
// CHECK_NOT_CALLED: imported$A$set_A$p12
// CHECK_NOT_CALLED: imported$get_p13
// CHECK_NOT_CALLED: imported$set_p13
// CHECK_NOT_CALLED: imported$get_p14
// CHECK_NOT_CALLED: imported$set_p15
import test.*
fun box(): String {
p1 = 1
if (p1 != 10101) return "test1: $p1"
p2 = 2
if (p2 != 20202) return "test2: $p2"
p3 = 3
if (p3 != 30303) return "test3: $p3"
4000000.p4 = 4
if (4000000.p4 != 404040404) return "test4: ${4000000.p4}"
5000000.p5 = 5
if (5000000.p5 != 505050505) return "test5: ${5000000.p5}"
6000000.p6 = 6
if (6000000.p6 != 606060606) return "test6: ${6000000.p6}"
val a = A()
a.p7 = 7
if (a.p7 != 70707) return "test7: ${a.p7}"
a.p8 = 8
if (a.p8 != 80808) return "test8: ${a.p8}"
a.p9 = 9
if (a.p9 != 90909) return "test9: ${a.p9}"
with (a) {
10000000.p10 = 10
if (10000000.p10 != 1010101010) return "test10: ${10000000.p10}"
11000000.p11 = 11
if (11000000.p11 != 1111111111) return "test11: ${11000000.p11}"
12000000.p12 = 12
if (12000000.p12 != 1212121212) return "test12: ${12000000.p12}"
}
a.p13 = 13
if (a.p13 != 131313) return "test13: ${a.p13}"
a.p14 = 14
if (a.p14 != 141414) return "test14: ${a.p14}"
a.p15 = 15
if (a.p15 != 151515) return "test15: ${a.p15}"
return "OK"
}