JS: refactoring
This commit is contained in:
@@ -107,6 +107,9 @@ public class KotlinTestUtils {
|
|||||||
private static final boolean DONT_IGNORE_TESTS_WORKING_ON_COMPATIBLE_BACKEND =
|
private static final boolean DONT_IGNORE_TESTS_WORKING_ON_COMPATIBLE_BACKEND =
|
||||||
Boolean.getBoolean("org.jetbrains.kotlin.dont.ignore.tests.working.on.compatible.backend");
|
Boolean.getBoolean("org.jetbrains.kotlin.dont.ignore.tests.working.on.compatible.backend");
|
||||||
|
|
||||||
|
private static final boolean SKIP_IR_TESTS =
|
||||||
|
Boolean.getBoolean("org.jetbrains.kotlin.skip.ir.tests");
|
||||||
|
|
||||||
|
|
||||||
private static final boolean AUTOMATICALLY_UNMUTE_PASSED_TESTS = false;
|
private static final boolean AUTOMATICALLY_UNMUTE_PASSED_TESTS = false;
|
||||||
private static final boolean AUTOMATICALLY_MUTE_FAILED_TESTS = false;
|
private static final boolean AUTOMATICALLY_MUTE_FAILED_TESTS = false;
|
||||||
@@ -1045,6 +1048,8 @@ public class KotlinTestUtils {
|
|||||||
// * sometimes, for too common/general names, it shows many variants to navigate
|
// * sometimes, for too common/general names, it shows many variants to navigate
|
||||||
// * it adds an additional step for navigation -- you must choose an exact file to navigate
|
// * it adds an additional step for navigation -- you must choose an exact file to navigate
|
||||||
public static void runTest0(DoTest test, TargetBackend targetBackend, String testDataFilePath) throws Exception {
|
public static void runTest0(DoTest test, TargetBackend targetBackend, String testDataFilePath) throws Exception {
|
||||||
|
if (SKIP_IR_TESTS && (targetBackend == TargetBackend.JS_IR || targetBackend == TargetBackend.JVM_IR)) return;
|
||||||
|
|
||||||
File testDataFile = new File(testDataFilePath);
|
File testDataFile = new File(testDataFilePath);
|
||||||
|
|
||||||
boolean isIgnored = isIgnoredTarget(targetBackend, testDataFile);
|
boolean isIgnored = isIgnoredTarget(targetBackend, testDataFile);
|
||||||
|
|||||||
@@ -17,4 +17,6 @@ class JsProgramFragment(val scope: JsScope, val packageFqn: String) {
|
|||||||
val inlineModuleMap: MutableMap<String, JsExpression> = LinkedHashMap()
|
val inlineModuleMap: MutableMap<String, JsExpression> = LinkedHashMap()
|
||||||
var tests: JsStatement? = null
|
var tests: JsStatement? = null
|
||||||
var mainFunction: JsStatement? = null
|
var mainFunction: JsStatement? = null
|
||||||
|
// TODO serialize/deserialize; teach Merger about this thing
|
||||||
|
val inlinedFunctionWrappers = mutableMapOf<String, JsGlobalBlock>()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,139 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
|
||||||
* that can be found in the license/LICENSE.txt file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.jetbrains.kotlin.js.inline
|
|
||||||
|
|
||||||
import org.jetbrains.kotlin.js.backend.ast.*
|
|
||||||
import org.jetbrains.kotlin.js.backend.ast.metadata.localAlias
|
|
||||||
import org.jetbrains.kotlin.js.backend.ast.metadata.staticRef
|
|
||||||
import org.jetbrains.kotlin.js.inline.util.collectDefinedNamesInAllScopes
|
|
||||||
import org.jetbrains.kotlin.js.inline.util.collectNamedFunctions
|
|
||||||
import org.jetbrains.kotlin.js.inline.util.getImportTag
|
|
||||||
import org.jetbrains.kotlin.js.inline.util.replaceNames
|
|
||||||
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
|
|
||||||
import java.util.ArrayList
|
|
||||||
import java.util.HashMap
|
|
||||||
|
|
||||||
fun applyWrapper(
|
|
||||||
wrapper: JsBlock, function: JsFunction, originalFunction: JsFunction,
|
|
||||||
inlineFunctionDepth: Int,
|
|
||||||
replacementsInducedByWrappers: MutableMap<JsFunction, Map<JsName, JsNameRef>>,
|
|
||||||
existingImports: MutableMap<String, JsName>,
|
|
||||||
additionalImports: MutableList<Triple<String, JsExpression, JsName>>,
|
|
||||||
existingNameBindings: MutableMap<JsName, String>,
|
|
||||||
additionalNameBindings: MutableList<JsNameBinding>,
|
|
||||||
inlinedModuleAliases: MutableSet<JsName>,
|
|
||||||
inverseNameBindings: Map<JsName, String>,
|
|
||||||
addPrevious: (JsStatement) -> Unit
|
|
||||||
) {
|
|
||||||
|
|
||||||
// TODO Decrypt the comment below
|
|
||||||
// Apparently we should avoid this trick when we implement fair support for crossinline
|
|
||||||
val replacements = replacementsInducedByWrappers.computeIfAbsent(originalFunction) { k ->
|
|
||||||
val newReplacements = HashMap<JsName, JsNameRef>()
|
|
||||||
|
|
||||||
val copiedStatements = ArrayList<JsStatement>()
|
|
||||||
val importStatements = ArrayList<Pair<String, JsVars.JsVar>>()
|
|
||||||
wrapper.statements.asSequence()
|
|
||||||
.filterNot { it is JsReturn }
|
|
||||||
.map { it.deepCopy() }
|
|
||||||
.forEach { statement ->
|
|
||||||
if (inlineFunctionDepth == 0) {
|
|
||||||
replaceExpressionsWithLocalAliases(statement, inlinedModuleAliases)
|
|
||||||
}
|
|
||||||
|
|
||||||
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? = if (inlineFunctionDepth == 0) name.localAlias else null
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
val definedNames = (importStatements.asSequence().map { it.second } + 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 ((tag, statement) in importStatements) {
|
|
||||||
val renamed = replaceNames(statement, newReplacements)
|
|
||||||
additionalImports.add(Triple(tag,renamed.initExpression, renamed.name))
|
|
||||||
additionalNameBindings.add(JsNameBinding(tag, renamed.name))
|
|
||||||
}
|
|
||||||
|
|
||||||
for (statement in copiedStatements) {
|
|
||||||
addPrevious(replaceNames(statement, newReplacements))
|
|
||||||
}
|
|
||||||
|
|
||||||
for ((key, value) in collectNamedFunctions(JsBlock(copiedStatements))) {
|
|
||||||
if (key.staticRef is JsFunction) {
|
|
||||||
key.staticRef = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
newReplacements
|
|
||||||
}
|
|
||||||
|
|
||||||
replaceNames(function, replacements)
|
|
||||||
|
|
||||||
// Copy nameBinding's for inlined localAlias'es
|
|
||||||
for (nameRef in replacements.values) {
|
|
||||||
val name = nameRef.name
|
|
||||||
if (name != null && !existingNameBindings.containsKey(name)) {
|
|
||||||
val tag = inverseNameBindings[name]
|
|
||||||
if (tag != null) {
|
|
||||||
existingNameBindings[name] = tag
|
|
||||||
additionalNameBindings.add(JsNameBinding(tag, name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun replaceExpressionsWithLocalAliases(statement: JsStatement, inlinedModuleAliases: MutableSet<JsName>) {
|
|
||||||
object : JsVisitorWithContextImpl() {
|
|
||||||
override fun endVisit(x: JsNameRef, ctx: JsContext<JsNode>) {
|
|
||||||
replaceIfNecessary(x, ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun endVisit(x: JsArrayAccess, ctx: JsContext<JsNode>) {
|
|
||||||
replaceIfNecessary(x, ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun replaceIfNecessary(expression: JsExpression, ctx: JsContext<JsNode>) {
|
|
||||||
val alias = expression.localAlias
|
|
||||||
if (alias != null) {
|
|
||||||
ctx.replaceMe(alias.makeRef())
|
|
||||||
inlinedModuleAliases.add(alias)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}.accept(statement)
|
|
||||||
}
|
|
||||||
@@ -35,7 +35,7 @@ private constructor(
|
|||||||
private val invokedFunction: JsFunction
|
private val invokedFunction: JsFunction
|
||||||
val namingContext = inliningContext.newNamingContext()
|
val namingContext = inliningContext.newNamingContext()
|
||||||
val body: JsBlock
|
val body: JsBlock
|
||||||
var resultExpr: JsExpression? = null
|
var resultExpr: JsNameRef? = null
|
||||||
private var resultName: JsName? = null
|
private var resultName: JsName? = null
|
||||||
var breakLabel: JsLabel? = null
|
var breakLabel: JsLabel? = null
|
||||||
private val currentStatement = inliningContext.statementContext.currentNode
|
private val currentStatement = inliningContext.statementContext.currentNode
|
||||||
@@ -56,7 +56,7 @@ private constructor(
|
|||||||
|
|
||||||
namingContext.applyRenameTo(body)
|
namingContext.applyRenameTo(body)
|
||||||
resultExpr = resultExpr?.let {
|
resultExpr = resultExpr?.let {
|
||||||
namingContext.applyRenameTo(it) as JsExpression
|
namingContext.applyRenameTo(it) as JsNameRef
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ import org.jetbrains.kotlin.js.translate.utils.JsDescriptorUtils.getModuleName
|
|||||||
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
|
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
|
||||||
import org.jetbrains.kotlin.resolve.inline.InlineStrategy
|
import org.jetbrains.kotlin.resolve.inline.InlineStrategy
|
||||||
import org.jetbrains.kotlin.utils.JsLibraryUtils
|
import org.jetbrains.kotlin.utils.JsLibraryUtils
|
||||||
import org.jetbrains.kotlin.utils.sure
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.StringReader
|
import java.io.StringReader
|
||||||
|
|
||||||
@@ -58,8 +57,7 @@ private val SPECIAL_FUNCTION_PATTERN = Regex("var\\s+($JS_IDENTIFIER)\\s*=\\s*($
|
|||||||
class FunctionReader(
|
class FunctionReader(
|
||||||
private val reporter: JsConfig.Reporter,
|
private val reporter: JsConfig.Reporter,
|
||||||
private val config: JsConfig,
|
private val config: JsConfig,
|
||||||
private val currentModuleName: JsName,
|
private val currentModuleName: JsName
|
||||||
private val moduleNameMap: Map<String, JsExpression>
|
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* fileContent: .js file content, that contains this module definition.
|
* fileContent: .js file content, that contains this module definition.
|
||||||
@@ -84,8 +82,8 @@ class FunctionReader(
|
|||||||
val offsetToSourceMapping by lazy(offsetToSourceMappingProvider)
|
val offsetToSourceMapping by lazy(offsetToSourceMappingProvider)
|
||||||
|
|
||||||
val wrapFunctionRegex = specialFunctions.entries
|
val wrapFunctionRegex = specialFunctions.entries
|
||||||
.singleOrNull { (_, v) -> v == SpecialFunction.WRAP_FUNCTION }?.key
|
.filter { (_, v) -> v == SpecialFunction.WRAP_FUNCTION } // TODO This is a hack! Investigate duplicates!
|
||||||
?.let { Regex("\\s*$it\\s*\\(\\s*").toPattern() }
|
.map { Regex("\\s*${it.key}\\s*\\(\\s*").toPattern() }
|
||||||
}
|
}
|
||||||
|
|
||||||
private val moduleNameToInfo by lazy {
|
private val moduleNameToInfo by lazy {
|
||||||
@@ -166,19 +164,20 @@ class FunctionReader(
|
|||||||
override fun toString() = text.substring(offset)
|
override fun toString() = text.substring(offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val emptyFunctionWrapper = FunctionWithWrapper(JsFunction(object : JsScope("") {}, ""), null)
|
private object NotFoundMarker : Any()
|
||||||
|
|
||||||
private val functionCache = object : SLRUCache<CallableDescriptor, FunctionWithWrapper>(50, 50) {
|
private val functionCache = object : SLRUCache<CallableDescriptor, Any>(50, 50) {
|
||||||
override fun createValue(descriptor: CallableDescriptor): FunctionWithWrapper =
|
// LibraryInlineFunctionDefinition | NotFoundMarker
|
||||||
readFunction(descriptor) ?: emptyFunctionWrapper
|
override fun createValue(key: CallableDescriptor): Any =
|
||||||
|
readFunction(key) ?: NotFoundMarker
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun get(descriptor: CallableDescriptor): FunctionWithWrapper? {
|
operator fun get(descriptor: CallableDescriptor): LibraryInlineFunctionDefinition? {
|
||||||
val existed = functionCache.get(descriptor)
|
val existed = functionCache.get(descriptor)
|
||||||
return if (existed == emptyFunctionWrapper) null else existed
|
return if (existed === NotFoundMarker) null else existed as LibraryInlineFunctionDefinition
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun readFunction(descriptor: CallableDescriptor): FunctionWithWrapper? {
|
private fun readFunction(descriptor: CallableDescriptor): LibraryInlineFunctionDefinition? {
|
||||||
val moduleName = getModuleName(descriptor)
|
val moduleName = getModuleName(descriptor)
|
||||||
|
|
||||||
if (moduleName !in moduleNameToInfo.keys()) return null
|
if (moduleName !in moduleNameToInfo.keys()) return null
|
||||||
@@ -191,7 +190,8 @@ class FunctionReader(
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun readFunctionFromSource(descriptor: CallableDescriptor, info: ModuleInfo): FunctionWithWrapper? {
|
// TODO move renamings to a proper place
|
||||||
|
private fun readFunctionFromSource(descriptor: CallableDescriptor, info: ModuleInfo): LibraryInlineFunctionDefinition? {
|
||||||
val source = info.fileContent
|
val source = info.fileContent
|
||||||
var tag = Namer.getFunctionTag(descriptor, config)
|
var tag = Namer.getFunctionTag(descriptor, config)
|
||||||
val tagForModule = tag
|
val tagForModule = tag
|
||||||
@@ -213,16 +213,24 @@ class FunctionReader(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val sourcePart = ShallowSubSequence(source, offset, source.length)
|
val sourcePart = ShallowSubSequence(source, offset, source.length)
|
||||||
val wrapFunctionMatcher = info.wrapFunctionRegex?.matcher(sourcePart)
|
var isWrapped = false
|
||||||
val isWrapped = wrapFunctionMatcher?.lookingAt() == true
|
for (regex in info.wrapFunctionRegex) {
|
||||||
if (isWrapped) {
|
val wrapFunctionMatcher = regex.matcher(sourcePart)
|
||||||
offset += wrapFunctionMatcher!!.end()
|
isWrapped = wrapFunctionMatcher.lookingAt() == true
|
||||||
|
if (isWrapped) {
|
||||||
|
offset += wrapFunctionMatcher!!.end()
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val position = info.offsetToSourceMapping[offset]
|
val position = info.offsetToSourceMapping[offset]
|
||||||
val jsScope = JsRootScope(JsProgram())
|
val jsScope = JsRootScope(JsProgram())
|
||||||
val functionExpr = parseFunction(source, info.filePath, position, offset, ThrowExceptionOnErrorReporter, jsScope) ?:
|
val functionExpr = try {
|
||||||
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()
|
functionExpr.fixForwardNameReferences()
|
||||||
val (function, wrapper) = if (isWrapped) {
|
val (function, wrapper) = if (isWrapped) {
|
||||||
InlineMetadata.decomposeWrapper(functionExpr) ?: return null
|
InlineMetadata.decomposeWrapper(functionExpr) ?: return null
|
||||||
@@ -230,7 +238,7 @@ class FunctionReader(
|
|||||||
else {
|
else {
|
||||||
FunctionWithWrapper(functionExpr, null)
|
FunctionWithWrapper(functionExpr, null)
|
||||||
}
|
}
|
||||||
val moduleReference = moduleNameMap[tagForModule]?.deepCopy() ?: currentModuleName.makeRef()
|
// val moduleReference = moduleNameMap[tagForModule]?.deepCopy() ?: currentModuleName.makeRef()
|
||||||
val wrapperStatements = wrapper?.statements?.filter { it !is JsReturn }
|
val wrapperStatements = wrapper?.statements?.filter { it !is JsReturn }
|
||||||
|
|
||||||
val sourceMap = info.sourceMap
|
val sourceMap = info.sourceMap
|
||||||
@@ -243,20 +251,22 @@ class FunctionReader(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val allDefinedNames = collectDefinedNamesInAllScopes(function)
|
val allDefinedNames = collectDefinedNamesInAllScopes(function)
|
||||||
val replacements = hashMapOf(info.moduleVariable to moduleReference,
|
// val replacements = hashMapOf(info.moduleVariable to moduleReference,
|
||||||
info.kotlinVariable to Namer.kotlinObject())
|
// info.kotlinVariable to Namer.kotlinObject())
|
||||||
replaceExternalNames(function, replacements, allDefinedNames)
|
// replaceExternalNames(function, replacements, allDefinedNames)
|
||||||
wrapperStatements?.forEach { replaceExternalNames(it, replacements, allDefinedNames) }
|
// wrapperStatements?.forEach { replaceExternalNames(it, replacements, allDefinedNames) }
|
||||||
|
|
||||||
function.markInlineArguments(descriptor)
|
function.markInlineArguments(descriptor)
|
||||||
markDefaultParams(function)
|
markDefaultParams(function)
|
||||||
|
// TODO maybe move elsewhere?
|
||||||
markSpecialFunctions(function, allDefinedNames, info, jsScope)
|
markSpecialFunctions(function, allDefinedNames, info, jsScope)
|
||||||
|
|
||||||
val namesWithoutSizeEffects = wrapperStatements.orEmpty().asSequence()
|
val namesWithoutSideEffects = wrapperStatements.orEmpty().asSequence()
|
||||||
.flatMap { collectDefinedNames(it).asSequence() }
|
.flatMap { collectDefinedNames(it).asSequence() }
|
||||||
.toSet()
|
.toSet()
|
||||||
function.accept(object : RecursiveJsVisitor() {
|
function.accept(object : RecursiveJsVisitor() {
|
||||||
override fun visitNameRef(nameRef: JsNameRef) {
|
override fun visitNameRef(nameRef: JsNameRef) {
|
||||||
if (nameRef.name in namesWithoutSizeEffects && nameRef.qualifier == null) {
|
if (nameRef.name in namesWithoutSideEffects && nameRef.qualifier == null) {
|
||||||
nameRef.sideEffects = SideEffectKind.PURE
|
nameRef.sideEffects = SideEffectKind.PURE
|
||||||
}
|
}
|
||||||
super.visitNameRef(nameRef)
|
super.visitNameRef(nameRef)
|
||||||
@@ -269,7 +279,7 @@ class FunctionReader(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return FunctionWithWrapper(function, wrapper)
|
return LibraryInlineFunctionDefinition(tagForModule, FunctionWithWrapper(function, wrapper), info)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun markSpecialFunctions(function: JsFunction, allDefinedNames: Set<JsName>, info: ModuleInfo, scope: JsScope) {
|
private fun markSpecialFunctions(function: JsFunction, allDefinedNames: Set<JsName>, info: ModuleInfo, scope: JsScope) {
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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 (<module>.js file)
|
||||||
|
open class LibraryInlineFunctionDefinition(
|
||||||
|
override val tag: String,
|
||||||
|
override val functionWithWrapper: FunctionWithWrapper,
|
||||||
|
val moduleInfo: FunctionReader.ModuleInfo
|
||||||
|
): InlineFunctionDefinition() {
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -9,26 +9,11 @@ import org.jetbrains.kotlin.js.backend.ast.*
|
|||||||
import org.jetbrains.kotlin.js.backend.ast.metadata.coroutineMetadata
|
import org.jetbrains.kotlin.js.backend.ast.metadata.coroutineMetadata
|
||||||
import org.jetbrains.kotlin.js.backend.ast.metadata.isInlineableCoroutineBody
|
import org.jetbrains.kotlin.js.backend.ast.metadata.isInlineableCoroutineBody
|
||||||
import org.jetbrains.kotlin.js.translate.expression.InlineMetadata
|
import org.jetbrains.kotlin.js.translate.expression.InlineMetadata
|
||||||
import java.util.ArrayList
|
|
||||||
import java.util.HashMap
|
|
||||||
import java.util.HashSet
|
|
||||||
|
|
||||||
class InlineSuspendFunctionSplitter(
|
class InlineSuspendFunctionSplitter(
|
||||||
val existingNameBindings: MutableMap<JsName, String>,
|
val scope: ProgramFragmentInliningScope
|
||||||
val existingImports: MutableMap<String, JsName>,
|
|
||||||
val inlineFunctionDepth: Int,
|
|
||||||
val inverseNameBindings: Map<JsName, String>,
|
|
||||||
val replacementsInducedByWrappers: MutableMap<JsFunction, Map<JsName, JsNameRef>>,
|
|
||||||
val addPrevious: (JsStatement) -> Unit
|
|
||||||
) : JsVisitorWithContextImpl() {
|
) : JsVisitorWithContextImpl() {
|
||||||
|
|
||||||
val inlinedModuleAliases = HashSet<JsName>()
|
|
||||||
|
|
||||||
val additionalNameBindings = ArrayList<JsNameBinding>()
|
|
||||||
|
|
||||||
val additionalImports = mutableListOf<Triple<String, JsExpression, JsName>>()
|
|
||||||
|
|
||||||
|
|
||||||
override fun endVisit(x: JsExpressionStatement, ctx: JsContext<*>) {
|
override fun endVisit(x: JsExpressionStatement, ctx: JsContext<*>) {
|
||||||
val e = x.expression
|
val e = x.expression
|
||||||
if (e is JsBinaryOperation) {
|
if (e is JsBinaryOperation) {
|
||||||
@@ -57,40 +42,24 @@ class InlineSuspendFunctionSplitter(
|
|||||||
private fun splitExportedSuspendInlineFunctionDeclarations(expression: JsExpression): JsFunction? {
|
private fun splitExportedSuspendInlineFunctionDeclarations(expression: JsExpression): JsFunction? {
|
||||||
val inlineMetadata = InlineMetadata.decompose(expression)
|
val inlineMetadata = InlineMetadata.decompose(expression)
|
||||||
if (inlineMetadata != null) {
|
if (inlineMetadata != null) {
|
||||||
val (originalFunction, wrapperBody) = inlineMetadata.function
|
inlineMetadata.function.let { f ->
|
||||||
if (originalFunction.coroutineMetadata != null) {
|
if (f.function.coroutineMetadata != null) {
|
||||||
val statementContext = lastStatementLevelContext
|
val statementContext = lastStatementLevelContext
|
||||||
|
|
||||||
// This function will be exported to JS
|
// This function will be exported to JS
|
||||||
val function = originalFunction.deepCopy()
|
val function = scope.importFunctionDefinition(PublicInlineFunctionDefinition(inlineMetadata.tag.value, inlineMetadata.function, scope.fragment, scope))
|
||||||
|
|
||||||
// Original function should be not be transformed into a state machine
|
// Original function should be not be transformed into a state machine
|
||||||
originalFunction.setName(null)
|
f.function.setName(null)
|
||||||
originalFunction.coroutineMetadata = null
|
f.function.coroutineMetadata = null
|
||||||
originalFunction.isInlineableCoroutineBody = true
|
f.function.isInlineableCoroutineBody = true
|
||||||
if (wrapperBody != null) {
|
|
||||||
// Extract local declarations
|
// Keep the `defineInlineFunction` for the inliner to find
|
||||||
applyWrapper(
|
statementContext.addNext(expression.makeStmt())
|
||||||
wrapperBody,
|
|
||||||
function,
|
// Return the function body to be used without inlining.
|
||||||
originalFunction,
|
return function
|
||||||
inlineFunctionDepth,
|
|
||||||
replacementsInducedByWrappers,
|
|
||||||
existingImports,
|
|
||||||
additionalImports,
|
|
||||||
existingNameBindings,
|
|
||||||
additionalNameBindings,
|
|
||||||
inlinedModuleAliases,
|
|
||||||
inverseNameBindings,
|
|
||||||
addPrevious
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep the `defineInlineFunction` for the inliner to find
|
|
||||||
statementContext.addNext(expression.makeStmt())
|
|
||||||
|
|
||||||
// Return the function body to be used without inlining.
|
|
||||||
return function
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ package org.jetbrains.kotlin.js.inline
|
|||||||
import org.jetbrains.kotlin.js.backend.ast.JsStatement
|
import org.jetbrains.kotlin.js.backend.ast.JsStatement
|
||||||
import org.jetbrains.kotlin.js.backend.ast.JsExpression
|
import org.jetbrains.kotlin.js.backend.ast.JsExpression
|
||||||
|
|
||||||
class InlineableResult(
|
data class InlineableResult(
|
||||||
val inlineableBody: JsStatement,
|
val inlineableBody: JsStatement,
|
||||||
val resultExpression: JsExpression?
|
val resultExpression: JsExpression?
|
||||||
)
|
)
|
||||||
|
|||||||
+14
-31
@@ -7,10 +7,8 @@ package org.jetbrains.kotlin.js.inline
|
|||||||
|
|
||||||
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
|
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
|
||||||
import org.jetbrains.kotlin.diagnostics.Errors
|
import org.jetbrains.kotlin.diagnostics.Errors
|
||||||
import org.jetbrains.kotlin.js.backend.ast.JsBlock
|
|
||||||
import org.jetbrains.kotlin.js.backend.ast.JsFunction
|
import org.jetbrains.kotlin.js.backend.ast.JsFunction
|
||||||
import org.jetbrains.kotlin.js.backend.ast.JsInvocation
|
import org.jetbrains.kotlin.js.backend.ast.JsInvocation
|
||||||
import org.jetbrains.kotlin.js.backend.ast.JsName
|
|
||||||
import org.jetbrains.kotlin.js.backend.ast.metadata.descriptor
|
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.inlineStrategy
|
||||||
import org.jetbrains.kotlin.js.backend.ast.metadata.psiElement
|
import org.jetbrains.kotlin.js.backend.ast.metadata.psiElement
|
||||||
@@ -23,27 +21,18 @@ import org.jetbrains.kotlin.js.inline.util.refreshLabelNames
|
|||||||
import org.jetbrains.kotlin.resolve.inline.InlineStrategy
|
import org.jetbrains.kotlin.resolve.inline.InlineStrategy
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class InlineDfsController(
|
/**
|
||||||
|
* There are two ways the inliner may arrive to an inline function declaration. Either the topmost visitor arrive to a declaration, which
|
||||||
|
* has never been used before. Or it visits it's invocation, and has to obtain the inline function body. Current strategy is to processes
|
||||||
|
* the inline function declaration before inlining it's invocation.
|
||||||
|
*
|
||||||
|
* Thus the inliner effectively implements a DFS. InlinerCycleReporter manages the DFS state. Also it detects and reports cycles.
|
||||||
|
*/
|
||||||
|
class InlinerCycleReporter(
|
||||||
val trace: DiagnosticSink,
|
val trace: DiagnosticSink,
|
||||||
val functions: Map<JsName, FunctionWithWrapper>,
|
private val functionContext: FunctionContext
|
||||||
val accessors: Map<String, FunctionWithWrapper>,
|
|
||||||
val functionContext: FunctionContext
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val functionsByWrapperNodes =
|
|
||||||
HashMap<JsBlock, FunctionWithWrapper>()
|
|
||||||
val functionsByFunctionNodes =
|
|
||||||
HashMap<JsFunction, FunctionWithWrapper>()
|
|
||||||
|
|
||||||
init {
|
|
||||||
(functions.values.asSequence() + accessors.values.asSequence()).forEach { f ->
|
|
||||||
functionsByFunctionNodes[f.function] = f
|
|
||||||
if (f.wrapperBody != null) {
|
|
||||||
functionsByWrapperNodes[f.wrapperBody] = f
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val processedFunctions = IdentitySet<JsFunction>()
|
private val processedFunctions = IdentitySet<JsFunction>()
|
||||||
private val inProcessFunctions = IdentitySet<JsFunction>()
|
private val inProcessFunctions = IdentitySet<JsFunction>()
|
||||||
// these are needed for error reporting, when inliner detects cycle
|
// these are needed for error reporting, when inliner detects cycle
|
||||||
@@ -51,6 +40,7 @@ class InlineDfsController(
|
|||||||
|
|
||||||
private val inlineCallInfos = LinkedList<JsCallInfo>()
|
private val inlineCallInfos = LinkedList<JsCallInfo>()
|
||||||
|
|
||||||
|
// TODO This looks like a hack
|
||||||
val currentNamedFunction: JsFunction?
|
val currentNamedFunction: JsFunction?
|
||||||
get() = if (namedFunctionsStack.empty()) null else namedFunctionsStack.peek()
|
get() = if (namedFunctionsStack.empty()) null else namedFunctionsStack.peek()
|
||||||
|
|
||||||
@@ -59,19 +49,14 @@ class InlineDfsController(
|
|||||||
assert(!inProcessFunctions.contains(function)) { "Inliner has revisited function" }
|
assert(!inProcessFunctions.contains(function)) { "Inliner has revisited function" }
|
||||||
inProcessFunctions.add(function)
|
inProcessFunctions.add(function)
|
||||||
|
|
||||||
if (function in functionsByFunctionNodes.keys) {
|
if (function in functionContext.functionsByFunctionNodes.keys) {
|
||||||
namedFunctionsStack.push(function)
|
namedFunctionsStack.push(function)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun endFunction(function: JsFunction) {
|
fun endFunction(function: JsFunction) {
|
||||||
refreshLabelNames(function.body, function.scope)
|
|
||||||
|
|
||||||
removeUnusedLocalFunctionDeclarations(function)
|
|
||||||
processedFunctions.add(function)
|
processedFunctions.add(function)
|
||||||
|
|
||||||
FunctionPostProcessor(function).apply()
|
|
||||||
|
|
||||||
assert(inProcessFunctions.contains(function))
|
assert(inProcessFunctions.contains(function))
|
||||||
inProcessFunctions.remove(function)
|
inProcessFunctions.remove(function)
|
||||||
|
|
||||||
@@ -82,17 +67,15 @@ class InlineDfsController(
|
|||||||
|
|
||||||
|
|
||||||
// Return true iff the definition should be visited by the inliner
|
// Return true iff the definition should be visited by the inliner
|
||||||
fun visitCall(call: JsInvocation): Boolean {
|
fun shouldProcess(definition: FunctionWithWrapper, call: JsInvocation): Boolean {
|
||||||
val definition = functionContext.getFunctionDefinition(call)
|
|
||||||
|
|
||||||
currentNamedFunction?.let {
|
currentNamedFunction?.let {
|
||||||
inlineCallInfos.add(JsCallInfo(call, it))
|
inlineCallInfos.add(JsCallInfo(call, it))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (definition.function in inProcessFunctions) {
|
||||||
if (inProcessFunctions.contains(definition.function)) {
|
|
||||||
reportInlineCycle(call, definition.function)
|
reportInlineCycle(call, definition.function)
|
||||||
} else if (!processedFunctions.contains(definition.function)) {
|
} else if (definition.function !in processedFunctions) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -9,144 +9,84 @@ import org.jetbrains.kotlin.js.backend.ast.*
|
|||||||
import org.jetbrains.kotlin.js.backend.ast.metadata.forcedReturnVariable
|
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.inlineStrategy
|
||||||
import org.jetbrains.kotlin.js.backend.ast.metadata.synthetic
|
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.FunctionContext
|
||||||
import org.jetbrains.kotlin.js.inline.context.InliningContext
|
import org.jetbrains.kotlin.js.inline.context.InliningContext
|
||||||
import org.jetbrains.kotlin.js.inline.util.FunctionWithWrapper
|
import org.jetbrains.kotlin.js.inline.util.refreshLabelNames
|
||||||
import org.jetbrains.kotlin.js.inline.util.getImportTag
|
|
||||||
import org.jetbrains.kotlin.js.translate.declaration.transformSpecialFunctionsToCoroutineMetadata
|
import org.jetbrains.kotlin.js.translate.declaration.transformSpecialFunctionsToCoroutineMetadata
|
||||||
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
|
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
|
||||||
import java.util.ArrayList
|
|
||||||
import java.util.HashMap
|
|
||||||
import java.util.HashSet
|
|
||||||
|
|
||||||
|
// TODO stateless?
|
||||||
class InlinerImpl(
|
class InlinerImpl(
|
||||||
val existingNameBindings: MutableMap<JsName, String>,
|
val cycleReporter: InlinerCycleReporter,
|
||||||
val existingImports: MutableMap<String, JsName>,
|
|
||||||
val inlineFunctionDepth: Int,
|
|
||||||
val dfsController: InlineDfsController,
|
|
||||||
val inverseNameBindings: Map<JsName, String>,
|
|
||||||
val functionContext: FunctionContext,
|
val functionContext: FunctionContext,
|
||||||
val addPrevious: (JsStatement) -> Unit
|
// TODO other way around? Need to find a correct inliner by function declaration.
|
||||||
|
val scope: InliningScope
|
||||||
) : JsVisitorWithContextImpl() {
|
) : JsVisitorWithContextImpl() {
|
||||||
val inlinedModuleAliases = HashSet<JsName>()
|
|
||||||
|
|
||||||
val replacementsInducedByWrappers: MutableMap<JsFunction, Map<JsName, JsNameRef>> = mutableMapOf()
|
|
||||||
|
|
||||||
val additionalNameBindings = ArrayList<JsNameBinding>()
|
|
||||||
|
|
||||||
val additionalImports = mutableListOf<Triple<String, JsExpression, JsName>>()
|
|
||||||
|
|
||||||
override fun visit(function: JsFunction, context: JsContext<*>): Boolean {
|
override fun visit(function: JsFunction, context: JsContext<*>): Boolean {
|
||||||
val functionWithWrapper = dfsController.functionsByFunctionNodes[function]
|
functionContext.functionsByFunctionNodes[function]?.let { (function, wrapper) ->
|
||||||
if (functionWithWrapper != null) {
|
visit(function, wrapper)
|
||||||
visit(functionWithWrapper)
|
|
||||||
return false
|
return false
|
||||||
} else {
|
|
||||||
dfsController.startFunction(function)
|
|
||||||
return super.visit(function, context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun endVisit(function: JsFunction, context: JsContext<*>) {
|
|
||||||
super.endVisit(function, context)
|
|
||||||
if (!dfsController.functionsByFunctionNodes.containsKey(function)) {
|
|
||||||
dfsController.endFunction(function)
|
|
||||||
}
|
}
|
||||||
|
visit(function, null)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(x: JsBlock, ctx: JsContext<*>): Boolean {
|
override fun visit(x: JsBlock, ctx: JsContext<*>): Boolean {
|
||||||
val functionWithWrapper = dfsController.functionsByWrapperNodes[x]
|
// TODO Seems like a very roundabout way. Probably should reuse same approach as in CoroutineTransformer and ...Splitter
|
||||||
if (functionWithWrapper != null) {
|
// TODO That approach might be missing inline properties. Check!
|
||||||
visit(functionWithWrapper)
|
functionContext.functionsByWrapperNodes[x]?.let { (function, wrapper) ->
|
||||||
|
visit(function, wrapper)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return super.visit(x, ctx)
|
return super.visit(x, ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun visit(functionWithWrapper: FunctionWithWrapper) {
|
private fun visit(function: JsFunction, wrapperBody: JsBlock?) {
|
||||||
dfsController.startFunction(functionWithWrapper.function)
|
cycleReporter.startFunction(function)
|
||||||
|
|
||||||
val wrapperBody = functionWithWrapper.wrapperBody
|
// TODO Different visitors?
|
||||||
if (wrapperBody != null) {
|
if (wrapperBody != null && scope is ProgramFragmentInliningScope) {
|
||||||
val existingImports = HashMap<String, JsName>()
|
PublicInlineFunctionInliningScope(scope.fragment, cycleReporter, functionContext, function, wrapperBody).process()
|
||||||
|
|
||||||
for (statement in wrapperBody.statements) {
|
|
||||||
if (statement is JsVars) {
|
|
||||||
val tag = getImportTag(statement)
|
|
||||||
if (tag != null) {
|
|
||||||
existingImports[tag] = statement.vars[0].name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val additionalStatements = mutableListOf<JsStatement>()
|
|
||||||
val innerInliner = InlinerImpl(
|
|
||||||
existingNameBindings,
|
|
||||||
existingImports,
|
|
||||||
inlineFunctionDepth + 1,
|
|
||||||
dfsController,
|
|
||||||
inverseNameBindings,
|
|
||||||
functionContext
|
|
||||||
) {
|
|
||||||
additionalStatements.add(it)
|
|
||||||
}
|
|
||||||
for (statement in wrapperBody.statements) {
|
|
||||||
if (statement !is JsReturn) {
|
|
||||||
innerInliner.acceptStatement(statement)
|
|
||||||
} else {
|
|
||||||
innerInliner.accept((statement.expression as JsFunction).body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val importStatements = innerInliner.additionalImports.map { (_, importExpr, name) ->
|
|
||||||
JsAstUtils.newVar(name, importExpr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO keep order
|
|
||||||
wrapperBody.statements.addAll(0, importStatements + additionalStatements)
|
|
||||||
} else {
|
} else {
|
||||||
accept(functionWithWrapper.function.body)
|
// TODO this is still not super-clear
|
||||||
|
accept(function.body)
|
||||||
}
|
}
|
||||||
|
|
||||||
dfsController.endFunction(functionWithWrapper.function)
|
// Cleanup
|
||||||
|
refreshLabelNames(function.body, function.scope)
|
||||||
|
removeUnusedLocalFunctionDeclarations(function)
|
||||||
|
FunctionPostProcessor(function).apply()
|
||||||
|
|
||||||
|
cycleReporter.endFunction(function)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(call: JsInvocation, context: JsContext<*>): Boolean {
|
override fun endVisit(call: JsInvocation, ctx: JsContext<JsNode>) {
|
||||||
if (!hasToBeInlined(call)) return true
|
if (hasToBeInlined(call)) {
|
||||||
|
|
||||||
if (dfsController.visitCall(call)) {
|
val definition = functionContext.getFunctionDefinition(call, scope)
|
||||||
val definition = functionContext.getFunctionDefinition(call)
|
|
||||||
|
|
||||||
for (i in 0 until call.arguments.size) {
|
if (cycleReporter.shouldProcess(definition.functionWithWrapper, call)) {
|
||||||
val argument = call.arguments[i]
|
definition.process()
|
||||||
call.arguments[i] = accept(argument)
|
|
||||||
}
|
}
|
||||||
visit(definition)
|
|
||||||
|
|
||||||
return false
|
inline(call, definition, ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
cycleReporter.endVisit(call)
|
||||||
}
|
|
||||||
|
|
||||||
override fun endVisit(x: JsInvocation, ctx: JsContext<JsNode>) {
|
|
||||||
if (hasToBeInlined(x)) {
|
|
||||||
inline(x, ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
dfsController.endVisit(x)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO This could be extracted into a separate pass.
|
||||||
|
// TODO Don't forget to run it on the freshly inlined code!
|
||||||
override fun doAcceptStatementList(statements: MutableList<JsStatement>) {
|
override fun doAcceptStatementList(statements: MutableList<JsStatement>) {
|
||||||
var i = 0
|
var i = 0
|
||||||
|
|
||||||
while (i < statements.size) {
|
while (i < statements.size) {
|
||||||
val additionalStatements =
|
val additionalStatements = ExpressionDecomposer.preserveEvaluationOrder(statements[i]) { node ->
|
||||||
ExpressionDecomposer.preserveEvaluationOrder(statements[i]) { node ->
|
node is JsInvocation && hasToBeInlined(node)
|
||||||
node is JsInvocation && hasToBeInlined(
|
}
|
||||||
node
|
|
||||||
)
|
|
||||||
}
|
|
||||||
statements.addAll(i, additionalStatements)
|
statements.addAll(i, additionalStatements)
|
||||||
i += additionalStatements.size + 1
|
i += additionalStatements.size + 1
|
||||||
}
|
}
|
||||||
@@ -154,61 +94,28 @@ class InlinerImpl(
|
|||||||
super.doAcceptStatementList(statements)
|
super.doAcceptStatementList(statements)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun inline(call: JsInvocation, context: JsContext<JsNode>) {
|
// TODO a lot of code... Probably a bad sign
|
||||||
var functionWithWrapper = functionContext.getFunctionDefinition(call)
|
private fun inline(call: JsInvocation, definition: InlineFunctionDefinition, context: JsContext<JsNode>) {
|
||||||
|
|
||||||
// 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
|
// This should be isolated
|
||||||
dfsController.functionsByFunctionNodes[functionWithWrapper.function]?.let {
|
|
||||||
functionWithWrapper = it
|
|
||||||
}
|
|
||||||
|
|
||||||
val function = functionWithWrapper.function.deepCopy()
|
val function = scope.importFunctionDefinition(definition)
|
||||||
|
// TODO This should be done inside the importer
|
||||||
function.body = transformSpecialFunctionsToCoroutineMetadata(function.body)
|
function.body = transformSpecialFunctionsToCoroutineMetadata(function.body)
|
||||||
if (functionWithWrapper.wrapperBody != null) {
|
|
||||||
applyWrapper(
|
|
||||||
functionWithWrapper.wrapperBody!!,
|
|
||||||
function,
|
|
||||||
functionWithWrapper.function,
|
|
||||||
inlineFunctionDepth,
|
|
||||||
replacementsInducedByWrappers,
|
|
||||||
existingImports,
|
|
||||||
additionalImports,
|
|
||||||
existingNameBindings,
|
|
||||||
additionalNameBindings,
|
|
||||||
inlinedModuleAliases,
|
|
||||||
inverseNameBindings
|
|
||||||
) {
|
|
||||||
addPrevious(accept(it))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val inliningContext = InliningContext(lastStatementLevelContext)
|
// -------------------
|
||||||
|
|
||||||
val inlineableResult =
|
val statementContext = lastStatementLevelContext
|
||||||
FunctionInlineMutator.getInlineableCallReplacement(call, function, inliningContext)
|
|
||||||
|
val (inlineableBody, resultExpression) =
|
||||||
|
FunctionInlineMutator.getInlineableCallReplacement(call, function, InliningContext(statementContext))
|
||||||
|
|
||||||
val inlineableBody = inlineableResult.inlineableBody
|
|
||||||
var resultExpression = inlineableResult.resultExpression
|
|
||||||
val statementContext = inliningContext.statementContext
|
|
||||||
// body of inline function can contain call to lambdas that need to be inlined
|
// body of inline function can contain call to lambdas that need to be inlined
|
||||||
val inlineableBodyWithLambdasInlined = accept(inlineableBody)
|
val inlineableBodyWithLambdasInlined = accept(inlineableBody)
|
||||||
assert(inlineableBody === inlineableBodyWithLambdasInlined)
|
assert(inlineableBody === inlineableBodyWithLambdasInlined)
|
||||||
|
|
||||||
// Support non-local return from secondary constructor
|
patchReturnsFromSecondaryConstructor(inlineableBody)
|
||||||
// Returns from secondary constructors should return `$this` object.
|
|
||||||
// TODO This seems brittle
|
|
||||||
val currentFunction = dfsController.currentNamedFunction
|
|
||||||
if (currentFunction != null) {
|
|
||||||
val returnVariable = currentFunction.forcedReturnVariable
|
|
||||||
if (returnVariable != null) {
|
|
||||||
inlineableBody.accept(object : RecursiveJsVisitor() {
|
|
||||||
override fun visitReturn(x: JsReturn) {
|
|
||||||
x.expression = returnVariable.makeRef()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
statementContext.addPrevious(JsAstUtils.flattenStatement(inlineableBody))
|
statementContext.addPrevious(JsAstUtils.flattenStatement(inlineableBody))
|
||||||
|
|
||||||
@@ -221,13 +128,28 @@ class InlinerImpl(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resultExpression = accept(resultExpression)
|
// TODO Why accept? Seems unnecessary... Some inline call in the qualifier? Shouldn't this be done along with the lambdas then?
|
||||||
resultExpression.synthetic = true
|
accept(resultExpression)?.let {
|
||||||
context.replaceMe(resultExpression)
|
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 {
|
private fun hasToBeInlined(call: JsInvocation): Boolean {
|
||||||
val strategy = call.inlineStrategy
|
val strategy = call.inlineStrategy
|
||||||
return if (strategy == null || !strategy.isInline) false else functionContext.hasFunctionDefinition(call)
|
return if (strategy == null || !strategy.isInline) false else functionContext.hasFunctionDefinition(call, scope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,336 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.js.inline
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.js.backend.ast.*
|
||||||
|
import org.jetbrains.kotlin.js.backend.ast.metadata.imported
|
||||||
|
import org.jetbrains.kotlin.js.backend.ast.metadata.localAlias
|
||||||
|
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.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<String, Map<JsName, JsNameRef>>()
|
||||||
|
|
||||||
|
protected fun computeIfAbsent(tag: String?, fn: () -> Map<JsName, JsNameRef>): Map<JsName, JsNameRef> {
|
||||||
|
if (tag == null) return fn()
|
||||||
|
|
||||||
|
return cache.computeIfAbsent(tag) { fn() }
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract fun importFunctionDefinition(f: InlineFunctionDefinition): JsFunction
|
||||||
|
|
||||||
|
abstract fun process()
|
||||||
|
|
||||||
|
abstract val fragment: JsProgramFragment
|
||||||
|
}
|
||||||
|
|
||||||
|
class ProgramFragmentInliningScope(
|
||||||
|
override val fragment: JsProgramFragment,
|
||||||
|
val functionContext: FunctionContext,
|
||||||
|
val rootInliner: JsInliner
|
||||||
|
) : InliningScope() {
|
||||||
|
|
||||||
|
private val existingModules = fragment.importedModules.mapTo(IdentitySet()) { it.internalName }
|
||||||
|
|
||||||
|
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 additionalDeclarations = mutableListOf<JsStatement>()
|
||||||
|
|
||||||
|
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() {
|
||||||
|
// 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
|
||||||
|
fragment.declarationBlock.statements.addAll(0, additionalDeclarations)
|
||||||
|
|
||||||
|
// post-processing
|
||||||
|
val block = JsBlock(fragment.declarationBlock, fragment.initializerBlock, fragment.exportBlock)
|
||||||
|
fragment.tests?.let { block.statements.add(it) }
|
||||||
|
fragment.mainFunction?.let { block.statements.add(it) }
|
||||||
|
|
||||||
|
simplifyWrappedFunctions(block)
|
||||||
|
removeUnusedFunctionDefinitions(block, collectNamedFunctions(block))
|
||||||
|
|
||||||
|
// TODO simplify
|
||||||
|
val usedImports = removeUnusedImports(block)
|
||||||
|
fragment.nameBindings.filter {
|
||||||
|
!it.name.imported || it.name in usedImports
|
||||||
|
}.let {
|
||||||
|
fragment.nameBindings.clear()
|
||||||
|
fragment.nameBindings.addAll(it)
|
||||||
|
}
|
||||||
|
val existingTags = fragment.nameBindings.map { it.key }.toSet()
|
||||||
|
val newImports = fragment.imports.filter { (k, _) -> k in existingTags }
|
||||||
|
fragment.imports.clear()
|
||||||
|
for ((k, v) in newImports) {
|
||||||
|
fragment.imports[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addInlinedModule(moduleName: JsName) {
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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<JsName, JsNameRef>()
|
||||||
|
|
||||||
|
val copiedStatements = ArrayList<JsStatement>()
|
||||||
|
val importStatements = ArrayList<Pair<String, JsVars.JsVar>>()
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
object : JsVisitorWithContextImpl() {
|
||||||
|
override fun endVisit(x: JsNameRef, ctx: JsContext<JsNode>) {
|
||||||
|
replaceIfNecessary(x, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun endVisit(x: JsArrayAccess, ctx: JsContext<JsNode>) {
|
||||||
|
replaceIfNecessary(x, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun replaceIfNecessary(expression: JsExpression, ctx: JsContext<JsNode>) {
|
||||||
|
val alias = expression.localAlias
|
||||||
|
if (alias != null) {
|
||||||
|
ctx.replaceMe(alias.makeRef())
|
||||||
|
addInlinedModule(alias)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}.accept(statement)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PublicInlineFunctionInliningScope(
|
||||||
|
override val fragment: JsProgramFragment,
|
||||||
|
cycleReporter: InlinerCycleReporter,
|
||||||
|
functionContext: FunctionContext,
|
||||||
|
val function: JsFunction,
|
||||||
|
val wrapperBody: JsBlock
|
||||||
|
) : InliningScope() {
|
||||||
|
|
||||||
|
val additionalStatements = mutableListOf<JsStatement>()
|
||||||
|
|
||||||
|
val innerInliner = InlinerImpl(
|
||||||
|
cycleReporter,
|
||||||
|
functionContext,
|
||||||
|
this
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun process() {
|
||||||
|
for (statement in wrapperBody.statements) {
|
||||||
|
if (statement !is JsReturn) {
|
||||||
|
innerInliner.acceptStatement(statement)
|
||||||
|
} else {
|
||||||
|
innerInliner.accept((statement.expression as JsFunction).body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO keep order
|
||||||
|
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<JsName, JsNameRef>()
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,160 +8,34 @@ package org.jetbrains.kotlin.js.inline
|
|||||||
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
|
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
|
||||||
import org.jetbrains.kotlin.js.backend.ast.*
|
import org.jetbrains.kotlin.js.backend.ast.*
|
||||||
import org.jetbrains.kotlin.js.config.JsConfig
|
import org.jetbrains.kotlin.js.config.JsConfig
|
||||||
import org.jetbrains.kotlin.js.inline.clean.*
|
|
||||||
import org.jetbrains.kotlin.js.inline.context.FunctionContext
|
import org.jetbrains.kotlin.js.inline.context.FunctionContext
|
||||||
import org.jetbrains.kotlin.js.inline.util.*
|
|
||||||
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
import org.jetbrains.kotlin.js.inline.util.IdentitySet
|
|
||||||
import org.jetbrains.kotlin.js.translate.general.AstGenerationResult
|
import org.jetbrains.kotlin.js.translate.general.AstGenerationResult
|
||||||
|
|
||||||
class JsInliner private constructor(
|
class JsInliner(
|
||||||
config: JsConfig,
|
val reporter: JsConfig.Reporter,
|
||||||
functionReader: FunctionReader,
|
val config: JsConfig,
|
||||||
val functions: Map<JsName, FunctionWithWrapper>,
|
val trace: DiagnosticSink,
|
||||||
val accessors: Map<String, FunctionWithWrapper>,
|
val translationResult: AstGenerationResult
|
||||||
private val inverseNameBindings: Map<JsName, String>,
|
|
||||||
private val moduleMap: Map<JsName, JsImportedModule>,
|
|
||||||
private val trace: DiagnosticSink
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val functionContext = FunctionContext(functionReader, config, functions, accessors)
|
init {
|
||||||
|
// TODO Isn't there a better way to achieve this? Also there is a bug with private inline properties
|
||||||
private fun addInlinedModules(fragment: JsProgramFragment, inlinedModuleAliases: Set<JsName>) {
|
DummyAccessorInvocationTransformer().let {
|
||||||
val existingModules = fragment.importedModules.mapTo(IdentitySet()) { it.internalName }
|
for (fragment in translationResult.newFragments) {
|
||||||
inlinedModuleAliases.forEach {
|
it.accept<JsGlobalBlock>(fragment.declarationBlock)
|
||||||
if (it !in existingModules) {
|
it.accept<JsGlobalBlock>(fragment.initializerBlock)
|
||||||
fragment.importedModules.add(moduleMap[it]!!.let {
|
|
||||||
// Copy so that the Merger.kt doesn't operate on the same instance in different fragments.
|
|
||||||
JsImportedModule(it.externalName, it.internalName, it.plainReference)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun process(fragment: JsProgramFragment, existingImports: MutableMap<String, JsName>) {
|
val functionContext = FunctionContext(this)
|
||||||
val existingNameBindings = fragment.nameBindings.associateTo(IdentityHashMap()) { it.name to it.key }
|
|
||||||
|
|
||||||
val dfsController = InlineDfsController(trace, functions, accessors, functionContext)
|
val cycleReporter = InlinerCycleReporter(trace, functionContext)
|
||||||
|
|
||||||
val additionalDeclarations = mutableListOf<JsStatement>()
|
fun process() {
|
||||||
val inliner = InlinerImpl(
|
for (fragment in translationResult.newFragments) {
|
||||||
existingNameBindings,
|
functionContext.scopeForFragment(fragment).process()
|
||||||
existingImports,
|
|
||||||
0,
|
|
||||||
dfsController,
|
|
||||||
inverseNameBindings,
|
|
||||||
functionContext
|
|
||||||
) {
|
|
||||||
additionalDeclarations.add(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
val inlineSuspendFnSplitter = InlineSuspendFunctionSplitter(
|
|
||||||
existingNameBindings,
|
|
||||||
existingImports,
|
|
||||||
0,
|
|
||||||
inverseNameBindings,
|
|
||||||
inliner.replacementsInducedByWrappers
|
|
||||||
) {
|
|
||||||
additionalDeclarations.add(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
inliner.acceptStatement(fragment.declarationBlock)
|
|
||||||
|
|
||||||
inlineSuspendFnSplitter.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)
|
|
||||||
}
|
|
||||||
inliner.acceptStatement(fragment.initializerBlock)
|
|
||||||
|
|
||||||
// TODO fix the order
|
|
||||||
fragment.declarationBlock.statements.addAll(0, additionalDeclarations)
|
|
||||||
fragment.nameBindings.addAll(inliner.additionalNameBindings)
|
|
||||||
// TODO actually bogus
|
|
||||||
fragment.nameBindings.addAll(inlineSuspendFnSplitter.additionalNameBindings)
|
|
||||||
|
|
||||||
(inliner.additionalImports.asSequence() + inlineSuspendFnSplitter.additionalImports.asSequence()).forEach { (tag, e, _) ->
|
|
||||||
fragment.imports[tag] = e
|
|
||||||
}
|
|
||||||
|
|
||||||
addInlinedModules(fragment, inliner.inlinedModuleAliases)
|
|
||||||
addInlinedModules(fragment, inlineSuspendFnSplitter.inlinedModuleAliases)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
// TODO decrypt; Possibly error-prone due to late renaming
|
|
||||||
// TODO Possibly apply post-processing outside of FunctionReader?
|
|
||||||
// Since we compile each source file in its own context (and we may loose these context when performing incremental compilation)
|
|
||||||
// we don't use contexts to generate proper names for modules. Instead, we generate all necessary information during
|
|
||||||
// translation and rely on it here.
|
|
||||||
private fun buildModuleNameMap(fragments: List<JsProgramFragment>): Map<String, JsExpression> {
|
|
||||||
return fragments.flatMap { it.inlineModuleMap.entries }.associate { (k, v) -> k to v }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun process(
|
|
||||||
reporter: JsConfig.Reporter,
|
|
||||||
config: JsConfig,
|
|
||||||
trace: DiagnosticSink,
|
|
||||||
translationResult: AstGenerationResult
|
|
||||||
) {
|
|
||||||
val accessorInvocationTransformer = DummyAccessorInvocationTransformer()
|
|
||||||
for (fragment in translationResult.newFragments) {
|
|
||||||
accessorInvocationTransformer.accept<JsGlobalBlock>(fragment.declarationBlock)
|
|
||||||
accessorInvocationTransformer.accept<JsGlobalBlock>(fragment.initializerBlock)
|
|
||||||
}
|
|
||||||
|
|
||||||
val referencedInlineFunctionTags = collectInlineFunctionTags(config, translationResult.newFragments)
|
|
||||||
val fragments = referencedInlineFunctionTags.mapNotNull {
|
|
||||||
translationResult.inlineFunctionTagMap[it]?.let { unit ->
|
|
||||||
translationResult.translate(unit).fragment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val functions = collectNamedFunctionsAndWrappers(translationResult.newFragments)
|
|
||||||
|
|
||||||
val accessors = collectAccessors(fragments)
|
|
||||||
|
|
||||||
// TODO update on fragment loading
|
|
||||||
val inverseNameBindings = inverseNameBindings(*fragments.toTypedArray())
|
|
||||||
|
|
||||||
val functionReader = FunctionReader(reporter, config, translationResult.innerModuleName, buildModuleNameMap(fragments))
|
|
||||||
|
|
||||||
val moduleMap = fragments.asSequence().flatMap { it.importedModules.asSequence() }.associate { it.internalName to it }
|
|
||||||
|
|
||||||
val inliner = JsInliner(config, functionReader, functions, accessors, inverseNameBindings, moduleMap, trace)
|
|
||||||
for (fragment in translationResult.newFragments) {
|
|
||||||
inliner.process(fragment, inverseNameBindings(fragment).entries.associateTo(mutableMapOf()) { (name, tag) -> tag to name })
|
|
||||||
}
|
|
||||||
|
|
||||||
for (fragment in translationResult.newFragments) {
|
|
||||||
val block = JsBlock(fragment.declarationBlock, fragment.initializerBlock, fragment.exportBlock)
|
|
||||||
val usedImports = removeUnusedImports(block)
|
|
||||||
for ((key, name) in fragment.nameBindings) {
|
|
||||||
if (name !in usedImports && key in fragment.imports) {
|
|
||||||
fragment.imports.remove(key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
simplifyWrappedFunctions(block)
|
|
||||||
removeUnusedFunctionDefinitions(block, collectNamedFunctions(block))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun inverseNameBindings(vararg fragments: JsProgramFragment): MutableMap<JsName, String> {
|
|
||||||
val name2Tag = mutableMapOf<JsName, String>()
|
|
||||||
fragments.forEach {
|
|
||||||
it.nameBindings.forEach { (tag, name) ->
|
|
||||||
name2Tag[name] = tag
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return name2Tag
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -21,29 +21,39 @@ import org.jetbrains.kotlin.js.backend.ast.*
|
|||||||
import org.jetbrains.kotlin.js.backend.ast.metadata.isCallableReference
|
import org.jetbrains.kotlin.js.backend.ast.metadata.isCallableReference
|
||||||
import org.jetbrains.kotlin.js.backend.ast.metadata.descriptor
|
import org.jetbrains.kotlin.js.backend.ast.metadata.descriptor
|
||||||
import org.jetbrains.kotlin.js.config.JsConfig
|
import org.jetbrains.kotlin.js.config.JsConfig
|
||||||
import org.jetbrains.kotlin.js.inline.FunctionReader
|
import org.jetbrains.kotlin.js.inline.*
|
||||||
import org.jetbrains.kotlin.js.inline.util.*
|
import org.jetbrains.kotlin.js.inline.util.*
|
||||||
import org.jetbrains.kotlin.js.translate.context.Namer
|
import org.jetbrains.kotlin.js.translate.context.Namer
|
||||||
import org.jetbrains.kotlin.js.translate.utils.JsDescriptorUtils.getModuleName
|
import org.jetbrains.kotlin.js.translate.general.AstGenerationResult
|
||||||
|
import java.util.HashMap
|
||||||
|
|
||||||
class FunctionContext(
|
class FunctionContext(
|
||||||
private val functionReader: FunctionReader,
|
val inliner: JsInliner
|
||||||
private val config: JsConfig,
|
|
||||||
private val functions: Map<JsName, FunctionWithWrapper>,
|
|
||||||
private val accessors: Map<String, FunctionWithWrapper>
|
|
||||||
) {
|
) {
|
||||||
fun lookUpStaticFunction(functionName: JsName?): FunctionWithWrapper? = functions[functionName]
|
private val functionReader = FunctionReader(inliner.reporter, inliner.config, inliner.translationResult.innerModuleName)
|
||||||
|
|
||||||
fun lookUpStaticFunctionByTag(functionTag: String): FunctionWithWrapper? = accessors[functionTag]
|
private data class FunctionsAndAccessors(val functions: Map<JsName, FunctionWithWrapper>, val accessors: Map<String, FunctionWithWrapper>)
|
||||||
|
|
||||||
fun getFunctionDefinition(call: JsInvocation): FunctionWithWrapper {
|
private val fragmentInfo = mutableMapOf<JsProgramFragment, FunctionsAndAccessors>()
|
||||||
return getFunctionDefinitionImpl(call)!!
|
|
||||||
|
private fun lookUpStaticFunction(functionName: JsName?, fragment: JsProgramFragment): FunctionWithWrapper? =
|
||||||
|
fragmentInfo[fragment]?.run { functions[functionName] }
|
||||||
|
|
||||||
|
private fun lookUpStaticFunctionByTag(functionTag: String, fragment: JsProgramFragment): FunctionWithWrapper? =
|
||||||
|
fragmentInfo[fragment]?.run { accessors[functionTag] }
|
||||||
|
|
||||||
|
fun getFunctionDefinition(call: JsInvocation, scope: InliningScope): InlineFunctionDefinition {
|
||||||
|
return getFunctionDefinitionImpl(call, scope)!!
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hasFunctionDefinition(call: JsInvocation): Boolean {
|
fun hasFunctionDefinition(call: JsInvocation, scope: InliningScope): Boolean {
|
||||||
return getFunctionDefinitionImpl(call) != null
|
return getFunctionDefinitionImpl(call, scope) != null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val functionsByWrapperNodes = HashMap<JsBlock, FunctionWithWrapper>()
|
||||||
|
|
||||||
|
val functionsByFunctionNodes = HashMap<JsFunction, FunctionWithWrapper>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets function definition by invocation.
|
* Gets function definition by invocation.
|
||||||
*
|
*
|
||||||
@@ -74,17 +84,19 @@ class FunctionContext(
|
|||||||
* 5. Qualifier can be JsNameRef with ref to case [3]
|
* 5. Qualifier can be JsNameRef with ref to case [3]
|
||||||
* in case of local function with closure.
|
* in case of local function with closure.
|
||||||
*/
|
*/
|
||||||
private fun getFunctionDefinitionImpl(call: JsInvocation): FunctionWithWrapper? {
|
private fun getFunctionDefinitionImpl(call: JsInvocation, scope: InliningScope): InlineFunctionDefinition? {
|
||||||
|
// Ensure we have the local function information
|
||||||
|
loadFragment(scope.fragment)
|
||||||
|
|
||||||
val descriptor = call.descriptor
|
val descriptor = call.descriptor
|
||||||
if (descriptor != null) {
|
if (descriptor != null) {
|
||||||
return lookUpFunctionDirect(descriptor) ?: lookUpFunctionIndirect(call) ?: lookUpFunctionExternal(descriptor)
|
return lookUpFunctionDirect(descriptor) ?: lookUpFunctionIndirect(call, scope) ?: lookUpFunctionExternal(descriptor)
|
||||||
}
|
}
|
||||||
|
|
||||||
return lookUpFunctionIndirect(call)
|
return lookUpFunctionIndirect(call, scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun lookUpFunctionIndirect(call: JsInvocation): FunctionWithWrapper? {
|
private fun lookUpFunctionIndirect(call: JsInvocation, scope: InliningScope): LocalInlineFunctionDefinition? {
|
||||||
/** remove ending `()` */
|
/** remove ending `()` */
|
||||||
val callQualifier: JsExpression = if (isCallInvocation(call)) {
|
val callQualifier: JsExpression = if (isCallInvocation(call)) {
|
||||||
(call.qualifier as JsNameRef).qualifier!!
|
(call.qualifier as JsNameRef).qualifier!!
|
||||||
@@ -97,19 +109,70 @@ class FunctionContext(
|
|||||||
return when (qualifier) {
|
return when (qualifier) {
|
||||||
is JsInvocation -> {
|
is JsInvocation -> {
|
||||||
tryExtractCallableReference(qualifier) ?: getSimpleName(qualifier)?.let { simpleName ->
|
tryExtractCallableReference(qualifier) ?: getSimpleName(qualifier)?.let { simpleName ->
|
||||||
lookUpStaticFunction(simpleName)?.let { if (isFunctionCreator(it.function)) it else null }
|
lookUpStaticFunction(simpleName, scope.fragment)?.let { if (isFunctionCreator(it.function)) it else null }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is JsNameRef -> lookUpStaticFunction(qualifier.name)
|
is JsNameRef -> lookUpStaticFunction(qualifier.name, scope.fragment)
|
||||||
is JsFunction -> FunctionWithWrapper(qualifier, null)
|
|
||||||
|
// 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
|
||||||
|
is JsFunction -> functionsByFunctionNodes[qualifier] ?: FunctionWithWrapper(qualifier, null)
|
||||||
else -> null
|
else -> null
|
||||||
|
}?.let {
|
||||||
|
LocalInlineFunctionDefinition(it, scope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun lookUpFunctionDirect(descriptor: CallableDescriptor): FunctionWithWrapper? =
|
fun functionTag(call: JsInvocation): String? {
|
||||||
lookUpStaticFunctionByTag(Namer.getFunctionTag(descriptor, config))
|
return call.descriptor?.let { Namer.getFunctionTag(it, inliner.config) }
|
||||||
|
}
|
||||||
|
|
||||||
private fun lookUpFunctionExternal(descriptor: CallableDescriptor): FunctionWithWrapper? = functionReader[descriptor]
|
private val newFragmentSet = inliner.translationResult.newFragments.toIdentitySet()
|
||||||
|
|
||||||
|
private val inliningScopeCache = mutableMapOf<JsProgramFragment, ProgramFragmentInliningScope>()
|
||||||
|
|
||||||
|
fun scopeForFragment(fragment: JsProgramFragment) = inliningScopeCache.computeIfAbsent(fragment) {
|
||||||
|
ProgramFragmentInliningScope(fragment, this, inliner)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadFragment(fragment: JsProgramFragment) {
|
||||||
|
fragmentInfo.computeIfAbsent(fragment) {
|
||||||
|
FunctionsAndAccessors(
|
||||||
|
collectNamedFunctionsAndWrappers(listOf(fragment)),
|
||||||
|
collectAccessors(listOf(fragment))
|
||||||
|
).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? {
|
||||||
|
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 lookUpFunctionExternal(descriptor: CallableDescriptor): LibraryInlineFunctionDefinition? = functionReader[descriptor]
|
||||||
|
|
||||||
private fun tryExtractCallableReference(invocation: JsInvocation): FunctionWithWrapper? {
|
private fun tryExtractCallableReference(invocation: JsInvocation): FunctionWithWrapper? {
|
||||||
if (invocation.isCallableReference) {
|
if (invocation.isCallableReference) {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import org.jetbrains.kotlin.js.inline.util.replaceNames
|
|||||||
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
|
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
|
||||||
|
|
||||||
class NamingContext(private val statementContext: JsContext<JsStatement>) {
|
class NamingContext(private val statementContext: JsContext<JsStatement>) {
|
||||||
private val renamings = mutableMapOf<JsName, JsExpression>()
|
private val renamings = mutableMapOf<JsName, JsNameRef>()
|
||||||
private val declarations = mutableListOf<JsVars>()
|
private val declarations = mutableListOf<JsVars>()
|
||||||
private var addedDeclarations = false
|
private var addedDeclarations = false
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ class NamingContext(private val statementContext: JsContext<JsStatement>) {
|
|||||||
return replaceNames(target, renamings)
|
return replaceNames(target, renamings)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun replaceName(name: JsName, replacement: JsExpression) {
|
fun replaceName(name: JsName, replacement: JsNameRef) {
|
||||||
assert(!renamings.containsKey(name)) { "$name has been renamed already" }
|
assert(!renamings.containsKey(name)) { "$name has been renamed already" }
|
||||||
|
|
||||||
renamings.put(name, replacement)
|
renamings.put(name, replacement)
|
||||||
|
|||||||
@@ -225,7 +225,7 @@ fun collectAccessors(scope: JsNode): Map<String, FunctionWithWrapper> {
|
|||||||
return accessors
|
return accessors
|
||||||
}
|
}
|
||||||
|
|
||||||
fun collectAccessors(fragments: List<JsProgramFragment>): Map<String, FunctionWithWrapper> {
|
fun collectAccessors(fragments: Iterable<JsProgramFragment>): Map<String, FunctionWithWrapper> {
|
||||||
val result = mutableMapOf<String, FunctionWithWrapper>()
|
val result = mutableMapOf<String, FunctionWithWrapper>()
|
||||||
for (fragment in fragments) {
|
for (fragment in fragments) {
|
||||||
result += collectAccessors(fragment.declarationBlock)
|
result += collectAccessors(fragment.declarationBlock)
|
||||||
|
|||||||
@@ -113,12 +113,12 @@ class K2JSTranslator(private val config: JsConfig) {
|
|||||||
if (hasError(diagnostics)) return TranslationResult.Fail(diagnostics)
|
if (hasError(diagnostics)) return TranslationResult.Fail(diagnostics)
|
||||||
checkCanceled()
|
checkCanceled()
|
||||||
|
|
||||||
JsInliner.process(
|
JsInliner(
|
||||||
reporter,
|
reporter,
|
||||||
config,
|
config,
|
||||||
analysisResult.bindingTrace,
|
analysisResult.bindingTrace,
|
||||||
translationResult
|
translationResult
|
||||||
)
|
).process()
|
||||||
if (hasError(diagnostics)) return TranslationResult.Fail(diagnostics)
|
if (hasError(diagnostics)) return TranslationResult.Fail(diagnostics)
|
||||||
checkCanceled()
|
checkCanceled()
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -53,7 +53,7 @@ class AstGenerationResult(
|
|||||||
private val sourceRoots = config.sourceMapRoots.map { File(it) }
|
private val sourceRoots = config.sourceMapRoots.map { File(it) }
|
||||||
private val deserializer = JsAstDeserializer(merger.program, sourceRoots)
|
private val deserializer = JsAstDeserializer(merger.program, sourceRoots)
|
||||||
|
|
||||||
fun translate(unit: TranslationUnit) =
|
fun translate(unit: TranslationUnit): FileTranslationResult =
|
||||||
when (unit) {
|
when (unit) {
|
||||||
is TranslationUnit.SourceFile -> translatedSourceFiles[unit]!!
|
is TranslationUnit.SourceFile -> translatedSourceFiles[unit]!!
|
||||||
is TranslationUnit.BinaryAst -> cache.getOrPut(unit) {
|
is TranslationUnit.BinaryAst -> cache.getOrPut(unit) {
|
||||||
|
|||||||
@@ -324,7 +324,7 @@ public final class Translation {
|
|||||||
|
|
||||||
Map<String, TranslationUnit> inlineFunctionTagMap = new HashMap<>();
|
Map<String, TranslationUnit> inlineFunctionTagMap = new HashMap<>();
|
||||||
|
|
||||||
Map<TranslationUnit.SourceFile, SourceFileTranslationResult> translatedSourceFiles = new HashMap<>();
|
Map<TranslationUnit.SourceFile, SourceFileTranslationResult> translatedSourceFiles = new LinkedHashMap<>();
|
||||||
|
|
||||||
for (TranslationUnit unit : units) {
|
for (TranslationUnit unit : units) {
|
||||||
if (unit instanceof TranslationUnit.SourceFile) {
|
if (unit instanceof TranslationUnit.SourceFile) {
|
||||||
|
|||||||
Reference in New Issue
Block a user