JS: reworking translator pipeline to generate multiple fragment each for source file

This commit is contained in:
Alexey Andreev
2017-02-10 18:15:35 +03:00
parent e909ef984c
commit 6711db86ee
11 changed files with 315 additions and 370 deletions
@@ -100,7 +100,7 @@ var JsName.imported by MetadataProperty(default = false)
var JsFunction.coroutineMetadata: CoroutineMetadata? by MetadataProperty(default = null) var JsFunction.coroutineMetadata: CoroutineMetadata? by MetadataProperty(default = null)
class CoroutineMetadata( data class CoroutineMetadata(
val doResumeName: JsName, val doResumeName: JsName,
val stateName: JsName, val stateName: JsName,
val exceptionStateName: JsName, val exceptionStateName: JsName,
@@ -16,8 +16,6 @@
package org.jetbrains.kotlin.js.facade; package org.jetbrains.kotlin.js.facade;
import org.jetbrains.kotlin.js.backend.ast.JsImportedModule;
import org.jetbrains.kotlin.js.backend.ast.JsProgram;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.descriptors.ModuleDescriptor; import org.jetbrains.kotlin.descriptors.ModuleDescriptor;
@@ -27,11 +25,8 @@ import org.jetbrains.kotlin.js.backend.ast.JsProgram;
import org.jetbrains.kotlin.js.config.JsConfig; import org.jetbrains.kotlin.js.config.JsConfig;
import org.jetbrains.kotlin.js.coroutine.CoroutineTransformer; import org.jetbrains.kotlin.js.coroutine.CoroutineTransformer;
import org.jetbrains.kotlin.js.facade.exceptions.TranslationException; import org.jetbrains.kotlin.js.facade.exceptions.TranslationException;
import org.jetbrains.kotlin.js.inline.JsInliner;
import org.jetbrains.kotlin.js.inline.clean.RemoveUnusedImportsKt; import org.jetbrains.kotlin.js.inline.clean.RemoveUnusedImportsKt;
import org.jetbrains.kotlin.js.inline.clean.ResolveTemporaryNamesKt; import org.jetbrains.kotlin.js.inline.clean.ResolveTemporaryNamesKt;
import org.jetbrains.kotlin.js.translate.context.StaticContext;
import org.jetbrains.kotlin.js.translate.context.TranslationContext;
import org.jetbrains.kotlin.js.translate.general.Translation; import org.jetbrains.kotlin.js.translate.general.Translation;
import org.jetbrains.kotlin.progress.ProgressIndicatorAndCompilationCanceledStatus; import org.jetbrains.kotlin.progress.ProgressIndicatorAndCompilationCanceledStatus;
import org.jetbrains.kotlin.psi.KtFile; import org.jetbrains.kotlin.psi.KtFile;
@@ -42,7 +37,6 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import static org.jetbrains.kotlin.diagnostics.DiagnosticUtils.hasError; import static org.jetbrains.kotlin.diagnostics.DiagnosticUtils.hasError;
import static org.jetbrains.kotlin.js.translate.utils.ExpandIsCallsKt.expandIsCalls;
/** /**
* An entry point of translator. * An entry point of translator.
@@ -80,11 +74,11 @@ public final class K2JSTranslator {
ModuleDescriptor moduleDescriptor = analysisResult.getModuleDescriptor(); ModuleDescriptor moduleDescriptor = analysisResult.getModuleDescriptor();
Diagnostics diagnostics = bindingTrace.getBindingContext().getDiagnostics(); Diagnostics diagnostics = bindingTrace.getBindingContext().getDiagnostics();
TranslationContext context = Translation.generateAst(bindingTrace, files, mainCallParameters, moduleDescriptor, config); JsProgram program = Translation.generateAst(bindingTrace, files, mainCallParameters, moduleDescriptor, config);
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled(); ProgressIndicatorAndCompilationCanceledStatus.checkCanceled();
if (hasError(diagnostics)) return new TranslationResult.Fail(diagnostics); if (hasError(diagnostics)) return new TranslationResult.Fail(diagnostics);
JsProgram program = JsInliner.process(context); //JsInliner.process(program);
ResolveTemporaryNamesKt.resolveTemporaryNames(program); ResolveTemporaryNamesKt.resolveTemporaryNames(program);
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled(); ProgressIndicatorAndCompilationCanceledStatus.checkCanceled();
if (hasError(diagnostics)) return new TranslationResult.Fail(diagnostics); if (hasError(diagnostics)) return new TranslationResult.Fail(diagnostics);
@@ -95,13 +89,13 @@ public final class K2JSTranslator {
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled(); ProgressIndicatorAndCompilationCanceledStatus.checkCanceled();
if (hasError(diagnostics)) return new TranslationResult.Fail(diagnostics); if (hasError(diagnostics)) return new TranslationResult.Fail(diagnostics);
expandIsCalls(program, context); //expandIsCalls(program, context);
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled(); ProgressIndicatorAndCompilationCanceledStatus.checkCanceled();
List<String> importedModules = new ArrayList<>(); List<String> importedModules = new ArrayList<>();
for (JsImportedModule module : context.getImportedModules()) { /*for (JsImportedModule module : context.getImportedModules()) {
importedModules.add(module.getExternalName()); importedModules.add(module.getExternalName());
} }*/
return new TranslationResult.Success(config, files, program, diagnostics, importedModules, moduleDescriptor, return new TranslationResult.Success(config, files, program, diagnostics, importedModules, moduleDescriptor,
bindingTrace.getBindingContext()); bindingTrace.getBindingContext());
} }
@@ -98,7 +98,7 @@ internal class DeclarationExporter(val context: StaticContext) {
val getterBody: JsExpression = if (simpleProperty) { val getterBody: JsExpression = if (simpleProperty) {
val accessToField = JsReturn(context.getInnerNameForDescriptor(declaration).makeRef()) val accessToField = JsReturn(context.getInnerNameForDescriptor(declaration).makeRef())
JsFunction(context.rootFunction.scope, JsBlock(accessToField), "$declaration getter") JsFunction(context.fragment.scope, JsBlock(accessToField), "$declaration getter")
} }
else { else {
context.getInnerNameForDescriptor(declaration.getter!!).makeRef() context.getInnerNameForDescriptor(declaration.getter!!).makeRef()
@@ -108,7 +108,7 @@ internal class DeclarationExporter(val context: StaticContext) {
if (declaration.isVar) { if (declaration.isVar) {
val setterBody: JsExpression = if (simpleProperty) { val setterBody: JsExpression = if (simpleProperty) {
val statements = mutableListOf<JsStatement>() val statements = mutableListOf<JsStatement>()
val function = JsFunction(context.rootFunction.scope, JsBlock(statements), "$declaration setter") val function = JsFunction(context.fragment.scope, JsBlock(statements), "$declaration setter")
val valueName = function.scope.declareTemporaryName("value") val valueName = function.scope.declareTemporaryName("value")
function.parameters += JsParameter(valueName) function.parameters += JsParameter(valueName)
statements += assignment(context.getInnerNameForDescriptor(declaration).makeRef(), valueName.makeRef()).makeStmt() statements += assignment(context.getInnerNameForDescriptor(declaration).makeRef(), valueName.makeRef()).makeStmt()
@@ -125,11 +125,11 @@ internal class DeclarationExporter(val context: StaticContext) {
private fun getLocalPackageReference(packageName: FqName): JsExpression { private fun getLocalPackageReference(packageName: FqName): JsExpression {
if (packageName.isRoot) { if (packageName.isRoot) {
return context.rootFunction.scope.declareName(Namer.getRootPackageName()).makeRef() return context.fragment.scope.declareName(Namer.getRootPackageName()).makeRef()
} }
var name = localPackageNames[packageName] var name = localPackageNames[packageName]
if (name == null) { if (name == null) {
name = context.rootFunction.scope.declareTemporaryName("package$" + packageName.shortName().asString()) name = context.fragment.scope.declareTemporaryName("package$" + packageName.shortName().asString())
localPackageNames.put(packageName, name) localPackageNames.put(packageName, name)
val parentRef = getLocalPackageReference(packageName.parent()) val parentRef = getLocalPackageReference(packageName.parent())
@@ -36,12 +36,11 @@ import org.jetbrains.kotlin.js.naming.NameSuggestion;
import org.jetbrains.kotlin.js.naming.SuggestedName; import org.jetbrains.kotlin.js.naming.SuggestedName;
import org.jetbrains.kotlin.js.translate.context.generator.Generator; import org.jetbrains.kotlin.js.translate.context.generator.Generator;
import org.jetbrains.kotlin.js.translate.context.generator.Rule; import org.jetbrains.kotlin.js.translate.context.generator.Rule;
import org.jetbrains.kotlin.js.translate.declaration.InterfaceFunctionCopier; import org.jetbrains.kotlin.js.translate.declaration.ClassModelGenerator;
import org.jetbrains.kotlin.js.translate.intrinsic.Intrinsics; import org.jetbrains.kotlin.js.translate.intrinsic.Intrinsics;
import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils; import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils;
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils; import org.jetbrains.kotlin.js.translate.utils.JsAstUtils;
import org.jetbrains.kotlin.name.FqName; import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.name.FqNameUnsafe;
import org.jetbrains.kotlin.resolve.BindingContext; import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.BindingTrace; import org.jetbrains.kotlin.resolve.BindingTrace;
import org.jetbrains.kotlin.resolve.DescriptorUtils; import org.jetbrains.kotlin.resolve.DescriptorUtils;
@@ -63,20 +62,12 @@ import static org.jetbrains.kotlin.js.translate.utils.JsDescriptorUtils.getSuper
* Aggregates all the static parts of the context. * Aggregates all the static parts of the context.
*/ */
public final class StaticContext { public final class StaticContext {
public static StaticContext generateStaticContext(
@NotNull BindingTrace bindingTrace,
@NotNull JsConfig config,
@NotNull ModuleDescriptor moduleDescriptor) {
JsProgram program = new JsProgram();
Namer namer = Namer.newInstance(program.getRootScope());
JsFunction rootFunction = JsAstUtils.createFunctionWithEmptyBody(program.getScope());
return new StaticContext(program, rootFunction, bindingTrace, namer, program.getRootScope(), config, moduleDescriptor);
}
@NotNull @NotNull
private final JsProgram program; private final JsProgram program;
@NotNull
private final JsProgramFragment fragment;
@NotNull @NotNull
private final BindingTrace bindingTrace; private final BindingTrace bindingTrace;
@NotNull @NotNull
@@ -132,51 +123,40 @@ public final class StaticContext {
@NotNull @NotNull
private final JsScope rootPackageScope; private final JsScope rootPackageScope;
@NotNull
private JsFunction rootFunction;
@NotNull
private final List<JsStatement> declarationStatements = new ArrayList<>();
@NotNull
private final List<JsStatement> topLevelStatements = new ArrayList<>();
@NotNull @NotNull
private final List<JsStatement> importStatements = new ArrayList<>(); private final List<JsStatement> importStatements = new ArrayList<>();
@NotNull @NotNull
private final DeclarationExporter exporter = new DeclarationExporter(this); private final DeclarationExporter exporter = new DeclarationExporter(this);
@NotNull
private final Set<ClassDescriptor> classes = new LinkedHashSet<>();
@NotNull @NotNull
private final Map<FqName, JsScope> packageScopes = new HashMap<>(); private final Map<FqName, JsScope> packageScopes = new HashMap<>();
//TODO: too many parameters in constructor @NotNull
private StaticContext( private final ClassModelGenerator classModelGenerator;
@NotNull JsProgram program,
@NotNull JsFunction rootFunction, public StaticContext(
@NotNull BindingTrace bindingTrace, @NotNull BindingTrace bindingTrace,
@NotNull Namer namer,
@NotNull JsScope rootScope,
@NotNull JsConfig config, @NotNull JsConfig config,
@NotNull ModuleDescriptor moduleDescriptor @NotNull ModuleDescriptor moduleDescriptor
) { ) {
this.program = program; program = new JsProgram();
this.rootFunction = rootFunction; JsFunction rootFunction = JsAstUtils.createFunctionWithEmptyBody(program.getScope());
fragment = new JsProgramFragment(rootFunction.getScope());
this.bindingTrace = bindingTrace; this.bindingTrace = bindingTrace;
this.namer = namer; this.namer = Namer.newInstance(program.getRootScope());
this.intrinsics = new Intrinsics(this); this.intrinsics = new Intrinsics(this);
this.rootScope = rootScope; this.rootScope = fragment.getScope();
this.config = config; this.config = config;
this.currentModule = moduleDescriptor; this.currentModule = moduleDescriptor;
this.rootFunction = rootFunction;
rootPackageScope = new JsObjectScope(rootScope, "<root package>"); rootPackageScope = new JsObjectScope(rootScope, "<root package>");
JsName kotlinName = rootScope.declareName(Namer.KOTLIN_NAME); JsName kotlinName = rootScope.declareName(Namer.KOTLIN_NAME);
importedModules.put(new JsImportedModuleKey(Namer.KOTLIN_LOWER_NAME, null), importedModules.put(new JsImportedModuleKey(Namer.KOTLIN_LOWER_NAME, null),
new JsImportedModule(Namer.KOTLIN_LOWER_NAME, kotlinName, null)); new JsImportedModule(Namer.KOTLIN_LOWER_NAME, kotlinName, null));
classModelGenerator = new ClassModelGenerator(this);
} }
@NotNull @NotNull
@@ -184,6 +164,11 @@ public final class StaticContext {
return program; return program;
} }
@NotNull
public JsProgramFragment getFragment() {
return fragment;
}
@NotNull @NotNull
public BindingTrace getBindingTrace() { public BindingTrace getBindingTrace() {
return bindingTrace; return bindingTrace;
@@ -408,7 +393,7 @@ public final class StaticContext {
String baseName = NameSuggestion.sanitizeName(suggested.getNames().get(0)); String baseName = NameSuggestion.sanitizeName(suggested.getNames().get(0));
if (suggested.getDescriptor() instanceof LocalVariableDescriptor || if (suggested.getDescriptor() instanceof LocalVariableDescriptor ||
suggested.getDescriptor() instanceof ValueParameterDescriptor suggested.getDescriptor() instanceof ValueParameterDescriptor
) { ) {
name = scope.declareTemporaryName(baseName); name = scope.declareTemporaryName(baseName);
} }
else { else {
@@ -474,7 +459,7 @@ public final class StaticContext {
// since local scope inherited from global scope. // since local scope inherited from global scope.
// TODO: remove prefix when problem with scopes is solved // TODO: remove prefix when problem with scopes is solved
JsName result = rootFunction.getScope().declareTemporaryName(suggestedName); JsName result = fragment.getScope().declareTemporaryName(suggestedName);
MetadataProperties.setImported(result, true); MetadataProperties.setImported(result, true);
importStatements.add(JsAstUtils.newVar(result, declaration)); importStatements.add(JsAstUtils.newVar(result, declaration));
return result; return result;
@@ -485,7 +470,7 @@ public final class StaticContext {
ModuleDescriptor module = DescriptorUtilsKt.getModule(descriptor); ModuleDescriptor module = DescriptorUtilsKt.getModule(descriptor);
JsName name = module != currentModule ? JsName name = module != currentModule ?
importDeclaration(suggestedName, getQualifiedReference(descriptor)) : importDeclaration(suggestedName, getQualifiedReference(descriptor)) :
rootFunction.getScope().declareTemporaryName(suggestedName); fragment.getScope().declareTemporaryName(suggestedName);
MetadataProperties.setDescriptor(name, descriptor); MetadataProperties.setDescriptor(name, descriptor);
return name; return name;
} }
@@ -611,16 +596,16 @@ public final class StaticContext {
} }
return getScopeForDescriptor(superclass).innerObjectScope("Scope for class " + descriptor.getName()); return getScopeForDescriptor(superclass).innerObjectScope("Scope for class " + descriptor.getName());
}; };
Rule<JsScope> generateNewScopesForPackageDescriptors = descriptor -> rootFunction.getScope(); Rule<JsScope> generateNewScopesForPackageDescriptors = descriptor -> fragment.getScope();
//TODO: never get there //TODO: never get there
Rule<JsScope> generateInnerScopesForMembers = Rule<JsScope> generateInnerScopesForMembers =
descriptor -> rootFunction.getScope().innerObjectScope("Scope for member " + descriptor.getName()); descriptor -> fragment.getScope().innerObjectScope("Scope for member " + descriptor.getName());
Rule<JsScope> createFunctionObjectsForCallableDescriptors = descriptor -> { Rule<JsScope> createFunctionObjectsForCallableDescriptors = descriptor -> {
if (!(descriptor instanceof CallableDescriptor)) { if (!(descriptor instanceof CallableDescriptor)) {
return null; return null;
} }
JsFunction correspondingFunction = JsAstUtils.createFunctionWithEmptyBody(rootFunction.getScope()); JsFunction correspondingFunction = JsAstUtils.createFunctionWithEmptyBody(fragment.getScope());
assert (!scopeToFunction.containsKey(correspondingFunction.getScope())) : "Scope to function value overridden for " + descriptor; assert (!scopeToFunction.containsKey(correspondingFunction.getScope())) : "Scope to function value overridden for " + descriptor;
scopeToFunction.put(correspondingFunction.getScope(), correspondingFunction); scopeToFunction.put(correspondingFunction.getScope(), correspondingFunction);
return correspondingFunction.getScope(); return correspondingFunction.getScope();
@@ -712,23 +697,18 @@ public final class StaticContext {
return deferredCallSites; return deferredCallSites;
} }
@NotNull
public JsFunction getRootFunction() {
return rootFunction;
}
@NotNull @NotNull
public List<JsStatement> getTopLevelStatements() { public List<JsStatement> getTopLevelStatements() {
return topLevelStatements; return fragment.getInitializerBlock().getStatements();
} }
@NotNull @NotNull
public List<JsStatement> getDeclarationStatements() { public List<JsStatement> getDeclarationStatements() {
return declarationStatements; return fragment.getDeclarationBlock().getStatements();
} }
public void addClass(@NotNull ClassDescriptor classDescriptor) { public void addClass(@NotNull ClassDescriptor classDescriptor) {
classes.add(classDescriptor); fragment.getClasses().put(getInnerNameForDescriptor(classDescriptor), classModelGenerator.generateClassModel(classDescriptor));
} }
public void export(@NotNull MemberDescriptor descriptor, boolean force) { public void export(@NotNull MemberDescriptor descriptor, boolean force) {
@@ -745,23 +725,23 @@ public final class StaticContext {
return currentModule; return currentModule;
} }
public void postProcess() { /*public void postProcess() {
addInterfaceDefaultMethods(); addInterfaceDefaultMethods();
rootFunction.getBody().getStatements().addAll(importStatements); rootFunction.getBody().getStatements().addAll(importStatements);
addClassPrototypes(); addClassPrototypes();
rootFunction.getBody().getStatements().addAll(declarationStatements); rootFunction.getBody().getStatements().addAll(declarationStatements);
rootFunction.getBody().getStatements().addAll(exporter.getStatements()); rootFunction.getBody().getStatements().addAll(exporter.getStatements());
rootFunction.getBody().getStatements().addAll(topLevelStatements); rootFunction.getBody().getStatements().addAll(topLevelStatements);
} }*/
private void addClassPrototypes() { /*private void addClassPrototypes() {
Set<ClassDescriptor> visited = new HashSet<>(); Set<ClassDescriptor> visited = new HashSet<>();
for (ClassDescriptor cls : classes) { for (ClassDescriptor cls : classes) {
addClassPrototypes(cls, visited); addClassPrototypes(cls, visited);
} }
} }*/
private void addClassPrototypes(@NotNull ClassDescriptor cls, @NotNull Set<ClassDescriptor> visited) { /*private void addClassPrototypes(@NotNull ClassDescriptor cls, @NotNull Set<ClassDescriptor> visited) {
if (!visited.add(cls)) return; if (!visited.add(cls)) return;
if (DescriptorUtilsKt.getModule(cls) != currentModule) return; if (DescriptorUtilsKt.getModule(cls) != currentModule) return;
if (isNativeObject(cls) || isLibraryObject(cls)) return; if (isNativeObject(cls) || isLibraryObject(cls)) return;
@@ -789,19 +769,5 @@ public final class StaticContext {
JsExpression constructorRef = new JsNameRef("constructor", prototype.deepCopy()); JsExpression constructorRef = new JsNameRef("constructor", prototype.deepCopy());
statements.add(JsAstUtils.assignment(constructorRef, classRef.deepCopy()).makeStmt()); statements.add(JsAstUtils.assignment(constructorRef, classRef.deepCopy()).makeStmt());
} }
} }*/
private void addInterfaceDefaultMethods() {
new InterfaceFunctionCopier(this).copyInterfaceFunctions(classes);
}
public boolean isBuiltinModule() {
for (ClassDescriptor cls : classes) {
FqNameUnsafe fqn = DescriptorUtils.getFqName(cls);
if ("kotlin.Enum".equals(fqn.asString())) {
return true;
}
}
return false;
}
} }
@@ -69,9 +69,9 @@ public class TranslationContext {
private final VariableDescriptor continuationParameterDescriptor; private final VariableDescriptor continuationParameterDescriptor;
@NotNull @NotNull
public static TranslationContext rootContext(@NotNull StaticContext staticContext, @NotNull JsFunction rootFunction) { public static TranslationContext rootContext(@NotNull StaticContext staticContext) {
JsBlock block = new JsBlock(staticContext.getTopLevelStatements()); DynamicContext rootDynamicContext = DynamicContext.rootContext(
DynamicContext rootDynamicContext = DynamicContext.rootContext(rootFunction.getScope(), block); staticContext.getFragment().getScope(), staticContext.getFragment().getInitializerBlock());
AliasingContext rootAliasingContext = AliasingContext.getCleanContext(); AliasingContext rootAliasingContext = AliasingContext.getCleanContext();
return new TranslationContext(null, staticContext, rootDynamicContext, rootAliasingContext, null, null); return new TranslationContext(null, staticContext, rootDynamicContext, rootAliasingContext, null, null);
} }
@@ -544,7 +544,7 @@ public class TranslationContext {
if (result == null && if (result == null &&
classOrConstructor instanceof ConstructorDescriptor && classOrConstructor instanceof ConstructorDescriptor &&
((ConstructorDescriptor) classOrConstructor).isPrimary() ((ConstructorDescriptor) classOrConstructor).isPrimary()
) { ) {
result = staticContext.getClassOrConstructorClosure((ClassDescriptor) classOrConstructor.getContainingDeclaration()); result = staticContext.getClassOrConstructorClosure((ClassDescriptor) classOrConstructor.getContainingDeclaration());
} }
return result; return result;
@@ -645,7 +645,7 @@ public class TranslationContext {
@NotNull @NotNull
public JsName createGlobalName(@NotNull String suggestedName) { public JsName createGlobalName(@NotNull String suggestedName) {
return staticContext.getRootFunction().getScope().declareTemporaryName(suggestedName); return staticContext.getFragment().getScope().declareTemporaryName(suggestedName);
} }
@NotNull @NotNull
@@ -655,7 +655,7 @@ public class TranslationContext {
@NotNull @NotNull
public JsFunction createRootScopedFunction(@NotNull String description) { public JsFunction createRootScopedFunction(@NotNull String description) {
return new JsFunction(staticContext.getRootFunction().getScope(), new JsBlock(), description); return new JsFunction(staticContext.getFragment().getScope(), new JsBlock(), description);
} }
public void addClass(@NotNull ClassDescriptor classDescriptor) { public void addClass(@NotNull ClassDescriptor classDescriptor) {
@@ -0,0 +1,30 @@
/*
* 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.js.translate.declaration
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.js.backend.ast.JsClassModel
import org.jetbrains.kotlin.js.translate.context.StaticContext
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassNotAny
class ClassModelGenerator(val context: StaticContext) {
fun generateClassModel(descriptor: ClassDescriptor): JsClassModel {
val superName = descriptor.getSuperClassNotAny()?.let { context.getInnerNameForDescriptor(it) }
val model = JsClassModel(context.getInnerNameForDescriptor(descriptor), superName)
return model
}
}
@@ -1,195 +0,0 @@
/*
* Copyright 2010-2016 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.js.translate.declaration
import org.jetbrains.kotlin.js.backend.ast.JsInvocation
import org.jetbrains.kotlin.js.backend.ast.JsName
import org.jetbrains.kotlin.js.backend.ast.JsNameRef
import org.jetbrains.kotlin.backend.common.bridges.generateBridgesForFunctionDescriptor
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.js.translate.context.Namer
import org.jetbrains.kotlin.js.translate.context.StaticContext
import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils.isNativeObject
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils.prototypeOf
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils.pureFqn
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.descriptorUtil.*
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import org.jetbrains.kotlin.utils.DFS
import org.jetbrains.kotlin.utils.identity
class InterfaceFunctionCopier(val context: StaticContext) {
private val classModels = mutableMapOf<ClassDescriptor, ClassModel>()
fun copyInterfaceFunctions(classes: Collection<ClassDescriptor>) {
val orderedClasses = DFS.topologicalOrder(classes) { current -> DescriptorUtils.getSuperclassDescriptors(current) }.asReversed()
for (classDescriptor in orderedClasses) {
addInterfaceDefaultMembers(classDescriptor, context)
}
}
private fun addInterfaceDefaultMembers(descriptor: ClassDescriptor, context: StaticContext) {
// optimization: don't do anything for native declarations
if (isNativeObject(descriptor)) return
val classModel = ClassModel(descriptor)
classModels[descriptor] = classModel
val members = descriptor.unsubstitutedMemberScope
.getContributedDescriptors(DescriptorKindFilter.FUNCTIONS)
.mapNotNull { it as? CallableMemberDescriptor }
for (member in members.filter { it.kind.isReal }) {
val names = generateAllNames(member, context)
val identifiers = names.map { it.ident }
when (member) {
is FunctionDescriptor -> {
if (member.hasOrInheritsParametersWithDefaultValue()) {
if (member.hasOwnParametersWithDefaultValue()) {
classModel.copiedFunctions += identifiers.map { it }
}
if (member.modality != Modality.ABSTRACT) {
classModel.copiedFunctions += identifiers.map { it + Namer.DEFAULT_PARAMETER_IMPLEMENTOR_SUFFIX }
}
}
else if (member.modality != Modality.ABSTRACT) {
classModel.copiedFunctions += identifiers
}
}
is PropertyDescriptor -> {
if (member.modality != Modality.ABSTRACT) {
classModel.copiedProperties += identifiers
}
}
}
}
for (member in members.filterIsInstance<FunctionDescriptor>()) {
moveDefaultFunction(member, context)
}
for (member in members.filterIsInstance<FunctionDescriptor>()
.filter { it.kind.isReal && it.hasOwnParametersWithDefaultValue() }) {
val names = generateAllNames(member, context)
val identifiers = names.map { it.ident }
classModel.copiedFunctions += identifiers
}
val superModels = DescriptorUtils.getSuperclassDescriptors(descriptor)
.filter { it.kind == ClassKind.INTERFACE && !isNativeObject(it) }
.map { classModels[it]!! }
for (superModel in superModels) {
for (name in superModel.copiedFunctions) {
if (classModel.copiedFunctions.add(name)) {
addDefaultMethodFromInterface(name, name, superModel.descriptor, descriptor, context)
}
}
for (name in superModel.copiedProperties) {
if (classModel.copiedProperties.add(name)) {
addDefaultPropertyFromInterface(name, superModel.descriptor, descriptor, context)
}
}
}
}
private fun moveDefaultFunction(function: FunctionDescriptor, context: StaticContext) {
if (function.kind.isReal || function.modality == Modality.ABSTRACT) return
val overriddenWithDefaultArg = function.overriddenDescriptors
.firstOrNull { it.hasOwnParametersWithDefaultValue() } ?: return
if (!DescriptorUtils.isInterface(overriddenWithDefaultArg.containingDeclaration)) return
val fakeImplementation = function.overriddenDescriptors.first { it.modality != Modality.ABSTRACT }
val interfaceFunctionName = context.getNameForDescriptor(overriddenWithDefaultArg).ident
val targetName = interfaceFunctionName + Namer.DEFAULT_PARAMETER_IMPLEMENTOR_SUFFIX
val sourceName = context.getNameForDescriptor(fakeImplementation).ident
addDefaultMethodFromInterface(sourceName, targetName, fakeImplementation.containingDeclaration as ClassDescriptor,
function.containingDeclaration as ClassDescriptor, context)
val overrideNames = generateAllNames(function, context).map { it.ident }
val namesFromBaseClass = function.overriddenDescriptors
.filter { !DescriptorUtils.isInterface(it.containingDeclaration) }
.flatMap { generateAllNames(it, context).map { it.ident }.asIterable() }
.distinct()
for (name in overrideNames.filter { it in namesFromBaseClass }) {
addDefaultMethodFromInterface(interfaceFunctionName, name, overriddenWithDefaultArg.containingDeclaration as ClassDescriptor,
function.containingDeclaration as ClassDescriptor, context)
}
}
private fun addDefaultMethodFromInterface(
sourceName: String,
targetName: String,
sourceDescriptor: ClassDescriptor,
targetDescriptor: ClassDescriptor,
context: StaticContext
) {
if (targetDescriptor.module != context.currentModule) return
val targetPrototype = prototypeOf(pureFqn(context.getInnerNameForDescriptor(targetDescriptor), null))
val sourcePrototype = prototypeOf(pureFqn(context.getInnerNameForDescriptor(sourceDescriptor), null))
val targetFunction = JsNameRef(targetName, targetPrototype)
val sourceFunction = JsNameRef(sourceName, sourcePrototype)
context.declarationStatements += JsAstUtils.assignment(targetFunction, sourceFunction).makeStmt()
}
private fun addDefaultPropertyFromInterface(
name: String,
sourceDescriptor: ClassDescriptor,
targetDescriptor: ClassDescriptor,
context: StaticContext
) {
if (targetDescriptor.module != context.currentModule) return
val targetPrototype = prototypeOf(pureFqn(context.getInnerNameForDescriptor(targetDescriptor), null))
val sourcePrototype = prototypeOf(pureFqn(context.getInnerNameForDescriptor(sourceDescriptor), null))
val nameLiteral = context.program.getStringLiteral(name)
val getPropertyDescriptor = JsInvocation(JsNameRef("getOwnPropertyDescriptor", "Object"), sourcePrototype, nameLiteral)
val defineProperty = JsAstUtils.defineProperty(targetPrototype, name, getPropertyDescriptor, context.program)
context.declarationStatements += defineProperty.makeStmt()
}
private fun generateAllNames(member: CallableMemberDescriptor, context: StaticContext): Sequence<JsName> {
return (generateBridges(member) + member).map { context.getNameForDescriptor(it) }.distinctBy { it.ident }
}
private fun generateBridges(member: CallableMemberDescriptor): Sequence<CallableMemberDescriptor> = when (member) {
is FunctionDescriptor -> {
generateBridgesForFunctionDescriptor(member, identity()) { false }
.map { it.from }
.asSequence()
}
is PropertyDescriptor -> generateBridgesForFunctionDescriptor(member.getter!!, identity()) { false }
.map { it.from }
.map { (it as PropertyAccessorDescriptor).correspondingProperty }
.asSequence()
else -> error("Expected either be function or property: $member")
}
private class ClassModel(val descriptor: ClassDescriptor) {
val copiedFunctions = mutableSetOf<String>()
val copiedProperties = mutableSetOf<String>()
}
}
@@ -1,62 +0,0 @@
/*
* Copyright 2010-2015 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.js.translate.declaration;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.js.facade.exceptions.TranslationRuntimeException;
import org.jetbrains.kotlin.js.translate.context.TranslationContext;
import org.jetbrains.kotlin.js.translate.general.AbstractTranslator;
import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils;
import org.jetbrains.kotlin.js.translate.utils.BindingUtils;
import org.jetbrains.kotlin.psi.KtDeclaration;
import org.jetbrains.kotlin.psi.KtFile;
import java.util.Collection;
public final class PackageDeclarationTranslator extends AbstractTranslator {
private final Iterable<KtFile> files;
public static void translateFiles(@NotNull Collection<KtFile> files, @NotNull TranslationContext context) {
new PackageDeclarationTranslator(files, context).translate();
}
private PackageDeclarationTranslator(@NotNull Iterable<KtFile> files, @NotNull TranslationContext context) {
super(context);
this.files = files;
}
private void translate() {
for (KtFile file : files) {
FileDeclarationVisitor fileVisitor = new FileDeclarationVisitor(context());
try {
for (KtDeclaration declaration : file.getDeclarations()) {
if (!AnnotationsUtils.isPredefinedObject(BindingUtils.getDescriptorForElement(bindingContext(), declaration))) {
declaration.accept(fileVisitor, context());
}
}
}
catch (TranslationRuntimeException e) {
throw e;
}
catch (RuntimeException | AssertionError e) {
throw new TranslationRuntimeException(file, e);
}
}
}
}
@@ -0,0 +1,93 @@
/*
* 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.js.translate.general
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.backend.ast.metadata.coroutineMetadata
import org.jetbrains.kotlin.js.inline.clean.resolveTemporaryNames
import org.jetbrains.kotlin.js.translate.context.Namer
class Merger(private val rootFunction: JsFunction, val internalModuleName: JsName) {
// Maps unique signature (see generateSignature) to names
private val nameTable = mutableMapOf<String, JsName>()
private val importBlock = JsGlobalBlock()
private val declarationBlock = JsGlobalBlock()
private val initializerBlock = JsGlobalBlock()
private val exportBlock = JsGlobalBlock()
// Add declaration and initialization statements from program fragment to resulting single program
fun addFragment(fragment: JsProgramFragment) {
val nameMap = buildNameMap(fragment)
nameMap.rename(fragment)
declarationBlock.statements += fragment.declarationBlock
initializerBlock.statements += fragment.initializerBlock
exportBlock.statements += fragment.exportBlock
}
private fun Map<JsName, JsName>.rename(name: JsName): JsName = getOrElse(name) { name }
// Builds mapping to map names from different fragments into single name when they denote single declaration
private fun buildNameMap(fragment: JsProgramFragment): Map<JsName, JsName> {
val nameMap = mutableMapOf<JsName, JsName>()
for (nameBinding in fragment.nameBindings) {
nameMap[nameBinding.name] = nameTable.getOrPut(nameBinding.key) {
fragment.scope.declareTemporaryName(nameBinding.name.ident).also { it.copyMetadataFrom(nameBinding.name) }
}
}
fragment.scope.findName(Namer.getRootPackageName())?.let { nameMap[it] = internalModuleName }
return nameMap
}
private fun Map<JsName, JsName>.rename(fragment: JsProgramFragment) {
rename(fragment.declarationBlock)
rename(fragment.exportBlock)
rename(fragment.initializerBlock)
}
private fun <T: JsNode> Map<JsName, JsName>.rename(node: T): T {
node.accept(object : RecursiveJsVisitor() {
override fun visitElement(node: JsNode) {
super.visitElement(node)
if (node is HasName) {
node.name = node.name?.let { name -> rename(name) }
}
if (node is JsFunction) {
val coroutineMetadata = node.coroutineMetadata
if (coroutineMetadata != null) {
node.coroutineMetadata = coroutineMetadata.copy(
baseClassRef = rename(coroutineMetadata.baseClassRef),
suspendObjectRef = rename(coroutineMetadata.suspendObjectRef)
)
}
}
}
})
return node
}
// Adds different boilerplate code (like imports, class prototypes, etc) to resulting program.
fun merge() {
rootFunction.body.statements.apply {
this += importBlock.statements
this += declarationBlock.statements
this += initializerBlock.statements
}
rootFunction.body.resolveTemporaryNames()
}
}
@@ -33,18 +33,17 @@ import org.jetbrains.kotlin.js.translate.context.Namer;
import org.jetbrains.kotlin.js.translate.context.StaticContext; import org.jetbrains.kotlin.js.translate.context.StaticContext;
import org.jetbrains.kotlin.js.translate.context.TemporaryVariable; import org.jetbrains.kotlin.js.translate.context.TemporaryVariable;
import org.jetbrains.kotlin.js.translate.context.TranslationContext; import org.jetbrains.kotlin.js.translate.context.TranslationContext;
import org.jetbrains.kotlin.js.translate.declaration.PackageDeclarationTranslator; import org.jetbrains.kotlin.js.translate.declaration.FileDeclarationVisitor;
import org.jetbrains.kotlin.js.translate.expression.ExpressionVisitor; import org.jetbrains.kotlin.js.translate.expression.ExpressionVisitor;
import org.jetbrains.kotlin.js.translate.expression.PatternTranslator; import org.jetbrains.kotlin.js.translate.expression.PatternTranslator;
import org.jetbrains.kotlin.js.translate.test.JSTestGenerator; import org.jetbrains.kotlin.js.translate.test.JSTestGenerator;
import org.jetbrains.kotlin.js.translate.test.JSTester; import org.jetbrains.kotlin.js.translate.test.JSTester;
import org.jetbrains.kotlin.js.translate.test.QUnitTester; import org.jetbrains.kotlin.js.translate.test.QUnitTester;
import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils;
import org.jetbrains.kotlin.js.translate.utils.BindingUtils;
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils; import org.jetbrains.kotlin.js.translate.utils.JsAstUtils;
import org.jetbrains.kotlin.js.translate.utils.mutator.AssignToExpressionMutator; import org.jetbrains.kotlin.js.translate.utils.mutator.AssignToExpressionMutator;
import org.jetbrains.kotlin.psi.KtExpression; import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.psi.KtFile;
import org.jetbrains.kotlin.psi.KtNamedFunction;
import org.jetbrains.kotlin.psi.KtUnaryExpression;
import org.jetbrains.kotlin.resolve.BindingTrace; import org.jetbrains.kotlin.resolve.BindingTrace;
import org.jetbrains.kotlin.resolve.bindingContextUtil.BindingContextUtilsKt; import org.jetbrains.kotlin.resolve.bindingContextUtil.BindingContextUtilsKt;
import org.jetbrains.kotlin.resolve.constants.CompileTimeConstant; import org.jetbrains.kotlin.resolve.constants.CompileTimeConstant;
@@ -71,6 +70,7 @@ import static org.jetbrains.kotlin.js.translate.utils.mutator.LastExpressionMuta
* Goal is to simplify interaction between translators. * Goal is to simplify interaction between translators.
*/ */
public final class Translation { public final class Translation {
private static final String ENUM_SIGNATURE = "kotlin$Enum";
private Translation() { private Translation() {
} }
@@ -234,7 +234,7 @@ public final class Translation {
} }
@NotNull @NotNull
public static TranslationContext generateAst( public static JsProgram generateAst(
@NotNull BindingTrace bindingTrace, @NotNull BindingTrace bindingTrace,
@NotNull Collection<KtFile> files, @NotNull Collection<KtFile> files,
@NotNull MainCallParameters mainCallParameters, @NotNull MainCallParameters mainCallParameters,
@@ -253,38 +253,49 @@ public final class Translation {
} }
@NotNull @NotNull
private static TranslationContext doGenerateAst( private static JsProgram doGenerateAst(
@NotNull BindingTrace bindingTrace, @NotNull BindingTrace bindingTrace,
@NotNull Collection<KtFile> files, @NotNull Collection<KtFile> files,
@NotNull MainCallParameters mainCallParameters, @NotNull MainCallParameters mainCallParameters,
@NotNull ModuleDescriptor moduleDescriptor, @NotNull ModuleDescriptor moduleDescriptor,
@NotNull JsConfig config @NotNull JsConfig config
) { ) {
StaticContext staticContext = StaticContext.generateStaticContext(bindingTrace, config, moduleDescriptor); JsProgram program = new JsProgram();
JsProgram program = staticContext.getProgram(); JsFunction rootFunction = new JsFunction(program.getRootScope(), new JsBlock(), "root function");
JsName rootPackageName = program.getRootScope().declareName(Namer.getRootPackageName()); Merger merger = new Merger(rootFunction);
List<JsProgramFragment> fragments = new ArrayList<JsProgramFragment>();
for (KtFile file : files) {
StaticContext staticContext = new StaticContext(bindingTrace, config, moduleDescriptor);
TranslationContext context = TranslationContext.rootContext(staticContext);
translateFile(context, file);
fragments.add(staticContext.getFragment());
merger.addFragment(staticContext.getFragment());
}
merger.merge();
JsFunction rootFunction = staticContext.getRootFunction();
JsBlock rootBlock = rootFunction.getBody(); JsBlock rootBlock = rootFunction.getBody();
List<JsStatement> statements = rootBlock.getStatements(); List<JsStatement> statements = rootBlock.getStatements();
program.getScope().declareName("_"); program.getScope().declareName("_");
TranslationContext context = TranslationContext.rootContext(staticContext, rootFunction); //staticContext.postProcess();
PackageDeclarationTranslator.translateFiles(files, context);
staticContext.postProcess();
statements.add(0, program.getStringLiteral("use strict").makeStmt()); statements.add(0, program.getStringLiteral("use strict").makeStmt());
if (!staticContext.isBuiltinModule()) { if (!isBuiltinModule(fragments)) {
defineModule(context, statements, config.getModuleId()); defineModule(program, statements, config.getModuleId());
} }
mayBeGenerateTests(files, rootBlock, context); //mayBeGenerateTests(files, rootBlock, context);
JsName rootPackageName = program.getScope().declareName(Namer.getRootPackageName());
rootFunction.getParameters().add(new JsParameter((rootPackageName))); rootFunction.getParameters().add(new JsParameter((rootPackageName)));
// Invoke function passing modules as arguments // Invoke function passing modules as arguments
// This should help minifier tool to recognize references to these modules as local variables and make them shorter. // This should help minifier tool to recognize references to these modules as local variables and make them shorter.
List<JsImportedModule> importedModuleList = new ArrayList<>(); List<JsImportedModule> importedModuleList = new ArrayList<>();
/*
for (JsImportedModule importedModule : staticContext.getImportedModules()) { for (JsImportedModule importedModule : staticContext.getImportedModules()) {
rootFunction.getParameters().add(new JsParameter(importedModule.getInternalName())); rootFunction.getParameters().add(new JsParameter(importedModule.getInternalName()));
importedModuleList.add(importedModule); importedModuleList.add(importedModule);
@@ -296,6 +307,7 @@ public final class Translation {
statements.add(statement); statements.add(statement);
} }
} }
*/
statements.add(new JsReturn(rootPackageName.makeRef())); statements.add(new JsReturn(rootPackageName.makeRef()));
@@ -303,13 +315,46 @@ public final class Translation {
block.getStatements().addAll(wrapIfNecessary(config.getModuleId(), rootFunction, importedModuleList, program, block.getStatements().addAll(wrapIfNecessary(config.getModuleId(), rootFunction, importedModuleList, program,
config.getModuleKind())); config.getModuleKind()));
return context; return program;
} }
private static void defineModule(@NotNull TranslationContext context, @NotNull List<JsStatement> statements, @NotNull String moduleId) { private static boolean isBuiltinModule(@NotNull List<JsProgramFragment> fragments) {
JsName rootPackageName = context.scope().findName(Namer.getRootPackageName()); for (JsProgramFragment fragment : fragments) {
for (JsNameBinding nameBinding : fragment.getNameBindings()) {
if (nameBinding.getKey().equals(ENUM_SIGNATURE) && !fragment.getImports().containsKey(ENUM_SIGNATURE)) {
return true;
}
}
}
return false;
}
private static void translateFile(@NotNull TranslationContext context, @NotNull KtFile file) {
FileDeclarationVisitor fileVisitor = new FileDeclarationVisitor(context);
try {
for (KtDeclaration declaration : file.getDeclarations()) {
if (!AnnotationsUtils.isPredefinedObject(BindingUtils.getDescriptorForElement(context.bindingContext(), declaration))) {
declaration.accept(fileVisitor, context);
}
}
}
catch (TranslationRuntimeException e) {
throw e;
}
catch (RuntimeException e) {
throw new TranslationRuntimeException(file, e);
}
catch (AssertionError e) {
throw new TranslationRuntimeException(file, e);
}
}
private static void defineModule(@NotNull JsProgram program, @NotNull List<JsStatement> statements, @NotNull String moduleId) {
JsName rootPackageName = program.getScope().findName(Namer.getRootPackageName());
if (rootPackageName != null) { if (rootPackageName != null) {
statements.add(new JsInvocation(context.namer().kotlin("defineModule"), context.program().getStringLiteral(moduleId), Namer namer = Namer.newInstance(program.getScope());
statements.add(new JsInvocation(namer.kotlin("defineModule"), program.getStringLiteral(moduleId),
rootPackageName.makeRef()).makeStmt()); rootPackageName.makeRef()).makeStmt());
} }
} }
@@ -0,0 +1,74 @@
/*
* 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.js.translate.utils
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.js.naming.encodeSignature
import org.jetbrains.kotlin.resolve.DescriptorUtils
// Unfortunately, our descriptor serializer can't serialize references to descriptors.
// We have to serialize entire class or package to serialize function or property.
// In case of declarations from external modules we end up with duplication of significant metadata.
// What we really need these descriptors is to determine which names from different fragments point to one declarations,
// so that merger could turn these names into one name. Actually, we need a way to get unique identifier for every
// declaration.
// This code does not generate unique identifiers, it's possible to pick two declarations with same strings. However,
// it's hard to do it unintentionally.
fun generateSignature(descriptor: DeclarationDescriptor): String? {
if (DescriptorUtils.isDescriptorWithLocalVisibility(descriptor)) return null
if (descriptor is DeclarationDescriptorWithVisibility && descriptor.visibility == Visibilities.PRIVATE &&
!AnnotationsUtils.isNativeObject(descriptor) && !AnnotationsUtils.isLibraryObject(descriptor)
) {
return null
}
return when (descriptor) {
is CallableDescriptor -> {
val parent = generateSignature(descriptor.containingDeclaration) ?: return null
if (descriptor !is VariableAccessorDescriptor && descriptor !is ConstructorDescriptor && descriptor.name.isSpecial) {
return null
}
// Make distinction between functions with zero parameters and properties
val separator = if (descriptor is FunctionDescriptor) "#" else "!"
parent + separator + escape(descriptor.name.asString()) + "|" + encodeSignature(descriptor)
}
is PackageFragmentDescriptor -> {
if (descriptor.fqName.isRoot) "" else escape(descriptor.fqName.pathSegments().map { escape(it.identifier) }.joinToString("."))
}
is ClassDescriptor -> {
val parent = generateSignature(descriptor.containingDeclaration) ?: return null
if (descriptor.name.isSpecial) return null
parent + "$" + escape(descriptor.name.asString())
}
else -> return null
}
}
private fun escape(s: String): String {
val sb = StringBuilder()
for (c in s) {
val escapedChar = when (c) {
'\\', '"', '.', '$', '#', '!', '<', '>', '|', '+', '-', ':', '*', '?' -> "\\$c"
else -> c.toString()
}
sb.append(escapedChar)
}
return sb.toString()
}