diff --git a/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsVisitorWithContextImpl.java b/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsVisitorWithContextImpl.java index 4a9f639ba56..d2b0dc8a816 100644 --- a/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsVisitorWithContextImpl.java +++ b/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsVisitorWithContextImpl.java @@ -33,7 +33,7 @@ import java.util.*; */ public class JsVisitorWithContextImpl extends JsVisitorWithContext { - private final Stack> statementContexts = new Stack>(); + protected final Stack> statementContexts = new Stack>(); public class ListContext extends JsContext { private List nodes; @@ -76,7 +76,7 @@ public class JsVisitorWithContextImpl extends JsVisitorWithContext { return null; } - protected void traverse(List nodes) { + public void traverse(List nodes) { assert previous.isEmpty(): "addPrevious() was called before traverse()"; assert next.isEmpty(): "addNext() was called before traverse()"; this.nodes = nodes; @@ -102,6 +102,9 @@ public class JsVisitorWithContextImpl extends JsVisitorWithContext { index += next.size(); } } + + previous.clear(); + next.clear(); } } diff --git a/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/metadata/metadataProperties.kt b/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/metadata/metadataProperties.kt index edb5d860254..8eef1365446 100644 --- a/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/metadata/metadataProperties.kt +++ b/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/metadata/metadataProperties.kt @@ -29,6 +29,8 @@ var JsName.staticRef: JsNode? by MetadataProperty(default = null) var JsName.descriptor: DeclarationDescriptor? by MetadataProperty(default = null) +var JsName.localAlias: JsName? by MetadataProperty(default = null) + // TODO: move this to module 'js.inliner' and change dependency on 'frontend' to dependency on 'descriptors' var JsInvocation.inlineStrategy: InlineStrategy? by MetadataProperty(default = null) diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutineTransformer.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutineTransformer.kt index cb0997fa46c..de2050a5f4f 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutineTransformer.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutineTransformer.kt @@ -21,7 +21,7 @@ import org.jetbrains.kotlin.js.backend.ast.metadata.coroutineMetadata import org.jetbrains.kotlin.js.translate.expression.InlineMetadata import org.jetbrains.kotlin.js.translate.utils.JsAstUtils -class CoroutineTransformer() : JsVisitorWithContextImpl() { +class CoroutineTransformer : JsVisitorWithContextImpl() { private val additionalStatementsByNode = mutableMapOf>() override fun endVisit(x: JsExpressionStatement, ctx: JsContext) { @@ -41,7 +41,7 @@ class CoroutineTransformer() : JsVisitorWithContextImpl() { val assignment = JsAstUtils.decomposeAssignment(expression) if (assignment != null) { val (lhs, rhs) = assignment - val function = rhs as? JsFunction ?: InlineMetadata.decompose(rhs)?.function + val function = rhs as? JsFunction ?: InlineMetadata.decompose(rhs)?.function?.function if (function?.coroutineMetadata != null) { val name = ((lhs as? JsNameRef)?.name ?: function.name)?.ident additionalStatementsByNode[x] = CoroutineFunctionTransformer(function, name).transform() @@ -60,7 +60,7 @@ class CoroutineTransformer() : JsVisitorWithContextImpl() { override fun visit(x: JsVars.JsVar, ctx: JsContext<*>): Boolean { val initExpression = x.initExpression if (initExpression != null) { - val function = initExpression as? JsFunction ?: InlineMetadata.decompose(initExpression)?.function + val function = initExpression as? JsFunction ?: InlineMetadata.decompose(initExpression)?.function?.function if (function?.coroutineMetadata != null) { val name = x.name.ident additionalStatementsByNode[x] = CoroutineFunctionTransformer(function, name).transform() 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 58f44600b81..65e2806c97f 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 @@ -26,22 +26,21 @@ import org.jetbrains.kotlin.js.inline.util.* import org.jetbrains.kotlin.js.inline.util.rewriters.ReturnReplacingVisitor import org.jetbrains.kotlin.js.translate.context.Namer -class FunctionInlineMutator -private constructor( +class FunctionInlineMutator( private val call: JsInvocation, - private val inliningContext: InliningContext + private val inliningContext: InliningContext, + function: JsFunction ) { private val invokedFunction: JsFunction - private val namingContext = inliningContext.newNamingContext() - private val body: JsBlock - private var resultExpr: JsExpression? = null + val namingContext = inliningContext.newNamingContext() + val body: JsBlock + var resultExpr: JsExpression? = null private var resultName: JsName? = null - private var breakLabel: JsLabel? = null + var breakLabel: JsLabel? = null private val currentStatement = inliningContext.statementContext.currentNode init { - val functionContext = inliningContext.functionContext - invokedFunction = uncoverClosure(functionContext.getFunctionDefinition(call).deepCopy()) + invokedFunction = uncoverClosure(function.deepCopy()) body = invokedFunction.body } @@ -166,9 +165,11 @@ private constructor( } companion object { - - @JvmStatic fun getInlineableCallReplacement(call: JsInvocation, inliningContext: InliningContext): InlineableResult { - val mutator = FunctionInlineMutator(call, inliningContext) + @JvmStatic fun getInlineableCallReplacement( + call: JsInvocation, function: JsFunction, + inliningContext: InliningContext + ): InlineableResult { + val mutator = FunctionInlineMutator(call, inliningContext, function) mutator.process() var inlineableBody: JsStatement = mutator.body @@ -181,6 +182,7 @@ private constructor( return InlineableResult(inlineableBody, mutator.resultExpr) } + @JvmStatic private fun getThisReplacement(call: JsInvocation): JsExpression? { if (isCallInvocation(call)) { diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionReader.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionReader.kt index d6fbe67979c..b8fd42f1c06 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionReader.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionReader.kt @@ -24,6 +24,7 @@ import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.js.backend.ast.* import org.jetbrains.kotlin.js.backend.ast.metadata.inlineStrategy import org.jetbrains.kotlin.js.config.JsConfig +import org.jetbrains.kotlin.js.inline.util.FunctionWithWrapper import org.jetbrains.kotlin.js.inline.util.IdentitySet import org.jetbrains.kotlin.js.inline.util.isCallInvocation import org.jetbrains.kotlin.js.parser.OffsetToSourceMapping @@ -154,8 +155,8 @@ class FunctionReader( override fun toString() = text.substring(offset) } - private val functionCache = object : SLRUCache(50, 50) { - override fun createValue(descriptor: CallableDescriptor): JsFunction = + private val functionCache = object : SLRUCache(50, 50) { + override fun createValue(descriptor: CallableDescriptor): FunctionWithWrapper = readFunction(descriptor).sure { "Could not read function: $descriptor" } } @@ -165,9 +166,9 @@ class FunctionReader( return currentModuleName != moduleName && moduleName in moduleNameToInfo.keys() } - operator fun get(descriptor: CallableDescriptor): JsFunction = functionCache.get(descriptor) + operator fun get(descriptor: CallableDescriptor): FunctionWithWrapper = functionCache.get(descriptor) - private fun readFunction(descriptor: CallableDescriptor): JsFunction? { + private fun readFunction(descriptor: CallableDescriptor): FunctionWithWrapper? { if (descriptor !in this) return null val moduleName = getModuleName(descriptor) @@ -180,7 +181,7 @@ class FunctionReader( return null } - private fun readFunctionFromSource(descriptor: CallableDescriptor, info: ModuleInfo): JsFunction? { + private fun readFunctionFromSource(descriptor: CallableDescriptor, info: ModuleInfo): FunctionWithWrapper? { val source = info.fileContent val tag = Namer.getFunctionTag(descriptor, config) val index = source.indexOf(tag) @@ -206,7 +207,7 @@ class FunctionReader( info.kotlinVariable to Namer.kotlinObject()) replaceExternalNames(function, replacements) function.markInlineArguments(descriptor) - return function + return FunctionWithWrapper(function, null) } } @@ -261,4 +262,4 @@ private fun replaceExternalNames(function: JsFunction, externalReplacements: Map } visitor.accept(function) -} +} \ No newline at end of file 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 4f3e8396af2..72d0650da8a 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 @@ -26,35 +26,38 @@ import org.jetbrains.kotlin.descriptors.DeclarationDescriptor; import org.jetbrains.kotlin.descriptors.FunctionDescriptor; import org.jetbrains.kotlin.diagnostics.DiagnosticSink; import org.jetbrains.kotlin.diagnostics.Errors; +import org.jetbrains.kotlin.js.backend.JsToStringGenerationVisitor; import org.jetbrains.kotlin.js.backend.ast.*; import org.jetbrains.kotlin.js.backend.ast.metadata.MetadataProperties; import org.jetbrains.kotlin.js.config.JsConfig; -import org.jetbrains.kotlin.js.inline.clean.FunctionPostProcessor; -import org.jetbrains.kotlin.js.inline.clean.RemoveUnusedFunctionDefinitionsKt; -import org.jetbrains.kotlin.js.inline.clean.RemoveUnusedLocalFunctionDeclarationsKt; +import org.jetbrains.kotlin.js.inline.clean.*; import org.jetbrains.kotlin.js.inline.context.FunctionContext; import org.jetbrains.kotlin.js.inline.context.InliningContext; import org.jetbrains.kotlin.js.inline.context.NamingContext; -import org.jetbrains.kotlin.js.inline.util.CollectUtilsKt; -import org.jetbrains.kotlin.js.inline.util.CollectionUtilsKt; -import org.jetbrains.kotlin.js.inline.util.NamingUtilsKt; +import org.jetbrains.kotlin.js.inline.util.*; import org.jetbrains.kotlin.resolve.inline.InlineStrategy; import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; -import static org.jetbrains.kotlin.js.inline.FunctionInlineMutator.getInlineableCallReplacement; import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.flattenStatement; +import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.pureFqn; public class JsInliner extends JsVisitorWithContextImpl { private final JsConfig config; - private final Map functions; - private final Map accessors; + private final Map functions; + private final Map accessors; private final Stack inliningContexts = new Stack<>(); private final Set processedFunctions = CollectionUtilsKt.IdentitySet(); private final Set inProcessFunctions = CollectionUtilsKt.IdentitySet(); private final FunctionReader functionReader; private final DiagnosticSink trace; + private Map existingImports = new HashMap<>(); + private JsContext statementContextForInline; + private final Map functionsByWrapperNodes = new HashMap<>(); + private final Map functionsByFunctionNodes = new HashMap<>(); // these are needed for error reporting, when inliner detects cycle private final Stack namedFunctionsStack = new Stack<>(); @@ -68,10 +71,12 @@ public class JsInliner extends JsVisitorWithContextImpl { @NotNull DiagnosticSink trace, @NotNull JsName currentModuleName, @NotNull List fragments, - @NotNull List fragmentsToProcess + @NotNull List fragmentsToProcess, + @NotNull List importStatements ) { - Map functions = CollectUtilsKt.collectNamedFunctions(fragments); - Map accessors = CollectUtilsKt.collectAccessors(fragments); + Map functions = CollectUtilsKt.collectNamedFunctionsAndWrappers(fragments); + Map accessors = CollectUtilsKt.collectAccessors(fragments); + DummyAccessorInvocationTransformer accessorInvocationTransformer = new DummyAccessorInvocationTransformer(); for (JsProgramFragment fragment : fragmentsToProcess) { accessorInvocationTransformer.accept(fragment.getDeclarationBlock()); @@ -79,27 +84,37 @@ public class JsInliner extends JsVisitorWithContextImpl { } FunctionReader functionReader = new FunctionReader(reporter, config, currentModuleName, fragments); JsInliner inliner = new JsInliner(config, functions, accessors, functionReader, trace); - List nodesToPostProcess = new ArrayList<>(); + + for (JsStatement statement : importStatements) { + inliner.processImportStatement(statement); + } + for (JsProgramFragment fragment : fragmentsToProcess) { - inliner.inliningContexts.push(inliner.new JsInliningContext()); - inliner.accept(fragment.getDeclarationBlock()); + inliner.inliningContexts.push(inliner.new JsInliningContext(inliner.new ListContext())); + inliner.acceptStatement(fragment.getDeclarationBlock()); // There can be inlined function in top-level initializers, we need to optimize them as well JsFunction fakeInitFunction = new JsFunction(JsDynamicScope.INSTANCE, fragment.getInitializerBlock(), ""); - inliner.accept(fakeInitFunction); + JsGlobalBlock initWrapper = new JsGlobalBlock(); + initWrapper.getStatements().add(new JsExpressionStatement(fakeInitFunction)); + inliner.accept(initWrapper); + initWrapper.getStatements().remove(initWrapper.getStatements().size() - 1); inliner.inliningContexts.pop(); - JsBlock block = new JsBlock(fragment.getDeclarationBlock(), fragment.getInitializerBlock(), fragment.getExportBlock()); - nodesToPostProcess.add(block); + fragment.getInitializerBlock().getStatements().addAll(0, initWrapper.getStatements()); } - RemoveUnusedFunctionDefinitionsKt.removeUnusedFunctionDefinitions(nodesToPostProcess, functions); + for (JsProgramFragment fragment : fragmentsToProcess) { + JsBlock block = new JsBlock(fragment.getDeclarationBlock(), fragment.getInitializerBlock(), fragment.getExportBlock()); + RemoveUnusedImportsKt.removeUnusedImports(block); + RemoveUnusedFunctionDefinitionsKt.removeUnusedFunctionDefinitions(block, CollectUtilsKt.collectNamedFunctions(block)); + } } private JsInliner( @NotNull JsConfig config, - @NotNull Map functions, - @NotNull Map accessors, + @NotNull Map functions, + @NotNull Map accessors, @NotNull FunctionReader functionReader, @NotNull DiagnosticSink trace ) { @@ -108,24 +123,74 @@ public class JsInliner extends JsVisitorWithContextImpl { this.accessors = accessors; this.functionReader = functionReader; this.trace = trace; + + Stream.concat(functions.values().stream(), accessors.values().stream()) + .forEach(f -> { + functionsByFunctionNodes.put(f.getFunction(), f); + if (f.getWrapperBody() != null) { + functionsByWrapperNodes.put(f.getWrapperBody(), f); + } + }); + } + + private void processImportStatement(JsStatement statement) { + if (statement instanceof JsVars) { + JsVars jsVars = (JsVars) statement; + String tag = getImportTag(jsVars); + if (tag != null) { + existingImports.put(tag, jsVars.getVars().get(0).getName()); + } + } + } + + @Nullable + private static String getImportTag(JsVars jsVars) { + if (jsVars.getVars().size() == 1) { + JsVars.JsVar jsVar = jsVars.getVars().get(0); + if (jsVar.getInitExpression() != null) { + return extractImportTag(jsVar.getInitExpression()); + } + } + + return null; } @Override public boolean visit(@NotNull JsFunction function, @NotNull JsContext context) { - inliningContexts.push(new JsInliningContext()); - assert !inProcessFunctions.contains(function): "Inliner has revisited function"; - inProcessFunctions.add(function); - - if (functions.containsValue(function)) { - namedFunctionsStack.push(function); + FunctionWithWrapper functionWithWrapper = functionsByFunctionNodes.get(function); + if (functionWithWrapper != null) { + visit(functionWithWrapper); + return false; + } + else { + startFunction(function); + return super.visit(function, context); } - - return super.visit(function, context); } @Override public void endVisit(@NotNull JsFunction function, @NotNull JsContext context) { super.endVisit(function, context); + if (!functionsByFunctionNodes.containsKey(function)) { + endFunction(function); + } + } + + private void startFunction(@NotNull JsFunction function) { + JsContext statementContext = statementContextForInline != null ? statementContextForInline : + getLastStatementLevelContext(); + + inliningContexts.push(new JsInliningContext(statementContext)); + + assert !inProcessFunctions.contains(function): "Inliner has revisited function"; + inProcessFunctions.add(function); + + if (functions.values().stream().anyMatch(namedFunction -> namedFunction.getFunction().equals(function))) { + namedFunctionsStack.push(function); + } + } + + private void endFunction(@NotNull JsFunction function) { NamingUtilsKt.refreshLabelNames(function.getBody(), function.getScope()); RemoveUnusedLocalFunctionDeclarationsKt.removeUnusedLocalFunctionDeclarations(function); @@ -143,6 +208,61 @@ public class JsInliner extends JsVisitorWithContextImpl { } } + @Override + public boolean visit(@NotNull JsBlock x, @NotNull JsContext ctx) { + FunctionWithWrapper functionWithWrapper = functionsByWrapperNodes.get(x); + if (functionWithWrapper != null) { + visit(functionWithWrapper); + return false; + } + return super.visit(x, ctx); + } + + private void visit(@NotNull FunctionWithWrapper functionWithWrapper) { + JsContext oldContextForInline = statementContextForInline; + Map oldExistingImports = existingImports; + + ListContext innerContext = new ListContext<>(); + + JsBlock wrapperBody = functionWithWrapper.getWrapperBody(); + List statements = null; + if (wrapperBody != null) { + existingImports = new HashMap<>(); + statementContexts.push(innerContext); + statementContextForInline = innerContext; + + for (JsStatement statement : wrapperBody.getStatements()) { + processImportStatement(statement); + } + assert functionWithWrapper.getWrapperBody() != null; + statements = functionWithWrapper.getWrapperBody().getStatements(); + if (!statements.isEmpty() && statements.get(statements.size() - 1) instanceof JsReturn) { + statements = statements.subList(0, statements.size() - 1); + } + + innerContext.traverse(statements); + statementContexts.pop(); + } + else { + statementContextForInline = getLastStatementLevelContext(); + } + + startFunction(functionWithWrapper.getFunction()); + + JsBlock block = new JsBlock(functionWithWrapper.getFunction().getBody()); + innerContext.traverse(block.getStatements()); + functionWithWrapper.getFunction().getBody().traverse(this, innerContext); + + endFunction(functionWithWrapper.getFunction()); + + if (statements != null) { + statements.addAll(block.getStatements().subList(0, block.getStatements().size() - 1)); + } + + statementContextForInline = oldContextForInline; + existingImports = oldExistingImports; + } + @Override public boolean visit(@NotNull JsInvocation call, @NotNull JsContext context) { if (!hasToBeInlined(call)) return true; @@ -153,13 +273,13 @@ public class JsInliner extends JsVisitorWithContextImpl { inlineCallInfos.add(new JsCallInfo(call, containingFunction)); } - JsFunction definition = getFunctionContext().getFunctionDefinition(call); + FunctionWithWrapper definition = getFunctionContext().getFunctionDefinition(call); - if (inProcessFunctions.contains(definition)) { - reportInlineCycle(call, definition); + if (inProcessFunctions.contains(definition.getFunction())) { + reportInlineCycle(call, definition.getFunction()); } - else if (!processedFunctions.contains(definition)) { - accept(definition); + else if (!processedFunctions.contains(definition.getFunction())) { + visit(definition); } return true; @@ -208,7 +328,19 @@ public class JsInliner extends JsVisitorWithContextImpl { } JsInliningContext inliningContext = getInliningContext(); - InlineableResult inlineableResult = getInlineableCallReplacement(call, inliningContext); + FunctionWithWrapper functionWithWrapper = inliningContext.getFunctionContext().getFunctionDefinition(call); + + // Since we could get functionWithWrapper as a simple function directly from staticRef (which always points on implementation) + // we should check if we have a known wrapper for it + if (functionsByFunctionNodes.containsKey(functionWithWrapper.getFunction())) { + functionWithWrapper = functionsByFunctionNodes.get(functionWithWrapper.getFunction()); + } + + JsFunction function = functionWithWrapper.getFunction().deepCopy(); + if (functionWithWrapper.getWrapperBody() != null) { + applyWrapper(functionWithWrapper.getWrapperBody(), function, inliningContext); + } + InlineableResult inlineableResult = FunctionInlineMutator.getInlineableCallReplacement(call, function, inliningContext); JsStatement inlineableBody = inlineableResult.getInlineableBody(); JsExpression resultExpression = inlineableResult.getResultExpression(); @@ -232,6 +364,64 @@ public class JsInliner extends JsVisitorWithContextImpl { context.replaceMe(resultExpression); } + private void applyWrapper( + @NotNull JsBlock wrapper, @NotNull JsFunction function, + @NotNull InliningContext inliningContext + ) { + Map replacements = new HashMap<>(); + + List copiedStatements = new ArrayList<>(); + for (JsStatement statement : wrapper.getStatements()) { + if (statement instanceof JsReturn) continue; + + statement = statement.deepCopy(); + if (statement instanceof JsVars) { + JsVars jsVars = (JsVars) statement; + String tag = getImportTag(jsVars); + if (tag != null) { + JsName name = jsVars.getVars().get(0).getName(); + JsName existingName = existingImports == null ? MetadataProperties.getLocalAlias(name) : null; + if (existingName == null) { + existingName = existingImports.computeIfAbsent(tag, t -> { + copiedStatements.add(jsVars); + JsName alias = JsScope.declareTemporaryName(name.getIdent()); + alias.copyMetadataFrom(name); + replacements.put(name, pureFqn(alias, null)); + return alias; + }); + } + + if (name != existingName) { + JsExpression replacement = pureFqn(existingName, null); + replacements.put(name, replacement); + } + + continue; + } + } + + copiedStatements.add(statement); + } + + Set definedNames = copiedStatements.stream() + .flatMap(node -> CollectUtilsKt.collectDefinedNamesInAllScopes(node).stream()) + .filter(name -> !replacements.containsKey(name)) + .collect(Collectors.toSet()); + for (JsName name : definedNames) { + JsName alias = JsScope.declareTemporaryName(name.getIdent()); + alias.copyMetadataFrom(name); + JsExpression replacement = pureFqn(alias, null); + replacements.put(name, replacement); + } + + for (JsStatement statement : copiedStatements) { + statement = RewriteUtilsKt.replaceNames(statement, replacements); + inliningContext.getStatementContextBeforeCurrentFunction().addPrevious(accept(statement)); + } + + RewriteUtilsKt.replaceNames(function, replacements); + } + private static boolean isSuspendWithCurrentContinuation(@Nullable DeclarationDescriptor descriptor) { if (!(descriptor instanceof FunctionDescriptor)) return false; return CommonCoroutineCodegenUtilKt.isBuiltInSuspendCoroutineOrReturn((FunctionDescriptor) descriptor.getOriginal()); @@ -262,6 +452,40 @@ public class JsInliner extends JsVisitorWithContextImpl { return namedFunctionsStack.peek(); } + @Nullable + private static String extractImportTag(@NotNull JsExpression expression) { + StringBuilder sb = new StringBuilder(); + return extractImportTagImpl(expression, sb) ? sb.toString() : null; + } + + private static boolean extractImportTagImpl(@NotNull JsExpression expression, @NotNull StringBuilder sb) { + if (expression instanceof JsNameRef) { + JsNameRef nameRef = (JsNameRef) expression; + if (nameRef.getQualifier() != null) { + if (!extractImportTagImpl(nameRef.getQualifier(), sb)) { + return false; + } + sb.append('.'); + } + sb.append(JsToStringGenerationVisitor.javaScriptString(nameRef.getIdent())); + return true; + } + else if (expression instanceof JsArrayAccess) { + JsArrayAccess arrayAccess = (JsArrayAccess) expression; + if (!extractImportTagImpl(arrayAccess.getArrayExpression(), sb)) { + return false; + } + sb.append("."); + if (!extractImportTagImpl(arrayAccess.getIndexExpression(), sb)) { + return false; + } + return true; + } + else { + return false; + } + } + private void reportInlineCycle(@NotNull JsInvocation call, @NotNull JsFunction calledFunction) { MetadataProperties.setInlineStrategy(call, InlineStrategy.NOT_INLINE); Iterator it = inlineCallInfos.descendingIterator(); @@ -291,20 +515,24 @@ public class JsInliner extends JsVisitorWithContextImpl { private class JsInliningContext implements InliningContext { private final FunctionContext functionContext; - JsInliningContext() { + @NotNull + private final JsContext statementContextBeforeCurrentFunction; + + JsInliningContext(@NotNull JsContext statementContextBeforeCurrentFunction) { functionContext = new FunctionContext(functionReader, config) { @Nullable @Override - protected JsFunction lookUpStaticFunction(@Nullable JsName functionName) { + protected FunctionWithWrapper lookUpStaticFunction(@Nullable JsName functionName) { return functions.get(functionName); } @Nullable @Override - protected JsFunction lookUpStaticFunctionByTag(@NotNull String functionTag) { + protected FunctionWithWrapper lookUpStaticFunctionByTag(@NotNull String functionTag) { return accessors.get(functionTag); } }; + this.statementContextBeforeCurrentFunction = statementContextBeforeCurrentFunction; } @NotNull @@ -324,6 +552,12 @@ public class JsInliner extends JsVisitorWithContextImpl { public FunctionContext getFunctionContext() { return functionContext; } + + @NotNull + @Override + public JsContext getStatementContextBeforeCurrentFunction() { + return statementContextBeforeCurrentFunction; + } } private static class JsCallInfo { diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/removeUnusedFunctionDefinitions.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/removeUnusedFunctionDefinitions.kt index ceead209a81..13a36601115 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/removeUnusedFunctionDefinitions.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/removeUnusedFunctionDefinitions.kt @@ -29,10 +29,10 @@ import org.jetbrains.kotlin.js.inline.util.collectFunctionReferencesInside * At now, it only removes unused local functions and function literals, * because named functions can be referenced from another module. */ -fun removeUnusedFunctionDefinitions(roots: List, functions: Map) { +fun removeUnusedFunctionDefinitions(root: JsNode, functions: Map) { val removable = with(UnusedLocalFunctionsCollector(functions)) { process() - roots.forEach { accept(it) } + accept(root) removableFunctions }.toSet() @@ -45,7 +45,7 @@ fun removeUnusedFunctionDefinitions(roots: List, functions: Map) : JsVisitorWithContextImpl() { @@ -79,9 +79,7 @@ private class UnusedLocalFunctionsCollector(private val functions: Map): Boolean { - return !(wasProcessed(x)) - } + override fun visit(x: JsFunction, ctx: JsContext<*>): Boolean = !wasProcessed(x) override fun endVisit(x: JsFunction, ctx: JsContext<*>) { processed.add(x) diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/resolveTemporaryNames.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/resolveTemporaryNames.kt index ca146a28a98..f923b8d62d1 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/resolveTemporaryNames.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/resolveTemporaryNames.kt @@ -108,7 +108,7 @@ private fun Scope.liftUsedNames(): Scope { scope.children.forEach { child -> scope.usedNames += scope.declaredNames traverse(child) - scope.usedNames += child.usedNames.filter { !it.isTemporary } + scope.usedNames += child.usedNames } } traverse(this) 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 d207c80d0b7..f2c52d907f4 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 @@ -25,11 +25,11 @@ import org.jetbrains.kotlin.js.inline.util.* import org.jetbrains.kotlin.js.translate.context.Namer abstract class FunctionContext(private val functionReader: FunctionReader, private val config: JsConfig) { - protected abstract fun lookUpStaticFunction(functionName: JsName?): JsFunction? + protected abstract fun lookUpStaticFunction(functionName: JsName?): FunctionWithWrapper? - protected abstract fun lookUpStaticFunctionByTag(functionTag: String): JsFunction? + protected abstract fun lookUpStaticFunctionByTag(functionTag: String): FunctionWithWrapper? - fun getFunctionDefinition(call: JsInvocation): JsFunction { + fun getFunctionDefinition(call: JsInvocation): FunctionWithWrapper { return getFunctionDefinitionImpl(call)!! } @@ -67,7 +67,7 @@ abstract class FunctionContext(private val functionReader: FunctionReader, priva * 5. Qualifier can be JsNameRef with ref to case [3] * in case of local function with closure. */ - private fun getFunctionDefinitionImpl(call: JsInvocation): JsFunction? { + private fun getFunctionDefinitionImpl(call: JsInvocation): FunctionWithWrapper? { val descriptor = call.descriptor if (descriptor != null) { if (descriptor in functionReader) return functionReader[descriptor] @@ -87,19 +87,19 @@ abstract class FunctionContext(private val functionReader: FunctionReader, priva return when (qualifier) { is JsInvocation -> { tryExtractCallableReference(qualifier) ?: getSimpleName(qualifier)?.let { simpleName -> - lookUpStaticFunction(simpleName)?.let { if (isFunctionCreator(it)) it else null } + lookUpStaticFunction(simpleName)?.let { if (isFunctionCreator(it.function)) it else null } } } is JsNameRef -> lookUpStaticFunction(qualifier.name) - is JsFunction -> qualifier + is JsFunction -> FunctionWithWrapper(qualifier, null) else -> null } } - private fun tryExtractCallableReference(invocation: JsInvocation): JsFunction? { + private fun tryExtractCallableReference(invocation: JsInvocation): FunctionWithWrapper? { if (invocation.isCallableReference) { val arg = invocation.arguments[1] - if (arg is JsFunction) return arg + if (arg is JsFunction) return FunctionWithWrapper(arg, null) } return null } diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/context/InliningContext.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/context/InliningContext.kt index 5b48897a7f7..5e210d3ca84 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/context/InliningContext.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/context/InliningContext.kt @@ -21,6 +21,9 @@ import org.jetbrains.kotlin.js.backend.ast.JsStatement interface InliningContext { val statementContext: JsContext + + val statementContextBeforeCurrentFunction: JsContext + val functionContext: FunctionContext fun newNamingContext(): NamingContext diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/collectUtils.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/collectUtils.kt index 8681be567c7..42811422f5d 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/collectUtils.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/collectUtils.kt @@ -114,11 +114,30 @@ fun collectDefinedNames(scope: JsNode): Set { return names } +fun collectDefinedNamesInAllScopes(scope: JsNode): Set { + val names = mutableSetOf() + + object : RecursiveJsVisitor() { + override fun visit(x: JsVars.JsVar) { + super.visit(x) + names += x.name + } + + override fun visitFunction(x: JsFunction) { + super.visitFunction(x) + x.name?.let { names += it } + names += x.parameters.map { it.name } + } + }.accept(scope) + + return names +} + fun JsFunction.collectFreeVariables() = collectUsedNames(body) - collectDefinedNames(body) - parameters.map { it.name } fun JsFunction.collectLocalVariables() = collectDefinedNames(body) + parameters.map { it.name } -fun collectNamedFunctions(scope: JsNode) = collectNamedFunctionsAndMetadata(scope).mapValues { it.value.first } +fun collectNamedFunctions(scope: JsNode) = collectNamedFunctionsAndMetadata(scope).mapValues { it.value.first.function } fun collectNamedFunctionsOrMetadata(scope: JsNode) = collectNamedFunctionsAndMetadata(scope).mapValues { it.value.second } @@ -131,8 +150,17 @@ fun collectNamedFunctions(fragments: List): Map> { - val namedFunctions = mutableMapOf>() +fun collectNamedFunctionsAndWrappers(fragments: List): Map { + val result = mutableMapOf() + for (fragment in fragments) { + result += collectNamedFunctionsAndMetadata(fragment.declarationBlock).mapValues { it.value.first } + result += collectNamedFunctionsAndMetadata(fragment.initializerBlock).mapValues { it.value.first } + } + return result +} + +fun collectNamedFunctionsAndMetadata(scope: JsNode): Map> { + val namedFunctions = mutableMapOf>() scope.accept(object : RecursiveJsVisitor() { override fun visitBinaryExpression(x: JsBinaryOperation) { @@ -141,9 +169,10 @@ fun collectNamedFunctionsAndMetadata(scope: JsNode): Map + namedFunctions[name] = Pair(FunctionWithWrapper(function, wrapper), right) + } } } } @@ -154,8 +183,7 @@ fun collectNamedFunctionsAndMetadata(scope: JsNode): Map namedFunctions[name] = Pair(function, initializer) } } @@ -165,22 +193,19 @@ fun collectNamedFunctionsAndMetadata(scope: JsNode): Map expression - else -> InlineMetadata.decompose(expression)?.function - } }) return namedFunctions } -fun collectAccessors(scope: JsNode): Map { - val accessors = hashMapOf() +data class FunctionWithWrapper(val function: JsFunction, val wrapperBody: JsBlock?) + +fun collectAccessors(scope: JsNode): Map { + val accessors = hashMapOf() scope.accept(object : RecursiveJsVisitor() { override fun visitInvocation(invocation: JsInvocation) { @@ -194,14 +219,19 @@ fun collectAccessors(scope: JsNode): Map { return accessors } -fun collectAccessors(fragments: List): Map { - val result = mutableMapOf() +fun collectAccessors(fragments: List): Map { + val result = mutableMapOf() for (fragment in fragments) { result += collectAccessors(fragment.declarationBlock) } return result } +fun extractFunction(expression: JsExpression) = when (expression) { + is JsFunction -> FunctionWithWrapper(expression, null) + else -> InlineMetadata.decompose(expression)?.function ?: InlineMetadata.tryExtractFunction(expression) +} + fun collectInstances(klass: Class, scope: JsNode): List { return with(InstanceCollector(klass, visitNestedDeclarations = false)) { accept(scope) diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/rewriters/NameReplacingVisitor.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/rewriters/NameReplacingVisitor.kt index 165e1f7dc82..0a38948cff0 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/rewriters/NameReplacingVisitor.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/rewriters/NameReplacingVisitor.kt @@ -21,23 +21,29 @@ import org.jetbrains.kotlin.js.backend.ast.* class NameReplacingVisitor(private val replaceMap: Map) : JsVisitorWithContextImpl() { override fun endVisit(x: JsNameRef, ctx: JsContext) { + if (x.qualifier != null) return val replacement = replaceMap[x.name] ?: return - ctx.replaceMe(replacement.deepCopy().source(x.source)) + val replacementCopy = accept(replacement.deepCopy().source(x.source)) + ctx.replaceMe(replacementCopy) } - override fun endVisit(x: JsVars.JsVar, ctx: JsContext) { - val replacement = replaceMap[x.name] - if (replacement is HasName) { - val replacementVar = JsVars.JsVar(replacement.name, x.initExpression) - ctx.replaceMe(replacementVar.source(x.source)) - } - } + override fun endVisit(x: JsVars.JsVar, ctx: JsContext<*>) = applyToNamedNode(x) - override fun endVisit(x: JsLabel, ctx: JsContext) { - val replacement = replaceMap[x.name] - if (replacement is HasName) { - val replacementLabel = JsLabel(replacement.name, x.statement) - ctx.replaceMe(replacementLabel) + override fun endVisit(x: JsLabel, ctx: JsContext<*>) = applyToNamedNode(x) + + override fun endVisit(x: JsFunction, ctx: JsContext<*>) = applyToNamedNode(x) + + override fun endVisit(x: JsParameter, ctx: JsContext<*>) = applyToNamedNode(x) + + private fun applyToNamedNode(x: HasName) { + while (true) { + val replacement = replaceMap[x.name] + if (replacement is HasName) { + x.name = replacement.name + } + else { + break + } } } } diff --git a/js/js.libraries/src/js/markerFunctions.js b/js/js.libraries/src/js/markerFunctions.js index cac14f87883..eab19252910 100644 --- a/js/js.libraries/src/js/markerFunctions.js +++ b/js/js.libraries/src/js/markerFunctions.js @@ -25,6 +25,16 @@ Kotlin.defineInlineFunction = function(tag, fun) { return fun; }; +Kotlin.wrapFunction = function(fun) { + var f = function() { + f = fun(); + return f.apply(this, arguments); + }; + return function() { + return f.apply(this, arguments); + }; +}; + Kotlin.isTypeOf = function(type) { return function (object) { return typeof object === type; diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java index 68d16e517e2..2027114e495 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java @@ -3935,6 +3935,18 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest { doTest(fileName); } + @TestMetadata("inlineInInlineWithLambda.kt") + public void testInlineInInlineWithLambda() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/inline/inlineInInlineWithLambda.kt"); + doTest(fileName); + } + + @TestMetadata("inlineInInlineWithLambdaPrivate.kt") + public void testInlineInInlineWithLambdaPrivate() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/inline/inlineInInlineWithLambdaPrivate.kt"); + doTest(fileName); + } + @TestMetadata("inlineInc.kt") public void testInlineInc() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/inline/inlineInc.kt"); @@ -4724,6 +4736,24 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest { doTest(fileName); } + @TestMetadata("importObjectInstance.kt") + public void testImportObjectInstance() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/inlineMultiModule/importObjectInstance.kt"); + doTest(fileName); + } + + @TestMetadata("inlineInInlineWithLambdaMultiModule.kt") + public void testInlineInInlineWithLambdaMultiModule() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/inlineMultiModule/inlineInInlineWithLambdaMultiModule.kt"); + doTest(fileName); + } + + @TestMetadata("internalNameClash.kt") + public void testInternalNameClash() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/inlineMultiModule/internalNameClash.kt"); + doTest(fileName); + } + @TestMetadata("keywordAsMemberName.kt") public void testKeywordAsMemberName() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/inlineMultiModule/keywordAsMemberName.kt"); diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/facade/K2JSTranslator.java b/js/js.translator/src/org/jetbrains/kotlin/js/facade/K2JSTranslator.java index 7ea6ef7bb16..e3528f37573 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/facade/K2JSTranslator.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/facade/K2JSTranslator.java @@ -135,7 +135,7 @@ public final class K2JSTranslator { List allFragments = new ArrayList<>(translationResult.getFragments()); JsInliner.process(reporter, config, analysisResult.getBindingTrace(), translationResult.getInnerModuleName(), - allFragments, newFragments); + allFragments, newFragments, translationResult.getImportStatements()); LabeledBlockToDoWhileTransformation.INSTANCE.apply(newFragments); diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/DeclarationExporter.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/DeclarationExporter.kt index 8e53e8ab9d6..4c96f475514 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/DeclarationExporter.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/DeclarationExporter.kt @@ -51,7 +51,7 @@ internal class DeclarationExporter(val context: StaticContext) { val qualifier = when { container is PackageFragmentDescriptor -> { - getLocalPackageReference(container.fqName) + getLocalPackageName(container.fqName).makeRef() } DescriptorUtils.isObject(container) -> { JsAstUtils.prototypeOf(context.getInnerNameForDescriptor(container).makeRef()) @@ -129,18 +129,18 @@ internal class DeclarationExporter(val context: StaticContext) { statements += JsAstUtils.defineProperty(qualifier, name, propertyLiteral).exportStatement(declaration) } - private fun getLocalPackageReference(packageName: FqName): JsExpression { + fun getLocalPackageName(packageName: FqName): JsName { if (packageName.isRoot) { - return context.fragment.scope.declareName(Namer.getRootPackageName()).makeRef() + return context.fragment.scope.declareName(Namer.getRootPackageName()) } var name = localPackageNames[packageName] if (name == null) { name = JsScope.declareTemporaryName("package$" + packageName.shortName().asString()) localPackageNames[packageName] = name statements += definePackageAlias(packageName.shortName().asString(), name, packageName.asString(), - getLocalPackageReference(packageName.parent())) + getLocalPackageName(packageName.parent()).makeRef()) } - return name.makeRef() + return name } private fun JsExpression.exportStatement(declaration: DeclarationDescriptor) = JsExpressionStatement(this).also { diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/InlineFunctionContext.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/InlineFunctionContext.kt new file mode 100644 index 00000000000..a28fe96f807 --- /dev/null +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/InlineFunctionContext.kt @@ -0,0 +1,28 @@ +/* + * 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.js.translate.context + +import org.jetbrains.kotlin.descriptors.CallableDescriptor +import org.jetbrains.kotlin.js.backend.ast.JsGlobalBlock +import org.jetbrains.kotlin.js.backend.ast.JsName + +class InlineFunctionContext(val descriptor: CallableDescriptor) { + val imports = mutableMapOf() + val importBlock = JsGlobalBlock() + val prototypeBlock = JsGlobalBlock() + val declarationsBlock = JsGlobalBlock() +} \ No newline at end of file diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/Namer.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/Namer.java index 2ffa9fe4a11..a54d4dc54e8 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/Namer.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/Namer.java @@ -94,6 +94,7 @@ public final class Namer { public static final JsNameRef IS_ARRAY_FUN_REF = new JsNameRef("isArray", "Array"); public static final String DEFINE_INLINE_FUNCTION = "defineInlineFunction"; + public static final String WRAP_FUNCTION = "wrapFunction"; public static final String DEFAULT_PARAMETER_IMPLEMENTOR_SUFFIX = "$default"; private static final JsNameRef JS_OBJECT = new JsNameRef("Object"); @@ -355,6 +356,11 @@ public final class Namer { return pureFqn(DEFINE_INLINE_FUNCTION, kotlinObject()); } + @NotNull + public static JsNameRef wrapFunction() { + return pureFqn(WRAP_FUNCTION, kotlinObject()); + } + @NotNull public static String suggestedModuleName(@NotNull String id) { if (id.isEmpty()) { diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/StaticContext.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/StaticContext.java index 41b7938100d..244add7e113 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/StaticContext.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/StaticContext.java @@ -533,6 +533,9 @@ public final class StaticContext { private final class InnerNameGenerator extends Generator { public InnerNameGenerator() { addRule(descriptor -> { + if (descriptor instanceof PackageFragmentDescriptor && DescriptorUtils.getContainingModule(descriptor) == currentModule) { + return exporter.getLocalPackageName(((PackageFragmentDescriptor) descriptor).getFqName()); + } if (descriptor instanceof FunctionDescriptor) { FunctionDescriptor initialDescriptor = ((FunctionDescriptor) descriptor).getInitialSignatureDescriptor(); if (initialDescriptor != null) { diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/TranslationContext.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/TranslationContext.java index 8c516ce6c43..2f387a9096a 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/TranslationContext.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/TranslationContext.java @@ -40,6 +40,7 @@ import org.jetbrains.kotlin.resolve.BindingContext; import org.jetbrains.kotlin.resolve.BindingTrace; import org.jetbrains.kotlin.resolve.DescriptorUtils; import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt; +import org.jetbrains.kotlin.resolve.inline.InlineUtil; import org.jetbrains.kotlin.resolve.scopes.receivers.ExtensionReceiver; import org.jetbrains.kotlin.serialization.js.ModuleKind; @@ -72,6 +73,9 @@ public class TranslationContext { @Nullable private final VariableDescriptor continuationParameterDescriptor; + @Nullable + private InlineFunctionContext inlineFunctionContext; + @NotNull public static TranslationContext rootContext(@NotNull StaticContext staticContext) { DynamicContext rootDynamicContext = DynamicContext.rootContext( @@ -104,6 +108,14 @@ public class TranslationContext { } continuationParameterDescriptor = calculateContinuationParameter(); + inlineFunctionContext = parent != null ? parent.inlineFunctionContext : null; + + DeclarationDescriptor parentDescriptor = parent != null ? parent.declarationDescriptor : null; + if (parentDescriptor != declarationDescriptor && + declarationDescriptor instanceof CallableDescriptor && + InlineUtil.isInline(declarationDescriptor)) { + inlineFunctionContext = new InlineFunctionContext((CallableDescriptor) declarationDescriptor); + } } private VariableDescriptor calculateContinuationParameter() { @@ -137,6 +149,11 @@ public class TranslationContext { return dynamicContext; } + @Nullable + public InlineFunctionContext getInlineFunctionContext() { + return inlineFunctionContext; + } + @NotNull public TranslationContext contextWithScope(@NotNull JsFunction fun) { return this.newFunctionBody(fun, aliasingContext, declarationDescriptor); @@ -323,8 +340,42 @@ public class TranslationContext { @NotNull public JsExpression getInnerReference(@NotNull DeclarationDescriptor descriptor) { + JsName name; + if (inlineFunctionContext == null || !isPublicInlineFunction() || + DescriptorUtils.isAncestor(inlineFunctionContext.getDescriptor(), descriptor, false)) { + name = getInnerNameForDescriptor(descriptor); + } + else { + String tag = staticContext.getTag(descriptor); + name = inlineFunctionContext.getImports().computeIfAbsent(tag, t -> { + JsExpression imported = createInlineLocalImportExpression(descriptor); + if (imported instanceof JsNameRef) { + JsNameRef importedNameRef = (JsNameRef) imported; + if (importedNameRef.getQualifier() == null && importedNameRef.getIdent().equals(Namer.getRootPackageName()) && + (descriptor instanceof PackageFragmentDescriptor || descriptor instanceof ModuleDescriptor)) { + return importedNameRef.getName(); + } + } + + JsName result = JsScope.declareTemporaryName(StaticContext.getSuggestedName(descriptor)); + if (isFromCurrentModule(descriptor) && !AnnotationsUtils.isNativeObject(descriptor)) { + MetadataProperties.setLocalAlias(result, getInnerNameForDescriptor(descriptor)); + } + MetadataProperties.setDescriptor(result, descriptor); + MetadataProperties.setStaticRef(result, imported); + MetadataProperties.setImported(result, true); + inlineFunctionContext.getImportBlock().getStatements().add(JsAstUtils.newVar(result, imported)); + return result; + }); + } + + return pureFqn(name, null); + } + + @NotNull + private JsExpression createInlineLocalImportExpression(@NotNull DeclarationDescriptor descriptor) { + JsExpression result = getQualifiedReference(descriptor); JsName name = getInnerNameForDescriptor(descriptor); - JsExpression result = pureFqn(name, null); SuggestedName suggested = staticContext.suggestName(descriptor); if (suggested != null && getConfig().getModuleKind() != ModuleKind.PLAIN && isPublicInlineFunction()) { @@ -695,7 +746,12 @@ public class TranslationContext { } public void addDeclarationStatement(@NotNull JsStatement statement) { - staticContext.getDeclarationStatements().add(statement); + if (inlineFunctionContext != null) { + inlineFunctionContext.getDeclarationsBlock().getStatements().add(statement); + } + else { + staticContext.getDeclarationStatements().add(statement); + } } public void addTopLevelStatement(@NotNull JsStatement statement) { @@ -725,15 +781,10 @@ public class TranslationContext { } public boolean isPublicInlineFunction() { - DeclarationDescriptor descriptor = declarationDescriptor; - while (descriptor instanceof FunctionDescriptor) { - FunctionDescriptor function = (FunctionDescriptor) descriptor; - if (function.isInline() && DescriptorUtilsKt.isEffectivelyPublicApi(function)) { - return true; - } - descriptor = descriptor.getContainingDeclaration(); - } - return false; + if (inlineFunctionContext == null) return false; + + CallableDescriptor function = inlineFunctionContext.getDescriptor(); + return function.getVisibility().effectiveVisibility(function, true).getPublicApi(); } @Nullable diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/AbstractDeclarationVisitor.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/AbstractDeclarationVisitor.kt index dd6085ef9c4..1c3269fcc65 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/AbstractDeclarationVisitor.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/AbstractDeclarationVisitor.kt @@ -113,7 +113,7 @@ abstract class AbstractDeclarationVisitor : TranslatorVisitor() { function.body.statements += FunctionBodyTranslator.setDefaultValueForArguments(descriptor, innerContext) } innerContext.translateFunction(expression, function) - return innerContext.wrapWithInlineMetadata(function, descriptor, context.config) + return innerContext.wrapWithInlineMetadata(function, descriptor) } // used from kotlinx.serialization diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/FileDeclarationVisitor.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/FileDeclarationVisitor.kt index 0f2a415902b..2d6af6ab072 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/FileDeclarationVisitor.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/FileDeclarationVisitor.kt @@ -15,15 +15,14 @@ */ package org.jetbrains.kotlin.js.translate.declaration -import org.jetbrains.kotlin.js.backend.ast.JsExpression -import org.jetbrains.kotlin.js.backend.ast.JsFunction -import org.jetbrains.kotlin.js.backend.ast.JsName import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.PropertyDescriptor +import org.jetbrains.kotlin.js.backend.ast.JsExpression import org.jetbrains.kotlin.js.translate.context.TranslationContext import org.jetbrains.kotlin.js.translate.utils.BindingUtils import org.jetbrains.kotlin.js.translate.utils.JsAstUtils import org.jetbrains.kotlin.js.translate.utils.JsDescriptorUtils +import org.jetbrains.kotlin.js.translate.utils.addFunctionButNotExport import org.jetbrains.kotlin.psi.KtElement import org.jetbrains.kotlin.psi.KtProperty import org.jetbrains.kotlin.resolve.BindingContext @@ -47,15 +46,15 @@ class FileDeclarationVisitor(private val context: TranslationContext) : Abstract override fun addFunction(descriptor: FunctionDescriptor, expression: JsExpression?, psi: KtElement?) { if (expression == null) return - addFunctionButNotExport(descriptor, expression) + context.addFunctionButNotExport(descriptor, expression) context.export(descriptor) } override fun addProperty(descriptor: PropertyDescriptor, getter: JsExpression, setter: JsExpression?) { if (!JsDescriptorUtils.isSimpleFinalProperty(descriptor)) { - addFunctionButNotExport(descriptor.getter!!, getter) + context.addFunctionButNotExport(descriptor.getter!!, getter) if (setter != null) { - addFunctionButNotExport(descriptor.setter!!, setter) + context.addFunctionButNotExport(descriptor.setter!!, setter) } } context.export(descriptor) @@ -64,18 +63,4 @@ class FileDeclarationVisitor(private val context: TranslationContext) : Abstract override fun getBackingFieldReference(descriptor: PropertyDescriptor): JsExpression { return context.getInnerReference(descriptor) } - - private fun addFunctionButNotExport(descriptor: FunctionDescriptor, expression: JsExpression): JsName { - val name = context.getInnerNameForDescriptor(descriptor) - when (expression) { - is JsFunction -> { - expression.name = name - context.addDeclarationStatement(expression.makeStmt()) - } - else -> { - context.addDeclarationStatement(JsAstUtils.newVar(name, expression)) - } - } - return name - } } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/FunctionTranslator.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/FunctionTranslator.kt index 3b50e37172f..b86e1aadfb3 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/FunctionTranslator.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/FunctionTranslator.kt @@ -19,18 +19,14 @@ package org.jetbrains.kotlin.js.translate.expression import com.intellij.openapi.vfs.VfsUtilCore import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.impl.PropertyAccessorDescriptorImpl -import org.jetbrains.kotlin.js.backend.ast.JsExpression -import org.jetbrains.kotlin.js.backend.ast.JsFunction -import org.jetbrains.kotlin.js.backend.ast.JsParameter -import org.jetbrains.kotlin.js.backend.ast.JsScope +import org.jetbrains.kotlin.js.backend.ast.* import org.jetbrains.kotlin.js.backend.ast.metadata.descriptor import org.jetbrains.kotlin.js.backend.ast.metadata.functionDescriptor import org.jetbrains.kotlin.js.backend.ast.metadata.hasDefaultValue import org.jetbrains.kotlin.js.config.JSConfigurationKeys -import org.jetbrains.kotlin.js.config.JsConfig +import org.jetbrains.kotlin.js.inline.util.FunctionWithWrapper import org.jetbrains.kotlin.js.translate.context.Namer import org.jetbrains.kotlin.js.translate.context.TranslationContext -import org.jetbrains.kotlin.js.translate.reference.CallExpressionTranslator.shouldBeInlined import org.jetbrains.kotlin.js.translate.utils.BindingUtils import org.jetbrains.kotlin.js.translate.utils.FunctionBodyTranslator.translateFunctionBody import org.jetbrains.kotlin.js.translate.utils.JsAstUtils @@ -104,9 +100,9 @@ fun TranslationContext.translateFunction(declaration: KtDeclarationWithBody, fun function.functionDescriptor = descriptor } -fun TranslationContext.wrapWithInlineMetadata(function: JsFunction, descriptor: FunctionDescriptor, config: JsConfig): JsExpression { - return if (shouldBeInlined(descriptor, this) && descriptor.isEffectivelyPublicApi) { - val metadata = InlineMetadata.compose(function, descriptor, config) +fun TranslationContext.wrapWithInlineMetadata(function: JsFunction, descriptor: FunctionDescriptor): JsExpression { + return if (descriptor.isInline && descriptor.isEffectivelyPublicApi) { + val metadata = InlineMetadata.compose(function, descriptor, this) val functionWithMetadata = metadata.functionWithMetadata config.configuration[JSConfigurationKeys.INCREMENTAL_RESULTS_CONSUMER]?.apply { @@ -129,6 +125,15 @@ fun TranslationContext.wrapWithInlineMetadata(function: JsFunction, descriptor: functionWithMetadata } else { - function + val block = if (descriptor.isInline) { + inlineFunctionContext!!.let { + JsBlock(it.importBlock.statements + it.prototypeBlock.statements + it.declarationsBlock.statements + + JsReturn(function)) + } + } + else { + null + } + if (block != null) InlineMetadata.wrapFunction(FunctionWithWrapper(function, block)) else function } } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/InlineMetadata.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/InlineMetadata.kt index 84045225a43..e797461b6ae 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/InlineMetadata.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/InlineMetadata.kt @@ -18,17 +18,21 @@ package org.jetbrains.kotlin.js.translate.expression import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.js.backend.ast.* -import org.jetbrains.kotlin.js.config.JsConfig +import org.jetbrains.kotlin.js.inline.util.FunctionWithWrapper import org.jetbrains.kotlin.js.translate.context.Namer +import org.jetbrains.kotlin.js.translate.context.TranslationContext private val METADATA_PROPERTIES_COUNT = 2 -class InlineMetadata(val tag: JsStringLiteral, val function: JsFunction) { +class InlineMetadata(val tag: JsStringLiteral, val function: FunctionWithWrapper) { companion object { @JvmStatic - fun compose(function: JsFunction, descriptor: CallableDescriptor, config: JsConfig): InlineMetadata { - val tag = JsStringLiteral(Namer.getFunctionTag(descriptor, config)) - return InlineMetadata(tag, function) + fun compose(function: JsFunction, descriptor: CallableDescriptor, context: TranslationContext): InlineMetadata { + val tag = JsStringLiteral(Namer.getFunctionTag(descriptor, context.config)) + val inliningContext = context.inlineFunctionContext!! + val block = JsBlock(inliningContext.importBlock.statements + inliningContext.prototypeBlock.statements + + inliningContext.declarationsBlock.statements + JsReturn(function)) + return InlineMetadata(tag, FunctionWithWrapper(function, block)) } @JvmStatic @@ -45,17 +49,44 @@ class InlineMetadata(val tag: JsStringLiteral, val function: JsFunction) { val arguments = call.arguments if (arguments.size != METADATA_PROPERTIES_COUNT) return null - val tag = arguments[0] as? JsStringLiteral - val function = arguments[1] as? JsFunction - if (tag == null || function == null) return null + val tag = arguments[0] as? JsStringLiteral ?: return null + val function = tryExtractFunction(arguments[1]) ?: return null return InlineMetadata(tag, function) } + + @JvmStatic + fun tryExtractFunction(callExpression: JsExpression): FunctionWithWrapper? { + if (callExpression is JsFunction) return FunctionWithWrapper(callExpression, null) + if (callExpression !is JsInvocation) return null + + val qualifier = callExpression.qualifier as? JsNameRef ?: return null + val qualifierQualifier = qualifier.qualifier as? JsNameRef ?: return null + if (qualifierQualifier.qualifier != null || qualifier.ident != Namer.WRAP_FUNCTION) return null + if (callExpression.arguments.size != 1) return null + + val argument = callExpression.arguments[0] as? JsFunction ?: return null + return decomposeWrapper(argument) + } + + @JvmStatic + fun decomposeWrapper(wrapperFunction: JsFunction): FunctionWithWrapper? { + val returnExpr = wrapperFunction.body.statements.lastOrNull() as? JsReturn ?: return null + val function = returnExpr.expression as? JsFunction ?: return null + + return FunctionWithWrapper(function, wrapperFunction.body) + } + + @JvmStatic + fun wrapFunction(function: FunctionWithWrapper): JsExpression { + val wrapperBody = function.wrapperBody ?: JsBlock(JsReturn(function.function)) + val wrapper = JsFunction(function.function.scope, wrapperBody, "") + return JsInvocation(Namer.wrapFunction(), wrapper) + } } val functionWithMetadata: JsExpression get() { - val propertiesList = listOf(tag, function) - return JsInvocation(Namer.createInlineFunction(), propertiesList) + return JsInvocation(Namer.createInlineFunction(), tag, wrapFunction(function)) } } \ No newline at end of file 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 7542594bb46..e506c93ff78 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 @@ -21,6 +21,7 @@ import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl import org.jetbrains.kotlin.js.backend.ast.* import org.jetbrains.kotlin.js.backend.ast.metadata.* import org.jetbrains.kotlin.js.descriptorUtils.isCoroutineLambda +import org.jetbrains.kotlin.js.inline.util.FunctionWithWrapper import org.jetbrains.kotlin.js.inline.util.getInnerFunction import org.jetbrains.kotlin.js.inline.util.rewriters.NameReplacingVisitor import org.jetbrains.kotlin.js.translate.context.TranslationContext @@ -33,6 +34,7 @@ import org.jetbrains.kotlin.js.translate.utils.FunctionBodyTranslator.setDefault import org.jetbrains.kotlin.js.translate.utils.FunctionBodyTranslator.translateFunctionBody import org.jetbrains.kotlin.js.translate.utils.JsAstUtils import org.jetbrains.kotlin.js.translate.utils.TranslationUtils.simpleReturnFunction +import org.jetbrains.kotlin.js.translate.utils.addFunctionButNotExport import org.jetbrains.kotlin.js.translate.utils.fillCoroutineMetadata import org.jetbrains.kotlin.js.translate.utils.finalElement import org.jetbrains.kotlin.psi.KtDeclarationWithBody @@ -65,20 +67,19 @@ class LiteralFunctionTranslator(context: TranslationContext) : AbstractTranslato val tracker = functionContext.usageTracker()!! + val name = invokingContext.getInnerNameForDescriptor(descriptor) if (tracker.hasCapturedExceptContaining()) { val lambdaCreator = simpleReturnFunction(invokingContext.scope(), lambda.source(declaration)) - lambdaCreator.name = invokingContext.getInnerNameForDescriptor(descriptor) lambdaCreator.isLocal = true if (descriptor in tracker.capturedDescriptors && !descriptor.isCoroutineLambda) { lambda.name = tracker.getNameForCapturedDescriptor(descriptor) } - lambdaCreator.name.staticRef = lambdaCreator + name.staticRef = lambdaCreator lambdaCreator.fillCoroutineMetadata(invokingContext, descriptor) lambdaCreator.source = declaration - return lambdaCreator.withCapturedParameters(descriptor, functionContext, invokingContext) + return lambdaCreator.withCapturedParameters(functionContext, name, invokingContext) } - lambda.name = invokingContext.getInnerNameForDescriptor(descriptor) if (descriptor in tracker.capturedDescriptors) { val capturedName = tracker.getNameForCapturedDescriptor(descriptor)!! val globalName = invokingContext.getInnerNameForDescriptor(descriptor) @@ -88,10 +89,10 @@ class LiteralFunctionTranslator(context: TranslationContext) : AbstractTranslato lambda.isLocal = true - invokingContext.addDeclarationStatement(lambda.makeStmt()) + invokingContext.addFunctionDeclaration(name, lambda) lambda.fillCoroutineMetadata(invokingContext, descriptor) - lambda.name.staticRef = lambda - return getReferenceToLambda(invokingContext, descriptor, lambda.name) + name.staticRef = lambda + return JsAstUtils.pureFqn(name, null) } fun JsFunction.fillCoroutineMetadata(context: TranslationContext, descriptor: FunctionDescriptor) { @@ -110,13 +111,22 @@ class LiteralFunctionTranslator(context: TranslationContext) : AbstractTranslato } } +private fun TranslationContext.addFunctionDeclaration(name: JsName, function: JsFunction) { + addFunctionButNotExport(name, if (isPublicInlineFunction) { + InlineMetadata.wrapFunction(FunctionWithWrapper(function, null)) + } + else { + function + }) +} + fun JsFunction.withCapturedParameters( - descriptor: CallableMemberDescriptor, context: TranslationContext, + functionName: JsName, invokingContext: TranslationContext ): JsExpression { - context.addDeclarationStatement(makeStmt()) - val ref = getReferenceToLambda(invokingContext, descriptor, name) + context.addFunctionDeclaration(functionName, this) + val ref = JsAstUtils.pureFqn(functionName, null) val invocation = JsInvocation(ref).apply { sideEffects = SideEffectKind.PURE } val invocationArguments = invocation.arguments @@ -155,19 +165,6 @@ fun JsFunction.withCapturedParameters( return invocation } -private fun getReferenceToLambda(context: TranslationContext, descriptor: CallableMemberDescriptor, name: JsName): JsExpression { - return if (context.isPublicInlineFunction) { - val fqn = context.getQualifiedReference(descriptor) - if (fqn is JsNameRef) { - fqn.name?.let { it.staticRef = name.staticRef } - } - fqn - } - else { - JsAstUtils.pureFqn(name, null) - } -} - private data class CapturedArgsParams(val arguments: List = listOf(), val parameters: List = listOf()) /** diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/AstGenerationResult.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/AstGenerationResult.kt index 4df95baacfb..04a0283cb2e 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/AstGenerationResult.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/AstGenerationResult.kt @@ -17,10 +17,7 @@ package org.jetbrains.kotlin.js.translate.general import org.jetbrains.kotlin.descriptors.DeclarationDescriptor -import org.jetbrains.kotlin.js.backend.ast.JsImportedModule -import org.jetbrains.kotlin.js.backend.ast.JsName -import org.jetbrains.kotlin.js.backend.ast.JsProgram -import org.jetbrains.kotlin.js.backend.ast.JsProgramFragment +import org.jetbrains.kotlin.js.backend.ast.* import org.jetbrains.kotlin.psi.KtFile class AstGenerationResult( @@ -29,6 +26,7 @@ class AstGenerationResult( val fragments: List, val fragmentMap: Map, val newFragments: List, + val importStatements: List, val fileMemberScopes: Map>, val importedModuleList: List ) \ No newline at end of file diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/Merger.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/Merger.kt index 8770fa27106..29b9b24872c 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/Merger.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/Merger.kt @@ -29,7 +29,7 @@ class Merger(private val rootFunction: JsFunction, val internalModuleName: JsNam // Maps unique signature (see generateSignature) to names private val nameTable = mutableMapOf() private val importedModuleTable = mutableMapOf() - private val importBlock = JsGlobalBlock() + val importBlock = JsGlobalBlock() private val declarationBlock = JsGlobalBlock() private val initializerBlock = JsGlobalBlock() private val exportBlock = JsGlobalBlock() diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/Translation.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/Translation.java index 9a4d4531157..52b6ce8e17f 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/Translation.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/Translation.java @@ -339,7 +339,7 @@ public final class Translation { config.getModuleKind())); return new AstGenerationResult(program, internalModuleName, fragments, fragmentMap, newFragments, - fileMemberScopes, importedModuleList); + merger.getImportBlock().getStatements(), fileMemberScopes, importedModuleList); } private static boolean isBuiltinModule(@NotNull List fragments) { diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/reference/ReferenceTranslator.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/reference/ReferenceTranslator.java index 4dbd524ffc0..5ff0afee8c4 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/reference/ReferenceTranslator.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/reference/ReferenceTranslator.java @@ -55,13 +55,13 @@ public final class ReferenceTranslator { JsExpression alias = context.getAliasForDescriptor(descriptor); if (alias != null) return alias; - if (shouldTranslateAsFQN(descriptor, context)) { + if (shouldTranslateAsFQN(descriptor)) { return context.getQualifiedReference(descriptor); } if (descriptor instanceof PropertyDescriptor) { PropertyDescriptor property = (PropertyDescriptor) descriptor; - if (context.isFromCurrentModule(property)) { + if (isLocallyAvailableDeclaration(context, property)) { return context.getInnerReference(property); } else { @@ -72,7 +72,7 @@ public final class ReferenceTranslator { } if (DescriptorUtils.isObject(descriptor) || DescriptorUtils.isEnumEntry(descriptor)) { - if (!context.isFromCurrentModule(descriptor)) { + if (!isLocallyAvailableDeclaration(context, descriptor)) { return getLazyReferenceToObject((ClassDescriptor) descriptor, context); } else { @@ -89,16 +89,12 @@ public final class ReferenceTranslator { if (AnnotationsUtils.isNativeObject(descriptor) || AnnotationsUtils.isLibraryObject(descriptor)) { return context.getInnerReference(descriptor); } - if (!shouldTranslateAsFQN(descriptor, context)) { - if (DescriptorUtils.isObject(descriptor) || DescriptorUtils.isEnumEntry(descriptor)) { - if (!context.isFromCurrentModule(descriptor)) { - return getPrototypeIfNecessary(descriptor, getLazyReferenceToObject(descriptor, context)); - } + if (DescriptorUtils.isObject(descriptor) || DescriptorUtils.isEnumEntry(descriptor)) { + if (!isLocallyAvailableDeclaration(context, descriptor)) { + return getPrototypeIfNecessary(descriptor, getLazyReferenceToObject(descriptor, context)); } - return context.getInnerReference(descriptor); } - - return getPrototypeIfNecessary(descriptor, context.getQualifiedReference(descriptor)); + return context.getInnerReference(descriptor); } @NotNull @@ -112,6 +108,10 @@ public final class ReferenceTranslator { return reference; } + private static boolean isLocallyAvailableDeclaration(@NotNull TranslationContext context, @NotNull DeclarationDescriptor descriptor) { + return context.isFromCurrentModule(descriptor) && !context.isPublicInlineFunction(); + } + @NotNull private static JsExpression getLazyReferenceToObject(@NotNull ClassDescriptor descriptor, @NotNull TranslationContext context) { DeclarationDescriptor container = descriptor.getContainingDeclaration(); @@ -119,8 +119,8 @@ public final class ReferenceTranslator { return new JsNameRef(context.getNameForDescriptor(descriptor), qualifier); } - private static boolean shouldTranslateAsFQN(@NotNull DeclarationDescriptor descriptor, @NotNull TranslationContext context) { - return isLocalVarOrFunction(descriptor) || context.isPublicInlineFunction(); + private static boolean shouldTranslateAsFQN(@NotNull DeclarationDescriptor descriptor) { + return isLocalVarOrFunction(descriptor); } private static boolean isLocalVarOrFunction(DeclarationDescriptor descriptor) { diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/TranslationUtils.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/TranslationUtils.java index d8af94eafcf..f9c00294905 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/TranslationUtils.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/TranslationUtils.java @@ -61,7 +61,7 @@ public final class TranslationUtils { @NotNull TranslationContext context) { JsExpression functionExpression = function; if (InlineUtil.isInline(descriptor)) { - InlineMetadata metadata = InlineMetadata.compose(function, descriptor, context.getConfig()); + InlineMetadata metadata = InlineMetadata.compose(function, descriptor, context); functionExpression = metadata.getFunctionWithMetadata(); } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/inlineUtils.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/inlineUtils.kt index d487013b5fb..8813badb6d3 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/inlineUtils.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/inlineUtils.kt @@ -55,7 +55,7 @@ fun setInlineCallMetadata( override fun visitInvocation(invocation: JsInvocation) { super.visitInvocation(invocation) - if (invocation.name in candidateNames) { + if (invocation.name in candidateNames || invocation.name?.descriptor?.original == descriptor.original) { invocation.descriptor = descriptor invocation.inlineStrategy = InlineStrategy.IN_PLACE invocation.psiElement = psiElement diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/utils.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/utils.kt index 7958c3e8e55..b8d9c29b86e 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/utils.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/utils.kt @@ -211,4 +211,20 @@ val PsiElement.finalElement: PsiElement is KtDeclarationWithBody -> (bodyExpression as? KtBlockExpression)?.rBrace ?: bodyExpression ?: this is KtLambdaExpression -> bodyExpression?.rBrace ?: this else -> this - } \ No newline at end of file + } + +fun TranslationContext.addFunctionButNotExport(descriptor: FunctionDescriptor, expression: JsExpression): JsName = + addFunctionButNotExport(getInnerNameForDescriptor(descriptor), expression) + +fun TranslationContext.addFunctionButNotExport(name: JsName, expression: JsExpression): JsName { + when (expression) { + is JsFunction -> { + expression.name = name + addDeclarationStatement(expression.makeStmt()) + } + else -> { + addDeclarationStatement(JsAstUtils.newVar(name, expression)) + } + } + return name +} diff --git a/js/js.translator/testData/box/inline/inlineInInlineWithLambda.kt b/js/js.translator/testData/box/inline/inlineInInlineWithLambda.kt new file mode 100644 index 00000000000..7edcf765f52 --- /dev/null +++ b/js/js.translator/testData/box/inline/inlineInInlineWithLambda.kt @@ -0,0 +1,28 @@ +var global = "" + +fun log(message: String) { + global += message + ";" +} + +fun baz(x: String) = "($x)" + +inline fun foo(): String { + return baz(bar { "OK" }) +} + +inline fun bar(noinline x: () -> String): String { + return "[" + baz(boo { shouldBeInlined(); x() }) + "]" +} + +fun boo(x: () -> String) = x() + +inline fun shouldBeInlined() { + log("shouldBeInlined") +} + +fun box(): String { + val result = foo() + if (result != "([(OK)])") return "fail1: $result" + if (global != "shouldBeInlined;") return "fail2: $global" + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/inline/inlineInInlineWithLambdaPrivate.kt b/js/js.translator/testData/box/inline/inlineInInlineWithLambdaPrivate.kt new file mode 100644 index 00000000000..e103721a530 --- /dev/null +++ b/js/js.translator/testData/box/inline/inlineInInlineWithLambdaPrivate.kt @@ -0,0 +1,27 @@ +var global = "" + +fun log(message: String) { + global += message + ";" +} + +fun baz(x: String) = "($x)" + +private inline fun foo(): String { + return baz(bar { "OK" }) +} + +private inline fun bar(noinline x: () -> String): String { + return "[" + baz(boo { shouldBeInlined(); x() }) + "]" +} + +fun boo(x: () -> String) = x() + +private inline fun shouldBeInlined() { + log("shouldBeInlined") +} + +fun box(): String { + val result = foo() + if (result != "([(OK)])") return "fail: $result" + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/inlineMultiModule/importObjectInstance.kt b/js/js.translator/testData/box/inlineMultiModule/importObjectInstance.kt new file mode 100644 index 00000000000..ffd5dd80d78 --- /dev/null +++ b/js/js.translator/testData/box/inlineMultiModule/importObjectInstance.kt @@ -0,0 +1,14 @@ +// MODULE: lib +// FILE: lib.kt + +object O { + fun bar() = "OK" +} + +inline fun foo() = O.bar() + +// MODULE: main(lib) +// FILE: main.kt +// CHECK_CONTAINS_NO_CALLS: box except=bar + +fun box() = foo() \ No newline at end of file diff --git a/js/js.translator/testData/box/inlineMultiModule/inlineInInlineWithLambdaMultiModule.kt b/js/js.translator/testData/box/inlineMultiModule/inlineInInlineWithLambdaMultiModule.kt new file mode 100644 index 00000000000..0d857acb8c3 --- /dev/null +++ b/js/js.translator/testData/box/inlineMultiModule/inlineInInlineWithLambdaMultiModule.kt @@ -0,0 +1,21 @@ +// MODULE: lib +// FILE: lib.kt +fun baz(x: String) = "($x)" + +inline fun foo(): String { + return baz(bar { "OK" }) +} + +inline fun bar(noinline x: () -> String): String { + return "[" + baz(boo { x() }) + "]" +} + +fun boo(x: () -> String) = x() + +// MODULE: main(lib) +// FILE: main.kt +fun box(): String { + val result = foo() + if (result != "([(OK)])") return "fail: $result" + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/inlineMultiModule/internalNameClash.kt b/js/js.translator/testData/box/inlineMultiModule/internalNameClash.kt new file mode 100644 index 00000000000..1d52ea9eea5 --- /dev/null +++ b/js/js.translator/testData/box/inlineMultiModule/internalNameClash.kt @@ -0,0 +1,31 @@ +// MODULE: lib1 +// FILE: lib1.kt +package lib1 + +fun foo() = "lib1" + +inline fun bar1() = foo() + +// MODULE: lib2 +// FILE: lib2.kt +package lib2 + +fun foo() = "lib2" + +inline fun bar2() = foo() + +// MODULE: main(lib1, lib2) +// FILE: main.kt + +import lib1.bar1 +import lib2.bar2 + +fun box(): String { + val a = bar1() + if (a != "lib1") return "fail1: $a" + + val b = bar2() + if (b != "lib2") return "fail2: $b" + + return "OK" +} \ No newline at end of file