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    
020    import com.google.dart.compiler.backend.js.ast.*;
021    import com.google.dart.compiler.backend.js.ast.metadata.MetadataPackage;
022    import com.intellij.util.SmartList;
023    import org.jetbrains.annotations.NotNull;
024    import org.jetbrains.annotations.Nullable;
025    import org.jetbrains.kotlin.descriptors.*;
026    import org.jetbrains.kotlin.js.translate.context.AliasingContext;
027    import org.jetbrains.kotlin.js.translate.context.Namer;
028    import org.jetbrains.kotlin.js.translate.context.TranslationContext;
029    import org.jetbrains.kotlin.js.translate.general.AbstractTranslator;
030    import org.jetbrains.kotlin.js.translate.utils.TranslationUtils;
031    import org.jetbrains.kotlin.psi.JetDeclarationWithBody;
032    import org.jetbrains.kotlin.psi.JetFunctionLiteralExpression;
033    import org.jetbrains.kotlin.resolve.DescriptorUtils;
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    import static org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilPackage.getIsEffectivelyPublicApi;
045    
046    public final class FunctionTranslator extends AbstractTranslator {
047        @NotNull
048        public static FunctionTranslator newInstance(@NotNull JetDeclarationWithBody function,
049                @NotNull TranslationContext context) {
050            return new FunctionTranslator(function, context);
051        }
052    
053        @NotNull
054        private TranslationContext functionBodyContext;
055        @NotNull
056        private final JetDeclarationWithBody functionDeclaration;
057        @Nullable
058        private JsName extensionFunctionReceiverName;
059        @NotNull
060        private final JsFunction functionObject;
061        @NotNull
062        private final FunctionDescriptor descriptor;
063    
064        private FunctionTranslator(@NotNull JetDeclarationWithBody functionDeclaration, @NotNull TranslationContext context) {
065            super(context);
066            this.descriptor = getFunctionDescriptor(context.bindingContext(), functionDeclaration);
067            this.functionDeclaration = functionDeclaration;
068            this.functionObject = context().getFunctionObject(descriptor);
069            assert this.functionObject.getParameters().isEmpty()
070                    : message(descriptor, "Function " + functionDeclaration.getText() + " processed for the second time.");
071            //NOTE: it's important we compute the context before we start the computation
072            this.functionBodyContext = getFunctionBodyContext();
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);
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) && getIsEffectivelyPublicApi(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            functionObject.getBody().getStatements().addAll(translateFunctionBody(descriptor, functionDeclaration, functionBodyContext).getStatements());
121        }
122    
123        @NotNull
124        private List<JsParameter> translateParameters() {
125            List<JsParameter> jsParameters = new SmartList<JsParameter>();
126            Map<DeclarationDescriptor, JsExpression> aliases = new HashMap<DeclarationDescriptor, JsExpression>();
127    
128            for (TypeParameterDescriptor type : descriptor.getTypeParameters()) {
129                if (type.isReified()) {
130                    String suggestedName = Namer.isInstanceSuggestedName(type);
131                    JsName paramName = functionObject.getScope().declareName(suggestedName);
132                    jsParameters.add(new JsParameter(paramName));
133                    aliases.put(type, paramName.makeRef());
134                }
135            }
136    
137            functionBodyContext = functionBodyContext.innerContextWithDescriptorsAliased(aliases);
138    
139            if (extensionFunctionReceiverName == null && descriptor.getValueParameters().isEmpty()) {
140                return jsParameters;
141            }
142    
143            mayBeAddThisParameterForExtensionFunction(jsParameters);
144            addParameters(jsParameters, descriptor, context());
145            return jsParameters;
146        }
147    
148        public static void addParameters(List<JsParameter> list, FunctionDescriptor descriptor, TranslationContext context) {
149            for (ValueParameterDescriptor valueParameter : descriptor.getValueParameters()) {
150                JsParameter jsParameter = new JsParameter(context.getNameForDescriptor(valueParameter));
151                MetadataPackage.setHasDefaultValue(jsParameter, valueParameter.hasDefaultValue());
152                list.add(jsParameter);
153            }
154        }
155    
156        private void mayBeAddThisParameterForExtensionFunction(@NotNull List<JsParameter> jsParameters) {
157            if (isExtensionFunction()) {
158                assert extensionFunctionReceiverName != null;
159                jsParameters.add(new JsParameter(extensionFunctionReceiverName));
160            }
161        }
162    
163        private boolean isExtensionFunction() {
164            return DescriptorUtils.isExtension(descriptor) && !(functionDeclaration instanceof JetFunctionLiteralExpression);
165        }
166    }