From 75668826d36ed44a3301eb1bf5807a1b21384fc0 Mon Sep 17 00:00:00 2001 From: Anton Bannykh Date: Wed, 12 Dec 2018 19:02:48 +0300 Subject: [PATCH] JS: simplification in process --- .../js/coroutine/CoroutineTransformer.kt | 1 - .../kotlin/js/inline/FunctionInlineMutator.kt | 4 +- .../kotlin/js/inline/FunctionReader.kt | 106 +++-- .../js/inline/InlineFunctionDefinition.kt | 64 +-- .../inline/InlineSuspendFunctionSplitter.kt | 4 +- .../kotlin/js/inline/InlinerCycleReporter.kt | 32 +- .../jetbrains/kotlin/js/inline/InlinerImpl.kt | 142 ++----- .../kotlin/js/inline/InliningScope.kt | 380 +++++++----------- .../jetbrains/kotlin/js/inline/JsInliner.kt | 94 ++++- .../js/inline/context/FunctionContext.kt | 69 ++-- .../js/inline/context/InliningContext.kt | 7 +- .../kotlin/js/inline/context/NamingContext.kt | 4 +- 12 files changed, 403 insertions(+), 504 deletions(-) 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 c9c6163c3a3..9eb00e9d7a1 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 @@ -19,7 +19,6 @@ package org.jetbrains.kotlin.js.coroutine import org.jetbrains.kotlin.js.backend.ast.* import org.jetbrains.kotlin.js.backend.ast.metadata.coroutineMetadata import org.jetbrains.kotlin.js.backend.ast.metadata.isInlineableCoroutineBody -import org.jetbrains.kotlin.js.inline.clean.LabeledBlockToDoWhileTransformation import org.jetbrains.kotlin.js.translate.declaration.transformCoroutineMetadataToSpecialFunctions import org.jetbrains.kotlin.js.translate.expression.InlineMetadata import org.jetbrains.kotlin.js.translate.utils.JsAstUtils 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 85f45587fe8..589344f519d 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 @@ -38,7 +38,7 @@ private constructor( var resultExpr: JsNameRef? = null private var resultName: JsName? = null var breakLabel: JsLabel? = null - private val currentStatement = inliningContext.statementContext.currentNode + private val currentStatement = inliningContext.currentStatement init { invokedFunction = uncoverClosure(function.deepCopy()) @@ -112,7 +112,7 @@ private constructor( val breakName = JsScope.declareTemporaryName(getBreakLabel()) this.breakLabel = JsLabel(breakName).apply { synthetic = true } - val visitor = ReturnReplacingVisitor(resultExpr as? JsNameRef, breakName.makeRef(), invokedFunction, call.isSuspend) + val visitor = ReturnReplacingVisitor(resultExpr, breakName.makeRef(), invokedFunction, call.isSuspend) visitor.accept(body) } 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 e41a1e13f30..a98850b29fd 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 @@ -46,8 +46,9 @@ import java.io.StringReader */ private val JS_IDENTIFIER_START = "\\p{Lu}\\p{Ll}\\p{Lt}\\p{Lm}\\p{Lo}\\p{Nl}\\\$_" private val JS_IDENTIFIER_PART = "$JS_IDENTIFIER_START\\p{Pc}\\p{Mc}\\p{Mn}\\d" -private val JS_IDENTIFIER="[$JS_IDENTIFIER_START][$JS_IDENTIFIER_PART]*" -private val DEFINE_MODULE_PATTERN = ("($JS_IDENTIFIER)\\.defineModule\\(\\s*(['\"])([^'\"]+)\\2\\s*,\\s*(\\w+)\\s*\\)").toRegex().toPattern() +private val JS_IDENTIFIER = "[$JS_IDENTIFIER_START][$JS_IDENTIFIER_PART]*" +private val DEFINE_MODULE_PATTERN = + ("($JS_IDENTIFIER)\\.defineModule\\(\\s*(['\"])([^'\"]+)\\2\\s*,\\s*(\\w+)\\s*\\)").toRegex().toPattern() private val DEFINE_MODULE_FIND_PATTERN = ".defineModule(" private val specialFunctions = enumValues().joinToString("|") { it.suggestedName } @@ -55,9 +56,9 @@ private val specialFunctionsByName = enumValues().associateBy { private val SPECIAL_FUNCTION_PATTERN = Regex("var\\s+($JS_IDENTIFIER)\\s*=\\s*($JS_IDENTIFIER)\\.($specialFunctions)\\s*;").toPattern() class FunctionReader( - private val reporter: JsConfig.Reporter, - private val config: JsConfig, - private val currentModuleName: JsName + private val reporter: JsConfig.Reporter, + private val config: JsConfig, + private val currentModuleName: JsName ) { /** * fileContent: .js file content, that contains this module definition. @@ -70,20 +71,20 @@ class FunctionReader( * The default variable is Kotlin, but it can be renamed by minifier. */ class ModuleInfo( - val filePath: String, - val fileContent: String, - val moduleVariable: String, - val kotlinVariable: String, - val specialFunctions: Map, - offsetToSourceMappingProvider: () -> OffsetToSourceMapping, - val sourceMap: SourceMap?, - val outputDir: File? + val filePath: String, + val fileContent: String, + val moduleVariable: String, + val kotlinVariable: String, + val specialFunctions: Map, + offsetToSourceMappingProvider: () -> OffsetToSourceMapping, + val sourceMap: SourceMap?, + val outputDir: File? ) { val offsetToSourceMapping by lazy(offsetToSourceMappingProvider) val wrapFunctionRegex = specialFunctions.entries - .filter { (_, v) -> v == SpecialFunction.WRAP_FUNCTION } // TODO This is a hack! Investigate duplicates! - .map { Regex("\\s*${it.key}\\s*\\(\\s*").toPattern() } + .filter { (_, v) -> v == SpecialFunction.WRAP_FUNCTION } // TODO This is a hack! Investigate duplicates! + .map { Regex("\\s*${it.key}\\s*\\(\\s*").toPattern() } } private val moduleNameToInfo by lazy { @@ -125,14 +126,14 @@ class FunctionReader( } val moduleInfo = ModuleInfo( - filePath = path, - fileContent = content, - moduleVariable = moduleVariable, - kotlinVariable = kotlinVariable, - specialFunctions = specialFunctions, - offsetToSourceMappingProvider = { OffsetToSourceMapping(content) }, - sourceMap = sourceMap, - outputDir = file?.parentFile + filePath = path, + fileContent = content, + moduleVariable = moduleVariable, + kotlinVariable = kotlinVariable, + specialFunctions = specialFunctions, + offsetToSourceMappingProvider = { OffsetToSourceMapping(content) }, + sourceMap = sourceMap, + outputDir = file?.parentFile ) result.put(moduleName, moduleInfo) @@ -164,37 +165,60 @@ class FunctionReader( override fun toString() = text.substring(offset) } - private object NotFoundMarker : Any() + object NotFoundMarker private val functionCache = object : SLRUCache(50, 50) { - // LibraryInlineFunctionDefinition | NotFoundMarker override fun createValue(key: CallableDescriptor): Any = readFunction(key) ?: NotFoundMarker } - operator fun get(descriptor: CallableDescriptor): LibraryInlineFunctionDefinition? { - val existed = functionCache.get(descriptor) - return if (existed === NotFoundMarker) null else existed as LibraryInlineFunctionDefinition + operator fun get(descriptor: CallableDescriptor, fragment: JsProgramFragment): FunctionWithWrapper? { + return functionCache.get(descriptor).let { + if (it === NotFoundMarker) null else { + val (fn, info) = it as Pair<*, *> + renameModules(descriptor, fn as FunctionWithWrapper, info as ModuleInfo, fragment) + } + } } - private fun readFunction(descriptor: CallableDescriptor): LibraryInlineFunctionDefinition? { + private fun renameModules( + descriptor: CallableDescriptor, + fn: FunctionWithWrapper, + info: ModuleInfo, + fragment: JsProgramFragment + ): FunctionWithWrapper { + val tag = Namer.getFunctionTag(descriptor, config) + val moduleReference = fragment.inlineModuleMap[tag]?.deepCopy() ?: currentModuleName.makeRef() + val allDefinedNames = collectDefinedNamesInAllScopes(fn.function) + val replacements = hashMapOf( + info.moduleVariable to moduleReference, + info.kotlinVariable to Namer.kotlinObject() + ) + replaceExternalNames(fn.function, replacements, allDefinedNames) + val wrapperStatements = fn.wrapperBody?.statements?.filter { it !is JsReturn } + wrapperStatements?.forEach { replaceExternalNames(it, replacements, allDefinedNames) } + + return fn + } + + private fun readFunction(descriptor: CallableDescriptor): Pair? { val moduleName = getModuleName(descriptor) if (moduleName !in moduleNameToInfo.keys()) return null for (info in moduleNameToInfo[moduleName]) { val function = readFunctionFromSource(descriptor, info) - if (function != null) return function + if (function != null) return function to info } return null } // TODO move renamings to a proper place - private fun readFunctionFromSource(descriptor: CallableDescriptor, info: ModuleInfo): LibraryInlineFunctionDefinition? { + private fun readFunctionFromSource(descriptor: CallableDescriptor, info: ModuleInfo): FunctionWithWrapper? { val source = info.fileContent var tag = Namer.getFunctionTag(descriptor, config) - val tagForModule = tag +// val tagForModule = tag var index = source.indexOf(tag) // Hack for compatibility with old versions of stdlib @@ -226,16 +250,14 @@ class FunctionReader( val position = info.offsetToSourceMapping[offset] val jsScope = JsRootScope(JsProgram()) val functionExpr = try { - parseFunction(source, info.filePath, position, offset, ThrowExceptionOnErrorReporter, jsScope) ?: - return null + parseFunction(source, info.filePath, position, offset, ThrowExceptionOnErrorReporter, jsScope) ?: return null } catch (t: Throwable) { throw Error("Exception while reading function '$tag' from ${info.filePath}", t) } functionExpr.fixForwardNameReferences() val (function, wrapper) = if (isWrapped) { InlineMetadata.decomposeWrapper(functionExpr) ?: return null - } - else { + } else { FunctionWithWrapper(functionExpr, null) } // val moduleReference = moduleNameMap[tagForModule]?.deepCopy() ?: currentModuleName.makeRef() @@ -262,8 +284,8 @@ class FunctionReader( markSpecialFunctions(function, allDefinedNames, info, jsScope) val namesWithoutSideEffects = wrapperStatements.orEmpty().asSequence() - .flatMap { collectDefinedNames(it).asSequence() } - .toSet() + .flatMap { collectDefinedNames(it).asSequence() } + .toSet() function.accept(object : RecursiveJsVisitor() { override fun visitNameRef(nameRef: JsNameRef) { if (nameRef.name in namesWithoutSideEffects && nameRef.qualifier == null) { @@ -279,7 +301,7 @@ class FunctionReader( } } - return LibraryInlineFunctionDefinition(tagForModule, FunctionWithWrapper(function, wrapper), info) + return FunctionWithWrapper(function, wrapper) } private fun markSpecialFunctions(function: JsFunction, allDefinedNames: Set, info: ModuleInfo, scope: JsScope) { @@ -365,7 +387,7 @@ private fun JsFunction.markInlineArguments(descriptor: CallableDescriptor) { inlineFuns.add(paramsJs[i + offset].name) } - val visitor = object: JsVisitorWithContextImpl() { + val visitor = object : JsVisitorWithContextImpl() { override fun endVisit(x: JsInvocation, ctx: JsContext<*>) { val qualifier: JsExpression? = if (isCallInvocation(x)) { (x.qualifier as? JsNameRef)?.qualifier @@ -385,7 +407,7 @@ private fun JsFunction.markInlineArguments(descriptor: CallableDescriptor) { } private fun replaceExternalNames(node: JsNode, replacements: Map, definedNames: Set) { - val visitor = object: JsVisitorWithContextImpl() { + val visitor = object : JsVisitorWithContextImpl() { override fun endVisit(x: JsNameRef, ctx: JsContext) { if (x.qualifier != null || x.name in definedNames) return @@ -407,5 +429,5 @@ private class ShallowSubSequence(private val underlying: CharSequence, private v } override fun subSequence(startIndex: Int, endIndex: Int): CharSequence = - ShallowSubSequence(underlying, start + startIndex, start + endIndex) + ShallowSubSequence(underlying, start + startIndex, start + endIndex) } \ No newline at end of file diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/InlineFunctionDefinition.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/InlineFunctionDefinition.kt index 07b3433c7e7..4294b6cf5b3 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/InlineFunctionDefinition.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/InlineFunctionDefinition.kt @@ -5,66 +5,8 @@ package org.jetbrains.kotlin.js.inline -import org.jetbrains.kotlin.js.backend.ast.JsProgramFragment import org.jetbrains.kotlin.js.inline.util.FunctionWithWrapper -sealed class InlineFunctionDefinition { - abstract val functionWithWrapper: FunctionWithWrapper - - abstract val tag: String? - - open fun process() {} - -// imports / nameBindings - // modules - // renameFor scope? - - -} - - -// Current module, new fragment. Expressed through `defineInlineFunction`. Should be self-contained -open class PublicInlineFunctionDefinition( - override val tag: String, - override val functionWithWrapper: FunctionWithWrapper, - val fragment: JsProgramFragment, - val scope: ProgramFragmentInliningScope -) : InlineFunctionDefinition() { - override fun process() { - scope.process() - } -} - -// Current module, from Binary AST -class BinaryInlineFunctionDefinition( - override val tag: String, - override val functionWithWrapper: FunctionWithWrapper, - val fragment: JsProgramFragment -): InlineFunctionDefinition() { - - -} - -// Current module, new fragment, used within scope only. Private functions, lambdas. -class LocalInlineFunctionDefinition( - override val functionWithWrapper: FunctionWithWrapper, - val scope: InliningScope -) : InlineFunctionDefinition() { - - override val tag = null - - override fun process() { - // TODO this is incorrect! - scope.process() - } -} - -// Deserialized from a binary dependency (.js file) -open class LibraryInlineFunctionDefinition( - override val tag: String, - override val functionWithWrapper: FunctionWithWrapper, - val moduleInfo: FunctionReader.ModuleInfo -): InlineFunctionDefinition() { - - -} \ No newline at end of file +class InlineFunctionDefinition( + val fn: FunctionWithWrapper, + val tag: String?) \ No newline at end of file diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/InlineSuspendFunctionSplitter.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/InlineSuspendFunctionSplitter.kt index 8951ee70f60..f85abdea2cd 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/InlineSuspendFunctionSplitter.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/InlineSuspendFunctionSplitter.kt @@ -47,10 +47,10 @@ class InlineSuspendFunctionSplitter( val statementContext = lastStatementLevelContext // This function will be exported to JS - val function = scope.importFunctionDefinition(PublicInlineFunctionDefinition(inlineMetadata.tag.value, inlineMetadata.function, scope.fragment, scope)) + val function = scope.importFunctionDefinition(InlineFunctionDefinition(inlineMetadata.function, inlineMetadata.tag.value)) // Original function should be not be transformed into a state machine - f.function.setName(null) + f.function.name = null f.function.coroutineMetadata = null f.function.isInlineableCoroutineBody = true diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/InlinerCycleReporter.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/InlinerCycleReporter.kt index 8a8d7d5b33b..bf8dea02485 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/InlinerCycleReporter.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/InlinerCycleReporter.kt @@ -12,12 +12,9 @@ import org.jetbrains.kotlin.js.backend.ast.JsInvocation import org.jetbrains.kotlin.js.backend.ast.metadata.descriptor import org.jetbrains.kotlin.js.backend.ast.metadata.inlineStrategy import org.jetbrains.kotlin.js.backend.ast.metadata.psiElement -import org.jetbrains.kotlin.js.inline.clean.FunctionPostProcessor -import org.jetbrains.kotlin.js.inline.clean.removeUnusedLocalFunctionDeclarations import org.jetbrains.kotlin.js.inline.context.FunctionContext import org.jetbrains.kotlin.js.inline.util.FunctionWithWrapper import org.jetbrains.kotlin.js.inline.util.IdentitySet -import org.jetbrains.kotlin.js.inline.util.refreshLabelNames import org.jetbrains.kotlin.resolve.inline.InlineStrategy import java.util.* @@ -66,13 +63,24 @@ class InlinerCycleReporter( } - // Return true iff the definition should be visited by the inliner - fun shouldProcess(definition: FunctionWithWrapper, call: JsInvocation): Boolean { - + fun withInlining(call: JsInvocation, body: () -> T): T { currentNamedFunction?.let { inlineCallInfos.add(JsCallInfo(call, it)) } + val result = body() + + if (!inlineCallInfos.isEmpty()) { + if (inlineCallInfos.last.call == call) { + inlineCallInfos.removeLast() + } + } + + return result + } + + // Return true iff the definition should be visited by the inliner + fun shouldProcess(definition: FunctionWithWrapper, call: JsInvocation?): Boolean { if (definition.function in inProcessFunctions) { reportInlineCycle(call, definition.function) } else if (definition.function !in processedFunctions) { @@ -82,16 +90,8 @@ class InlinerCycleReporter( return false } - fun endVisit(x: JsInvocation) { - if (!inlineCallInfos.isEmpty()) { - if (inlineCallInfos.last.call == x) { - inlineCallInfos.removeLast() - } - } - } - - private fun reportInlineCycle(call: JsInvocation, calledFunction: JsFunction) { - call.inlineStrategy = InlineStrategy.NOT_INLINE + private fun reportInlineCycle(call: JsInvocation?, calledFunction: JsFunction) { + call?.inlineStrategy = InlineStrategy.NOT_INLINE val it = inlineCallInfos.descendingIterator() while (it.hasNext()) { diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/InlinerImpl.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/InlinerImpl.kt index 770e9852f1a..7a33f1fb64a 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/InlinerImpl.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/InlinerImpl.kt @@ -6,76 +6,71 @@ package org.jetbrains.kotlin.js.inline import org.jetbrains.kotlin.js.backend.ast.* -import org.jetbrains.kotlin.js.backend.ast.metadata.forcedReturnVariable import org.jetbrains.kotlin.js.backend.ast.metadata.inlineStrategy -import org.jetbrains.kotlin.js.backend.ast.metadata.synthetic -import org.jetbrains.kotlin.js.inline.clean.FunctionPostProcessor -import org.jetbrains.kotlin.js.inline.clean.removeUnusedLocalFunctionDeclarations import org.jetbrains.kotlin.js.inline.context.FunctionContext -import org.jetbrains.kotlin.js.inline.context.InliningContext -import org.jetbrains.kotlin.js.inline.util.refreshLabelNames -import org.jetbrains.kotlin.js.translate.declaration.transformSpecialFunctionsToCoroutineMetadata +import org.jetbrains.kotlin.js.inline.util.FunctionWithWrapper +import org.jetbrains.kotlin.js.inline.util.extractFunction +import org.jetbrains.kotlin.js.translate.expression.InlineMetadata import org.jetbrains.kotlin.js.translate.utils.JsAstUtils // TODO stateless? class InlinerImpl( - val cycleReporter: InlinerCycleReporter, - val functionContext: FunctionContext, - // TODO other way around? Need to find a correct inliner by function declaration. + val jsInliner: JsInliner, val scope: InliningScope ) : JsVisitorWithContextImpl() { - override fun visit(function: JsFunction, context: JsContext<*>): Boolean { - functionContext.functionsByFunctionNodes[function]?.let { (function, wrapper) -> - visit(function, wrapper) - return false + override fun visit(x: JsBinaryOperation, ctx: JsContext<*>): Boolean { + val assignment = JsAstUtils.decomposeAssignment(x) + if (assignment != null) { + val (left, right) = assignment + if (left is JsNameRef) { + val name = left.name + if (name != null) { + extractFunction(right)?.let { function -> + jsInliner.process(InlineFunctionDefinition(function, null), null, scope) + } + } + } } - visit(function, null) - return true - } - override fun visit(x: JsBlock, ctx: JsContext<*>): Boolean { - // TODO Seems like a very roundabout way. Probably should reuse same approach as in CoroutineTransformer and ...Splitter - // TODO That approach might be missing inline properties. Check! - functionContext.functionsByWrapperNodes[x]?.let { (function, wrapper) -> - visit(function, wrapper) - return false - } return super.visit(x, ctx) } - private fun visit(function: JsFunction, wrapperBody: JsBlock?) { - cycleReporter.startFunction(function) - - // TODO Different visitors? - if (wrapperBody != null && scope is ProgramFragmentInliningScope) { - PublicInlineFunctionInliningScope(scope.fragment, cycleReporter, functionContext, function, wrapperBody).process() - } else { - // TODO this is still not super-clear - accept(function.body) + override fun visit(x: JsVars.JsVar, ctx: JsContext<*>): Boolean { + val initializer = x.initExpression + val name = x.name + if (initializer != null && name != null) { + extractFunction(initializer)?.let { function -> + jsInliner.process(InlineFunctionDefinition(function, null), null, scope) + } } - // Cleanup - refreshLabelNames(function.body, function.scope) - removeUnusedLocalFunctionDeclarations(function) - FunctionPostProcessor(function).apply() - - cycleReporter.endFunction(function) + return super.visit(x, ctx) } + override fun visit(x: JsInvocation, ctx: JsContext<*>): Boolean { + InlineMetadata.decompose(x)?.let { + jsInliner.process(InlineFunctionDefinition(it.function, it.tag.value), x, scope) + } + + return super.visit(x, ctx) + } + + override fun endVisit(call: JsInvocation, ctx: JsContext) { if (hasToBeInlined(call)) { + val (inlineableBody, resultExpression) = jsInliner.inline(scope, call, lastStatementLevelContext.currentNode) - val definition = functionContext.getFunctionDefinition(call, scope) + lastStatementLevelContext.addPrevious(JsAstUtils.flattenStatement(inlineableBody)) - if (cycleReporter.shouldProcess(definition.functionWithWrapper, call)) { - definition.process() + // Assumes, that resultExpression == null, when result is not needed. + // @see FunctionInlineMutator.isResultNeeded() + if (resultExpression == null) { + lastStatementLevelContext.removeMe() + } else { + ctx.replaceMe(resultExpression) } - - inline(call, definition, ctx) } - - cycleReporter.endVisit(call) } // TODO This could be extracted into a separate pass. @@ -94,62 +89,9 @@ class InlinerImpl( super.doAcceptStatementList(statements) } - // TODO a lot of code... Probably a bad sign - private fun inline(call: JsInvocation, definition: InlineFunctionDefinition, context: JsContext) { - - // --------------- - // This should be isolated - - val function = scope.importFunctionDefinition(definition) - // TODO This should be done inside the importer - function.body = transformSpecialFunctionsToCoroutineMetadata(function.body) - - // ------------------- - - val statementContext = lastStatementLevelContext - - val (inlineableBody, resultExpression) = - FunctionInlineMutator.getInlineableCallReplacement(call, function, InliningContext(statementContext)) - - // body of inline function can contain call to lambdas that need to be inlined - val inlineableBodyWithLambdasInlined = accept(inlineableBody) - assert(inlineableBody === inlineableBodyWithLambdasInlined) - - patchReturnsFromSecondaryConstructor(inlineableBody) - - statementContext.addPrevious(JsAstUtils.flattenStatement(inlineableBody)) - - /* - * Assumes, that resultExpression == null, when result is not needed. - * @see FunctionInlineMutator.isResultNeeded() - */ - if (resultExpression == null) { - statementContext.removeMe() - return - } - - // TODO Why accept? Seems unnecessary... Some inline call in the qualifier? Shouldn't this be done along with the lambdas then? - accept(resultExpression)?.let { - it.synthetic = true - context.replaceMe(it) - } - } - - private fun patchReturnsFromSecondaryConstructor(inlineableBody: JsStatement) { - // Support non-local return from secondary constructor - // Returns from secondary constructors should return `$this` object. - // TODO This seems brittle - cycleReporter.currentNamedFunction?.forcedReturnVariable?.let { returnVariable -> - inlineableBody.accept(object : RecursiveJsVisitor() { - override fun visitReturn(x: JsReturn) { - x.expression = returnVariable.makeRef() - } - }) - } - } private fun hasToBeInlined(call: JsInvocation): Boolean { val strategy = call.inlineStrategy - return if (strategy == null || !strategy.isInline) false else functionContext.hasFunctionDefinition(call, scope) + return if (strategy == null || !strategy.isInline) false else jsInliner.functionContext.hasFunctionDefinition(call, scope) } } \ No newline at end of file diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/InliningScope.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/InliningScope.kt index 3d099d451cf..2193c846e03 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/InliningScope.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/InliningScope.kt @@ -12,75 +12,134 @@ import org.jetbrains.kotlin.js.backend.ast.metadata.staticRef import org.jetbrains.kotlin.js.inline.clean.removeUnusedFunctionDefinitions import org.jetbrains.kotlin.js.inline.clean.removeUnusedImports import org.jetbrains.kotlin.js.inline.clean.simplifyWrappedFunctions -import org.jetbrains.kotlin.js.inline.context.FunctionContext import org.jetbrains.kotlin.js.inline.util.* +import org.jetbrains.kotlin.js.translate.declaration.transformSpecialFunctionsToCoroutineMetadata import org.jetbrains.kotlin.js.translate.utils.JsAstUtils import java.util.* - // Handles interpreting an inline function in terms of the current context. // Either an program fragment, or a public inline function sealed class InliningScope { - private val cache = mutableMapOf>() + abstract val fragment: JsProgramFragment - protected fun computeIfAbsent(tag: String?, fn: () -> Map): Map { - if (tag == null) return fn() + abstract fun addInlinedDeclaration(tag: String?, declaration: JsStatement) - return cache.computeIfAbsent(tag) { fn() } + abstract fun hasImport(tag: String): JsName? + + abstract fun addImport(tag: String, vars: JsVars) + + open fun preprocess(statement: JsStatement) {} + + abstract fun update() + + private val publicFunctionCache = mutableMapOf>() + + private val localFunctionCache = mutableMapOf>() + + private fun computeIfAbsent(tag: String?, function: JsFunction, fn: () -> Map): Map { + if (tag == null) return localFunctionCache.computeIfAbsent(function) { fn() } + + return publicFunctionCache.computeIfAbsent(tag) { fn() } } - abstract fun importFunctionDefinition(f: InlineFunctionDefinition): JsFunction + fun importFunctionDefinition(definition: InlineFunctionDefinition): JsFunction { + // Apparently we should avoid this trick when we implement fair support for crossinline + // That's because crossinline lambdas inline into the declaration block and specialize those. + val replacements = computeIfAbsent(definition.tag, definition.fn.function) { + val newReplacements = HashMap() - abstract fun process() + val copiedStatements = ArrayList() + val importStatements = mutableMapOf() - abstract val fragment: JsProgramFragment + definition.fn.wrapperBody?.let { + it.statements.asSequence() + .filterNot { it is JsReturn } + .map { it.deepCopy() } + .forEach { statement -> + preprocess(statement) + + if (statement is JsVars) { + val tag = getImportTag(statement) + if (tag != null) { + val name = statement.vars[0].name + val existingName = name.localAlias ?: hasImport(tag) ?: JsScope.declareTemporaryName(name.ident).also { + it.copyMetadataFrom(name) + importStatements[statement] = tag + } + + if (name !== existingName) { + val replacement = JsAstUtils.pureFqn(existingName, null) + newReplacements[name] = replacement + } + } + } + + copiedStatements.add(statement) + } + } + + copiedStatements.asSequence() + .flatMap { node -> collectDefinedNamesInAllScopes(node).asSequence() } + .filter { name -> !newReplacements.containsKey(name) } + .forEach { name -> + val alias = JsScope.declareTemporaryName(name.ident) + alias.copyMetadataFrom(name) + val replacement = JsAstUtils.pureFqn(alias, null) + newReplacements[name] = replacement + } + + // Apply renaming and restore the static ref links + JsBlock(copiedStatements).let { + replaceNames(it, newReplacements) + + // Restore the staticRef links + for ((key, value) in collectNamedFunctions(it)) { + if (key.staticRef is JsFunction) { + key.staticRef = value + } + } + } + + copiedStatements.forEach { + if (it is JsVars && it in importStatements) { + addImport(importStatements[it]!!, it) + } else { + addInlinedDeclaration(definition.tag, it) + } + } + + newReplacements + } + + val paramMap = definition.fn.function.parameters.associate { + val alias = JsScope.declareTemporaryName(it.name.ident) + alias.copyMetadataFrom(it.name) + it.name to JsAstUtils.pureFqn(alias, null) + } + + val result = definition.fn.function.deepCopy() + + replaceNames(result, replacements) + replaceNames(result, paramMap) + + result.body = transformSpecialFunctionsToCoroutineMetadata(result.body) + + return result + } } class ProgramFragmentInliningScope( - override val fragment: JsProgramFragment, - val functionContext: FunctionContext, - val rootInliner: JsInliner + override val fragment: JsProgramFragment ) : InliningScope() { - private val existingModules = fragment.importedModules.mapTo(IdentitySet()) { it.internalName } + private val existingModules = fragment.importedModules.associateTo(mutableMapOf()) { it.key to it } - private val existingImports = fragment.nameBindings.associateTo(mutableMapOf()) { it.key to it.name } - - private val existingNameBindings = fragment.nameBindings.associateTo(IdentityHashMap()) { it.name to it.key } + private val existingBindings = fragment.nameBindings.associateTo(mutableMapOf()) { it.key to it.name } private val additionalDeclarations = mutableListOf() - private var processed = false - - override fun process() { - if (!processed) { - // TODO is this even needed? - processed = true - - val inliner = InlinerImpl(rootInliner.cycleReporter, functionContext, this) - - // TODO any way and/or need to visit everything inside the fragment? - inliner.acceptStatement(fragment.declarationBlock) - - // TODO Atm it's placed after inliner in order not to perform the body inlining twice. Is that OK? - // Ideally it could be moved to the coroutine transformers. The info regarding which inline function wrappers have been imported - // on top level should be persisted for that sake. Also it going to be needed in order to avoid duplicate code. - InlineSuspendFunctionSplitter(this).accept(fragment.declarationBlock) - - // Mostly for the sake of post-processor - // TODO are inline function marked with @Test possible? - if (fragment.tests != null) { - inliner.acceptStatement(fragment.tests) - } - // TODO wrap in a function in order to do the post-processing - inliner.acceptStatement(fragment.initializerBlock) - - updateProgramFragment() - } - } - - private fun updateProgramFragment() { + override fun update() { // TODO fix the order // TODO this probably will be replaced with a special tag -> block map for the imported stuff, so that we can merge same imports. // TODO in that case this method will become obsolete @@ -110,125 +169,25 @@ class ProgramFragmentInliningScope( } } - private fun addInlinedModule(module: JsImportedModule) { -// if (moduleName !in existingModules) { -// fragment.importedModules.add(moduleMap[moduleName]!!.let { -// // Copy so that the Merger.kt doesn't operate on the same instance in different fragments. -// JsImportedModule(it.externalName, it.internalName, it.plainReference) -// }) -// } + override fun hasImport(tag: String): JsName? = existingBindings[tag] + + override fun addImport(tag: String, vars: JsVars) { + val name = vars.vars[0].name + val expr = vars.vars[0].initExpression + fragment.imports[tag] = expr + fragment.nameBindings.add(JsNameBinding(tag, name)) + existingBindings[tag] = name } - private fun addImport(tag: String, e: JsExpression) { - fragment.imports[tag] = e - } - - private fun addNameBinding(binding: JsNameBinding) { - fragment.nameBindings.add(binding) - existingNameBindings[binding.name] = binding.key - } - - override fun importFunctionDefinition(f: InlineFunctionDefinition): JsFunction { - - // Apparently we should avoid this trick when we implement fair support for crossinline - // That's because crossinline lambdas inline into the declaration block and specialize those. - val replacements = computeIfAbsent(f.tag) { - val newReplacements = HashMap() - - val copiedStatements = ArrayList() - val importStatements = ArrayList>() - - f.functionWithWrapper.wrapperBody?.let { - it.statements.asSequence() - .filterNot { it is JsReturn } - .map { it.deepCopy() } - .forEach { statement -> - replaceExpressionsWithLocalAliases(statement) - - if (statement is JsVars) { - val tag = getImportTag(statement) - if (tag != null) { - // TODO handle JsVars with multiple vars? - val name = statement.vars[0].name - var existingName: JsName? = name.localAlias - if (existingName == null) { - existingName = existingImports.computeIfAbsent(tag) { - importStatements.add(tag to statement.vars[0]) - val alias = JsScope.declareTemporaryName(name.ident) - alias.copyMetadataFrom(name) - newReplacements[name] = JsAstUtils.pureFqn(alias, null) - alias - } - } - - if (name !== existingName) { - val replacement = JsAstUtils.pureFqn(existingName, null) - newReplacements[name] = replacement - } - - return@forEach - } - } - - copiedStatements.add(statement) - } - } - - (importStatements.asSequence().map { JsVars(it.second) } + copiedStatements.asSequence()) - .flatMap { node -> collectDefinedNamesInAllScopes(node).asSequence() } - .filter { name -> !newReplacements.containsKey(name) } - .forEach { name -> - val alias = JsScope.declareTemporaryName(name.ident) - alias.copyMetadataFrom(name) - val replacement = JsAstUtils.pureFqn(alias, null) - newReplacements[name] = replacement - } - - - for ((tag, statement) in importStatements) { - val renamed = replaceNames(statement, newReplacements) - // TODO shouldn't this be done at `existingImports.computeIfAbsent` moment? - addImport(tag, renamed.initExpression) - - addNameBinding(JsNameBinding(tag, renamed.name)) - } - - if (f.tag != null) { - fragment.inlinedFunctionWrappers[f.tag!!] = JsGlobalBlock().also { - copiedStatements.mapTo(it.statements) { replaceNames(it, newReplacements) } - } - } else { - // TODO Handle it better? - for (statement in copiedStatements) { - additionalDeclarations.add(replaceNames(statement, newReplacements)) - } - } - - // TODO shouldn't this be moved to renamer? - for ((key, value) in collectNamedFunctions(JsBlock(copiedStatements))) { - if (key.staticRef is JsFunction) { - key.staticRef = value - } - } - - newReplacements + override fun addInlinedDeclaration(tag: String?, declaration: JsStatement) { + if (tag != null) { + fragment.inlinedFunctionWrappers.computeIfAbsent(tag) { JsGlobalBlock() }.statements.add(declaration) + } else { + additionalDeclarations.add(declaration) } - - val paramMap = f.functionWithWrapper.function.parameters.associate { - val alias = JsScope.declareTemporaryName(it.name.ident) - alias.copyMetadataFrom(it.name) - it.name to JsAstUtils.pureFqn(alias, null) - } - - val result = f.functionWithWrapper.function.deepCopy() - - replaceNames(result, replacements) - replaceNames(result, paramMap) - - return result } - private fun replaceExpressionsWithLocalAliases(statement: JsStatement) { + override fun preprocess(statement: JsStatement) { object : JsVisitorWithContextImpl() { override fun endVisit(x: JsNameRef, ctx: JsContext) { replaceIfNecessary(x, ctx) @@ -241,97 +200,44 @@ class ProgramFragmentInliningScope( private fun replaceIfNecessary(expression: JsExpression, ctx: JsContext) { val alias = expression.localAlias if (alias != null) { - // TODO wrong! - ctx.replaceMe(alias.internalName.makeRef()) - addInlinedModule(alias) + ctx.replaceMe(addInlinedModule(alias).makeRef()) } } }.accept(statement) } + + private fun addInlinedModule(module: JsImportedModule): JsName { + return existingModules.computeIfAbsent(module.key) { + // Copy so that the Merger.kt doesn't operate on the same instance in different fragments. + // TODO What about nameBindings? + JsImportedModule(module.externalName, module.internalName, module.plainReference).also { + fragment.importedModules.add(it) + } + }.internalName + } } class PublicInlineFunctionInliningScope( - override val fragment: JsProgramFragment, - cycleReporter: InlinerCycleReporter, - functionContext: FunctionContext, val function: JsFunction, - val wrapperBody: JsBlock + val wrapperBody: JsBlock, + override val fragment: JsProgramFragment ) : InliningScope() { - val additionalStatements = mutableListOf() - val innerInliner = InlinerImpl( - cycleReporter, - functionContext, - this - ) + override fun addInlinedDeclaration(tag: String?, declaration: JsStatement) { + additionalStatements.add(declaration) + } - override fun process() { - for (statement in wrapperBody.statements) { - if (statement !is JsReturn) { - innerInliner.acceptStatement(statement) - } else { - innerInliner.accept((statement.expression as JsFunction).body) - } - } + override fun hasImport(tag: String): JsName? { + return null // TODO + } - // TODO keep order + override fun addImport(tag: String, vars: JsVars) { + additionalStatements.add(vars) // TODO + } + + override fun update() { wrapperBody.statements.addAll(0, additionalStatements) } - - private fun addPrevious(statement: JsStatement) { - // TODO Is this correct? - additionalStatements.add(innerInliner.accept(statement)) - } - - override fun importFunctionDefinition(f: InlineFunctionDefinition): JsFunction { - // TODO Decrypt the comment below - // Apparently we should avoid this trick when we implement fair support for crossinline - val replacements = computeIfAbsent(f.tag) { - val newReplacements = HashMap() - - // TODO Why don't we collect existing imports? - - val copiedStatements = f.functionWithWrapper.wrapperBody!!.statements.asSequence() - .filterNot { it is JsReturn } - .map { it.deepCopy() }.toList() - - val definedNames = copiedStatements.asSequence() - .flatMap { node -> collectDefinedNamesInAllScopes(node).asSequence() } - .filter { name -> !newReplacements.containsKey(name) } - .toSet() - for (name in definedNames) { - val alias = JsScope.declareTemporaryName(name.ident) - alias.copyMetadataFrom(name) - val replacement = JsAstUtils.pureFqn(alias, null) - newReplacements[name] = replacement - } - - for (statement in copiedStatements) { - addPrevious(replaceNames(statement, newReplacements)) - } - - for ((key, value) in collectNamedFunctions(JsBlock(copiedStatements))) { - if (key.staticRef is JsFunction) { - key.staticRef = value - } - } - - newReplacements - } - - val paramMap = f.functionWithWrapper.function.parameters.associate { - val alias = JsScope.declareTemporaryName(it.name.ident) - alias.copyMetadataFrom(it.name) - it.name to JsAstUtils.pureFqn(alias, null) - } - - val result = f.functionWithWrapper.function.deepCopy() - - replaceNames(result, replacements) - replaceNames(result, paramMap) - - return result - } } \ No newline at end of file diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/JsInliner.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/JsInliner.kt index b4edf18c70c..54b6d4e7bf4 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/JsInliner.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/JsInliner.kt @@ -7,8 +7,15 @@ package org.jetbrains.kotlin.js.inline import org.jetbrains.kotlin.diagnostics.DiagnosticSink import org.jetbrains.kotlin.js.backend.ast.* +import org.jetbrains.kotlin.js.backend.ast.metadata.forcedReturnVariable +import org.jetbrains.kotlin.js.backend.ast.metadata.synthetic import org.jetbrains.kotlin.js.config.JsConfig +import org.jetbrains.kotlin.js.inline.clean.FunctionPostProcessor +import org.jetbrains.kotlin.js.inline.clean.removeUnusedLocalFunctionDeclarations import org.jetbrains.kotlin.js.inline.context.FunctionContext +import org.jetbrains.kotlin.js.inline.context.InliningContext +import org.jetbrains.kotlin.js.inline.util.refreshLabelNames +import org.jetbrains.kotlin.js.translate.declaration.transformSpecialFunctionsToCoroutineMetadata import org.jetbrains.kotlin.js.translate.general.AstGenerationResult @@ -35,7 +42,92 @@ class JsInliner( fun process() { for (fragment in translationResult.newFragments) { - functionContext.scopeForFragment(fragment).process() + process(fragment) + } + } + + fun process(fragment: JsProgramFragment) { + val fragmentScope = functionContext.scopeForFragment(fragment) ?: return + + // TODO any way and/or need to visit everything inside the fragment? + fragmentScope.process(fragment.declarationBlock) + + // TODO Atm it's placed after inliner in order not to perform the body inlining twice. Is that OK? + // Ideally it could be moved to the coroutine transformers. The info regarding which inline function wrappers have been imported + // on top level should be persisted for that sake. Also it going to be needed in order to avoid duplicate code. + InlineSuspendFunctionSplitter(fragmentScope).accept(fragment.declarationBlock) + + // Mostly for the sake of post-processor + // TODO are inline function marked with @Test possible? + fragment.tests?.let { fragmentScope.process(it) } + + // TODO wrap in a function in order to do the post-processing + fragmentScope.process(fragment.initializerBlock) + + fragmentScope.update() + } + + fun process(inlineFn: InlineFunctionDefinition, call: JsInvocation?, containingScope: InliningScope) { + if (cycleReporter.shouldProcess(inlineFn.fn, call)) { + val (function, wrapperBody) = inlineFn.fn + + cycleReporter.startFunction(function) + + if (wrapperBody != null) { + val scope = PublicInlineFunctionInliningScope(function, wrapperBody, containingScope.fragment) + scope.process(wrapperBody) + scope.update() + } else { + containingScope.process(function) + } + + // Cleanup + refreshLabelNames(function.body, function.scope) + removeUnusedLocalFunctionDeclarations(function) + FunctionPostProcessor(function).apply() + + cycleReporter.endFunction(function) + } + } + + private fun InliningScope.process(node: JsNode) { + InlinerImpl(this@JsInliner, this).accept(node) + } + + // TODO a lot of code... Probably a bad sign + fun inline(scope: InliningScope, call: JsInvocation, currentStatement: JsStatement?): InlineableResult { + val definition = functionContext.getFunctionDefinition(call, scope) + + return cycleReporter.withInlining(call) { + + val function = scope.importFunctionDefinition(definition) + + val inliningContext = InliningContext(currentStatement) + + val (inlineableBody, resultExpression) = FunctionInlineMutator.getInlineableCallReplacement(call, function, inliningContext) + + // body of inline function can contain call to lambdas that need to be inlined + scope.process(inlineableBody) + + patchReturnsFromSecondaryConstructor(inlineableBody) + + // TODO shouldn't we process the resultExpression qualifier along with the lambda inlining? + resultExpression?.synthetic = true + + InlineableResult(JsBlock(inliningContext.previousStatements + inlineableBody), resultExpression) + } + } + + private fun patchReturnsFromSecondaryConstructor(inlineableBody: JsStatement) { + // Support non-local return from secondary constructor + // Returns from secondary constructors should return `$this` object. + // TODO This seems brittle + cycleReporter.currentNamedFunction?.forcedReturnVariable?.let { returnVariable -> + inlineableBody.accept(object : RecursiveJsVisitor() { + override fun visitReturn(x: JsReturn) { + x.expression = returnVariable.makeRef() + } + }) } } } \ 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 2be752c0df5..492c98cdae2 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 @@ -16,15 +16,12 @@ package org.jetbrains.kotlin.js.inline.context -import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.js.backend.ast.* import org.jetbrains.kotlin.js.backend.ast.metadata.isCallableReference import org.jetbrains.kotlin.js.backend.ast.metadata.descriptor -import org.jetbrains.kotlin.js.config.JsConfig import org.jetbrains.kotlin.js.inline.* import org.jetbrains.kotlin.js.inline.util.* import org.jetbrains.kotlin.js.translate.context.Namer -import org.jetbrains.kotlin.js.translate.general.AstGenerationResult import java.util.HashMap class FunctionContext( @@ -52,8 +49,6 @@ class FunctionContext( return getFunctionDefinitionImpl(call, scope) != null } - val functionsByWrapperNodes = HashMap() - val functionsByFunctionNodes = HashMap() /** @@ -88,17 +83,13 @@ class FunctionContext( */ private fun getFunctionDefinitionImpl(call: JsInvocation, scope: InliningScope): InlineFunctionDefinition? { // Ensure we have the local function information + // TODO is this necessary? loadFragment(scope.fragment) - val descriptor = call.descriptor - if (descriptor != null) { - return lookUpFunctionDirect(descriptor) ?: lookUpFunctionIndirect(call, scope) ?: lookUpFunctionExternal(descriptor) - } - - return lookUpFunctionIndirect(call, scope) + return lookUpFunctionDirect(call) ?: lookUpFunctionIndirect(call, scope) ?: lookUpFunctionExternal(call, scope.fragment) } - private fun lookUpFunctionIndirect(call: JsInvocation, scope: InliningScope): LocalInlineFunctionDefinition? { + private fun lookUpFunctionIndirect(call: JsInvocation, scope: InliningScope): InlineFunctionDefinition? { /** remove ending `()` */ val callQualifier: JsExpression = if (isCallInvocation(call)) { (call.qualifier as JsNameRef).qualifier!! @@ -121,21 +112,23 @@ class FunctionContext( is JsFunction -> functionsByFunctionNodes[qualifier] ?: FunctionWithWrapper(qualifier, null) else -> null }?.let { - LocalInlineFunctionDefinition(it, scope) + InlineFunctionDefinition(it, null).also { definition -> + if (scope.fragment in newFragments) { + inliner.process(definition, call, scope) + } + } } } - fun functionTag(call: JsInvocation): String? { - return call.descriptor?.let { Namer.getFunctionTag(it, inliner.config) } - } - - private val newFragmentSet = inliner.translationResult.newFragments.toIdentitySet() - private val inliningScopeCache = mutableMapOf() - fun scopeForFragment(fragment: JsProgramFragment) = inliningScopeCache.computeIfAbsent(fragment) { - ProgramFragmentInliningScope(fragment, this, inliner) - } + private val newFragments = inliner.translationResult.newFragments.toIdentitySet() + + fun scopeForFragment(fragment: JsProgramFragment) = if (fragment in newFragments) { + inliningScopeCache.computeIfAbsent(fragment) { + ProgramFragmentInliningScope(fragment) + } + } else null private fun loadFragment(fragment: JsProgramFragment) { fragmentInfo.computeIfAbsent(fragment) { @@ -145,36 +138,38 @@ class FunctionContext( ).also { (functions, accessors) -> (functions.values.asSequence() + accessors.values.asSequence()).forEach { f -> functionsByFunctionNodes[f.function] = f - if (f.wrapperBody != null) { - functionsByWrapperNodes[f.wrapperBody] = f - } } } } } - private fun fragmentByTag(tag: String): JsProgramFragment? { + fun fragmentByTag(tag: String): JsProgramFragment? { return inliner.translationResult.inlineFunctionTagMap[tag]?.let { unit -> inliner.translationResult.translate(unit).fragment.also { loadFragment(it) } } } - private fun lookUpFunctionDirect(descriptor: CallableDescriptor): InlineFunctionDefinition? = - Namer.getFunctionTag(descriptor, inliner.config).let { tag -> - fragmentByTag(tag)?.let { fragment -> - lookUpStaticFunctionByTag(tag, fragment)?.let { - if (fragment !in newFragmentSet) { - BinaryInlineFunctionDefinition(tag, it, fragment) - } else { - // TODO This is a wrong scope =( - PublicInlineFunctionDefinition(tag, it, fragment, scopeForFragment(fragment)) + private fun lookUpFunctionDirect(call: JsInvocation): InlineFunctionDefinition? = + call.descriptor?.let { descriptor -> + Namer.getFunctionTag(descriptor, inliner.config).let { tag -> + fragmentByTag(tag)?.let { definitionFragment -> + return lookUpStaticFunctionByTag(tag, definitionFragment)?.let { fn -> + InlineFunctionDefinition(fn, tag).also { definition -> + scopeForFragment(definitionFragment)?.let { definitionScope -> + inliner.process(definition, call, definitionScope) + } + } } } } } - - private fun lookUpFunctionExternal(descriptor: CallableDescriptor): LibraryInlineFunctionDefinition? = functionReader[descriptor] + private fun lookUpFunctionExternal(call: JsInvocation, fragment: JsProgramFragment): InlineFunctionDefinition? = + call.descriptor?.let { descriptor -> + functionReader[descriptor, fragment]?.let { + InlineFunctionDefinition(it, Namer.getFunctionTag(descriptor, inliner.config)) + } + } private fun tryExtractCallableReference(invocation: JsInvocation): FunctionWithWrapper? { if (invocation.isCallableReference) { 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 afe1cda835d..c13f80fb6a4 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 @@ -16,9 +16,10 @@ package org.jetbrains.kotlin.js.inline.context -import org.jetbrains.kotlin.js.backend.ast.JsContext import org.jetbrains.kotlin.js.backend.ast.JsStatement -class InliningContext(val statementContext: JsContext) { - fun newNamingContext() = NamingContext(statementContext) +class InliningContext(val currentStatement: JsStatement?) { + val previousStatements = mutableListOf() + + fun newNamingContext() = NamingContext(previousStatements) } 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 0a2f89b9b0c..8edea9ec1b9 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 @@ -21,14 +21,14 @@ import org.jetbrains.kotlin.js.backend.ast.metadata.synthetic import org.jetbrains.kotlin.js.inline.util.replaceNames import org.jetbrains.kotlin.js.translate.utils.JsAstUtils -class NamingContext(private val statementContext: JsContext) { +class NamingContext(private val previousStatements: MutableList) { private val renamings = mutableMapOf() private val declarations = mutableListOf() private var addedDeclarations = false fun applyRenameTo(target: JsNode): JsNode { if (!addedDeclarations) { - statementContext.addPrevious(declarations) + previousStatements.addAll(declarations) addedDeclarations = true }