Support new JS inline function format in multi-module projects

This commit is contained in:
Alexey Andreev
2017-07-14 14:25:15 +03:00
parent 7c421b0b83
commit 445a80c755
6 changed files with 99 additions and 50 deletions
@@ -26,7 +26,8 @@ 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(
class FunctionInlineMutator
private constructor(
private val call: JsInvocation,
private val inliningContext: InliningContext,
function: JsFunction
@@ -182,7 +183,6 @@ class FunctionInlineMutator(
return InlineableResult(inlineableBody, mutator.resultExpr)
}
@JvmStatic
private fun getThisReplacement(call: JsInvocation): JsExpression? {
if (isCallInvocation(call)) {
@@ -22,15 +22,16 @@ import com.intellij.util.containers.SLRUCache
import org.jetbrains.kotlin.builtins.isFunctionTypeOrSubtype
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.backend.ast.metadata.SideEffectKind
import org.jetbrains.kotlin.js.backend.ast.metadata.inlineStrategy
import org.jetbrains.kotlin.js.backend.ast.metadata.sideEffects
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.inline.util.*
import org.jetbrains.kotlin.js.parser.OffsetToSourceMapping
import org.jetbrains.kotlin.js.parser.parseFunction
import org.jetbrains.kotlin.js.parser.sourcemaps.*
import org.jetbrains.kotlin.js.translate.context.Namer
import org.jetbrains.kotlin.js.translate.expression.InlineMetadata
import org.jetbrains.kotlin.js.translate.utils.JsDescriptorUtils.getModuleName
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
import org.jetbrains.kotlin.resolve.inline.InlineStrategy
@@ -193,21 +194,49 @@ class FunctionReader(
offset++
}
val wrapFunctionMatcher = wrapFunctionRegex.matcher(ShallowSubSequence(source, offset, source.length))
val isWrapped = wrapFunctionMatcher.lookingAt()
if (isWrapped) {
offset += wrapFunctionMatcher.end()
}
val position = info.offsetToSourceMapping[offset]
val function = parseFunction(source, info.filePath, position, offset, ThrowExceptionOnErrorReporter, JsRootScope(JsProgram()))
val functionExpr = parseFunction(source, info.filePath, position, offset, ThrowExceptionOnErrorReporter, JsRootScope(JsProgram()))
functionExpr.fixForwardNameReferences()
val (function, wrapper) = if (isWrapped) {
InlineMetadata.decomposeWrapper(functionExpr) ?: return null
}
else {
FunctionWithWrapper(functionExpr, null)
}
val moduleReference = moduleNameMap[tag]?.deepCopy() ?: currentModuleName.makeRef()
val sourceMap = info.sourceMap
if (sourceMap != null) {
val remapper = SourceMapLocationRemapper(sourceMap)
remapper.remap(function)
wrapper?.let { remapper.remap(it) }
}
val replacements = hashMapOf(info.moduleVariable to moduleReference,
info.kotlinVariable to Namer.kotlinObject())
replaceExternalNames(function, replacements)
wrapper?.let { replaceExternalNames(it, replacements) }
function.markInlineArguments(descriptor)
return FunctionWithWrapper(function, null)
val namesWithoutSizeEffects = wrapper?.statements.orEmpty().asSequence()
.flatMap { collectDefinedNames(it).asSequence() }
.toSet()
function.accept(object : RecursiveJsVisitor() {
override fun visitNameRef(nameRef: JsNameRef) {
if (nameRef.name in namesWithoutSizeEffects && nameRef.qualifier == null) {
nameRef.sideEffects = SideEffectKind.PURE
}
super.visitNameRef(nameRef)
}
})
return FunctionWithWrapper(function, wrapper)
}
}
@@ -246,14 +275,12 @@ private fun JsFunction.markInlineArguments(descriptor: CallableDescriptor) {
visitor.accept(this)
}
private fun replaceExternalNames(function: JsFunction, externalReplacements: Map<String, JsExpression>) {
val replacements = externalReplacements.filterKeys { !function.scope.hasOwnName(it) }
if (replacements.isEmpty()) return
private fun replaceExternalNames(node: JsNode, replacements: Map<String, JsExpression>) {
val skipNames = collectDefinedNamesInAllScopes(node)
val visitor = object: JsVisitorWithContextImpl() {
override fun endVisit(x: JsNameRef, ctx: JsContext<JsNode>) {
if (x.qualifier != null) return
if (x.qualifier != null || x.name in skipNames) return
replacements[x.ident]?.let {
ctx.replaceMe(it)
@@ -261,5 +288,19 @@ private fun replaceExternalNames(function: JsFunction, externalReplacements: Map
}
}
visitor.accept(function)
visitor.accept(node)
}
private val wrapFunctionRegex = Regex("\\s*[a-zA-Z_$][a-zA-Z0-9_$]*\\s*\\.\\s*wrapFunction\\s*\\(\\s*").toPattern()
private class ShallowSubSequence(private val underlying: CharSequence, private val start: Int, end: Int) : CharSequence {
override val length: Int = end - start
override fun get(index: Int): Char {
if (index !in 0 until length) throw IndexOutOfBoundsException("$index is out of bounds 0..$length")
return underlying[index + start]
}
override fun subSequence(startIndex: Int, endIndex: Int): CharSequence =
ShallowSubSequence(underlying, start + startIndex, start + endIndex)
}
@@ -1108,6 +1108,8 @@ public class JsAstMapper {
}
private <T extends JsNode> T withLocation(T astNode, Node node) {
if (astNode == null) return null;
CodePosition location = node.getPosition();
if (location != null) {
JsLocation jsLocation = new JsLocation(fileName, location.getLine(), location.getOffset());
@@ -37,10 +37,9 @@
package com.google.gwt.dev.js.rhino;
import org.jetbrains.kotlin.js.parser.ParserEvents;
import java.io.IOException;
import java.util.Observable;
import java.util.ArrayList;
import java.util.List;
/**
* This class implements the JavaScript parser.
@@ -51,12 +50,21 @@ import java.util.Observable;
* @see TokenStream
*/
public class Parser extends Observable {
public class Parser {
private List<ParserListener> listeners;
public Parser(IRFactory nf, boolean insideFunction) {
this.nf = nf;
this.insideFunction = insideFunction;
}
public void addListener(ParserListener listener) {
if (listeners == null) {
listeners = new ArrayList<>();
}
listeners.add(listener);
}
private void mustMatchToken(TokenStream ts, int toMatch, String messageId) throws IOException, JavaScriptException {
int tt;
if ((tt = ts.getToken()) != toMatch) {
@@ -167,7 +175,11 @@ public class Parser extends Observable {
}
private Node function(TokenStream ts, boolean isExpr) throws IOException, JavaScriptException {
notifyObservers(new ParserEvents.OnFunctionParsingStart());
if (listeners != null) {
for (ParserListener listener : listeners) {
listener.functionStarted();
}
}
CodePosition basePosition = ts.tokenPosition;
Node nameNode;
@@ -247,7 +259,11 @@ public class Parser extends Observable {
wellTerminated(ts, TokenStream.FUNCTION);
}
notifyObservers(new ParserEvents.OnFunctionParsingEnd(ts));
if (listeners != null) {
for (ParserListener listener : listeners) {
listener.functionEnded(ts);
}
}
return pn;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
* 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.
@@ -14,13 +14,10 @@
* limitations under the License.
*/
package org.jetbrains.kotlin.js.parser
package com.google.gwt.dev.js.rhino
import com.google.gwt.dev.js.rhino.*
interface ParserListener {
fun functionStarted()
object ParserEvents {
class OnFunctionParsingStart
class OnFunctionParsingEnd(val tokenStream: TokenStream)
}
fun functionEnded(tokenStream: TokenStream)
}
@@ -18,13 +18,9 @@ package org.jetbrains.kotlin.js.parser
import com.google.gwt.dev.js.JsAstMapper
import com.google.gwt.dev.js.rhino.*
import org.jetbrains.kotlin.js.backend.ast.JsFunction
import org.jetbrains.kotlin.js.backend.ast.JsFunctionScope
import org.jetbrains.kotlin.js.backend.ast.JsScope
import org.jetbrains.kotlin.js.backend.ast.JsStatement
import org.jetbrains.kotlin.js.backend.ast.*
import java.io.Reader
import java.io.StringReader
import java.util.*
fun parse(code: String, reporter: ErrorReporter, scope: JsScope, fileName: String): List<JsStatement> {
val insideFunction = scope is JsFunctionScope
@@ -34,27 +30,24 @@ fun parse(code: String, reporter: ErrorReporter, scope: JsScope, fileName: Strin
}
}
fun parseFunction(code: String, fileName: String, position: CodePosition, offset: Int, reporter: ErrorReporter, scope: JsScope): JsFunction =
parse(code, position, offset, reporter, insideFunction = false) {
addObserver(FunctionParsingObserver())
primaryExpr(it)
}.toJsAst(scope, fileName, JsAstMapper::mapFunction)
fun parseFunction(code: String, fileName: String, position: CodePosition, offset: Int, reporter: ErrorReporter, scope: JsScope): JsFunction {
val rootNode = parse(code, position, offset, reporter, insideFunction = false) {
addListener(FunctionParsingObserver())
primaryExpr(it)
}
return rootNode.toJsAst(scope, fileName, JsAstMapper::mapFunction)
}
private class FunctionParsingObserver : Observer {
private class FunctionParsingObserver : ParserListener {
var functionsStarted = 0
override fun update(o: Observable?, arg: Any?) {
when (arg) {
is ParserEvents.OnFunctionParsingStart -> {
functionsStarted++
}
is ParserEvents.OnFunctionParsingEnd -> {
functionsStarted--
override fun functionStarted() {
functionsStarted++
}
if (functionsStarted == 0) {
arg.tokenStream.ungetToken(TokenStream.EOF)
}
}
override fun functionEnded(tokenStream: TokenStream) {
if (--functionsStarted == 0) {
tokenStream.ungetToken(TokenStream.EOF)
}
}
}