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