001    /*
002     * Copyright 2010-2015 JetBrains s.r.o.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    
017    package org.jetbrains.kotlin.js.translate.expression;
018    
019    import com.google.dart.compiler.backend.js.ast.*;
020    import com.google.dart.compiler.backend.js.ast.metadata.MetadataProperties;
021    import com.intellij.util.SmartList;
022    import org.jetbrains.annotations.NotNull;
023    import org.jetbrains.annotations.Nullable;
024    import org.jetbrains.kotlin.descriptors.*;
025    import org.jetbrains.kotlin.js.translate.context.AliasingContext;
026    import org.jetbrains.kotlin.js.translate.context.Namer;
027    import org.jetbrains.kotlin.js.translate.context.TranslationContext;
028    import org.jetbrains.kotlin.js.translate.general.AbstractTranslator;
029    import org.jetbrains.kotlin.js.translate.utils.TranslationUtils;
030    import org.jetbrains.kotlin.psi.KtDeclarationWithBody;
031    import org.jetbrains.kotlin.psi.KtFunctionLiteralExpression;
032    import org.jetbrains.kotlin.resolve.DescriptorUtils;
033    import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt;
034    
035    import java.util.HashMap;
036    import java.util.List;
037    import java.util.Map;
038    
039    import static org.jetbrains.kotlin.js.translate.reference.CallExpressionTranslator.shouldBeInlined;
040    import static org.jetbrains.kotlin.js.translate.utils.BindingUtils.getFunctionDescriptor;
041    import static org.jetbrains.kotlin.js.translate.utils.ErrorReportingUtils.message;
042    import static org.jetbrains.kotlin.js.translate.utils.FunctionBodyTranslator.translateFunctionBody;
043    import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.setParameters;
044    
045    public final class FunctionTranslator extends AbstractTranslator {
046        @NotNull
047        public static FunctionTranslator newInstance(@NotNull KtDeclarationWithBody function,
048                @NotNull TranslationContext context) {
049            return new FunctionTranslator(function, context);
050        }
051    
052        @NotNull
053        private TranslationContext functionBodyContext;
054        @NotNull
055        private final KtDeclarationWithBody functionDeclaration;
056        @Nullable
057        private JsName extensionFunctionReceiverName;
058        @NotNull
059        private final JsFunction functionObject;
060        @NotNull
061        private final FunctionDescriptor descriptor;
062    
063        private FunctionTranslator(@NotNull KtDeclarationWithBody functionDeclaration, @NotNull TranslationContext context) {
064            super(context);
065            this.descriptor = getFunctionDescriptor(context.bindingContext(), functionDeclaration);
066            this.functionDeclaration = functionDeclaration;
067            this.functionObject = context().getFunctionObject(descriptor);
068            assert this.functionObject.getParameters().isEmpty()
069                    : message(descriptor, "Function " + functionDeclaration.getText() + " processed for the second time.");
070            //NOTE: it's important we compute the context before we start the computation
071            this.functionBodyContext = getFunctionBodyContext();
072        }
073    
074        @NotNull
075        private TranslationContext getFunctionBodyContext() {
076            AliasingContext aliasingContext;
077            if (isExtensionFunction()) {
078                DeclarationDescriptor expectedReceiverDescriptor = descriptor.getExtensionReceiverParameter();
079                assert expectedReceiverDescriptor != null;
080                extensionFunctionReceiverName = functionObject.getScope().declareName(Namer.getReceiverParameterName());
081                //noinspection ConstantConditions
082                aliasingContext = context().aliasingContext().inner(expectedReceiverDescriptor, extensionFunctionReceiverName.makeRef());
083            }
084            else {
085                aliasingContext = null;
086            }
087            return context().newFunctionBody(functionObject, aliasingContext);
088        }
089    
090        @NotNull
091        public JsPropertyInitializer translateAsEcma5PropertyDescriptor() {
092            generateFunctionObject();
093            return TranslationUtils.translateFunctionAsEcma5PropertyDescriptor(functionObject, descriptor, context());
094        }
095    
096        @NotNull
097        public JsPropertyInitializer translateAsMethod() {
098            JsName functionName = context().getNameForDescriptor(descriptor);
099            generateFunctionObject();
100    
101            if (shouldBeInlined(descriptor) && DescriptorUtilsKt.isEffectivelyPublicApi(descriptor)) {
102                InlineMetadata metadata = InlineMetadata.compose(functionObject, descriptor);
103                return new JsPropertyInitializer(functionName.makeRef(), metadata.getFunctionWithMetadata());
104            }
105    
106            return new JsPropertyInitializer(functionName.makeRef(), functionObject);
107        }
108    
109        private void generateFunctionObject() {
110            setParameters(functionObject, translateParameters());
111            translateBody();
112        }
113    
114        private void translateBody() {
115            if (!functionDeclaration.hasBody()) {
116                assert descriptor instanceof ConstructorDescriptor || descriptor.getModality().equals(Modality.ABSTRACT);
117                return;
118            }
119            functionObject.getBody().getStatements().addAll(translateFunctionBody(descriptor, functionDeclaration, functionBodyContext).getStatements());
120        }
121    
122        @NotNull
123        private List<JsParameter> translateParameters() {
124            List<JsParameter> jsParameters = new SmartList<JsParameter>();
125            Map<DeclarationDescriptor, JsExpression> aliases = new HashMap<DeclarationDescriptor, JsExpression>();
126    
127            for (TypeParameterDescriptor type : descriptor.getTypeParameters()) {
128                if (type.isReified()) {
129                    String suggestedName = Namer.isInstanceSuggestedName(type);
130                    JsName paramName = functionObject.getScope().declareName(suggestedName);
131                    jsParameters.add(new JsParameter(paramName));
132                    aliases.put(type, paramName.makeRef());
133                }
134            }
135    
136            functionBodyContext = functionBodyContext.innerContextWithDescriptorsAliased(aliases);
137    
138            if (extensionFunctionReceiverName == null && descriptor.getValueParameters().isEmpty()) {
139                return jsParameters;
140            }
141    
142            mayBeAddThisParameterForExtensionFunction(jsParameters);
143            addParameters(jsParameters, descriptor, context());
144            return jsParameters;
145        }
146    
147        public static void addParameters(List<JsParameter> list, FunctionDescriptor descriptor, TranslationContext context) {
148            for (ValueParameterDescriptor valueParameter : descriptor.getValueParameters()) {
149                JsParameter jsParameter = new JsParameter(context.getNameForDescriptor(valueParameter));
150                MetadataProperties.setHasDefaultValue(jsParameter, DescriptorUtilsKt.hasDefaultValue(valueParameter));
151                list.add(jsParameter);
152            }
153        }
154    
155        private void mayBeAddThisParameterForExtensionFunction(@NotNull List<JsParameter> jsParameters) {
156            if (isExtensionFunction()) {
157                assert extensionFunctionReceiverName != null;
158                jsParameters.add(new JsParameter(extensionFunctionReceiverName));
159            }
160        }
161    
162        private boolean isExtensionFunction() {
163            return DescriptorUtils.isExtension(descriptor) && !(functionDeclaration instanceof KtFunctionLiteralExpression);
164        }
165    }