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 }