JS: avoid repeated export of the same declaration
This commit is contained in:
@@ -54,6 +54,8 @@ var JsInvocation.boxing: Boolean by MetadataProperty(default = false)
|
||||
|
||||
var JsVars.exportedPackage: String? by MetadataProperty(default = null)
|
||||
|
||||
var JsExpressionStatement.exportedTag: String? by MetadataProperty(default = null)
|
||||
|
||||
/**
|
||||
* For function and lambda bodies indicates what declaration corresponds to.
|
||||
* When absent (`null`) on body of a named function, this function is from external JS module.
|
||||
|
||||
@@ -270,6 +270,7 @@ message Debugger {
|
||||
|
||||
message ExpressionStatement {
|
||||
required Expression expression = 1;
|
||||
optional int32 exported_tag_id = 2;
|
||||
}
|
||||
|
||||
message Vars {
|
||||
|
||||
@@ -142,7 +142,11 @@ class JsAstDeserializer(private val program: JsProgram) {
|
||||
|
||||
StatementCase.EXPRESSION -> {
|
||||
val expressionProto = proto.expression
|
||||
JsExpressionStatement(deserialize(expressionProto.expression))
|
||||
JsExpressionStatement(deserialize(expressionProto.expression)).also {
|
||||
if (expressionProto.hasExportedTagId()) {
|
||||
it.exportedTag = deserializeString(expressionProto.exportedTagId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StatementCase.VARS -> {
|
||||
|
||||
@@ -19331,6 +19331,15 @@ public final class JsAstProtoBuf {
|
||||
* <code>required .org.jetbrains.kotlin.serialization.js.ast.Expression expression = 1;</code>
|
||||
*/
|
||||
org.jetbrains.kotlin.serialization.js.ast.JsAstProtoBuf.Expression getExpression();
|
||||
|
||||
/**
|
||||
* <code>optional int32 exported_tag_id = 2;</code>
|
||||
*/
|
||||
boolean hasExportedTagId();
|
||||
/**
|
||||
* <code>optional int32 exported_tag_id = 2;</code>
|
||||
*/
|
||||
int getExportedTagId();
|
||||
}
|
||||
/**
|
||||
* Protobuf type {@code org.jetbrains.kotlin.serialization.js.ast.ExpressionStatement}
|
||||
@@ -19395,6 +19404,11 @@ public final class JsAstProtoBuf {
|
||||
bitField0_ |= 0x00000001;
|
||||
break;
|
||||
}
|
||||
case 16: {
|
||||
bitField0_ |= 0x00000002;
|
||||
exportedTagId_ = input.readInt32();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (org.jetbrains.kotlin.protobuf.InvalidProtocolBufferException e) {
|
||||
@@ -19444,8 +19458,24 @@ public final class JsAstProtoBuf {
|
||||
return expression_;
|
||||
}
|
||||
|
||||
public static final int EXPORTED_TAG_ID_FIELD_NUMBER = 2;
|
||||
private int exportedTagId_;
|
||||
/**
|
||||
* <code>optional int32 exported_tag_id = 2;</code>
|
||||
*/
|
||||
public boolean hasExportedTagId() {
|
||||
return ((bitField0_ & 0x00000002) == 0x00000002);
|
||||
}
|
||||
/**
|
||||
* <code>optional int32 exported_tag_id = 2;</code>
|
||||
*/
|
||||
public int getExportedTagId() {
|
||||
return exportedTagId_;
|
||||
}
|
||||
|
||||
private void initFields() {
|
||||
expression_ = org.jetbrains.kotlin.serialization.js.ast.JsAstProtoBuf.Expression.getDefaultInstance();
|
||||
exportedTagId_ = 0;
|
||||
}
|
||||
private byte memoizedIsInitialized = -1;
|
||||
public final boolean isInitialized() {
|
||||
@@ -19471,6 +19501,9 @@ public final class JsAstProtoBuf {
|
||||
if (((bitField0_ & 0x00000001) == 0x00000001)) {
|
||||
output.writeMessage(1, expression_);
|
||||
}
|
||||
if (((bitField0_ & 0x00000002) == 0x00000002)) {
|
||||
output.writeInt32(2, exportedTagId_);
|
||||
}
|
||||
output.writeRawBytes(unknownFields);
|
||||
}
|
||||
|
||||
@@ -19484,6 +19517,10 @@ public final class JsAstProtoBuf {
|
||||
size += org.jetbrains.kotlin.protobuf.CodedOutputStream
|
||||
.computeMessageSize(1, expression_);
|
||||
}
|
||||
if (((bitField0_ & 0x00000002) == 0x00000002)) {
|
||||
size += org.jetbrains.kotlin.protobuf.CodedOutputStream
|
||||
.computeInt32Size(2, exportedTagId_);
|
||||
}
|
||||
size += unknownFields.size();
|
||||
memoizedSerializedSize = size;
|
||||
return size;
|
||||
@@ -19580,6 +19617,8 @@ public final class JsAstProtoBuf {
|
||||
super.clear();
|
||||
expression_ = org.jetbrains.kotlin.serialization.js.ast.JsAstProtoBuf.Expression.getDefaultInstance();
|
||||
bitField0_ = (bitField0_ & ~0x00000001);
|
||||
exportedTagId_ = 0;
|
||||
bitField0_ = (bitField0_ & ~0x00000002);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -19607,6 +19646,10 @@ public final class JsAstProtoBuf {
|
||||
to_bitField0_ |= 0x00000001;
|
||||
}
|
||||
result.expression_ = expression_;
|
||||
if (((from_bitField0_ & 0x00000002) == 0x00000002)) {
|
||||
to_bitField0_ |= 0x00000002;
|
||||
}
|
||||
result.exportedTagId_ = exportedTagId_;
|
||||
result.bitField0_ = to_bitField0_;
|
||||
return result;
|
||||
}
|
||||
@@ -19616,6 +19659,9 @@ public final class JsAstProtoBuf {
|
||||
if (other.hasExpression()) {
|
||||
mergeExpression(other.getExpression());
|
||||
}
|
||||
if (other.hasExportedTagId()) {
|
||||
setExportedTagId(other.getExportedTagId());
|
||||
}
|
||||
setUnknownFields(
|
||||
getUnknownFields().concat(other.unknownFields));
|
||||
return this;
|
||||
@@ -19712,6 +19758,38 @@ public final class JsAstProtoBuf {
|
||||
return this;
|
||||
}
|
||||
|
||||
private int exportedTagId_ ;
|
||||
/**
|
||||
* <code>optional int32 exported_tag_id = 2;</code>
|
||||
*/
|
||||
public boolean hasExportedTagId() {
|
||||
return ((bitField0_ & 0x00000002) == 0x00000002);
|
||||
}
|
||||
/**
|
||||
* <code>optional int32 exported_tag_id = 2;</code>
|
||||
*/
|
||||
public int getExportedTagId() {
|
||||
return exportedTagId_;
|
||||
}
|
||||
/**
|
||||
* <code>optional int32 exported_tag_id = 2;</code>
|
||||
*/
|
||||
public Builder setExportedTagId(int value) {
|
||||
bitField0_ |= 0x00000002;
|
||||
exportedTagId_ = value;
|
||||
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* <code>optional int32 exported_tag_id = 2;</code>
|
||||
*/
|
||||
public Builder clearExportedTagId() {
|
||||
bitField0_ = (bitField0_ & ~0x00000002);
|
||||
exportedTagId_ = 0;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
// @@protoc_insertion_point(builder_scope:org.jetbrains.kotlin.serialization.js.ast.ExpressionStatement)
|
||||
}
|
||||
|
||||
|
||||
@@ -144,6 +144,11 @@ class JsAstSerializer {
|
||||
override fun visitExpressionStatement(x: JsExpressionStatement) {
|
||||
val statementBuilder = ExpressionStatement.newBuilder()
|
||||
statementBuilder.expression = serialize(x.expression)
|
||||
val tag = x.exportedTag
|
||||
if (tag != null) {
|
||||
statementBuilder.exportedTagId = serialize(tag)
|
||||
}
|
||||
|
||||
builder.expression = statementBuilder.build()
|
||||
}
|
||||
|
||||
|
||||
@@ -3467,6 +3467,12 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("multipleExport.kt")
|
||||
public void testMultipleExport() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/incremental/multipleExport.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("simple.kt")
|
||||
public void testSimple() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/incremental/simple.kt");
|
||||
|
||||
@@ -76,6 +76,13 @@ public class DirectiveTestUtils {
|
||||
}
|
||||
};
|
||||
|
||||
private static final DirectiveHandler PROPERTY_WRITE_COUNT = new DirectiveHandler("PROPERTY_WRITE_COUNT") {
|
||||
@Override
|
||||
void processEntry(@NotNull JsNode ast, @NotNull ArgumentsHelper arguments) throws Exception {
|
||||
checkPropertyWriteCount(ast, arguments.getNamedArgument("name"), Integer.parseInt(arguments.getNamedArgument("count")));
|
||||
}
|
||||
};
|
||||
|
||||
private static final DirectiveHandler FUNCTION_CALLED_IN_SCOPE = new DirectiveHandler("CHECK_CALLED_IN_SCOPE") {
|
||||
@Override
|
||||
void processEntry(@NotNull JsNode ast, @NotNull ArgumentsHelper arguments) throws Exception {
|
||||
@@ -292,6 +299,7 @@ public class DirectiveTestUtils {
|
||||
PROPERTY_NOT_USED,
|
||||
PROPERTY_NOT_READ_FROM,
|
||||
PROPERTY_NOT_WRITTEN_TO,
|
||||
PROPERTY_WRITE_COUNT,
|
||||
FUNCTION_CALLED_IN_SCOPE,
|
||||
FUNCTION_NOT_CALLED_IN_SCOPE,
|
||||
FUNCTIONS_HAVE_SAME_LINES,
|
||||
@@ -332,6 +340,11 @@ public class DirectiveTestUtils {
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkPropertyWriteCount(JsNode node, String propertyName, int expectedCount) throws Exception {
|
||||
PropertyReferenceCollector counter = PropertyReferenceCollector.Companion.collect(node);
|
||||
assertEquals("Property write count: " + propertyName, expectedCount, counter.unqualifiedWriteCount(propertyName));
|
||||
}
|
||||
|
||||
|
||||
public static void checkFunctionNotCalled(@NotNull JsNode node, @NotNull String functionName, @Nullable String exceptFunction)
|
||||
throws Exception {
|
||||
|
||||
@@ -25,10 +25,12 @@ import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
|
||||
class PropertyReferenceCollector : RecursiveJsVisitor() {
|
||||
|
||||
private val identReadSet = hashSetOf<String>()
|
||||
private val identWriteSet = hashSetOf<String>()
|
||||
private val identWriteMap = hashMapOf<String, Int>()
|
||||
|
||||
fun hasUnqualifiedReads(expectedIdent: String) = expectedIdent in identReadSet
|
||||
fun hasUnqualifiedWrites(expectedIdent: String) = expectedIdent in identWriteSet
|
||||
fun hasUnqualifiedWrites(expectedIdent: String) = expectedIdent in identWriteMap
|
||||
|
||||
fun unqualifiedWriteCount(expectedIdent: String): Int = identWriteMap[expectedIdent] ?: 0
|
||||
|
||||
override fun visitNameRef(nameRef: JsNameRef) {
|
||||
super.visitNameRef(nameRef)
|
||||
@@ -40,7 +42,7 @@ class PropertyReferenceCollector : RecursiveJsVisitor() {
|
||||
JsAstUtils.decomposeAssignment(x)?.let { (left, right) ->
|
||||
(left as? JsNameRef)?.let { nameRef ->
|
||||
assignmentToProperty = true
|
||||
identWriteSet.add(nameRef.ident)
|
||||
identWriteMap[nameRef.ident] = 1 + unqualifiedWriteCount(nameRef.ident)
|
||||
nameRef.qualifier?.accept(this)
|
||||
right.accept(this)
|
||||
}
|
||||
|
||||
+21
-12
@@ -20,6 +20,7 @@ import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.annotations.isInlineOnlyOrReifiable
|
||||
import org.jetbrains.kotlin.js.backend.ast.*
|
||||
import org.jetbrains.kotlin.js.backend.ast.metadata.exportedPackage
|
||||
import org.jetbrains.kotlin.js.backend.ast.metadata.exportedTag
|
||||
import org.jetbrains.kotlin.js.backend.ast.metadata.staticRef
|
||||
import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils
|
||||
import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils.isLibraryObject
|
||||
@@ -70,25 +71,26 @@ internal class DeclarationExporter(val context: StaticContext) {
|
||||
exportProperty(descriptor, qualifier)
|
||||
}
|
||||
else -> {
|
||||
assign(descriptor, qualifier, context.getInnerNameForDescriptor(descriptor).makeRef())
|
||||
assign(descriptor, qualifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun assign(descriptor: DeclarationDescriptor, qualifier: JsExpression, expression: JsExpression) {
|
||||
private fun assign(descriptor: DeclarationDescriptor, qualifier: JsExpression) {
|
||||
val exportedName = context.getInnerNameForDescriptor(descriptor)
|
||||
val expression = exportedName.makeRef()
|
||||
val propertyName = context.getNameForDescriptor(descriptor)
|
||||
if (propertyName.staticRef == null) {
|
||||
if (expression !is JsNameRef || expression.name !== propertyName) {
|
||||
propertyName.staticRef = expression
|
||||
}
|
||||
if (propertyName.staticRef == null && exportedName != propertyName) {
|
||||
propertyName.staticRef = expression
|
||||
}
|
||||
statements += assignment(JsNameRef(propertyName, qualifier), expression).makeStmt()
|
||||
statements += assignment(JsNameRef(propertyName, qualifier), expression).exportStatement(descriptor)
|
||||
}
|
||||
|
||||
private fun exportObject(declaration: ClassDescriptor, qualifier: JsExpression) {
|
||||
val name = context.getNameForDescriptor(declaration)
|
||||
statements += JsAstUtils.defineGetter(context.program, qualifier, name.ident,
|
||||
context.getNameForObjectInstance(declaration).makeRef())
|
||||
val expression = JsAstUtils.defineGetter(context.program, qualifier, name.ident,
|
||||
context.getNameForObjectInstance(declaration).makeRef())
|
||||
statements += expression.exportStatement(declaration)
|
||||
}
|
||||
|
||||
private fun exportProperty(declaration: PropertyDescriptor, qualifier: JsExpression) {
|
||||
@@ -98,12 +100,15 @@ internal class DeclarationExporter(val context: StaticContext) {
|
||||
val simpleProperty = JsDescriptorUtils.isSimpleFinalProperty(declaration) &&
|
||||
!TranslationUtils.shouldAccessViaFunctions(declaration)
|
||||
|
||||
val exportedName: JsName
|
||||
val getterBody: JsExpression = if (simpleProperty) {
|
||||
val accessToField = JsReturn(context.getInnerNameForDescriptor(declaration).makeRef())
|
||||
exportedName = context.getInnerNameForDescriptor(declaration)
|
||||
val accessToField = JsReturn(exportedName.makeRef())
|
||||
JsFunction(context.fragment.scope, JsBlock(accessToField), "$declaration getter")
|
||||
}
|
||||
else {
|
||||
context.getInnerNameForDescriptor(declaration.getter!!).makeRef()
|
||||
exportedName = context.getInnerNameForDescriptor(declaration.getter!!)
|
||||
exportedName.makeRef()
|
||||
}
|
||||
propertyLiteral.propertyInitializers += JsPropertyInitializer(JsNameRef("get"), getterBody)
|
||||
|
||||
@@ -122,7 +127,7 @@ internal class DeclarationExporter(val context: StaticContext) {
|
||||
propertyLiteral.propertyInitializers += JsPropertyInitializer(JsNameRef("set"), setterBody)
|
||||
}
|
||||
|
||||
statements += JsAstUtils.defineProperty(qualifier, name, propertyLiteral, context.program).makeStmt()
|
||||
statements += JsAstUtils.defineProperty(qualifier, name, propertyLiteral, context.program).exportStatement(declaration)
|
||||
}
|
||||
|
||||
private fun getLocalPackageReference(packageName: FqName): JsExpression {
|
||||
@@ -142,6 +147,10 @@ internal class DeclarationExporter(val context: StaticContext) {
|
||||
}
|
||||
return name.makeRef()
|
||||
}
|
||||
|
||||
private fun JsExpression.exportStatement(declaration: DeclarationDescriptor) = JsExpressionStatement(this).also {
|
||||
it.exportedTag = context.getTag(declaration)
|
||||
}
|
||||
}
|
||||
|
||||
private fun MemberDescriptor.shouldBeExported(force: Boolean) =
|
||||
|
||||
@@ -212,7 +212,7 @@ public final class StaticContext {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getTag(@NotNull DeclarationDescriptor descriptor) {
|
||||
public String getTag(@NotNull DeclarationDescriptor descriptor) {
|
||||
String tag;
|
||||
if (!tagCache.containsKey(descriptor)) {
|
||||
tag = SignatureUtilsKt.generateSignature(descriptor);
|
||||
|
||||
@@ -20,6 +20,7 @@ import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
||||
import org.jetbrains.kotlin.js.backend.ast.*
|
||||
import org.jetbrains.kotlin.js.backend.ast.metadata.coroutineMetadata
|
||||
import org.jetbrains.kotlin.js.backend.ast.metadata.exportedPackage
|
||||
import org.jetbrains.kotlin.js.backend.ast.metadata.exportedTag
|
||||
import org.jetbrains.kotlin.js.translate.context.Namer
|
||||
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
|
||||
|
||||
@@ -35,6 +36,7 @@ class Merger(private val rootFunction: JsFunction, val internalModuleName: JsNam
|
||||
private val classes = mutableMapOf<JsName, JsClassModel>()
|
||||
private val importedModulesImpl = mutableListOf<JsImportedModule>()
|
||||
private val exportedPackages = mutableMapOf<String, JsName>()
|
||||
private val exportedTags = mutableSetOf<String>()
|
||||
|
||||
// Add declaration and initialization statements from program fragment to resulting single program
|
||||
fun addFragment(fragment: JsProgramFragment) {
|
||||
@@ -100,6 +102,10 @@ class Merger(private val rootFunction: JsFunction, val internalModuleName: JsNam
|
||||
exportedPackages[exportedPackage] = localName
|
||||
}
|
||||
}
|
||||
else if (statement is JsExpressionStatement) {
|
||||
val exportedTag = statement.exportedTag
|
||||
if (exportedTag != null && !exportedTags.add(exportedTag)) continue
|
||||
}
|
||||
exportBlock.statements += nameMap.rename(statement)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -553,7 +553,7 @@ public final class JsAstUtils {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static JsStatement defineGetter(
|
||||
public static JsExpression defineGetter(
|
||||
@NotNull JsProgram program,
|
||||
@NotNull JsExpression receiver,
|
||||
@NotNull String name,
|
||||
@@ -561,7 +561,7 @@ public final class JsAstUtils {
|
||||
) {
|
||||
JsObjectLiteral propertyLiteral = new JsObjectLiteral(true);
|
||||
propertyLiteral.getPropertyInitializers().add(new JsPropertyInitializer(new JsNameRef("get"), body));
|
||||
return defineProperty(receiver, name, propertyLiteral, program).makeStmt();
|
||||
return defineProperty(receiver, name, propertyLiteral, program);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
// PROPERTY_WRITE_COUNT: name=foo_61zpoe$ count=1
|
||||
// FILE: a.kt
|
||||
fun foo(x: String): String = x
|
||||
|
||||
inline fun o() = foo("O")
|
||||
|
||||
// FILE: b.kt
|
||||
inline fun k() = foo("K")
|
||||
|
||||
// FILE: c.kt
|
||||
// RECOMPILE
|
||||
fun box() = o() + k()
|
||||
Reference in New Issue
Block a user