001    /*
002     * Copyright 2010-2016 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.KtLambdaExpression;
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            MetadataProperties.setFunctionDescriptor(functionObject, descriptor);
073        }
074    
075        @NotNull
076        private TranslationContext getFunctionBodyContext() {
077            AliasingContext aliasingContext;
078            if (isExtensionFunction()) {
079                DeclarationDescriptor expectedReceiverDescriptor = descriptor.getExtensionReceiverParameter();
080                assert expectedReceiverDescriptor != null;
081                extensionFunctionReceiverName = functionObject.getScope().declareName(Namer.getReceiverParameterName());
082                //noinspection ConstantConditions
083                aliasingContext = context().aliasingContext().inner(expectedReceiverDescriptor, extensionFunctionReceiverName.makeRef());
084            }
085            else {
086                aliasingContext = null;
087            }
088            return context().newFunctionBody(functionObject, aliasingContext, descriptor);
089        }
090    
091        @NotNull
092        public JsPropertyInitializer translateAsEcma5PropertyDescriptor() {
093            generateFunctionObject();
094            return TranslationUtils.translateFunctionAsEcma5PropertyDescriptor(functionObject, descriptor, context());
095        }
096    
097        @NotNull
098        public JsPropertyInitializer translateAsMethod() {
099            JsName functionName = context().getNameForDescriptor(descriptor);
100            generateFunctionObject();
101    
102            if (shouldBeInlined(descriptor) && DescriptorUtilsKt.isEffectivelyPublicApi(descriptor)) {
103                InlineMetadata metadata = InlineMetadata.compose(functionObject, descriptor);
104                return new JsPropertyInitializer(functionName.makeRef(), metadata.getFunctionWithMetadata());
105            }
106    
107            return new JsPropertyInitializer(functionName.makeRef(), functionObject);
108        }
109    
110        private void generateFunctionObject() {
111            setParameters(functionObject, translateParameters());
112            translateBody();
113        }
114    
115        private void translateBody() {
116            if (!functionDeclaration.hasBody()) {
117                assert descriptor instanceof ConstructorDescriptor || descriptor.getModality().equals(Modality.ABSTRACT);
118                return;
119            }
120            JsBlock body = translateFunctionBody(descriptor, functionDeclaration, functionBodyContext);
121            functionObject.getBody().getStatements().addAll(body.getStatements());
122        }
123    
124        @NotNull
125        private List<JsParameter> translateParameters() {
126            List<JsParameter> jsParameters = new SmartList<JsParameter>();
127            Map<DeclarationDescriptor, JsExpression> aliases = new HashMap<DeclarationDescriptor, JsExpression>();
128    
129            for (TypeParameterDescriptor type : descriptor.getTypeParameters()) {
130                if (type.isReified()) {
131                    JsName paramNameForType = context().getNameForDescriptor(type);
132                    jsParameters.add(new JsParameter(paramNameForType));
133    
134                    String suggestedName = Namer.isInstanceSuggestedName(type);
135                    JsName paramName = functionObject.getScope().declareName(suggestedName);
136                    jsParameters.add(new JsParameter(paramName));
137                    aliases.put(type, paramName.makeRef());
138                }
139            }
140    
141            functionBodyContext = functionBodyContext.innerContextWithDescriptorsAliased(aliases);
142    
143            if (extensionFunctionReceiverName == null && descriptor.getValueParameters().isEmpty()) {
144                return jsParameters;
145            }
146    
147            mayBeAddThisParameterForExtensionFunction(jsParameters);
148            addParameters(jsParameters, descriptor, context());
149            return jsParameters;
150        }
151    
152        public static void addParameters(List<JsParameter> list, FunctionDescriptor descriptor, TranslationContext context) {
153            for (ValueParameterDescriptor valueParameter : descriptor.getValueParameters()) {
154                JsParameter jsParameter = new JsParameter(context.getNameForDescriptor(valueParameter));
155                MetadataProperties.setHasDefaultValue(jsParameter, DescriptorUtilsKt.hasDefaultValue(valueParameter));
156                list.add(jsParameter);
157            }
158        }
159    
160        private void mayBeAddThisParameterForExtensionFunction(@NotNull List<JsParameter> jsParameters) {
161            if (isExtensionFunction()) {
162                assert extensionFunctionReceiverName != null;
163                jsParameters.add(new JsParameter(extensionFunctionReceiverName));
164            }
165        }
166    
167        private boolean isExtensionFunction() {
168            return DescriptorUtils.isExtension(descriptor) && !(functionDeclaration instanceof KtLambdaExpression);
169        }
170    }