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.JetFunctionLiteralExpression;
033    import org.jetbrains.k2js.translate.context.AliasingContext;
034    import org.jetbrains.k2js.translate.context.Namer;
035    import org.jetbrains.k2js.translate.context.TranslationContext;
036    import org.jetbrains.k2js.translate.general.AbstractTranslator;
037    import org.jetbrains.k2js.translate.utils.JsDescriptorUtils;
038    import org.jetbrains.k2js.translate.utils.TranslationUtils;
039    
040    import java.util.Collections;
041    import java.util.List;
042    
043    import static org.jetbrains.k2js.translate.utils.BindingUtils.getFunctionDescriptor;
044    import static org.jetbrains.k2js.translate.utils.ErrorReportingUtils.message;
045    import static org.jetbrains.k2js.translate.utils.FunctionBodyTranslator.translateFunctionBody;
046    import static org.jetbrains.k2js.translate.utils.JsAstUtils.setParameters;
047    
048    public final class FunctionTranslator extends AbstractTranslator {
049        @NotNull
050        public static FunctionTranslator newInstance(@NotNull JetDeclarationWithBody function,
051                @NotNull TranslationContext context) {
052            return new FunctionTranslator(function, context);
053        }
054    
055        @NotNull
056        private final TranslationContext functionBodyContext;
057        @NotNull
058        private final JetDeclarationWithBody functionDeclaration;
059        @Nullable
060        private JsName extensionFunctionReceiverName;
061        @NotNull
062        private final JsFunction functionObject;
063        @NotNull
064        private final FunctionDescriptor descriptor;
065    
066        private FunctionTranslator(@NotNull JetDeclarationWithBody functionDeclaration, @NotNull TranslationContext context) {
067            super(context);
068            this.descriptor = getFunctionDescriptor(context.bindingContext(), functionDeclaration);
069            this.functionDeclaration = functionDeclaration;
070            this.functionObject = context().getFunctionObject(descriptor);
071            assert this.functionObject.getParameters().isEmpty()
072                    : message(descriptor, "Function " + functionDeclaration.getText() + " processed for the second time.");
073            //NOTE: it's important we compute the context before we start the computation
074            this.functionBodyContext = getFunctionBodyContext();
075        }
076    
077        @NotNull
078        private TranslationContext getFunctionBodyContext() {
079            AliasingContext aliasingContext;
080            if (isExtensionFunction()) {
081                DeclarationDescriptor expectedReceiverDescriptor = descriptor.getReceiverParameter();
082                assert expectedReceiverDescriptor != null;
083                extensionFunctionReceiverName = functionObject.getScope().declareName(Namer.getReceiverParameterName());
084                //noinspection ConstantConditions
085                aliasingContext = context().aliasingContext().inner(expectedReceiverDescriptor, extensionFunctionReceiverName.makeRef());
086            }
087            else {
088                aliasingContext = null;
089            }
090            return context().newFunctionBody(functionObject, aliasingContext);
091        }
092    
093        @NotNull
094        public JsPropertyInitializer translateAsEcma5PropertyDescriptor() {
095            generateFunctionObject();
096            return TranslationUtils.translateFunctionAsEcma5PropertyDescriptor(functionObject, descriptor, context());
097        }
098    
099        @NotNull
100        public JsPropertyInitializer translateAsMethod() {
101            JsName functionName = context().getNameForDescriptor(descriptor);
102            generateFunctionObject();
103            return new JsPropertyInitializer(functionName.makeRef(), functionObject);
104        }
105    
106        private void generateFunctionObject() {
107            setParameters(functionObject, translateParameters());
108            translateBody();
109        }
110    
111        private void translateBody() {
112            if (!functionDeclaration.hasBody()) {
113                assert descriptor.getModality().equals(Modality.ABSTRACT);
114                return;
115            }
116            functionObject.getBody().getStatements().addAll(translateFunctionBody(descriptor, functionDeclaration, functionBodyContext).getStatements());
117        }
118    
119        @NotNull
120        private List<JsParameter> translateParameters() {
121            if (extensionFunctionReceiverName == null && descriptor.getValueParameters().isEmpty()) {
122                return Collections.emptyList();
123            }
124    
125            List<JsParameter> jsParameters = new SmartList<JsParameter>();
126            mayBeAddThisParameterForExtensionFunction(jsParameters);
127            addParameters(jsParameters, descriptor, context());
128            return jsParameters;
129        }
130    
131        public static void addParameters(List<JsParameter> list, FunctionDescriptor descriptor, TranslationContext context) {
132            for (ValueParameterDescriptor valueParameter : descriptor.getValueParameters()) {
133                list.add(new JsParameter(context.getNameForDescriptor(valueParameter)));
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    }