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