From c08e2e8ca004264e096b4e881ffdea0bbff33c6a Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Wed, 4 May 2016 16:48:15 +0300 Subject: [PATCH] JS/Inlining: always create temporary variables to alias expressions in following cases: * when decomposing expression that contains inline calls, alias all subexpressions * when substituting arguments to inline call * for value returned from inline function. Instead, rely on dedicated optimization pass. Improve lookup of function to inline, since the old one relied on immediate optimizations. Give more hints to optimizer. This should make inlining more stable in different hard-to-reproduce corner cases with evaluation order. --- .../kotlin/js/inline/ExpressionDecomposer.kt | 23 +++++----- .../kotlin/js/inline/FunctionInlineMutator.kt | 14 +++---- .../jetbrains/kotlin/js/inline/JsInliner.java | 14 +++++-- .../js/inline/clean/ReferenceTracker.kt | 4 +- .../clean/TemporaryAssignmentElimination.kt | 2 +- .../removeUnusedLocalFunctionDeclarations.kt | 29 +++++++++++-- .../js/inline/context/FunctionContext.kt | 34 +++++---------- .../kotlin/js/inline/context/NamingContext.kt | 6 +-- .../kotlin/js/inline/util/invocationUtils.kt | 18 +++++++- .../kotlin/js/inline/util/namingUtils.kt | 23 +++++----- .../util/rewriters/ReturnReplacingVisitor.kt | 17 ++++---- .../kotlin/js/inline/util/sideEffectUtils.kt | 42 +++++++------------ .../expression/LiteralFunctionTranslator.kt | 3 +- .../factories/KotlinFunctionIntrinsic.java | 8 ++-- 14 files changed, 129 insertions(+), 108 deletions(-) diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/ExpressionDecomposer.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/ExpressionDecomposer.kt index 2c174b4a503..40b2318ad76 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/ExpressionDecomposer.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/ExpressionDecomposer.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 JetBrains s.r.o. + * 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. @@ -17,10 +17,10 @@ package org.jetbrains.kotlin.js.inline import com.google.dart.compiler.backend.js.ast.* +import com.google.dart.compiler.backend.js.ast.metadata.staticRef import com.google.dart.compiler.backend.js.ast.metadata.synthetic import com.intellij.util.SmartList import org.jetbrains.kotlin.js.inline.util.IdentitySet -import org.jetbrains.kotlin.js.inline.util.canHaveOwnSideEffect import org.jetbrains.kotlin.js.translate.utils.JsAstUtils.* import org.jetbrains.kotlin.js.translate.utils.jsAstUtils.* @@ -62,7 +62,7 @@ internal class ExpressionDecomposer private constructor( val decomposer = with (statement) { val extractable = match(canBeExtractedByInliner) val containsExtractable = withParentsOfNodes(extractable) - val nodesWithSideEffect = match { it is JsExpression && it.canHaveOwnSideEffect() } + val nodesWithSideEffect = match { it !is JsLiteral.JsValueLiteral } val containsNodeWithSideEffect = withParentsOfNodes(nodesWithSideEffect) ExpressionDecomposer(scope, containsExtractable, containsNodeWithSideEffect) @@ -169,7 +169,7 @@ internal class ExpressionDecomposer private constructor( // Must be (someThingWithSideEffect).x = arg2, because arg1 can have side effect assert(arg1 is JsNameRef) { "Valid JavaScript left-hand side must be JsNameRef, got: $this" } val arg1AsRef = arg1 as JsNameRef - arg1AsRef.qualifier = arg1AsRef.qualifier!!.extractToTemporary() + arg1AsRef.qualifier = arg1AsRef.qualifier?.extractToTemporary() } else { arg1 = arg1.extractToTemporary() @@ -317,22 +317,21 @@ internal class ExpressionDecomposer private constructor( addStatement(tmp.variable) return tmp.nameRef } - + private inner class Temporary(val value: JsExpression? = null) { val name: JsName = scope.declareTemporary() - val variable: JsVars = newVar(name, value) + val variable: JsVars = newVar(name, value).apply { + synthetic = true + name.staticRef = value + } val nameRef: JsExpression get() = name.makeRef() - init { - variable.synthetic = true - } - fun assign(value: JsExpression): JsStatement { - val statement = JsExpressionStatement(assignment(nameRef, value)) - statement.synthetic = true + val statement = JsExpressionStatement(assignment(nameRef, value)).apply { synthetic = true } + name.staticRef = value return statement } } diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionInlineMutator.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionInlineMutator.kt index 8308d8f5867..fc11ee06564 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionInlineMutator.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionInlineMutator.kt @@ -28,7 +28,7 @@ import org.jetbrains.kotlin.js.inline.util.rewriters.ReturnReplacingVisitor class FunctionInlineMutator private constructor( private val call: JsInvocation, - private val inliningContext: InliningContext + inliningContext: InliningContext ) { private val invokedFunction: JsFunction private val namingContext: NamingContext @@ -85,7 +85,9 @@ private constructor( } private fun applyCapturedArgs(call: JsInvocation, inner: JsFunction, outer: JsFunction) { - val namingContext = inliningContext.newNamingContext() + // We want statements that introduce temporary variables to be added immediately after applying renamings, + // so that further processing that involves detection of inlineable function had a chance to recognize them. + val namingContext = NamingContext(inner.scope) { inner.body.statements.addAll(0, it) } val arguments = call.arguments val parameters = outer.parameters aliasArgumentsIfNeeded(namingContext, arguments, parameters) @@ -98,11 +100,9 @@ private constructor( var thisReplacement = getThisReplacement(call) if (thisReplacement == null || thisReplacement is JsLiteral.JsThisRef) return - if (thisReplacement.needToAlias()) { - val thisName = namingContext.getFreshName(getThisAlias()) - namingContext.newVar(thisName, thisReplacement) - thisReplacement = thisName.makeRef() - } + val thisName = namingContext.getFreshName(getThisAlias()) + namingContext.newVar(thisName, thisReplacement) + thisReplacement = thisName.makeRef() replaceThisReference(block, thisReplacement) } diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/JsInliner.java b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/JsInliner.java index 8e3e5c3ad69..9d87f391fb1 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/JsInliner.java +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/JsInliner.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 JetBrains s.r.o. + * 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. @@ -19,6 +19,7 @@ package org.jetbrains.kotlin.js.inline; import com.google.dart.compiler.backend.js.ast.*; import com.google.dart.compiler.backend.js.ast.metadata.MetadataProperties; import com.intellij.psi.PsiElement; +import kotlin.Unit; import kotlin.jvm.functions.Function1; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -58,7 +59,6 @@ public class JsInliner extends JsVisitorWithContextImpl { if (!(node instanceof JsInvocation)) return false; JsInvocation call = (JsInvocation) node; - return hasToBeInlined(call); } }; @@ -199,7 +199,7 @@ public class JsInliner extends JsVisitorWithContextImpl { if (currentStatement instanceof JsExpressionStatement && ((JsExpressionStatement) currentStatement).getExpression() == call && - (resultExpression == null || !SideEffectUtilsKt.canHaveSideEffect(resultExpression)) + resultExpression == null ) { statementContext.removeMe(); } @@ -267,7 +267,13 @@ public class JsInliner extends JsVisitorWithContextImpl { @Override public NamingContext newNamingContext() { JsScope scope = getFunctionContext().getScope(); - return new NamingContext(scope, getStatementContext()); + return new NamingContext(scope, new Function1, Unit>() { + @Override + public Unit invoke(List statements) { + getStatementContext().addPrevious(statements); + return null; + } + }); } @NotNull diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/ReferenceTracker.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/ReferenceTracker.kt index 0ca921713a8..5647f4fd16b 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/ReferenceTracker.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/ReferenceTracker.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 JetBrains s.r.o. + * 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. @@ -67,7 +67,7 @@ internal class ReferenceTracker { return referenceFromTo.getOrPut(referrer, { IdentitySet() }) } - private fun isKnown(ref: Reference): Boolean { + fun isKnown(ref: Reference): Boolean { return removableCandidates.containsKey(ref) } diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/TemporaryAssignmentElimination.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/TemporaryAssignmentElimination.kt index 1a5b383f50b..b9d0a5fc9ef 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/TemporaryAssignmentElimination.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/TemporaryAssignmentElimination.kt @@ -69,7 +69,7 @@ internal class TemporaryAssignmentElimination(private val root: JsBlock) { val propertyMutation = JsAstUtils.decomposeAssignment(x.expression) if (propertyMutation != null) { val (target, value) = propertyMutation - if (!target.canHaveSideEffect()) { + if (!target.canHaveSideEffect(namesToProcess)) { val usage = Usage.PropertyMutation(x, target) tryRecord(value, usage) accept(value) diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/removeUnusedLocalFunctionDeclarations.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/removeUnusedLocalFunctionDeclarations.kt index 1af50764715..4d478d0e426 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/removeUnusedLocalFunctionDeclarations.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/removeUnusedLocalFunctionDeclarations.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 JetBrains s.r.o. + * 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. @@ -20,6 +20,7 @@ import com.google.dart.compiler.backend.js.ast.* import com.google.dart.compiler.backend.js.ast.metadata.staticRef import org.jetbrains.kotlin.js.inline.util.collectUsedNames +import org.jetbrains.kotlin.js.inline.util.transitiveStaticRef /** * Removes unused local function declarations like: @@ -55,7 +56,7 @@ private class UnusedInstanceCollector : JsVisitorWithContextImpl() { val references = collectUsedNames(x) references.filterNotNull() - .forEach { tracker.addRemovableReference(name, it) } + .forEach { tracker.addRemovableReference(name, it) } return false } @@ -73,8 +74,28 @@ private class UnusedInstanceCollector : JsVisitorWithContextImpl() { private fun isLocalFunctionDeclaration(jsVar: JsVars.JsVar): Boolean { val name = jsVar.name val expr = jsVar.initExpression - val staticRef = name?.staticRef - return staticRef != null && staticRef == expr + // For the case like this: `b = a; c = b;`, where `a` is a function. In this case we should remove both declaration, + // although second one contains 'usage' of `b`. + // see `inlineEvaluationOrder/cases/lambdaWithClosure.kt`. + if (expr is JsNameRef && (expr.name?.let { tracker.isKnown(it) } ?: false)) return true + + val staticRef = name?.staticRef + return staticRef != null && staticRef == expr && isFunctionReference(expr) } } + +// For RHS of `var a = b;` checks whether *b* is a reference to a function or a closure instantiation, direct or indirect. +private fun isFunctionReference(expr: JsExpression): Boolean { + val qualifier = when (expr) { + // `var tmp = foo(closure)`, where `foo` is a closure constructor. + is JsInvocation -> expr.qualifier + + // Either alias to another variable that holds function or a lambda without closure. + is JsNameRef -> expr + + else -> null + } + + return qualifier?.transitiveStaticRef is JsFunction +} \ No newline at end of file diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/context/FunctionContext.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/context/FunctionContext.kt index 02f0a6ec9c3..9b4e4a772b4 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/context/FunctionContext.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/context/FunctionContext.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 JetBrains s.r.o. + * 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. @@ -17,11 +17,11 @@ package org.jetbrains.kotlin.js.inline.context import com.google.dart.compiler.backend.js.ast.* -import com.google.dart.compiler.backend.js.ast.metadata.staticRef - -import org.jetbrains.kotlin.js.inline.FunctionReader import com.google.dart.compiler.backend.js.ast.metadata.descriptor -import org.jetbrains.kotlin.js.inline.util.* +import org.jetbrains.kotlin.js.inline.FunctionReader +import org.jetbrains.kotlin.js.inline.util.getSimpleName +import org.jetbrains.kotlin.js.inline.util.isCallInvocation +import org.jetbrains.kotlin.js.inline.util.transitiveStaticRef abstract class FunctionContext( private val function: JsFunction, @@ -76,31 +76,19 @@ abstract class FunctionContext( if (descriptor != null && descriptor in functionReader) return functionReader[descriptor] /** remove ending `()` */ - var callQualifier: JsNode = call.qualifier - - /** remove ending `.call()` */ - if (isCallInvocation(call)) { - callQualifier = (callQualifier as JsNameRef).qualifier!! + val callQualifier: JsExpression = if (isCallInvocation(call)) { + (call.qualifier as JsNameRef).qualifier!! } - - /** in case 4, 5 get ref (reduce 4, 5 to 2, 3 accordingly) */ - @Suppress("USELESS_CAST") // NB do not remove 'as JsNode' below until KT-10752 is fixed - if (callQualifier is JsNameRef) { - val staticRef = callQualifier.name?.staticRef - - callQualifier = when (staticRef) { - is JsNameRef -> staticRef as JsNode - is JsInvocation -> staticRef as JsNode - is JsFunction, null -> callQualifier - else -> throw AssertionError("Unexpected static reference type ${staticRef.javaClass}") - } + else { + call.qualifier } /** process cases 2, 3 */ - val qualifier = callQualifier + val qualifier = callQualifier.transitiveStaticRef return when (qualifier) { is JsInvocation -> lookUpStaticFunction(getSimpleName(qualifier)!!) is JsNameRef -> lookUpStaticFunction(qualifier.name) + is JsFunction -> qualifier else -> null } } diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/context/NamingContext.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/context/NamingContext.kt index 008eed0a950..c6383c2d7a0 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/context/NamingContext.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/context/NamingContext.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 JetBrains s.r.o. + * 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. @@ -27,7 +27,7 @@ import org.jetbrains.kotlin.js.inline.util.replaceNames class NamingContext( private val scope: JsScope, - private val statementContext: JsContext + private val statementConsumer: (List) -> Unit ) { private val renamings = IdentityHashMap() private val declarations = ArrayList() @@ -35,7 +35,7 @@ class NamingContext( fun applyRenameTo(target: JsNode): JsNode { if (!addedDeclarations) { - statementContext.addPrevious(declarations) + statementConsumer(declarations) addedDeclarations = true } diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/invocationUtils.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/invocationUtils.kt index f119c179b18..78ce1e3c623 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/invocationUtils.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/invocationUtils.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 JetBrains s.r.o. + * 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. @@ -121,3 +121,19 @@ private fun getStaticRef(invocation: JsInvocation): JsNode? { val qualifierName = (qualifier as? HasName)?.name return qualifierName?.staticRef } + +/** + * If an expression A references to another expression B, which in turn references to C, or a corresponding expression + * at the end of a chain of references. They are usually JsNameRef expressions with JsFunction at the very end. + * This chain is produced when there are lots of aliases created from aliases, i.e. `var $tmp1 = foo; var $tmp2 = $tmp1;`. + * So for `$tmp2` we should get reference to `foo`. + */ +val JsExpression.transitiveStaticRef: JsExpression + get() { + var qualifier = this + while (qualifier is JsNameRef) { + val staticRef = qualifier.name?.staticRef as? JsExpression ?: break + qualifier = staticRef + } + return qualifier + } \ No newline at end of file diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/namingUtils.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/namingUtils.kt index 4148b2388fb..2a32ca76b28 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/namingUtils.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/namingUtils.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 JetBrains s.r.o. + * 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. @@ -17,6 +17,7 @@ package org.jetbrains.kotlin.js.inline.util import com.google.dart.compiler.backend.js.ast.* +import com.google.dart.compiler.backend.js.ast.metadata.staticRef import org.jetbrains.kotlin.js.inline.context.NamingContext import org.jetbrains.kotlin.js.inline.util.rewriters.LabelNameRefreshingVisitor @@ -30,14 +31,16 @@ fun aliasArgumentsIfNeeded( for ((arg, param) in arguments.zip(parameters)) { val paramName = param.name - val replacement = - if (arg.needToAlias()) { - val freshName = context.getFreshName(paramName) - context.newVar(freshName, arg) - freshName.makeRef() - } else { - arg - } + + val replacement: JsExpression = if (arg is JsFunction && isFunctionCreator(arg)) { + arg + } + else { + val freshName = context.getFreshName(paramName).apply { staticRef = arg } + context.newVar(freshName, arg) + val freshNameRef = freshName.makeRef() + freshNameRef + } context.replaceName(paramName, replacement) } @@ -60,7 +63,7 @@ fun renameLocalNames( function: JsFunction ) { for (name in collectDefinedNames(function.body)) { - val freshName = context.getFreshName(name) + val freshName = context.getFreshName(name).apply { staticRef = name.staticRef } context.replaceName(name, freshName.makeRef()) } } diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/rewriters/ReturnReplacingVisitor.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/rewriters/ReturnReplacingVisitor.kt index 1dd71e6c7b9..93003c1b32c 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/rewriters/ReturnReplacingVisitor.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/rewriters/ReturnReplacingVisitor.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 JetBrains s.r.o. + * 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. @@ -20,7 +20,6 @@ import com.google.dart.compiler.backend.js.ast.* import com.google.dart.compiler.backend.js.ast.metadata.functionDescriptor import com.google.dart.compiler.backend.js.ast.metadata.returnTarget import com.google.dart.compiler.backend.js.ast.metadata.synthetic -import org.jetbrains.kotlin.js.inline.util.canHaveSideEffect import org.jetbrains.kotlin.js.translate.utils.JsAstUtils class ReturnReplacingVisitor( @@ -55,14 +54,16 @@ class ReturnReplacingVisitor( } private fun getReturnReplacement(returnExpression: JsExpression?): JsExpression? { - if (returnExpression != null) { + return if (returnExpression != null) { if (resultRef != null) { - return JsAstUtils.assignment(resultRef, returnExpression).apply { synthetic = true } + JsAstUtils.assignment(resultRef, returnExpression).apply { synthetic = true } + } + else { + returnExpression } - - if (returnExpression.canHaveSideEffect()) return returnExpression } - - return null + else { + null + } } } \ No newline at end of file diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/sideEffectUtils.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/sideEffectUtils.kt index c6b6ceecbdf..ac57cc779cf 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/sideEffectUtils.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/sideEffectUtils.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 JetBrains s.r.o. + * 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. @@ -16,34 +16,20 @@ package org.jetbrains.kotlin.js.inline.util -import com.google.dart.compiler.backend.js.ast.JsLiteral.* import com.google.dart.compiler.backend.js.ast.* -import com.google.dart.compiler.backend.js.ast.metadata.typeCheck +import com.google.dart.compiler.backend.js.ast.metadata.HasMetadata +import com.google.dart.compiler.backend.js.ast.metadata.sideEffects import org.jetbrains.kotlin.js.translate.utils.jsAstUtils.any -fun JsExpression.canHaveSideEffect(): Boolean = - any { it is JsExpression && it.canHaveOwnSideEffect() } +fun JsExpression.canHaveSideEffect(localVars: Set) = + any { it is JsExpression && it.canHaveOwnSideEffect(localVars) } -fun JsExpression.canHaveOwnSideEffect(): Boolean = - when (this) { - is JsValueLiteral, - is JsConditional, - is JsArrayAccess, - is JsArrayLiteral, - is JsNameRef -> false - is JsBinaryOperation -> operator.isAssignment - is JsInvocation -> !isFunctionCreatorInvocation(this) - else -> true - } - -fun JsExpression.needToAlias(): Boolean = - any { it is JsExpression && it.shouldHaveOwnAlias() } - -fun JsExpression.shouldHaveOwnAlias(): Boolean = - when (this) { - is JsConditional, - is JsBinaryOperation, - is JsArrayLiteral -> true - is JsInvocation -> if (typeCheck == null) canHaveSideEffect() else false - else -> canHaveOwnSideEffect() - } +fun JsExpression.canHaveOwnSideEffect(vars: Set) = when (this) { + is JsConditional, + is JsLiteral -> false + is JsBinaryOperation -> operator.isAssignment + is JsNameRef -> !(qualifier == null && name in vars) && sideEffects + is JsInvocation -> !isFunctionCreatorInvocation(this) && sideEffects + is HasMetadata -> sideEffects + else -> true +} diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/LiteralFunctionTranslator.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/LiteralFunctionTranslator.kt index c605931e0ca..25d4d276621 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/LiteralFunctionTranslator.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/LiteralFunctionTranslator.kt @@ -19,6 +19,7 @@ package org.jetbrains.kotlin.js.translate.expression import com.google.dart.compiler.backend.js.ast.* import com.google.dart.compiler.backend.js.ast.metadata.functionDescriptor import com.google.dart.compiler.backend.js.ast.metadata.isLocal +import com.google.dart.compiler.backend.js.ast.metadata.sideEffects import com.google.dart.compiler.backend.js.ast.metadata.staticRef import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.js.inline.util.getInnerFunction @@ -62,7 +63,7 @@ class LiteralFunctionTranslator(context: TranslationContext) : AbstractTranslato } lambda.isLocal = true - return invokingContext.define(descriptor, lambda) + return invokingContext.define(descriptor, lambda).apply { sideEffects = false } } } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/functions/factories/KotlinFunctionIntrinsic.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/functions/factories/KotlinFunctionIntrinsic.java index eabf72d1e06..96d7a54bfe3 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/functions/factories/KotlinFunctionIntrinsic.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/functions/factories/KotlinFunctionIntrinsic.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 JetBrains s.r.o. + * 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. @@ -18,7 +18,6 @@ package org.jetbrains.kotlin.js.translate.intrinsic.functions.factories; import com.google.dart.compiler.backend.js.ast.JsExpression; import com.google.dart.compiler.backend.js.ast.JsInvocation; -import com.google.dart.compiler.backend.js.ast.JsNameRef; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.js.translate.context.Namer; @@ -31,10 +30,10 @@ import java.util.List; public class KotlinFunctionIntrinsic extends FunctionIntrinsic { @NotNull - private final JsNameRef function; + private final String functionName; public KotlinFunctionIntrinsic(@NotNull String functionName) { - function = JsAstUtils.fqnWithoutSideEffects(functionName, Namer.kotlinObject()); + this.functionName = functionName; } @NotNull @@ -44,6 +43,7 @@ public class KotlinFunctionIntrinsic extends FunctionIntrinsic { @NotNull List arguments, @NotNull TranslationContext context ) { + JsExpression function = JsAstUtils.fqnWithoutSideEffects(functionName, Namer.kotlinObject()); return new JsInvocation(function, receiver == null ? arguments : TranslationUtils.generateInvocationArguments(receiver, arguments)); } }