All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jetbrains.kotlin.js.translate.context.TranslationContext Maven / Gradle / Ivy

There is a newer version: 2.0.0
Show newest version
/*
 * Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
 * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
 */

package org.jetbrains.kotlin.js.translate.context;

import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.config.CommonConfigurationKeysKt;
import org.jetbrains.kotlin.config.LanguageFeature;
import org.jetbrains.kotlin.config.LanguageVersionSettings;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.descriptors.annotations.Annotations;
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor;
import org.jetbrains.kotlin.descriptors.impl.TypeAliasConstructorDescriptor;
import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl;
import org.jetbrains.kotlin.incremental.components.NoLookupLocation;
import org.jetbrains.kotlin.js.backend.ast.*;
import org.jetbrains.kotlin.js.backend.ast.metadata.LocalAlias;
import org.jetbrains.kotlin.js.backend.ast.metadata.MetadataProperties;
import org.jetbrains.kotlin.js.backend.ast.metadata.SpecialFunction;
import org.jetbrains.kotlin.js.config.JsConfig;
import org.jetbrains.kotlin.js.naming.NameSuggestion;
import org.jetbrains.kotlin.js.naming.SuggestedName;
import org.jetbrains.kotlin.js.sourceMap.SourceFilePathResolver;
import org.jetbrains.kotlin.js.translate.declaration.ClassModelGenerator;
import org.jetbrains.kotlin.js.translate.intrinsic.Intrinsics;
import org.jetbrains.kotlin.js.translate.reference.CallExpressionTranslator;
import org.jetbrains.kotlin.js.translate.reference.ReferenceTranslator;
import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils;
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils;
import org.jetbrains.kotlin.js.translate.utils.TranslationUtils;
import org.jetbrains.kotlin.js.translate.utils.UtilsKt;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.psi.KtExpression;
import org.jetbrains.kotlin.psi.KtSimpleNameExpression;
import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.BindingTrace;
import org.jetbrains.kotlin.resolve.DescriptorUtils;
import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt;
import org.jetbrains.kotlin.resolve.inline.InlineUtil;
import org.jetbrains.kotlin.resolve.scopes.receivers.ExtensionReceiver;
import org.jetbrains.kotlin.serialization.js.ModuleKind;
import org.jetbrains.kotlin.types.expressions.PreliminaryDeclarationVisitor;

import java.util.*;

import static org.jetbrains.kotlin.js.descriptorUtils.DescriptorUtilsKt.isCoroutineLambda;
import static org.jetbrains.kotlin.js.descriptorUtils.DescriptorUtilsKt.shouldBeExported;
import static org.jetbrains.kotlin.js.translate.context.UsageTrackerKt.getNameForCapturedDescriptor;
import static org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils.isNativeObject;
import static org.jetbrains.kotlin.js.translate.utils.BindingUtils.getDescriptorForElement;
import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.pureFqn;
import static org.jetbrains.kotlin.resolve.BindingContextUtils.isCapturedInClosure;
import static org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueKindUtilsKt.hasNoWritersInClosures;

/**
 * All the info about the state of the translation process.
 */
public class TranslationContext {
    @NotNull
    private final DynamicContext dynamicContext;
    @NotNull
    private final StaticContext staticContext;
    @NotNull
    private final AliasingContext aliasingContext;
    @Nullable
    private final UsageTracker usageTracker;
    @Nullable
    private final TranslationContext parent;
    @Nullable
    private final DeclarationDescriptor declarationDescriptor;
    @Nullable
    private final ClassDescriptor classDescriptor;
    @Nullable
    private final ValueParameterDescriptor continuationParameterDescriptor;

    @Nullable
    private InlineFunctionContext inlineFunctionContext;

    @NotNull
    public static TranslationContext rootContext(@NotNull StaticContext staticContext) {
        DynamicContext rootDynamicContext = DynamicContext.rootContext(
                staticContext.getFragment().getScope(), staticContext.getFragment().getInitializerBlock());
        AliasingContext rootAliasingContext = AliasingContext.getCleanContext();
        return new TranslationContext(null, staticContext, rootDynamicContext, rootAliasingContext, null, null);
    }

    private final Map expressionToTempConstVariableCache = new HashMap<>();

    private TranslationContext(
            @Nullable TranslationContext parent,
            @NotNull StaticContext staticContext,
            @NotNull DynamicContext dynamicContext,
            @NotNull AliasingContext aliasingContext,
            @Nullable UsageTracker usageTracker,
            @Nullable DeclarationDescriptor declarationDescriptor
    ) {
        this.parent = parent;
        this.dynamicContext = dynamicContext;
        this.staticContext = staticContext;
        this.aliasingContext = aliasingContext;
        this.usageTracker = usageTracker;
        this.declarationDescriptor = declarationDescriptor;
        if (declarationDescriptor instanceof ClassDescriptor) {
            this.classDescriptor = (ClassDescriptor) declarationDescriptor;
        }
        else {
            this.classDescriptor = parent != null ? parent.classDescriptor : null;
        }

        continuationParameterDescriptor = calculateContinuationParameter();
        inlineFunctionContext = parent != null ? parent.inlineFunctionContext : null;

        DeclarationDescriptor parentDescriptor = parent != null ? parent.declarationDescriptor : null;
        if (parentDescriptor != declarationDescriptor &&
            declarationDescriptor instanceof CallableDescriptor &&
            InlineUtil.isInline(declarationDescriptor)) {
            inlineFunctionContext = new InlineFunctionContext((CallableDescriptor) declarationDescriptor);
        }
    }

    private ValueParameterDescriptor calculateContinuationParameter() {
        if (parent != null && parent.declarationDescriptor == declarationDescriptor) {
            return parent.continuationParameterDescriptor;
        }
        if (declarationDescriptor instanceof FunctionDescriptor) {
            FunctionDescriptor function = (FunctionDescriptor) declarationDescriptor;
            if (function.isSuspend()) {
                ClassDescriptor continuationDescriptor =
                        DescriptorUtilKt.findContinuationClassDescriptor(
                                getCurrentModule(), NoLookupLocation.FROM_BACKEND,
                                getLanguageVersionSettings().supportsFeature(LanguageFeature.ReleaseCoroutines));

                return new ValueParameterDescriptorImpl(function, null, function.getValueParameters().size(),
                                                        Annotations.Companion.getEMPTY(), Name.identifier("continuation"),
                                                        continuationDescriptor.getDefaultType(), false, false, false, null,
                                                        SourceElement.NO_SOURCE);
            }
        }
        return null;
    }

    @Nullable
    public UsageTracker usageTracker() {
        return usageTracker;
    }

    @NotNull
    public DynamicContext dynamicContext() {
        return dynamicContext;
    }

    @Nullable
    public InlineFunctionContext getInlineFunctionContext() {
        return inlineFunctionContext;
    }

    @NotNull
    public TranslationContext contextWithScope(@NotNull JsFunction fun) {
        return this.newFunctionBody(fun, aliasingContext, declarationDescriptor);
    }

    @NotNull
    private TranslationContext newFunctionBody(
            @NotNull JsFunction fun, @Nullable AliasingContext aliasingContext,
            DeclarationDescriptor descriptor
    ) {
        DynamicContext dynamicContext = DynamicContext.newContext(fun.getScope(), fun.getBody());
        if (aliasingContext == null) {
            aliasingContext = this.aliasingContext.inner();
        }

        return new TranslationContext(this, this.staticContext, dynamicContext, aliasingContext, this.usageTracker, descriptor);
    }

    @NotNull
    public TranslationContext newFunctionBodyWithUsageTracker(@NotNull JsFunction fun, @NotNull MemberDescriptor descriptor) {
        DynamicContext dynamicContext = DynamicContext.newContext(fun.getScope(), fun.getBody());
        UsageTracker usageTracker = new UsageTracker(this.usageTracker, descriptor, bindingContext());
        return new TranslationContext(this, this.staticContext, dynamicContext, this.aliasingContext.inner(), usageTracker, descriptor);
    }

    @NotNull
    public TranslationContext innerWithUsageTracker(@NotNull MemberDescriptor descriptor) {
        UsageTracker usageTracker = new UsageTracker(this.usageTracker, descriptor, bindingContext());
        return new TranslationContext(this, staticContext, dynamicContext, aliasingContext.inner(), usageTracker, descriptor);
    }

    @NotNull
    public TranslationContext inner(@NotNull MemberDescriptor descriptor) {
        return new TranslationContext(this, staticContext, dynamicContext, aliasingContext.inner(), usageTracker, descriptor);
    }

    @NotNull
    public TranslationContext innerBlock(@NotNull JsBlock block) {
        return new TranslationContext(this, staticContext, dynamicContext.innerBlock(block), aliasingContext, usageTracker,
                                      this.declarationDescriptor);
    }

    @NotNull
    public TranslationContext innerBlock() {
        return innerBlock(new JsBlock());
    }

    @NotNull
    public TranslationContext newDeclaration(@NotNull DeclarationDescriptor descriptor) {
        JsBlock innerBlock = getBlockForDescriptor(descriptor);
        if (innerBlock == null) {
            innerBlock = dynamicContext.jsBlock();
        }
        DynamicContext dynamicContext = DynamicContext.newContext(getScopeForDescriptor(descriptor), innerBlock);
        return new TranslationContext(this, staticContext, dynamicContext, aliasingContext, usageTracker, descriptor);
    }

    @NotNull
    private TranslationContext innerWithAliasingContext(AliasingContext aliasingContext) {
        return new TranslationContext(this, staticContext, dynamicContext, aliasingContext, usageTracker, declarationDescriptor);
    }

    @NotNull
    public TranslationContext innerContextWithAliased(@NotNull DeclarationDescriptor correspondingDescriptor, @NotNull JsExpression alias) {
        return this.innerWithAliasingContext(aliasingContext.inner(correspondingDescriptor, alias));
    }

    @NotNull
    public TranslationContext innerContextWithAliasesForExpressions(@NotNull Map aliases) {
        if (aliases.isEmpty()) return this;
        return this.innerWithAliasingContext(aliasingContext.withExpressionsAliased(aliases));
    }

    @NotNull
    public TranslationContext innerContextWithDescriptorsAliased(@NotNull Map aliases) {
        if (aliases.isEmpty()) return this;
        return this.innerWithAliasingContext(aliasingContext.withDescriptorsAliased(aliases));
    }

    @Nullable
    private JsBlock getBlockForDescriptor(@NotNull DeclarationDescriptor descriptor) {
        if (descriptor instanceof CallableDescriptor) {
            return getFunctionObject((CallableDescriptor) descriptor).getBody();
        }
        else {
            return null;
        }
    }

    @Nullable
    public ClassDescriptor getClassDescriptor() {
        return classDescriptor;
    }

    @NotNull
    public BindingContext bindingContext() {
        return staticContext.getBindingContext();
    }

    @NotNull
    public BindingTrace bindingTrace() {
        return staticContext.getBindingTrace();
    }

    @NotNull
    public JsScope getScopeForDescriptor(@NotNull DeclarationDescriptor descriptor) {
        return staticContext.getScopeForDescriptor(descriptor);
    }

    @NotNull
    public JsName getNameForElement(@NotNull PsiElement element) {
        DeclarationDescriptor descriptor = getDescriptorForElement(bindingContext(), element);
        return getNameForDescriptor(descriptor);
    }

    @NotNull
    public JsName getNameForDescriptor(@NotNull DeclarationDescriptor descriptor) {
        return staticContext.getNameForDescriptor(descriptor);
    }

    @NotNull
    public JsName getInnerNameForDescriptor(@NotNull DeclarationDescriptor descriptor) {
        return staticContext.getInnerNameForDescriptor(descriptor);
    }

    @NotNull
    public JsName getInlineableInnerNameForDescriptor(@NotNull DeclarationDescriptor descriptor) {
        JsName name;
        if (inlineFunctionContext == null || !isPublicInlineFunction() || !shouldBeExported(descriptor, getConfig()) ||
            DescriptorUtils.isAncestor(inlineFunctionContext.getDescriptor(), descriptor, false)) {
            name = getInnerNameForDescriptor(descriptor);
        }
        else {
            String tag = staticContext.getTag(descriptor);
            name = inlineFunctionContext.getImports().get(tag);
            if (name == null) {
                name = createInlineableInnerNameForDescriptor(descriptor);
                inlineFunctionContext.getImports().put(tag, name);
            }
        }

        return name;
    }

    private JsName createInlineableInnerNameForDescriptor(@NotNull DeclarationDescriptor descriptor) {
        assert inlineFunctionContext != null;

        JsExpression imported = createInlineLocalImportExpression(descriptor);
        if (imported instanceof JsNameRef) {
            JsNameRef importedNameRef = (JsNameRef) imported;
            if (importedNameRef.getQualifier() == null && importedNameRef.getIdent().equals(Namer.getRootPackageName()) &&
                (descriptor instanceof PackageFragmentDescriptor || descriptor instanceof ModuleDescriptor)) {
                return importedNameRef.getName();
            }
        }

        JsName result = JsScope.declareTemporaryName(StaticContext.getSuggestedName(descriptor));
        if (isFromCurrentModule(descriptor) && !AnnotationsUtils.isNativeObject(descriptor)) {
            MetadataProperties.setLocalAlias(result, new LocalAlias(getInnerNameForDescriptor(descriptor), staticContext.getTag(descriptor)));
        }
        MetadataProperties.setDescriptor(result, descriptor);
        MetadataProperties.setStaticRef(result, imported);
        MetadataProperties.setImported(result, true);
        inlineFunctionContext.getImportBlock().getStatements().add(JsAstUtils.newVar(result, imported));

        return result;
    }

    @NotNull
    public JsExpression getReferenceToIntrinsic(@NotNull String intrinsicName) {
        return pureFqn(getNameForIntrinsic(intrinsicName), null);
    }

    @NotNull
    public JsName getNameForIntrinsic(@NotNull String intrinsicName) {
        JsName result;
        if (inlineFunctionContext == null || !isPublicInlineFunction()) {
            result = staticContext.getNameForIntrinsic(intrinsicName);
        }
        else {
            String tag = "intrinsic:" + intrinsicName;
            result = inlineFunctionContext.getImports().computeIfAbsent(tag, t -> {
                JsExpression imported = TranslationUtils.getIntrinsicFqn(intrinsicName);

                JsName name = JsScope.declareTemporaryName(NameSuggestion.sanitizeName(intrinsicName));
                MetadataProperties.setImported(name, true);
                inlineFunctionContext.getImportBlock().getStatements().add(JsAstUtils.newVar(name, imported));
                return name;
            });
        }

        return result;
    }


    @NotNull
    public JsName getNameForObjectInstance(@NotNull ClassDescriptor descriptor) {
        return staticContext.getNameForObjectInstance(descriptor);
    }

    @NotNull
    public JsExpression getQualifiedReference(@NotNull DeclarationDescriptor descriptor) {
        JsExpression result = staticContext.getQualifiedReference(descriptor);
        if (isPublicInlineFunction()) {
            if (isFromCurrentModule(descriptor)) {
                if (descriptor instanceof MemberDescriptor) {
                    staticContext.export((MemberDescriptor) descriptor, true);
                }
            }
            else {
                ModuleDescriptor module = DescriptorUtils.getContainingModule(descriptor);
                if (module != staticContext.getCurrentModule() && !isInlineFunction(descriptor)) {
                    JsExpression replacement = staticContext.exportModuleForInline(module);
                    if (replacement != null) {
                        result = replaceModuleReference(result, getInnerNameForDescriptor(module), replacement);
                    }
                }
            }
        }
        return result;
    }

    private boolean isInlineFunction(@NotNull DeclarationDescriptor descriptor) {
        if (!(descriptor instanceof CallableDescriptor)) return false;
        return CallExpressionTranslator.shouldBeInlined((CallableDescriptor) descriptor, this);
    }

    private static JsExpression replaceModuleReference(
            @NotNull JsExpression expression,
            @NotNull JsName expectedModuleName,
            @NotNull JsExpression reexportExpr
    ) {
        if (expression instanceof JsNameRef) {
            JsNameRef nameRef = (JsNameRef) expression;
            if (nameRef.getQualifier() == null) {
                return expectedModuleName == nameRef.getName() ? reexportExpr : expression;
            }
            else {
                JsExpression newQualifier = replaceModuleReference(nameRef.getQualifier(), expectedModuleName, reexportExpr);
                if (newQualifier == nameRef.getQualifier()) {
                    return expression;
                }
                JsExpression result = nameRef.getName() != null ?
                                      new JsNameRef(nameRef.getName(), newQualifier) :
                                      new JsNameRef(nameRef.getIdent(), newQualifier);
                result.copyMetadataFrom(nameRef);
                return result;
            }
        }
        else {
            return expression;
        }
    }

    @NotNull
    public JsExpression getInnerReference(@NotNull DeclarationDescriptor descriptor) {
        return pureFqn(getInlineableInnerNameForDescriptor(descriptor), null);
    }

    @NotNull
    private JsExpression createInlineLocalImportExpression(@NotNull DeclarationDescriptor descriptor) {
        JsExpression result = getQualifiedReference(descriptor);
        JsName name = getInnerNameForDescriptor(descriptor);

        SuggestedName suggested = staticContext.suggestName(descriptor);
        if (suggested != null && getConfig().getModuleKind() != ModuleKind.PLAIN && isPublicInlineFunction()) {
            String moduleId = AnnotationsUtils.getModuleName(suggested.getDescriptor());
            if (moduleId != null) {
                JsExpression replacement = staticContext.exportModuleForInline(moduleId, new JsImportedModule(moduleId, name, null));
                result = replaceModuleReference(result, name, replacement);
            }
            else if (isNativeObject(suggested.getDescriptor()) && DescriptorUtils.isTopLevelDeclaration(suggested.getDescriptor())) {
                String fileModuleId = AnnotationsUtils.getFileModuleName(bindingContext(), suggested.getDescriptor());
                if (fileModuleId != null) {
                    JsImportedModule fileModuleName = staticContext.getImportedModule(fileModuleId, null);
                    JsExpression replacement = staticContext.exportModuleForInline(fileModuleId, fileModuleName);
                    result = replaceModuleReference(staticContext.getQualifiedReference(descriptor), fileModuleName.getInternalName(), replacement);
                }
            }
        }

        return result;
    }

    @NotNull
    public JsName getNameForBackingField(@NotNull VariableDescriptorWithAccessors property) {
        return staticContext.getNameForBackingField(property);
    }

    @NotNull
    public TemporaryVariable declareTemporary(@Nullable JsExpression initExpression, @Nullable Object source) {
        return dynamicContext.declareTemporary(initExpression, source);
    }

    @NotNull
    public JsExpression defineTemporary(@NotNull JsExpression initExpression) {
        TemporaryVariable var = dynamicContext.declareTemporary(initExpression, initExpression.getSource());
        addStatementToCurrentBlock(var.assignmentStatement());
        return var.reference();
    }

    @NotNull
    public JsExpression cacheExpressionIfNeeded(@NotNull JsExpression expression) {
        return TranslationUtils.isCacheNeeded(expression) ? defineTemporary(expression) : expression;
    }

    @NotNull
    public TemporaryConstVariable getOrDeclareTemporaryConstVariable(@NotNull JsExpression expression) {
        TemporaryConstVariable tempVar = expressionToTempConstVariableCache.get(expression);

        if (tempVar == null) {
            TemporaryVariable tmpVar = declareTemporary(expression, expression.getSource());

            tempVar = new TemporaryConstVariable(tmpVar.name(), tmpVar.assignmentExpression());

            expressionToTempConstVariableCache.put(expression, tempVar);
            expressionToTempConstVariableCache.put(tmpVar.assignmentExpression(), tempVar);
        }

        return tempVar;
    }

    @NotNull
    public Namer namer() {
        return staticContext.getNamer();
    }

    @NotNull
    public Intrinsics intrinsics() {
        return staticContext.getIntrinsics();
    }

    @NotNull
    public JsProgram program() {
        return staticContext.getProgram();
    }

    @NotNull
    public JsConfig getConfig() {
        return staticContext.getConfig();
    }

    @NotNull
    public JsScope scope() {
        return dynamicContext.getScope();
    }

    @NotNull
    public AliasingContext aliasingContext() {
        return aliasingContext;
    }

    @NotNull
    public JsFunction getFunctionObject(@NotNull CallableDescriptor descriptor) {
        return staticContext.getFunctionWithScope(descriptor);
    }

    public void addStatementToCurrentBlock(@NotNull JsStatement statement) {
        dynamicContext.jsBlock().getStatements().add(statement);
    }

    public void addStatementsToCurrentBlock(@NotNull Collection statements) {
        dynamicContext.jsBlock().getStatements().addAll(statements);
    }

    public void addStatementsToCurrentBlockFrom(@NotNull TranslationContext context) {
        addStatementsToCurrentBlockFrom(context.dynamicContext().jsBlock());
    }

    public void addStatementsToCurrentBlockFrom(@NotNull JsBlock block) {
        dynamicContext.jsBlock().getStatements().addAll(block.getStatements());
    }

    public boolean currentBlockIsEmpty() {
        return dynamicContext.jsBlock().isEmpty();
    }

    public void moveVarsFrom(@NotNull TranslationContext context) {
        dynamicContext.moveVarsFrom(context.dynamicContext());
    }

    @NotNull
    public JsBlock getCurrentBlock() {
        return dynamicContext.jsBlock();
    }

    @Nullable
    public JsExpression getAliasForDescriptor(@NotNull DeclarationDescriptor descriptor) {
        if (continuationParameterDescriptor != descriptor) {
            JsExpression nameRef = captureIfNeedAndGetCapturedName(descriptor);
            if (nameRef != null) {
                return nameRef;
            }
        }

        JsExpression alias = aliasingContext.getAliasForDescriptor(descriptor);
        return alias != null ? alias.deepCopy() : null;
    }

    @NotNull
    public JsExpression getDispatchReceiver(@NotNull ReceiverParameterDescriptor descriptor) {
        JsExpression alias = getAliasForDescriptor(descriptor);
        if (alias != null) {
            return alias;
        }
        if (isCoroutineLambda(descriptor.getContainingDeclaration())) {
            JsNameRef result = new JsNameRef("$$controller$$", JsAstUtils.stateMachineReceiver());
            MetadataProperties.setCoroutineController(result, true);
            return result;
        }

        if (DescriptorUtils.isObject(descriptor.getContainingDeclaration())) {
            if (isConstructorOrDirectScope(descriptor.getContainingDeclaration())) {
                return new JsThisRef();
            }
            else {
                ClassDescriptor objectDescriptor = (ClassDescriptor) descriptor.getContainingDeclaration();
                return ReferenceTranslator.translateAsValueReference(objectDescriptor, this);
            }
        }

        if (descriptor.getValue() instanceof ExtensionReceiver) return new JsThisRef();

        ClassifierDescriptor classifier = descriptor.getValue().getType().getConstructor().getDeclarationDescriptor();

        // TODO: can't tell why this assertion is valid, revisit this code later
        assert classifier instanceof ClassDescriptor;

        ClassDescriptor cls = (ClassDescriptor) classifier;
        assert classDescriptor != null;
        return getDispatchReceiverPath(classDescriptor, cls, new JsThisRef());
    }

    private boolean isConstructorOrDirectScope(DeclarationDescriptor descriptor) {
        return descriptor == DescriptorUtils.getParentOfType(declarationDescriptor, ClassDescriptor.class, false);
    }

    @NotNull
    private JsExpression getDispatchReceiverPath(
            @NotNull ClassDescriptor from, @NotNull ClassDescriptor to,
            @NotNull JsExpression qualifier
    ) {
        while (true) {
            JsExpression alias = getAliasForDescriptor(from.getThisAsReceiverParameter());
            if (alias != null) {
                qualifier = alias;
            }

            if (from == to || !from.isInner() || !(from.getContainingDeclaration() instanceof ClassDescriptor)) {
                break;
            }

            qualifier = new JsNameRef(Namer.OUTER_FIELD_NAME, qualifier);
            from = (ClassDescriptor) from.getContainingDeclaration();
        }

        return qualifier;
    }

    @Nullable
    private JsExpression captureIfNeedAndGetCapturedName(@NotNull DeclarationDescriptor descriptor) {
        if (usageTracker != null) {
            usageTracker.used(descriptor);

            JsName name = getNameForCapturedDescriptor(usageTracker, descriptor);
            if (name != null) return getCapturedReference(name);
        }

        return null;
    }

    @Nullable
    public JsExpression captureTypeIfNeedAndGetCapturedName(@NotNull TypeParameterDescriptor descriptor) {
        if (usageTracker == null) return null;

        usageTracker.used(descriptor);

        JsName name = usageTracker.getCapturedTypes().get(descriptor);
        return name != null ? getCapturedReference(name) : null;
    }

    @NotNull
    public JsName getCapturedTypeName(@NotNull TypeParameterDescriptor descriptor) {
        JsName result = usageTracker != null ? usageTracker.getCapturedTypes().get(descriptor) : null;
        if (result == null) {
            result = getNameForDescriptor(descriptor);
        }

        return result;
    }

    @NotNull
    private JsExpression getCapturedReference(@NotNull JsName name) {
        JsExpression result;
        if (shouldCaptureViaThis()) {
            result = new JsThisRef();
            int depth = getOuterLocalClassDepth();
            for (int i = 0; i < depth; ++i) {
                result = new JsNameRef(Namer.OUTER_FIELD_NAME, result);
            }
            result = new JsNameRef(name, result);
        }
        else {
            result = name.makeRef();
        }
        return result;
    }

    private int getOuterLocalClassDepth() {
        if (usageTracker == null) return 0;
        MemberDescriptor capturingDescriptor = usageTracker.getContainingDescriptor();
        if (!(capturingDescriptor instanceof ClassDescriptor)) return 0;

        ClassDescriptor capturingClassDescriptor = (ClassDescriptor) capturingDescriptor;
        ClassDescriptor currentDescriptor = classDescriptor;
        if (currentDescriptor == null) return 0;

        int depth = 0;
        while (currentDescriptor != capturingClassDescriptor) {
            DeclarationDescriptor container = currentDescriptor.getContainingDeclaration();
            if (!(container instanceof ClassDescriptor)) return 0;
            currentDescriptor = (ClassDescriptor) container;
            depth++;
        }
        return depth;
    }

    private boolean shouldCaptureViaThis() {
        if (declarationDescriptor == null) return false;

        if (DescriptorUtils.isDescriptorWithLocalVisibility(declarationDescriptor)) return false;
        if (declarationDescriptor instanceof ConstructorDescriptor &&
            DescriptorUtils.isDescriptorWithLocalVisibility(declarationDescriptor.getContainingDeclaration())) return false;

        return true;
    }

    @Nullable
    public DeclarationDescriptor getDeclarationDescriptor() {
        return declarationDescriptor;
    }

    public void putClassOrConstructorClosure(@NotNull MemberDescriptor descriptor, @NotNull List closure) {
        staticContext.putClassOrConstructorClosure(descriptor, closure);
    }

    @Nullable
    public List getClassOrConstructorClosure(@NotNull MemberDescriptor classOrConstructor) {
        if (classOrConstructor instanceof TypeAliasConstructorDescriptor) {
            ClassConstructorDescriptor constructorDescriptor = ((TypeAliasConstructorDescriptor) classOrConstructor).getUnderlyingConstructorDescriptor();
            return getClassOrConstructorClosure(constructorDescriptor);
        }

        List result = staticContext.getClassOrConstructorClosure(classOrConstructor);
        if (result == null &&
            classOrConstructor instanceof ConstructorDescriptor &&
            ((ConstructorDescriptor) classOrConstructor).isPrimary()
        ) {
            result = staticContext.getClassOrConstructorClosure((ClassDescriptor) classOrConstructor.getContainingDeclaration());
        }
        return result;
    }

    /**
     * Gets an expression to pass to a constructor of a closure function. I.e. consider the case:
     *
     * ```
     * fun a(x) {
     *     fun b(y) = x + y
     *     return b
     * }
     * ```
     *
     * Here, `x` is a free variable of `b`. Transform `a` into the following form:
     *
     * ```
     * fun a(x) {
     *     fun b0(x0) = { y -> x0 * y }
     *     return b0(x)
     * }
     * ```
     *
     * This function generates arguments passed to newly generated `b0` closure, as well as for the similar case of local class and
     * object expression.
     *
     * @param descriptor represents a free variable or, more generally, free declaration.
     * @return expression to pass to a closure constructor.
     */
    @NotNull
    public JsExpression getArgumentForClosureConstructor(@NotNull DeclarationDescriptor descriptor) {
        JsExpression alias = getAliasForDescriptor(descriptor);
        if (alias != null) return alias;
        if (descriptor instanceof ReceiverParameterDescriptor) {
            return getDispatchReceiver((ReceiverParameterDescriptor) descriptor);
        }
        if (isCoroutineLambda(descriptor)) {
            return new JsThisRef();
        }
        return getNameForDescriptor(descriptor).makeRef();
    }

    @NotNull
    public JsExpression getTypeArgumentForClosureConstructor(@NotNull TypeParameterDescriptor descriptor) {
        JsExpression captured = null;
        if (usageTracker != null) {
            JsName name = usageTracker.getCapturedTypes().get(descriptor);
            if (name != null) {
                captured = name.makeRef();
            }
        }

        return captured != null ? captured : getNameForDescriptor(descriptor).makeRef();
    }

    @Nullable
    public JsName getOuterClassReference(ClassDescriptor descriptor) {
        DeclarationDescriptor container = descriptor.getContainingDeclaration();
        if (!(container instanceof ClassDescriptor) || !descriptor.isInner()) {
            return null;
        }

        return staticContext.getScopeForDescriptor(descriptor).declareName(Namer.OUTER_FIELD_NAME);
    }

    public void startDeclaration() {
        ClassDescriptor classDescriptor = this.classDescriptor;
        if (classDescriptor != null && !(classDescriptor.getContainingDeclaration() instanceof ClassOrPackageFragmentDescriptor)) {
            staticContext.getDeferredCallSites().put(classDescriptor, new ArrayList<>());
        }
    }

    @NotNull
    public List endDeclaration() {
        List result = null;
        if (classDescriptor != null) {
            result = staticContext.getDeferredCallSites().remove(classDescriptor);
        }
        if (result == null) {
            result = Collections.emptyList();
        }
        return result;
    }

    public boolean shouldBeDeferred(@NotNull ClassConstructorDescriptor constructor) {
        ClassDescriptor classDescriptor = constructor.getContainingDeclaration();
        return staticContext.getDeferredCallSites().containsKey(classDescriptor);
    }

    private boolean isValWithWriterInDifferentScope(VariableDescriptor descriptor) {
        //TODO: Simplify this code once KT-17694 is fixed
        if (!(descriptor instanceof LocalVariableDescriptor)) {
            return false;
        }
        PreliminaryDeclarationVisitor preliminaryVisitor =
                PreliminaryDeclarationVisitor.Companion.getVisitorByVariable(descriptor, bindingContext());
        return (preliminaryVisitor == null ||
                !hasNoWritersInClosures(descriptor.getContainingDeclaration(), preliminaryVisitor.writers(descriptor), bindingContext()));
    }

    public boolean isBoxedLocalCapturedInClosure(CallableDescriptor descriptor) {
        if (isCapturedInClosure(bindingContext(), descriptor)) {
            VariableDescriptor localVariable = (VariableDescriptor) descriptor;
            return localVariable.isVar() || isValWithWriterInDifferentScope(localVariable);
        }
        return false;
    }

    public void deferConstructorCall(@NotNull ClassConstructorDescriptor constructor, @NotNull List invocationArgs) {
        ClassDescriptor classDescriptor = constructor.getContainingDeclaration();
        List callSites = staticContext.getDeferredCallSites().get(classDescriptor);
        if (callSites == null) throw new IllegalStateException("This method should be call only when `shouldBeDeferred` method " +
                                                               "reports true for given constructor: " + constructor);
        callSites.add(new DeferredCallSite(constructor, invocationArgs, this));
    }

    public void addInlineCall(@NotNull CallableDescriptor descriptor) {
        staticContext.addInlineCall(descriptor);
    }

    public void addDeclarationStatement(@NotNull JsStatement statement) {
        if (inlineFunctionContext != null) {
            inlineFunctionContext.getDeclarationsBlock().getStatements().add(statement);
        }
        else {
            staticContext.getDeclarationStatements().add(statement);
        }
    }

    public void addTopLevelStatement(@NotNull JsStatement statement) {
        staticContext.getTopLevelStatements().add(statement);
    }

    @NotNull
    public JsFunction createRootScopedFunction(@NotNull DeclarationDescriptor descriptor) {
        return createRootScopedFunction(descriptor.toString());
    }

    @NotNull
    public JsFunction createRootScopedFunction(@NotNull String description) {
        return new JsFunction(staticContext.getFragment().getScope(), new JsBlock(), description);
    }

    public void addClass(@NotNull ClassDescriptor classDescriptor) {
        if (inlineFunctionContext != null) {
            JsClassModel classModel = new ClassModelGenerator(this).generateClassModel(classDescriptor);
            List targetStatements = inlineFunctionContext.getPrototypeBlock().getStatements();
            JsName superName = classModel.getSuperName();
            if (superName != null) {
                targetStatements.addAll(UtilsKt.createPrototypeStatements(superName, classModel.getName()));
            }
            targetStatements.addAll(classModel.getPostDeclarationBlock().getStatements());
        }
        else {
            staticContext.addClass(classDescriptor);
        }
    }

    public void export(@NotNull MemberDescriptor descriptor) {
        staticContext.export(descriptor, false);
    }

    public boolean isFromCurrentModule(@NotNull DeclarationDescriptor descriptor) {
        return staticContext.getCurrentModule() == DescriptorUtilsKt.getModule(descriptor);
    }

    public boolean isPublicInlineFunction() {
        if (inlineFunctionContext == null) return false;

        return shouldBeExported(inlineFunctionContext.getDescriptor(), getConfig());
    }

    @Nullable
    public ValueParameterDescriptor getContinuationParameterDescriptor() {
        return continuationParameterDescriptor;
    }

    @NotNull
    public ModuleDescriptor getCurrentModule() {
        return staticContext.getCurrentModule();
    }

    @Nullable
    public TranslationContext getParent() {
        return parent;
    }

    @NotNull
    public JsExpression declareConstantValue(
            @NotNull DeclarationDescriptor descriptor,
            @NotNull KtSimpleNameExpression expression,
            @NotNull JsExpression value) {
        if (!isPublicInlineFunction() && isFromCurrentModule(descriptor)) {
            return ReferenceTranslator.translateSimpleName(expression, this);
        }

        // Tag shouldn't be null if we cannot reference this descriptor locally.
        String tag = Objects.requireNonNull(staticContext.getTag(descriptor));
        String suggestedName = StaticContext.getSuggestedName(descriptor);

        return declareConstantValue(suggestedName, tag, value, descriptor);
    }

    @NotNull
    public JsExpression declareConstantValue(
            @NotNull String suggestedName,
            @NotNull String tag,
            @NotNull JsExpression value,
            @Nullable DeclarationDescriptor descriptor
    ) {
        if (inlineFunctionContext == null || !isPublicInlineFunction()) {
            return staticContext.importDeclaration(suggestedName, tag, value).makeRef();
        }
        else {
            return inlineFunctionContext.getImports().computeIfAbsent(tag, t -> {
                JsName result = JsScope.declareTemporaryName(suggestedName);
                MetadataProperties.setImported(result, true);
                if (descriptor != null && isFromCurrentModule(descriptor) && !AnnotationsUtils.isNativeObject(descriptor)) {
                    MetadataProperties.setLocalAlias(result, new LocalAlias(getInnerNameForDescriptor(descriptor), tag));
                }
                inlineFunctionContext.getImportBlock().getStatements().add(JsAstUtils.newVar(result, value));
                return result;
            }).makeRef();
        }
    }

    @NotNull
    public JsName getNameForSpecialFunction(@NotNull SpecialFunction function) {
        if (inlineFunctionContext == null || !isPublicInlineFunction()) {
            return staticContext.getNameForSpecialFunction(function);
        }
        else {
            String tag = TranslationUtils.getTagForSpecialFunction(function);
            return inlineFunctionContext.getImports().computeIfAbsent(tag, t -> {
                JsExpression imported = Namer.createSpecialFunction(function);
                JsName result = JsScope.declareTemporaryName(function.getSuggestedName());
                MetadataProperties.setImported(result, true);
                MetadataProperties.setSpecialFunction(result, function);
                inlineFunctionContext.getImportBlock().getStatements().add(JsAstUtils.newVar(result, imported));
                return result;
            });
        }
    }

    @NotNull
    public SourceFilePathResolver getSourceFilePathResolver() {
        return staticContext.getSourceFilePathResolver();
    }

    @NotNull
    public JsName getVariableForPropertyMetadata(@NotNull VariableDescriptorWithAccessors property) {
        return staticContext.getVariableForPropertyMetadata(property);
    }

    @NotNull
    public LanguageVersionSettings getLanguageVersionSettings() {
        return CommonConfigurationKeysKt.getLanguageVersionSettings(staticContext.getConfig().getConfiguration());
    }

    public void reportInlineFunctionTag(@NotNull String tag) {
        staticContext.reportInlineFunctionTag(tag);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy