Support new JS inline function format in multi-module projects
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+6
-9
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user