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.JsFunction;
021    import com.google.dart.compiler.backend.js.ast.JsName;
022    import com.google.dart.compiler.backend.js.ast.JsParameter;
023    import com.google.dart.compiler.backend.js.ast.JsPropertyInitializer;
024    import com.intellij.util.SmartList;
025    import org.jetbrains.annotations.NotNull;
026    import org.jetbrains.annotations.Nullable;
027    import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
028    import org.jetbrains.jet.lang.descriptors.FunctionDescriptor;
029    import org.jetbrains.jet.lang.descriptors.Modality;
030    import org.jetbrains.jet.lang.descriptors.ValueParameterDescriptor;
031    import org.jetbrains.jet.lang.psi.JetDeclarationWithBody;
032    import org.jetbrains.jet.lang.psi.JetExpression;
033    import org.jetbrains.jet.lang.psi.JetFunctionLiteralExpression;
034    import org.jetbrains.k2js.translate.context.AliasingContext;
035    import org.jetbrains.k2js.translate.context.Namer;
036    import org.jetbrains.k2js.translate.context.TranslationContext;
037    import org.jetbrains.k2js.translate.general.AbstractTranslator;
038    import org.jetbrains.k2js.translate.utils.JsDescriptorUtils;
039    import org.jetbrains.k2js.translate.utils.TranslationUtils;
040    
041    import java.util.Collections;
042    import java.util.List;
043    
044    import static org.jetbrains.k2js.translate.utils.BindingUtils.getFunctionDescriptor;
045    import static org.jetbrains.k2js.translate.utils.ErrorReportingUtils.message;
046    import static org.jetbrains.k2js.translate.utils.FunctionBodyTranslator.translateFunctionBody;
047    import static org.jetbrains.k2js.translate.utils.JsAstUtils.setParameters;
048    import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.getExpectedReceiverDescriptor;
049    
050    public final class FunctionTranslator extends AbstractTranslator {
051        @NotNull
052        public static FunctionTranslator newInstance(@NotNull JetDeclarationWithBody function,
053                @NotNull TranslationContext context) {
054            return new FunctionTranslator(function, context);
055        }
056    
057        @NotNull
058        private final TranslationContext functionBodyContext;
059        @NotNull
060        private final JetDeclarationWithBody functionDeclaration;
061        @Nullable
062        private JsName extensionFunctionReceiverName;
063        @NotNull
064        private final JsFunction functionObject;
065        @NotNull
066        private final FunctionDescriptor descriptor;
067    
068        private FunctionTranslator(@NotNull JetDeclarationWithBody functionDeclaration, @NotNull TranslationContext context) {
069            super(context);
070            this.descriptor = getFunctionDescriptor(context.bindingContext(), functionDeclaration);
071            this.functionDeclaration = functionDeclaration;
072            this.functionObject = context().getFunctionObject(descriptor);
073            assert this.functionObject.getParameters().isEmpty()
074                    : message(bindingContext(), descriptor, "Function " + functionDeclaration.getText() + " processed for the second time.");
075            //NOTE: it's important we compute the context before we start the computation
076            this.functionBodyContext = getFunctionBodyContext();
077        }
078    
079        @NotNull
080        private TranslationContext getFunctionBodyContext() {
081            AliasingContext aliasingContext;
082            if (isExtensionFunction()) {
083                DeclarationDescriptor expectedReceiverDescriptor = getExpectedReceiverDescriptor(descriptor);
084                assert expectedReceiverDescriptor != null;
085                extensionFunctionReceiverName = functionObject.getScope().declareName(Namer.getReceiverParameterName());
086                //noinspection ConstantConditions
087                aliasingContext = context().aliasingContext().inner(expectedReceiverDescriptor, extensionFunctionReceiverName.makeRef());
088            }
089            else {
090                aliasingContext = null;
091            }
092            return context().newFunctionBody(functionObject, aliasingContext, null);
093        }
094    
095        @NotNull
096        public JsPropertyInitializer translateAsEcma5PropertyDescriptor() {
097            generateFunctionObject();
098            return TranslationUtils.translateFunctionAsEcma5PropertyDescriptor(functionObject, descriptor, context());
099        }
100    
101        @NotNull
102        public JsPropertyInitializer translateAsMethod() {
103            JsName functionName = context().getNameForDescriptor(descriptor);
104            generateFunctionObject();
105            return new JsPropertyInitializer(functionName.makeRef(), functionObject);
106        }
107    
108        private void generateFunctionObject() {
109            setParameters(functionObject, translateParameters());
110            translateBody();
111        }
112    
113        private void translateBody() {
114            JetExpression jetBodyExpression = functionDeclaration.getBodyExpression();
115            if (jetBodyExpression == null) {
116                assert 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            if (extensionFunctionReceiverName == null && descriptor.getValueParameters().isEmpty()) {
125                return Collections.emptyList();
126            }
127    
128            List<JsParameter> jsParameters = new SmartList<JsParameter>();
129            mayBeAddThisParameterForExtensionFunction(jsParameters);
130            addParameters(jsParameters, descriptor, context());
131            return jsParameters;
132        }
133    
134        public static void addParameters(List<JsParameter> list, FunctionDescriptor descriptor, TranslationContext context) {
135            for (ValueParameterDescriptor valueParameter : descriptor.getValueParameters()) {
136                list.add(new JsParameter(context.getNameForDescriptor(valueParameter)));
137            }
138        }
139    
140        private void mayBeAddThisParameterForExtensionFunction(@NotNull List<JsParameter> jsParameters) {
141            if (isExtensionFunction()) {
142                assert extensionFunctionReceiverName != null;
143                jsParameters.add(new JsParameter(extensionFunctionReceiverName));
144            }
145        }
146    
147        private boolean isExtensionFunction() {
148            return JsDescriptorUtils.isExtension(descriptor) && !(functionDeclaration instanceof JetFunctionLiteralExpression);
149        }
150    }