JS: convert JsInliner to Kotlin
This commit is contained in:
@@ -1,757 +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 com.intellij.psi.PsiElement;
|
|
||||||
import kotlin.jvm.functions.Function1;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import org.jetbrains.kotlin.backend.common.CommonCoroutineCodegenUtilKt;
|
|
||||||
import org.jetbrains.kotlin.config.CommonConfigurationKeysKt;
|
|
||||||
import org.jetbrains.kotlin.config.LanguageVersionSettings;
|
|
||||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor;
|
|
||||||
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.ast.*;
|
|
||||||
import org.jetbrains.kotlin.js.backend.ast.metadata.MetadataProperties;
|
|
||||||
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.InliningContext;
|
|
||||||
import org.jetbrains.kotlin.js.inline.context.NamingContext;
|
|
||||||
import org.jetbrains.kotlin.js.inline.util.*;
|
|
||||||
import org.jetbrains.kotlin.js.translate.expression.InlineMetadata;
|
|
||||||
import org.jetbrains.kotlin.resolve.inline.InlineStrategy;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.function.Function;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
import static org.jetbrains.kotlin.js.inline.util.CollectUtilsKt.getImportTag;
|
|
||||||
import static org.jetbrains.kotlin.js.inline.util.CollectionUtilsKt.IdentitySet;
|
|
||||||
import static org.jetbrains.kotlin.js.translate.declaration.InlineCoroutineUtilKt.transformSpecialFunctionsToCoroutineMetadata;
|
|
||||||
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, FunctionWithWrapper> functions;
|
|
||||||
private final Set<JsFunction> namedFunctionsSet;
|
|
||||||
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<>();
|
|
||||||
private final LinkedList<JsCallInfo> inlineCallInfos = new LinkedList<>();
|
|
||||||
private final Function1<JsNode, Boolean> canBeExtractedByInliner =
|
|
||||||
node -> node instanceof JsInvocation && hasToBeInlined((JsInvocation) node);
|
|
||||||
private int inlineFunctionDepth;
|
|
||||||
|
|
||||||
private final Map<JsWrapperKey, Map<JsName, JsNameRef>> replacementsInducedByWrappers = new HashMap<>();
|
|
||||||
|
|
||||||
private final Map<JsName, String> inverseNameBindings;
|
|
||||||
|
|
||||||
private Map<JsName, String> existingNameBindings = new HashMap<>();
|
|
||||||
|
|
||||||
private final List<JsNameBinding> additionalNameBindings = new ArrayList<>();
|
|
||||||
|
|
||||||
private final Set<JsName> inlinedModuleAliases = new HashSet<>();
|
|
||||||
|
|
||||||
public static void process(
|
|
||||||
@NotNull JsConfig.Reporter reporter,
|
|
||||||
@NotNull JsConfig config,
|
|
||||||
@NotNull DiagnosticSink trace,
|
|
||||||
@NotNull JsName currentModuleName,
|
|
||||||
@NotNull List<JsProgramFragment> fragments,
|
|
||||||
@NotNull List<JsProgramFragment> fragmentsToProcess,
|
|
||||||
@NotNull List<JsStatement> importStatements
|
|
||||||
) {
|
|
||||||
Map<JsName, FunctionWithWrapper> functions = CollectUtilsKt.collectNamedFunctionsAndWrappers(fragments);
|
|
||||||
Map<String, FunctionWithWrapper> accessors = CollectUtilsKt.collectAccessors(fragments);
|
|
||||||
Map<JsName, String> inverseNameBindings = CollectUtilsKt.collectNameBindings(fragments);
|
|
||||||
|
|
||||||
DummyAccessorInvocationTransformer accessorInvocationTransformer = new DummyAccessorInvocationTransformer();
|
|
||||||
for (JsProgramFragment fragment : fragmentsToProcess) {
|
|
||||||
accessorInvocationTransformer.accept(fragment.getDeclarationBlock());
|
|
||||||
accessorInvocationTransformer.accept(fragment.getInitializerBlock());
|
|
||||||
}
|
|
||||||
FunctionReader functionReader = new FunctionReader(reporter, config, currentModuleName, fragments);
|
|
||||||
JsInliner inliner = new JsInliner(config, functions, accessors, inverseNameBindings, functionReader, trace);
|
|
||||||
|
|
||||||
for (JsStatement statement : importStatements) {
|
|
||||||
inliner.processImportStatement(statement);
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<JsName, JsImportedModule> moduleMap = fillModuleMap(buildModuleMap(fragments), fragmentsToProcess);
|
|
||||||
|
|
||||||
for (JsProgramFragment fragment : fragmentsToProcess) {
|
|
||||||
inliner.existingImports.clear();
|
|
||||||
inliner.additionalNameBindings.clear();
|
|
||||||
inliner.inlinedModuleAliases.clear();
|
|
||||||
inliner.existingNameBindings = CollectUtilsKt.collectNameBindings(Collections.singletonList(fragment));
|
|
||||||
|
|
||||||
inliner.acceptStatement(fragment.getDeclarationBlock());
|
|
||||||
// Mostly for the sake of post-processor
|
|
||||||
// TODO are inline function marked with @Test possible?
|
|
||||||
if (fragment.getTests() != null) {
|
|
||||||
inliner.acceptStatement(fragment.getTests());
|
|
||||||
}
|
|
||||||
|
|
||||||
// There can be inlined function in top-level initializers, we need to optimize them as well
|
|
||||||
JsFunction fakeInitFunction = new JsFunction(JsDynamicScope.INSTANCE, fragment.getInitializerBlock(), "");
|
|
||||||
JsGlobalBlock initWrapper = new JsGlobalBlock();
|
|
||||||
initWrapper.getStatements().add(new JsExpressionStatement(fakeInitFunction));
|
|
||||||
inliner.accept(initWrapper);
|
|
||||||
initWrapper.getStatements().remove(initWrapper.getStatements().size() - 1);
|
|
||||||
|
|
||||||
fragment.getInitializerBlock().getStatements().addAll(0, initWrapper.getStatements());
|
|
||||||
fragment.getNameBindings().addAll(inliner.additionalNameBindings);
|
|
||||||
inliner.addInlinedModules(fragment, moduleMap);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (JsProgramFragment fragment : fragmentsToProcess) {
|
|
||||||
JsBlock block = new JsBlock(fragment.getDeclarationBlock(), fragment.getInitializerBlock(), fragment.getExportBlock());
|
|
||||||
RemoveUnusedImportsKt.removeUnusedImports(block);
|
|
||||||
SimplifyWrappedFunctionsKt.simplifyWrappedFunctions(block);
|
|
||||||
RemoveUnusedFunctionDefinitionsKt.removeUnusedFunctionDefinitions(block, CollectUtilsKt.collectNamedFunctions(block));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addInlinedModules(JsProgramFragment fragment, Map<JsName, JsImportedModule> moduleMap) {
|
|
||||||
Set<JsName> localMap = buildModuleMap(Collections.singletonList(fragment)).keySet();
|
|
||||||
for (JsName inlinedModuleName : inlinedModuleAliases) {
|
|
||||||
if (!localMap.contains(inlinedModuleName)) {
|
|
||||||
fragment.getImportedModules().add(moduleMap.get(inlinedModuleName));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Map<JsName, JsImportedModule> buildModuleMap(List<JsProgramFragment> fragments) {
|
|
||||||
return fillModuleMap(new HashMap<>(), fragments);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Map<JsName, JsImportedModule> fillModuleMap(Map<JsName, JsImportedModule> map, List<JsProgramFragment> fragments) {
|
|
||||||
for (JsProgramFragment fragment : fragments) {
|
|
||||||
for (JsImportedModule module : fragment.getImportedModules()) {
|
|
||||||
map.put(module.getInternalName(), module);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private JsInliner(
|
|
||||||
@NotNull JsConfig config,
|
|
||||||
@NotNull Map<JsName, FunctionWithWrapper> functions,
|
|
||||||
@NotNull Map<String, FunctionWithWrapper> accessors,
|
|
||||||
@NotNull Map<JsName, String> inverseNameBindings,
|
|
||||||
@NotNull FunctionReader functionReader,
|
|
||||||
@NotNull DiagnosticSink trace
|
|
||||||
) {
|
|
||||||
this.config = config;
|
|
||||||
this.functions = functions;
|
|
||||||
this.namedFunctionsSet = IdentitySet();
|
|
||||||
for (FunctionWithWrapper functionWithWrapper : functions.values()) {
|
|
||||||
namedFunctionsSet.add(functionWithWrapper.getFunction());
|
|
||||||
}
|
|
||||||
this.accessors = accessors;
|
|
||||||
this.inverseNameBindings = inverseNameBindings;
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean visit(@NotNull JsFunction function, @NotNull JsContext context) {
|
|
||||||
FunctionWithWrapper functionWithWrapper = functionsByFunctionNodes.get(function);
|
|
||||||
if (functionWithWrapper != null) {
|
|
||||||
visit(functionWithWrapper);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (statementContextForInline == null) {
|
|
||||||
statementContextForInline = getLastStatementLevelContext();
|
|
||||||
startFunction(function);
|
|
||||||
boolean result = super.visit(function, context);
|
|
||||||
statementContextForInline = null;
|
|
||||||
return result;
|
|
||||||
} else {
|
|
||||||
startFunction(function);
|
|
||||||
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) {
|
|
||||||
inliningContexts.push(new JsInliningContext(statementContextForInline));
|
|
||||||
|
|
||||||
assert !inProcessFunctions.contains(function): "Inliner has revisited function";
|
|
||||||
inProcessFunctions.add(function);
|
|
||||||
|
|
||||||
if (namedFunctionsSet.contains(function)) {
|
|
||||||
namedFunctionsStack.push(function);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void endFunction(@NotNull JsFunction function) {
|
|
||||||
NamingUtilsKt.refreshLabelNames(function.getBody(), function.getScope());
|
|
||||||
|
|
||||||
RemoveUnusedLocalFunctionDeclarationsKt.removeUnusedLocalFunctionDeclarations(function);
|
|
||||||
processedFunctions.add(function);
|
|
||||||
|
|
||||||
new FunctionPostProcessor(function).apply();
|
|
||||||
|
|
||||||
assert inProcessFunctions.contains(function);
|
|
||||||
inProcessFunctions.remove(function);
|
|
||||||
|
|
||||||
inliningContexts.pop();
|
|
||||||
|
|
||||||
if (!namedFunctionsStack.empty() && namedFunctionsStack.peek() == function) {
|
|
||||||
namedFunctionsStack.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@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;
|
|
||||||
int oldInlineFunctionDepth = inlineFunctionDepth;
|
|
||||||
|
|
||||||
ListContext<JsStatement> innerContext = new ListContext<>();
|
|
||||||
|
|
||||||
JsBlock wrapperBody = functionWithWrapper.getWrapperBody();
|
|
||||||
List<JsStatement> statements = null;
|
|
||||||
if (wrapperBody != null) {
|
|
||||||
existingImports = new HashMap<>();
|
|
||||||
statementContexts.push(innerContext);
|
|
||||||
statementContextForInline = innerContext;
|
|
||||||
inlineFunctionDepth++;
|
|
||||||
|
|
||||||
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 {
|
|
||||||
if (statementContextForInline == null) 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;
|
|
||||||
inlineFunctionDepth = oldInlineFunctionDepth;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean visit(@NotNull JsInvocation call, @NotNull JsContext context) {
|
|
||||||
if (!hasToBeInlined(call)) return true;
|
|
||||||
|
|
||||||
JsFunction containingFunction = getCurrentNamedFunction();
|
|
||||||
|
|
||||||
if (containingFunction != null) {
|
|
||||||
inlineCallInfos.add(new JsCallInfo(call, containingFunction));
|
|
||||||
}
|
|
||||||
|
|
||||||
FunctionWithWrapper definition = getFunctionContext().getFunctionDefinition(call);
|
|
||||||
|
|
||||||
if (inProcessFunctions.contains(definition.getFunction())) {
|
|
||||||
reportInlineCycle(call, definition.getFunction());
|
|
||||||
}
|
|
||||||
else if (!processedFunctions.contains(definition.getFunction())) {
|
|
||||||
for (int i = 0; i < call.getArguments().size(); ++i) {
|
|
||||||
JsExpression argument = call.getArguments().get(i);
|
|
||||||
call.getArguments().set(i, accept(argument));
|
|
||||||
}
|
|
||||||
visit(definition);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void endVisit(@NotNull JsInvocation x, @NotNull JsContext ctx) {
|
|
||||||
if (hasToBeInlined(x)) {
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
JsContext<JsNode> context = (JsContext) ctx;
|
|
||||||
inline(x, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
JsCallInfo lastCallInfo = null;
|
|
||||||
|
|
||||||
if (!inlineCallInfos.isEmpty()) {
|
|
||||||
lastCallInfo = inlineCallInfos.getLast();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastCallInfo != null && lastCallInfo.call == x) {
|
|
||||||
inlineCallInfos.removeLast();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void endVisit(@NotNull JsExpressionStatement x, @NotNull JsContext ctx) {
|
|
||||||
JsExpression e = x.getExpression();
|
|
||||||
if (e instanceof JsBinaryOperation) {
|
|
||||||
JsBinaryOperation binOp = (JsBinaryOperation) e;
|
|
||||||
if (binOp.getOperator() == JsBinaryOperator.ASG) {
|
|
||||||
JsExpression argument2 = binOp.getArg2();
|
|
||||||
if (argument2 != null) {
|
|
||||||
JsFunction splitSuspendInlineFunction = splitExportedSuspendInlineFunctionDeclarations(argument2);
|
|
||||||
if (splitSuspendInlineFunction != null) {
|
|
||||||
binOp.setArg2(splitSuspendInlineFunction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
super.endVisit(x, ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void endVisit(@NotNull JsVars.JsVar x, @NotNull JsContext ctx) {
|
|
||||||
JsExpression initExpression = x.getInitExpression();
|
|
||||||
if (initExpression != null) {
|
|
||||||
JsFunction splitSuspendInlineFunction = splitExportedSuspendInlineFunctionDeclarations(initExpression);
|
|
||||||
if (splitSuspendInlineFunction != null) {
|
|
||||||
x.setInitExpression(splitSuspendInlineFunction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private JsFunction splitExportedSuspendInlineFunctionDeclarations(@NotNull JsExpression expression) {
|
|
||||||
InlineMetadata inlineMetadata = InlineMetadata.decompose(expression);
|
|
||||||
if (inlineMetadata != null) {
|
|
||||||
FunctionWithWrapper functionWithWrapper = inlineMetadata.getFunction();
|
|
||||||
JsFunction originalFunction = functionWithWrapper.getFunction();
|
|
||||||
if (MetadataProperties.getCoroutineMetadata(originalFunction) != null) {
|
|
||||||
JsContext<JsStatement> statementContext = getLastStatementLevelContext();
|
|
||||||
|
|
||||||
// This function will be exported to JS
|
|
||||||
JsFunction function = originalFunction.deepCopy();
|
|
||||||
|
|
||||||
// Original function should be not be transformed into a state machine
|
|
||||||
originalFunction.setName(null);
|
|
||||||
MetadataProperties.setCoroutineMetadata(originalFunction, null);
|
|
||||||
MetadataProperties.setInlineableCoroutineBody(originalFunction, true);
|
|
||||||
if (functionWithWrapper.getWrapperBody() != null) {
|
|
||||||
// Extract local declarations
|
|
||||||
applyWrapper(functionWithWrapper.getWrapperBody(), function, originalFunction, new JsInliningContext(statementContext));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doAcceptStatementList(List<JsStatement> statements) {
|
|
||||||
// at top level of js ast, contexts stack can be empty,
|
|
||||||
// but there is no inline calls anyway
|
|
||||||
if(!inliningContexts.isEmpty()) {
|
|
||||||
int i = 0;
|
|
||||||
|
|
||||||
while (i < statements.size()) {
|
|
||||||
List<JsStatement> additionalStatements =
|
|
||||||
ExpressionDecomposer.preserveEvaluationOrder(statements.get(i), canBeExtractedByInliner);
|
|
||||||
statements.addAll(i, additionalStatements);
|
|
||||||
i += additionalStatements.size() + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
super.doAcceptStatementList(statements);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void inline(@NotNull JsInvocation call, @NotNull JsContext<JsNode> context) {
|
|
||||||
DeclarationDescriptor callDescriptor = MetadataProperties.getDescriptor(call);
|
|
||||||
if (isSuspendWithCurrentContinuation(callDescriptor,
|
|
||||||
CommonConfigurationKeysKt.getLanguageVersionSettings(config.getConfiguration()))) {
|
|
||||||
inlineSuspendWithCurrentContinuation(call, context);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
JsInliningContext inliningContext = getInliningContext();
|
|
||||||
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();
|
|
||||||
function.setBody(transformSpecialFunctionsToCoroutineMetadata(function.getBody()));
|
|
||||||
if (functionWithWrapper.getWrapperBody() != null) {
|
|
||||||
applyWrapper(functionWithWrapper.getWrapperBody(), function, functionWithWrapper.getFunction(), inliningContext);
|
|
||||||
}
|
|
||||||
InlineableResult inlineableResult = FunctionInlineMutator.getInlineableCallReplacement(call, function, inliningContext);
|
|
||||||
|
|
||||||
JsStatement inlineableBody = inlineableResult.getInlineableBody();
|
|
||||||
JsExpression resultExpression = inlineableResult.getResultExpression();
|
|
||||||
JsContext<JsStatement> statementContext = inliningContext.getStatementContext();
|
|
||||||
// body of inline function can contain call to lambdas that need to be inlined
|
|
||||||
JsStatement inlineableBodyWithLambdasInlined = accept(inlineableBody);
|
|
||||||
assert inlineableBody == inlineableBodyWithLambdasInlined;
|
|
||||||
|
|
||||||
// Support non-local return from secondary constructor
|
|
||||||
// Returns from secondary constructors should return `$this` object.
|
|
||||||
JsFunction currentFunction = getCurrentNamedFunction();
|
|
||||||
if (currentFunction != null) {
|
|
||||||
JsName returnVariable = MetadataProperties.getForcedReturnVariable(currentFunction);
|
|
||||||
if (returnVariable != null) {
|
|
||||||
inlineableBody.accept(new RecursiveJsVisitor() {
|
|
||||||
@Override
|
|
||||||
public void visitReturn(@NotNull JsReturn x) {
|
|
||||||
x.setExpression(returnVariable.makeRef());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
statementContext.addPrevious(flattenStatement(inlineableBody));
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Assumes, that resultExpression == null, when result is not needed.
|
|
||||||
* @see FunctionInlineMutator.isResultNeeded()
|
|
||||||
*/
|
|
||||||
if (resultExpression == null) {
|
|
||||||
statementContext.removeMe();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
resultExpression = accept(resultExpression);
|
|
||||||
MetadataProperties.setSynthetic(resultExpression, true);
|
|
||||||
context.replaceMe(resultExpression);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void applyWrapper(
|
|
||||||
@NotNull JsBlock wrapper, @NotNull JsFunction function, @NotNull JsFunction originalFunction,
|
|
||||||
@NotNull InliningContext inliningContext
|
|
||||||
) {
|
|
||||||
// Apparently we should avoid this trick when we implement fair support for crossinline
|
|
||||||
Function<JsWrapperKey, Map<JsName, JsNameRef>> replacementGen = k -> {
|
|
||||||
JsContext<JsStatement> ctx = k.context;
|
|
||||||
|
|
||||||
Map<JsName, JsNameRef> newReplacements = new HashMap<>();
|
|
||||||
|
|
||||||
List<JsStatement> copiedStatements = new ArrayList<>();
|
|
||||||
for (JsStatement statement : wrapper.getStatements()) {
|
|
||||||
if (statement instanceof JsReturn) continue;
|
|
||||||
|
|
||||||
statement = statement.deepCopy();
|
|
||||||
if (inlineFunctionDepth == 0) {
|
|
||||||
replaceExpressionsWithLocalAliases(statement);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (statement instanceof JsVars) {
|
|
||||||
JsVars jsVars = (JsVars) statement;
|
|
||||||
String tag = getImportTag(jsVars);
|
|
||||||
if (tag != null) {
|
|
||||||
JsName name = jsVars.getVars().get(0).getName();
|
|
||||||
JsName existingName = inlineFunctionDepth == 0 ? MetadataProperties.getLocalAlias(name) : null;
|
|
||||||
if (existingName == null) {
|
|
||||||
existingName = existingImports.computeIfAbsent(tag, t -> {
|
|
||||||
copiedStatements.add(jsVars);
|
|
||||||
JsName alias = JsScope.declareTemporaryName(name.getIdent());
|
|
||||||
alias.copyMetadataFrom(name);
|
|
||||||
newReplacements.put(name, pureFqn(alias, null));
|
|
||||||
return alias;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name != existingName) {
|
|
||||||
JsNameRef replacement = pureFqn(existingName, null);
|
|
||||||
newReplacements.put(name, replacement);
|
|
||||||
}
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
copiedStatements.add(statement);
|
|
||||||
}
|
|
||||||
|
|
||||||
Set<JsName> definedNames = copiedStatements.stream()
|
|
||||||
.flatMap(node -> CollectUtilsKt.collectDefinedNamesInAllScopes(node).stream())
|
|
||||||
.filter(name -> !newReplacements.containsKey(name))
|
|
||||||
.collect(Collectors.toSet());
|
|
||||||
for (JsName name : definedNames) {
|
|
||||||
JsName alias = JsScope.declareTemporaryName(name.getIdent());
|
|
||||||
alias.copyMetadataFrom(name);
|
|
||||||
JsNameRef replacement = pureFqn(alias, null);
|
|
||||||
newReplacements.put(name, replacement);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (JsStatement statement : copiedStatements) {
|
|
||||||
statement = RewriteUtilsKt.replaceNames(statement, newReplacements);
|
|
||||||
ctx.addPrevious(accept(statement));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Map.Entry<JsName, JsFunction> entry : CollectUtilsKt.collectNamedFunctions(new JsBlock(copiedStatements)).entrySet()) {
|
|
||||||
if (MetadataProperties.getStaticRef(entry.getKey()) instanceof JsFunction) {
|
|
||||||
MetadataProperties.setStaticRef(entry.getKey(), entry.getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return newReplacements;
|
|
||||||
};
|
|
||||||
|
|
||||||
JsWrapperKey key = new JsWrapperKey(inliningContext.getStatementContextBeforeCurrentFunction(), originalFunction);
|
|
||||||
Map<JsName, JsNameRef> replacements = replacementsInducedByWrappers.computeIfAbsent(key, replacementGen);
|
|
||||||
|
|
||||||
RewriteUtilsKt.replaceNames(function, replacements);
|
|
||||||
|
|
||||||
// Copy nameBinding's for inlined localAlias'es
|
|
||||||
for (JsNameRef nameRef : replacements.values()) {
|
|
||||||
JsName name = nameRef.getName();
|
|
||||||
if (name != null && !existingNameBindings.containsKey(name)) {
|
|
||||||
String tag = inverseNameBindings.get(name);
|
|
||||||
if (tag != null) {
|
|
||||||
existingNameBindings.put(name, tag);
|
|
||||||
additionalNameBindings.add(new JsNameBinding(tag, name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void replaceExpressionsWithLocalAliases(@NotNull JsStatement statement) {
|
|
||||||
new JsVisitorWithContextImpl() {
|
|
||||||
@Override
|
|
||||||
public void endVisit(@NotNull JsNameRef x, @NotNull JsContext ctx) {
|
|
||||||
replaceIfNecessary(x, ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void endVisit(@NotNull JsArrayAccess x, @NotNull JsContext ctx) {
|
|
||||||
replaceIfNecessary(x, ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void replaceIfNecessary(@NotNull JsExpression expression, @NotNull JsContext ctx) {
|
|
||||||
JsName alias = MetadataProperties.getLocalAlias(expression);
|
|
||||||
if (alias != null) {
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
JsContext<JsNode> context = (JsContext) ctx;
|
|
||||||
context.replaceMe(alias.makeRef());
|
|
||||||
inlinedModuleAliases.add(alias);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}.accept(statement);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isSuspendWithCurrentContinuation(
|
|
||||||
@Nullable DeclarationDescriptor descriptor,
|
|
||||||
@NotNull LanguageVersionSettings languageVersionSettings
|
|
||||||
) {
|
|
||||||
if (!(descriptor instanceof FunctionDescriptor)) return false;
|
|
||||||
return CommonCoroutineCodegenUtilKt.isBuiltInSuspendCoroutineUninterceptedOrReturn(
|
|
||||||
(FunctionDescriptor) descriptor.getOriginal(), languageVersionSettings
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void inlineSuspendWithCurrentContinuation(@NotNull JsInvocation call, @NotNull JsContext<JsNode> context) {
|
|
||||||
JsExpression lambda = call.getArguments().get(0);
|
|
||||||
JsExpression continuationArg = call.getArguments().get(call.getArguments().size() - 1);
|
|
||||||
|
|
||||||
JsInvocation invocation = new JsInvocation(lambda, continuationArg);
|
|
||||||
MetadataProperties.setSuspend(invocation, true);
|
|
||||||
context.replaceMe(accept(invocation));
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private JsInliningContext getInliningContext() {
|
|
||||||
return inliningContexts.peek();
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private FunctionContext getFunctionContext() {
|
|
||||||
return getInliningContext().getFunctionContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private JsFunction getCurrentNamedFunction() {
|
|
||||||
if (namedFunctionsStack.empty()) return null;
|
|
||||||
return namedFunctionsStack.peek();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void reportInlineCycle(@NotNull JsInvocation call, @NotNull JsFunction calledFunction) {
|
|
||||||
MetadataProperties.setInlineStrategy(call, InlineStrategy.NOT_INLINE);
|
|
||||||
Iterator<JsCallInfo> it = inlineCallInfos.descendingIterator();
|
|
||||||
|
|
||||||
while (it.hasNext()) {
|
|
||||||
JsCallInfo callInfo = it.next();
|
|
||||||
PsiElement psiElement = MetadataProperties.getPsiElement(callInfo.call);
|
|
||||||
|
|
||||||
CallableDescriptor descriptor = MetadataProperties.getDescriptor(callInfo.call);
|
|
||||||
if (psiElement != null && descriptor != null) {
|
|
||||||
trace.report(Errors.INLINE_CALL_CYCLE.on(psiElement, descriptor));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (callInfo.containingFunction == calledFunction) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean hasToBeInlined(@NotNull JsInvocation call) {
|
|
||||||
InlineStrategy strategy = MetadataProperties.getInlineStrategy(call);
|
|
||||||
if (strategy == null || !strategy.isInline()) return false;
|
|
||||||
|
|
||||||
return getFunctionContext().hasFunctionDefinition(call);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class JsInliningContext implements InliningContext {
|
|
||||||
private final FunctionContext functionContext;
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private final JsContext<JsStatement> statementContextBeforeCurrentFunction;
|
|
||||||
|
|
||||||
JsInliningContext(@NotNull JsContext<JsStatement> statementContextBeforeCurrentFunction) {
|
|
||||||
functionContext = new FunctionContext(functionReader, config) {
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
protected FunctionWithWrapper lookUpStaticFunction(@Nullable JsName functionName) {
|
|
||||||
return functions.get(functionName);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
protected FunctionWithWrapper lookUpStaticFunctionByTag(@NotNull String functionTag) {
|
|
||||||
return accessors.get(functionTag);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
this.statementContextBeforeCurrentFunction = statementContextBeforeCurrentFunction;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
@Override
|
|
||||||
public NamingContext newNamingContext() {
|
|
||||||
return new NamingContext(getStatementContext());
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
@Override
|
|
||||||
public JsContext<JsStatement> getStatementContext() {
|
|
||||||
return getLastStatementLevelContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
@Override
|
|
||||||
public FunctionContext getFunctionContext() {
|
|
||||||
return functionContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
@Override
|
|
||||||
public JsContext<JsStatement> getStatementContextBeforeCurrentFunction() {
|
|
||||||
return statementContextBeforeCurrentFunction;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class JsCallInfo {
|
|
||||||
@NotNull
|
|
||||||
public final JsInvocation call;
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public final JsFunction containingFunction;
|
|
||||||
|
|
||||||
private JsCallInfo(@NotNull JsInvocation call, @NotNull JsFunction function) {
|
|
||||||
this.call = call;
|
|
||||||
containingFunction = function;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static class JsWrapperKey {
|
|
||||||
final JsContext<JsStatement> context;
|
|
||||||
private final JsFunction function;
|
|
||||||
|
|
||||||
public JsWrapperKey(@NotNull JsContext<JsStatement> context, @NotNull JsFunction function) {
|
|
||||||
this.context = context;
|
|
||||||
this.function = function;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
JsWrapperKey key = (JsWrapperKey) o;
|
|
||||||
return Objects.equals(context, key.context) && Objects.equals(function, key.function);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(context, function);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,653 @@
|
|||||||
|
/*
|
||||||
|
* 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.backend.common.*
|
||||||
|
import org.jetbrains.kotlin.config.*
|
||||||
|
import org.jetbrains.kotlin.config.LanguageVersionSettings
|
||||||
|
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.ast.*
|
||||||
|
import org.jetbrains.kotlin.js.backend.ast.metadata.*
|
||||||
|
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.InliningContext
|
||||||
|
import org.jetbrains.kotlin.js.inline.context.NamingContext
|
||||||
|
import org.jetbrains.kotlin.js.inline.util.*
|
||||||
|
import org.jetbrains.kotlin.js.translate.expression.InlineMetadata
|
||||||
|
import org.jetbrains.kotlin.resolve.inline.InlineStrategy
|
||||||
|
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.js.inline.util.getImportTag
|
||||||
|
import org.jetbrains.kotlin.js.inline.util.IdentitySet
|
||||||
|
import org.jetbrains.kotlin.js.translate.declaration.transformSpecialFunctionsToCoroutineMetadata
|
||||||
|
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils.flattenStatement
|
||||||
|
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils.pureFqn
|
||||||
|
|
||||||
|
class JsInliner private constructor(
|
||||||
|
private val config: JsConfig,
|
||||||
|
private val functions: Map<JsName, FunctionWithWrapper>,
|
||||||
|
private val accessors: Map<String, FunctionWithWrapper>,
|
||||||
|
private val inverseNameBindings: Map<JsName, String>,
|
||||||
|
private val functionReader: FunctionReader,
|
||||||
|
private val trace: DiagnosticSink
|
||||||
|
) : JsVisitorWithContextImpl() {
|
||||||
|
private val namedFunctionsSet: MutableSet<JsFunction> = functions.values.mapTo(IdentitySet()) { it.function }
|
||||||
|
private val inliningContexts = Stack<JsInliningContext>()
|
||||||
|
private val processedFunctions = IdentitySet<JsFunction>()
|
||||||
|
private val inProcessFunctions = IdentitySet<JsFunction>()
|
||||||
|
private var existingImports: MutableMap<String, JsName> = HashMap()
|
||||||
|
private var statementContextForInline: JsContext<JsStatement>? = null
|
||||||
|
|
||||||
|
private val functionsByWrapperNodes = HashMap<JsBlock, FunctionWithWrapper>()
|
||||||
|
private 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// these are needed for error reporting, when inliner detects cycle
|
||||||
|
private val namedFunctionsStack = Stack<JsFunction>()
|
||||||
|
private val inlineCallInfos = LinkedList<JsCallInfo>()
|
||||||
|
private val canBeExtractedByInliner: (JsNode) -> Boolean = { node -> node is JsInvocation && hasToBeInlined(node) }
|
||||||
|
private var inlineFunctionDepth: Int = 0
|
||||||
|
|
||||||
|
private val replacementsInducedByWrappers = HashMap<JsWrapperKey, Map<JsName, JsNameRef>>()
|
||||||
|
|
||||||
|
private var existingNameBindings: MutableMap<JsName, String> = HashMap()
|
||||||
|
|
||||||
|
private val additionalNameBindings = ArrayList<JsNameBinding>()
|
||||||
|
|
||||||
|
private val inlinedModuleAliases = HashSet<JsName>()
|
||||||
|
|
||||||
|
private val inliningContext: JsInliningContext
|
||||||
|
get() = inliningContexts.peek()
|
||||||
|
|
||||||
|
private val functionContext: FunctionContext
|
||||||
|
get() = inliningContext.functionContext
|
||||||
|
|
||||||
|
private val currentNamedFunction: JsFunction?
|
||||||
|
get() = if (namedFunctionsStack.empty()) null else namedFunctionsStack.peek()
|
||||||
|
|
||||||
|
private fun addInlinedModules(fragment: JsProgramFragment, moduleMap: Map<JsName, JsImportedModule>) {
|
||||||
|
val localMap = buildModuleMap(listOf(fragment)).keys
|
||||||
|
for (inlinedModuleName in inlinedModuleAliases) {
|
||||||
|
if (!localMap.contains(inlinedModuleName)) {
|
||||||
|
fragment.importedModules.add(moduleMap[inlinedModuleName]!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processImportStatement(statement: JsStatement) {
|
||||||
|
if (statement is JsVars) {
|
||||||
|
val tag = getImportTag(statement)
|
||||||
|
if (tag != null) {
|
||||||
|
existingImports[tag] = statement.vars[0].name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(function: JsFunction, context: JsContext<*>): Boolean {
|
||||||
|
val functionWithWrapper = functionsByFunctionNodes[function]
|
||||||
|
if (functionWithWrapper != null) {
|
||||||
|
visit(functionWithWrapper)
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
if (statementContextForInline == null) {
|
||||||
|
statementContextForInline = lastStatementLevelContext
|
||||||
|
startFunction(function)
|
||||||
|
val result = super.visit(function, context)
|
||||||
|
statementContextForInline = null
|
||||||
|
return result
|
||||||
|
} else {
|
||||||
|
startFunction(function)
|
||||||
|
return super.visit(function, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun endVisit(function: JsFunction, context: JsContext<*>) {
|
||||||
|
super.endVisit(function, context)
|
||||||
|
if (!functionsByFunctionNodes.containsKey(function)) {
|
||||||
|
endFunction(function)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startFunction(function: JsFunction) {
|
||||||
|
inliningContexts.push(JsInliningContext(statementContextForInline!!))
|
||||||
|
|
||||||
|
assert(!inProcessFunctions.contains(function)) { "Inliner has revisited function" }
|
||||||
|
inProcessFunctions.add(function)
|
||||||
|
|
||||||
|
if (namedFunctionsSet.contains(function)) {
|
||||||
|
namedFunctionsStack.push(function)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun endFunction(function: JsFunction) {
|
||||||
|
refreshLabelNames(function.body, function.scope)
|
||||||
|
|
||||||
|
removeUnusedLocalFunctionDeclarations(function)
|
||||||
|
processedFunctions.add(function)
|
||||||
|
|
||||||
|
FunctionPostProcessor(function).apply()
|
||||||
|
|
||||||
|
assert(inProcessFunctions.contains(function))
|
||||||
|
inProcessFunctions.remove(function)
|
||||||
|
|
||||||
|
inliningContexts.pop()
|
||||||
|
|
||||||
|
if (!namedFunctionsStack.empty() && namedFunctionsStack.peek() == function) {
|
||||||
|
namedFunctionsStack.pop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(x: JsBlock, ctx: JsContext<*>): Boolean {
|
||||||
|
val functionWithWrapper = functionsByWrapperNodes[x]
|
||||||
|
if (functionWithWrapper != null) {
|
||||||
|
visit(functionWithWrapper)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return super.visit(x, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun visit(functionWithWrapper: FunctionWithWrapper) {
|
||||||
|
val oldContextForInline = statementContextForInline
|
||||||
|
val oldExistingImports = existingImports
|
||||||
|
val oldInlineFunctionDepth = inlineFunctionDepth
|
||||||
|
|
||||||
|
val innerContext = ListContext<JsStatement>()
|
||||||
|
|
||||||
|
val wrapperBody = functionWithWrapper.wrapperBody
|
||||||
|
var statements: MutableList<JsStatement>? = null
|
||||||
|
if (wrapperBody != null) {
|
||||||
|
existingImports = HashMap()
|
||||||
|
statementContexts.push(innerContext)
|
||||||
|
statementContextForInline = innerContext
|
||||||
|
inlineFunctionDepth++
|
||||||
|
|
||||||
|
for (statement in wrapperBody.statements) {
|
||||||
|
processImportStatement(statement)
|
||||||
|
}
|
||||||
|
statements = wrapperBody.statements
|
||||||
|
if (!statements.isEmpty() && statements[statements.size - 1] is JsReturn) {
|
||||||
|
statements = statements.subList(0, statements.size - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
innerContext.traverse(statements)
|
||||||
|
statementContexts.pop()
|
||||||
|
} else {
|
||||||
|
if (statementContextForInline == null) statementContextForInline = lastStatementLevelContext
|
||||||
|
}
|
||||||
|
|
||||||
|
startFunction(functionWithWrapper.function)
|
||||||
|
|
||||||
|
val block = JsBlock(functionWithWrapper.function.body)
|
||||||
|
innerContext.traverse(block.statements)
|
||||||
|
functionWithWrapper.function.body.traverse(this, innerContext)
|
||||||
|
|
||||||
|
endFunction(functionWithWrapper.function)
|
||||||
|
|
||||||
|
statements?.addAll(block.statements.subList(0, block.statements.size - 1))
|
||||||
|
|
||||||
|
statementContextForInline = oldContextForInline
|
||||||
|
existingImports = oldExistingImports
|
||||||
|
inlineFunctionDepth = oldInlineFunctionDepth
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(call: JsInvocation, context: JsContext<*>): Boolean {
|
||||||
|
if (!hasToBeInlined(call)) return true
|
||||||
|
|
||||||
|
val containingFunction = currentNamedFunction
|
||||||
|
|
||||||
|
if (containingFunction != null) {
|
||||||
|
inlineCallInfos.add(JsCallInfo(call, containingFunction))
|
||||||
|
}
|
||||||
|
|
||||||
|
val definition = functionContext.getFunctionDefinition(call)
|
||||||
|
|
||||||
|
if (inProcessFunctions.contains(definition.function)) {
|
||||||
|
reportInlineCycle(call, definition.function)
|
||||||
|
} else if (!processedFunctions.contains(definition.function)) {
|
||||||
|
for (i in 0 until call.arguments.size) {
|
||||||
|
val argument = call.arguments[i]
|
||||||
|
call.arguments[i] = accept(argument)
|
||||||
|
}
|
||||||
|
visit(definition)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun endVisit(x: JsInvocation, ctx: JsContext<JsNode>) {
|
||||||
|
if (hasToBeInlined(x)) {
|
||||||
|
inline(x, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastCallInfo: JsCallInfo? = null
|
||||||
|
|
||||||
|
if (!inlineCallInfos.isEmpty()) {
|
||||||
|
lastCallInfo = inlineCallInfos.last
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastCallInfo != null && lastCallInfo.call == x) {
|
||||||
|
inlineCallInfos.removeLast()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun endVisit(x: JsExpressionStatement, ctx: JsContext<*>) {
|
||||||
|
val e = x.expression
|
||||||
|
if (e is JsBinaryOperation) {
|
||||||
|
if (e.operator == JsBinaryOperator.ASG) {
|
||||||
|
e.arg2?.let { argument2 ->
|
||||||
|
val splitSuspendInlineFunction = splitExportedSuspendInlineFunctionDeclarations(argument2)
|
||||||
|
if (splitSuspendInlineFunction != null) {
|
||||||
|
e.arg2 = splitSuspendInlineFunction
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
super.endVisit(x, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun endVisit(x: JsVars.JsVar, ctx: JsContext<*>) {
|
||||||
|
val initExpression = x.initExpression ?: return
|
||||||
|
|
||||||
|
val splitSuspendInlineFunction = splitExportedSuspendInlineFunctionDeclarations(initExpression)
|
||||||
|
if (splitSuspendInlineFunction != null) {
|
||||||
|
x.initExpression = splitSuspendInlineFunction
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun splitExportedSuspendInlineFunctionDeclarations(expression: JsExpression): JsFunction? {
|
||||||
|
val inlineMetadata = InlineMetadata.decompose(expression)
|
||||||
|
if (inlineMetadata != null) {
|
||||||
|
val (originalFunction, wrapperBody) = inlineMetadata.function
|
||||||
|
if (originalFunction.coroutineMetadata != null) {
|
||||||
|
val statementContext = lastStatementLevelContext
|
||||||
|
|
||||||
|
// This function will be exported to JS
|
||||||
|
val function = originalFunction.deepCopy()
|
||||||
|
|
||||||
|
// Original function should be not be transformed into a state machine
|
||||||
|
originalFunction.setName(null)
|
||||||
|
originalFunction.coroutineMetadata = null
|
||||||
|
originalFunction.isInlineableCoroutineBody = true
|
||||||
|
if (wrapperBody != null) {
|
||||||
|
// Extract local declarations
|
||||||
|
applyWrapper(wrapperBody, function, originalFunction, JsInliningContext(statementContext))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun doAcceptStatementList(statements: MutableList<JsStatement>) {
|
||||||
|
// at top level of js ast, contexts stack can be empty,
|
||||||
|
// but there is no inline calls anyway
|
||||||
|
if (!inliningContexts.isEmpty()) {
|
||||||
|
var i = 0
|
||||||
|
|
||||||
|
while (i < statements.size) {
|
||||||
|
val additionalStatements = ExpressionDecomposer.preserveEvaluationOrder(statements[i], canBeExtractedByInliner)
|
||||||
|
statements.addAll(i, additionalStatements)
|
||||||
|
i += additionalStatements.size + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
super.doAcceptStatementList(statements)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun inline(call: JsInvocation, context: JsContext<JsNode>) {
|
||||||
|
val callDescriptor = call.descriptor
|
||||||
|
if (isSuspendWithCurrentContinuation(
|
||||||
|
callDescriptor,
|
||||||
|
config.configuration.languageVersionSettings
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
inlineSuspendWithCurrentContinuation(call, context)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val inliningContext = inliningContext
|
||||||
|
var functionWithWrapper = inliningContext.functionContext.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
|
||||||
|
functionsByFunctionNodes[functionWithWrapper.function]?.let {
|
||||||
|
functionWithWrapper = it
|
||||||
|
}
|
||||||
|
|
||||||
|
val function = functionWithWrapper.function.deepCopy()
|
||||||
|
function.body = transformSpecialFunctionsToCoroutineMetadata(function.body)
|
||||||
|
if (functionWithWrapper.wrapperBody != null) {
|
||||||
|
applyWrapper(functionWithWrapper.wrapperBody!!, function, functionWithWrapper.function, inliningContext)
|
||||||
|
}
|
||||||
|
val inlineableResult = FunctionInlineMutator.getInlineableCallReplacement(call, function, inliningContext)
|
||||||
|
|
||||||
|
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
|
||||||
|
val inlineableBodyWithLambdasInlined = accept(inlineableBody)
|
||||||
|
assert(inlineableBody === inlineableBodyWithLambdasInlined)
|
||||||
|
|
||||||
|
// Support non-local return from secondary constructor
|
||||||
|
// Returns from secondary constructors should return `$this` object.
|
||||||
|
val currentFunction = 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(flattenStatement(inlineableBody))
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Assumes, that resultExpression == null, when result is not needed.
|
||||||
|
* @see FunctionInlineMutator.isResultNeeded()
|
||||||
|
*/
|
||||||
|
if (resultExpression == null) {
|
||||||
|
statementContext.removeMe()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resultExpression = accept(resultExpression)
|
||||||
|
resultExpression.synthetic = true
|
||||||
|
context.replaceMe(resultExpression)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun applyWrapper(
|
||||||
|
wrapper: JsBlock, function: JsFunction, originalFunction: JsFunction,
|
||||||
|
inliningContext: InliningContext
|
||||||
|
) {
|
||||||
|
val key = JsWrapperKey(inliningContext.statementContextBeforeCurrentFunction, originalFunction)
|
||||||
|
|
||||||
|
// Apparently we should avoid this trick when we implement fair support for crossinline
|
||||||
|
val replacements = replacementsInducedByWrappers.computeIfAbsent(key) { k ->
|
||||||
|
val ctx = k.context
|
||||||
|
|
||||||
|
val newReplacements = HashMap<JsName, JsNameRef>()
|
||||||
|
|
||||||
|
val copiedStatements = ArrayList<JsStatement>()
|
||||||
|
wrapper.statements.asSequence()
|
||||||
|
.filterNot { it is JsReturn }
|
||||||
|
.map { it.deepCopy() }
|
||||||
|
.forEach { statement ->
|
||||||
|
if (inlineFunctionDepth == 0) {
|
||||||
|
replaceExpressionsWithLocalAliases(statement)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (statement is JsVars) {
|
||||||
|
val tag = getImportTag(statement)
|
||||||
|
if (tag != null) {
|
||||||
|
val name = statement.vars[0].name
|
||||||
|
var existingName: JsName? = if (inlineFunctionDepth == 0) name.localAlias else null
|
||||||
|
if (existingName == null) {
|
||||||
|
existingName = existingImports.computeIfAbsent(tag) {
|
||||||
|
copiedStatements.add(statement)
|
||||||
|
val alias = JsScope.declareTemporaryName(name.ident)
|
||||||
|
alias.copyMetadataFrom(name)
|
||||||
|
newReplacements[name] = pureFqn(alias, null)
|
||||||
|
alias
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name !== existingName) {
|
||||||
|
val replacement = pureFqn(existingName, null)
|
||||||
|
newReplacements[name] = replacement
|
||||||
|
}
|
||||||
|
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
copiedStatements.add(statement)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = pureFqn(alias, null)
|
||||||
|
newReplacements[name] = replacement
|
||||||
|
}
|
||||||
|
|
||||||
|
for (statement in copiedStatements) {
|
||||||
|
ctx.addPrevious(accept(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) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun inlineSuspendWithCurrentContinuation(call: JsInvocation, context: JsContext<JsNode>) {
|
||||||
|
val lambda = call.arguments[0]
|
||||||
|
val continuationArg = call.arguments[call.arguments.size - 1]
|
||||||
|
|
||||||
|
val invocation = JsInvocation(lambda, continuationArg)
|
||||||
|
invocation.isSuspend = true
|
||||||
|
context.replaceMe(accept(invocation))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reportInlineCycle(call: JsInvocation, calledFunction: JsFunction) {
|
||||||
|
call.inlineStrategy = InlineStrategy.NOT_INLINE
|
||||||
|
val it = inlineCallInfos.descendingIterator()
|
||||||
|
|
||||||
|
while (it.hasNext()) {
|
||||||
|
val callInfo = it.next()
|
||||||
|
val psiElement = callInfo.call.psiElement
|
||||||
|
|
||||||
|
val descriptor = callInfo.call.descriptor
|
||||||
|
if (psiElement != null && descriptor != null) {
|
||||||
|
trace.report(Errors.INLINE_CALL_CYCLE.on(psiElement, descriptor))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (callInfo.containingFunction == calledFunction) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun hasToBeInlined(call: JsInvocation): Boolean {
|
||||||
|
val strategy = call.inlineStrategy
|
||||||
|
return if (strategy == null || !strategy.isInline) false else functionContext.hasFunctionDefinition(call)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class JsInliningContext internal constructor(override val statementContextBeforeCurrentFunction: JsContext<JsStatement>) :
|
||||||
|
InliningContext {
|
||||||
|
override val functionContext: FunctionContext
|
||||||
|
|
||||||
|
override val statementContext: JsContext<JsStatement>
|
||||||
|
get() = lastStatementLevelContext
|
||||||
|
|
||||||
|
init {
|
||||||
|
functionContext = object : FunctionContext(functionReader, config) {
|
||||||
|
override fun lookUpStaticFunction(functionName: JsName?): FunctionWithWrapper? {
|
||||||
|
return functions[functionName]
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun lookUpStaticFunctionByTag(functionTag: String): FunctionWithWrapper? {
|
||||||
|
return accessors[functionTag]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun newNamingContext(): NamingContext {
|
||||||
|
return NamingContext(statementContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class JsCallInfo(val call: JsInvocation, val containingFunction: JsFunction)
|
||||||
|
|
||||||
|
internal class JsWrapperKey(val context: JsContext<JsStatement>, private val function: JsFunction) {
|
||||||
|
|
||||||
|
override fun equals(o: Any?): Boolean {
|
||||||
|
if (this === o) return true
|
||||||
|
if (o == null || javaClass != o.javaClass) return false
|
||||||
|
val key = o as JsWrapperKey?
|
||||||
|
return context == key!!.context && function == key.function
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return Objects.hash(context, function)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun process(
|
||||||
|
reporter: JsConfig.Reporter,
|
||||||
|
config: JsConfig,
|
||||||
|
trace: DiagnosticSink,
|
||||||
|
currentModuleName: JsName,
|
||||||
|
fragments: List<JsProgramFragment>,
|
||||||
|
fragmentsToProcess: List<JsProgramFragment>,
|
||||||
|
importStatements: List<JsStatement>
|
||||||
|
) {
|
||||||
|
val functions = collectNamedFunctionsAndWrappers(fragments)
|
||||||
|
val accessors = collectAccessors(fragments)
|
||||||
|
val inverseNameBindings = collectNameBindings(fragments)
|
||||||
|
|
||||||
|
val accessorInvocationTransformer = DummyAccessorInvocationTransformer()
|
||||||
|
for (fragment in fragmentsToProcess) {
|
||||||
|
accessorInvocationTransformer.accept<JsGlobalBlock>(fragment.declarationBlock)
|
||||||
|
accessorInvocationTransformer.accept<JsGlobalBlock>(fragment.initializerBlock)
|
||||||
|
}
|
||||||
|
val functionReader = FunctionReader(reporter, config, currentModuleName, fragments)
|
||||||
|
val inliner = JsInliner(config, functions, accessors, inverseNameBindings, functionReader, trace)
|
||||||
|
|
||||||
|
for (statement in importStatements) {
|
||||||
|
inliner.processImportStatement(statement)
|
||||||
|
}
|
||||||
|
|
||||||
|
val moduleMap = fillModuleMap(buildModuleMap(fragments), fragmentsToProcess)
|
||||||
|
|
||||||
|
for (fragment in fragmentsToProcess) {
|
||||||
|
inliner.existingImports.clear()
|
||||||
|
inliner.additionalNameBindings.clear()
|
||||||
|
inliner.inlinedModuleAliases.clear()
|
||||||
|
inliner.existingNameBindings = collectNameBindings(listOf(fragment))
|
||||||
|
|
||||||
|
inliner.acceptStatement<JsGlobalBlock>(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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// There can be inlined function in top-level initializers, we need to optimize them as well
|
||||||
|
val fakeInitFunction = JsFunction(JsDynamicScope, fragment.initializerBlock, "")
|
||||||
|
val initWrapper = JsGlobalBlock()
|
||||||
|
initWrapper.statements.add(JsExpressionStatement(fakeInitFunction))
|
||||||
|
inliner.accept(initWrapper)
|
||||||
|
initWrapper.statements.removeAt(initWrapper.statements.size - 1)
|
||||||
|
|
||||||
|
fragment.initializerBlock.getStatements().addAll(0, initWrapper.statements)
|
||||||
|
fragment.nameBindings.addAll(inliner.additionalNameBindings)
|
||||||
|
inliner.addInlinedModules(fragment, moduleMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (fragment in fragmentsToProcess) {
|
||||||
|
val block = JsBlock(fragment.declarationBlock, fragment.initializerBlock, fragment.exportBlock)
|
||||||
|
removeUnusedImports(block)
|
||||||
|
simplifyWrappedFunctions(block)
|
||||||
|
removeUnusedFunctionDefinitions(block, collectNamedFunctions(block))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildModuleMap(fragments: List<JsProgramFragment>): MutableMap<JsName, JsImportedModule> {
|
||||||
|
return fillModuleMap(HashMap(), fragments)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fillModuleMap(
|
||||||
|
map: MutableMap<JsName, JsImportedModule>,
|
||||||
|
fragments: List<JsProgramFragment>
|
||||||
|
): MutableMap<JsName, JsImportedModule> {
|
||||||
|
for (fragment in fragments) {
|
||||||
|
for (module in fragment.importedModules) {
|
||||||
|
map[module.internalName] = module
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isSuspendWithCurrentContinuation(
|
||||||
|
descriptor: DeclarationDescriptor?,
|
||||||
|
languageVersionSettings: LanguageVersionSettings
|
||||||
|
): Boolean {
|
||||||
|
return (descriptor as? FunctionDescriptor)?.original?.isBuiltInSuspendCoroutineUninterceptedOrReturn(
|
||||||
|
languageVersionSettings
|
||||||
|
) ?: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -230,7 +230,7 @@ fun collectAccessors(fragments: List<JsProgramFragment>): Map<String, FunctionWi
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
fun collectNameBindings(fragments: List<JsProgramFragment>): Map<JsName, String> {
|
fun collectNameBindings(fragments: List<JsProgramFragment>): MutableMap<JsName, String> {
|
||||||
val result = mutableMapOf<JsName, String>()
|
val result = mutableMapOf<JsName, String>()
|
||||||
for (fragment in fragments) {
|
for (fragment in fragments) {
|
||||||
for (binding in fragment.nameBindings) {
|
for (binding in fragment.nameBindings) {
|
||||||
|
|||||||
Reference in New Issue
Block a user