001    /*
002     * Copyright 2010-2013 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.k2js.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.jet.lang.descriptors.DeclarationDescriptor;
026    import org.jetbrains.jet.lang.descriptors.FunctionDescriptor;
027    import org.jetbrains.jet.lang.descriptors.Modality;
028    import org.jetbrains.jet.lang.descriptors.ValueParameterDescriptor;
029    import org.jetbrains.jet.lang.psi.JetDeclarationWithBody;
030    import org.jetbrains.jet.lang.psi.JetFunctionLiteralExpression;
031    import org.jetbrains.k2js.translate.context.AliasingContext;
032    import org.jetbrains.k2js.translate.context.Namer;
033    import org.jetbrains.k2js.translate.context.TranslationContext;
034    import org.jetbrains.k2js.translate.general.AbstractTranslator;
035    import org.jetbrains.k2js.translate.utils.JsDescriptorUtils;
036    import org.jetbrains.k2js.translate.utils.TranslationUtils;
037    
038    import java.util.Collections;
039    import java.util.List;
040    
041    import static org.jetbrains.k2js.translate.utils.BindingUtils.getFunctionDescriptor;
042    import static org.jetbrains.k2js.translate.utils.ErrorReportingUtils.message;
043    import static org.jetbrains.k2js.translate.utils.FunctionBodyTranslator.translateFunctionBody;
044    import static org.jetbrains.k2js.translate.utils.JsAstUtils.setParameters;
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 final 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            return new JsPropertyInitializer(functionName.makeRef(), functionObject);
102        }
103    
104        private void generateFunctionObject() {
105            setParameters(functionObject, translateParameters());
106            translateBody();
107        }
108    
109        private void translateBody() {
110            if (!functionDeclaration.hasBody()) {
111                assert descriptor.getModality().equals(Modality.ABSTRACT);
112                return;
113            }
114            functionObject.getBody().getStatements().addAll(translateFunctionBody(descriptor, functionDeclaration, functionBodyContext).getStatements());
115        }
116    
117        @NotNull
118        private List<JsParameter> translateParameters() {
119            if (extensionFunctionReceiverName == null && descriptor.getValueParameters().isEmpty()) {
120                return Collections.emptyList();
121            }
122    
123            List<JsParameter> jsParameters = new SmartList<JsParameter>();
124            mayBeAddThisParameterForExtensionFunction(jsParameters);
125            addParameters(jsParameters, descriptor, context());
126            return jsParameters;
127        }
128    
129        public static void addParameters(List<JsParameter> list, FunctionDescriptor descriptor, TranslationContext context) {
130            for (ValueParameterDescriptor valueParameter : descriptor.getValueParameters()) {
131                JsParameter jsParameter = new JsParameter(context.getNameForDescriptor(valueParameter));
132                MetadataPackage.setHasDefaultValue(jsParameter, valueParameter.hasDefaultValue());
133                list.add(jsParameter);
134            }
135        }
136    
137        private void mayBeAddThisParameterForExtensionFunction(@NotNull List<JsParameter> jsParameters) {
138            if (isExtensionFunction()) {
139                assert extensionFunctionReceiverName != null;
140                jsParameters.add(new JsParameter(extensionFunctionReceiverName));
141            }
142        }
143    
144        private boolean isExtensionFunction() {
145            return JsDescriptorUtils.isExtension(descriptor) && !(functionDeclaration instanceof JetFunctionLiteralExpression);
146        }
147    }