diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionInlineMutator.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionInlineMutator.kt index 65e2806c97f..73993fc41bb 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionInlineMutator.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionInlineMutator.kt @@ -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)) { diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionReader.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionReader.kt index b8fd42f1c06..c8a1206944f 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionReader.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionReader.kt @@ -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) { - val replacements = externalReplacements.filterKeys { !function.scope.hasOwnName(it) } - - if (replacements.isEmpty()) return +private fun replaceExternalNames(node: JsNode, replacements: Map) { + val skipNames = collectDefinedNamesInAllScopes(node) val visitor = object: JsVisitorWithContextImpl() { override fun endVisit(x: JsNameRef, ctx: JsContext) { - 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) } \ No newline at end of file diff --git a/js/js.parser/src/com/google/gwt/dev/js/JsAstMapper.java b/js/js.parser/src/com/google/gwt/dev/js/JsAstMapper.java index a8185afe8b7..5893b4f2dc5 100644 --- a/js/js.parser/src/com/google/gwt/dev/js/JsAstMapper.java +++ b/js/js.parser/src/com/google/gwt/dev/js/JsAstMapper.java @@ -1108,6 +1108,8 @@ public class JsAstMapper { } private 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()); diff --git a/js/js.parser/src/com/google/gwt/dev/js/rhino/Parser.java b/js/js.parser/src/com/google/gwt/dev/js/rhino/Parser.java index d338239415a..ec226b44311 100644 --- a/js/js.parser/src/com/google/gwt/dev/js/rhino/Parser.java +++ b/js/js.parser/src/com/google/gwt/dev/js/rhino/Parser.java @@ -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 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; } diff --git a/js/js.parser/src/org/jetbrains/kotlin/js/parser/ParserEvents.kt b/js/js.parser/src/com/google/gwt/dev/js/rhino/ParserListener.kt similarity index 70% rename from js/js.parser/src/org/jetbrains/kotlin/js/parser/ParserEvents.kt rename to js/js.parser/src/com/google/gwt/dev/js/rhino/ParserListener.kt index f1c376ebc33..654b5ed1eb3 100644 --- a/js/js.parser/src/org/jetbrains/kotlin/js/parser/ParserEvents.kt +++ b/js/js.parser/src/com/google/gwt/dev/js/rhino/ParserListener.kt @@ -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) -} \ No newline at end of file + fun functionEnded(tokenStream: TokenStream) +} diff --git a/js/js.parser/src/org/jetbrains/kotlin/js/parser/parserUtils.kt b/js/js.parser/src/org/jetbrains/kotlin/js/parser/parserUtils.kt index aa0ba95c5fb..57a9c8a1db2 100644 --- a/js/js.parser/src/org/jetbrains/kotlin/js/parser/parserUtils.kt +++ b/js/js.parser/src/org/jetbrains/kotlin/js/parser/parserUtils.kt @@ -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 { 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) } } }