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 import com.google.dart.compiler.backend.js.ast.*;
020 import com.intellij.util.SmartList;
021 import org.jetbrains.annotations.NotNull;
022 import org.jetbrains.annotations.Nullable;
023 import org.jetbrains.jet.lang.descriptors.*;
024 import org.jetbrains.jet.lang.descriptors.impl.AnonymousFunctionDescriptor;
025 import org.jetbrains.jet.lang.psi.JetClassBody;
026 import org.jetbrains.jet.lang.psi.JetClassOrObject;
027 import org.jetbrains.jet.lang.psi.JetDeclarationWithBody;
028 import org.jetbrains.jet.lang.resolve.DescriptorUtils;
029 import org.jetbrains.k2js.translate.context.AliasingContext;
030 import org.jetbrains.k2js.translate.context.Namer;
031 import org.jetbrains.k2js.translate.context.TranslationContext;
032 import org.jetbrains.k2js.translate.context.UsageTracker;
033 import org.jetbrains.k2js.translate.declaration.ClassTranslator;
034 import org.jetbrains.k2js.translate.general.AbstractTranslator;
035 import org.jetbrains.k2js.translate.utils.JsAstUtils;
036
037 import java.util.List;
038
039 import static org.jetbrains.k2js.translate.utils.BindingUtils.getFunctionDescriptor;
040 import static org.jetbrains.k2js.translate.utils.FunctionBodyTranslator.translateFunctionBody;
041 import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.getExpectedReceiverDescriptor;
042
043 public class LiteralFunctionTranslator extends AbstractTranslator {
044 private static final String CAPTURED_VALUE_FIELD = "v";
045
046 private final JetDeclarationWithBody declaration;
047 private final FunctionDescriptor descriptor;
048 private final JsFunction jsFunction;
049 private final TranslationContext functionContext;
050 private final boolean inConstructorOrTopLevel;
051 private final ClassDescriptor outerClass;
052 private final JsName receiverName;
053 private JsNameRef tempRef = null;
054
055 private LiteralFunctionTranslator(@NotNull JetDeclarationWithBody declaration, @NotNull TranslationContext context) {
056 super(context);
057
058 this.declaration = declaration;
059 this.descriptor = getFunctionDescriptor(context().bindingContext(), declaration);
060
061 DeclarationDescriptor receiverDescriptor = getExpectedReceiverDescriptor(descriptor);
062 jsFunction = new JsFunction(context().scope(), new JsBlock());
063
064 AliasingContext aliasingContext;
065 if (receiverDescriptor == null) {
066 receiverName = null;
067 aliasingContext = null;
068 }
069 else {
070 receiverName = jsFunction.getScope().declareName(Namer.getReceiverParameterName());
071 aliasingContext = context().aliasingContext().inner(receiverDescriptor, receiverName.makeRef());
072 }
073
074 if (descriptor.getContainingDeclaration() instanceof ConstructorDescriptor) {
075 // KT-2388
076 inConstructorOrTopLevel = true;
077 jsFunction.setName(jsFunction.getScope().declareName(Namer.CALLEE_NAME));
078 outerClass = (ClassDescriptor) descriptor.getContainingDeclaration().getContainingDeclaration();
079 assert outerClass != null;
080
081 if (aliasingContext == null) {
082 aliasingContext = context().aliasingContext();
083 }
084
085 aliasingContext = aliasingContext.notShareableThisAliased(outerClass, new JsNameRef("o", jsFunction.getName().makeRef()));
086 }
087 else {
088 outerClass = null;
089 inConstructorOrTopLevel = DescriptorUtils.isTopLevelDeclaration(descriptor);
090 }
091
092 UsageTracker funTracker = new UsageTracker(descriptor, context().usageTracker(), outerClass);
093 functionContext = context().newFunctionBody(jsFunction, aliasingContext, funTracker);
094 }
095
096 private void translateBody() {
097 JsBlock functionBody = translateFunctionBody(descriptor, declaration, functionContext);
098 jsFunction.getBody().getStatements().addAll(functionBody.getStatements());
099 }
100
101 @NotNull
102 private JsExpression finish() {
103 JsExpression result;
104
105 if (inConstructorOrTopLevel) {
106 result = jsFunction;
107
108 if (outerClass != null) {
109 UsageTracker usageTracker = functionContext.usageTracker();
110 assert usageTracker != null;
111 if (usageTracker.isUsed()) {
112 result = new JsInvocation(context().namer().kotlin("assignOwner"), jsFunction, JsLiteral.THIS);
113 }
114 else {
115 jsFunction.setName(null);
116 }
117 }
118 }
119 else {
120 JsNameRef funReference = context().define(getSuggestedName(functionContext, descriptor), jsFunction);
121
122 InnerFunctionTranslator innerTranslator = new InnerFunctionTranslator(descriptor, functionContext, jsFunction, tempRef);
123 result = innerTranslator.translate(funReference, context());
124 }
125
126 addRegularParameters(descriptor, jsFunction, functionContext, receiverName);
127
128 return result;
129 }
130
131 @NotNull
132 private JsExpression translate() {
133 translateBody();
134 return finish();
135 }
136
137 @NotNull
138 public JsVars translateLocalNamedFunction() {
139 // Add ability to capture this named function.
140 // Will be available like `foo.v` (for function `foo`)
141 // Can not generate direct call because function may have some closures.
142 JsName funName = functionContext.getNameForDescriptor(descriptor);
143 JsNameRef alias = new JsNameRef(CAPTURED_VALUE_FIELD, funName.makeRef());
144 functionContext.aliasingContext().registerAlias(descriptor, alias);
145
146 translateBody();
147
148 UsageTracker funTracker = functionContext.usageTracker();
149 assert funTracker != null;
150 boolean funIsCaptured = funTracker.isCaptured(descriptor);
151
152 // Create temporary variable name which will be contain reference to the function.
153 JsName temp;
154 if (funIsCaptured) {
155 assert !inConstructorOrTopLevel : "A recursive closure in constructor is unsupported.";
156 // Use `context()` because it should be created in the scope which contain call.
157 temp = context().scope().declareTemporary();
158 tempRef = temp.makeRef();
159 }
160 else {
161 temp = null;
162 }
163
164 JsExpression result = finish();
165
166 List<JsVars.JsVar> vars = new SmartList<JsVars.JsVar>();
167
168 if (funIsCaptured) {
169 JsVars.JsVar tempVar = new JsVars.JsVar(temp, new JsObjectLiteral());
170 vars.add(tempVar);
171
172 // Save `result` to the field of temporary variable if the function is captured.
173 result = JsAstUtils.assignment(new JsNameRef(CAPTURED_VALUE_FIELD, temp.makeRef()), result);
174
175 }
176
177 JsVars.JsVar fun = new JsVars.JsVar(funName, result);
178 vars.add(fun);
179
180 return new JsVars(vars, /*mulitline =*/ false);
181 }
182
183 private static void addRegularParameters(
184 @NotNull FunctionDescriptor descriptor,
185 @NotNull JsFunction fun,
186 @NotNull TranslationContext funContext,
187 @Nullable JsName receiverName
188 ) {
189 if (receiverName != null) {
190 fun.getParameters().add(new JsParameter(receiverName));
191 }
192 FunctionTranslator.addParameters(fun.getParameters(), descriptor, funContext);
193 }
194
195 private static String getSuggestedName(TranslationContext context, DeclarationDescriptor descriptor) {
196 String suggestedName = "";
197 DeclarationDescriptor containingDeclaration = descriptor.getContainingDeclaration();
198 if (containingDeclaration != null &&
199 !(containingDeclaration instanceof ClassOrPackageFragmentDescriptor) &&
200 !(containingDeclaration instanceof AnonymousFunctionDescriptor)) {
201 suggestedName = context.getNameForDescriptor(containingDeclaration).getIdent();
202 }
203
204 if (!suggestedName.isEmpty() && !suggestedName.endsWith("$")) {
205 suggestedName += "$";
206 }
207
208 if (descriptor.getName().isSpecial()) {
209 suggestedName += "f";
210 }
211 else {
212 suggestedName += context.getNameForDescriptor(descriptor).getIdent();
213 }
214 return suggestedName;
215 }
216
217 @NotNull
218 public static JsVars translateLocalNamedFunction(@NotNull JetDeclarationWithBody declaration, @NotNull TranslationContext outerContext) {
219 return new LiteralFunctionTranslator(declaration, outerContext).translateLocalNamedFunction();
220 }
221
222 @NotNull
223 public static JsExpression translate(@NotNull JetDeclarationWithBody declaration, @NotNull TranslationContext outerContext) {
224 return new LiteralFunctionTranslator(declaration, outerContext).translate();
225 }
226
227 // TODO: Probably should be moved to other place
228 @NotNull
229 public static JsExpression translate(
230 @NotNull ClassDescriptor outerClass,
231 @NotNull TranslationContext outerClassContext,
232 @NotNull JetClassOrObject declaration,
233 @NotNull ClassDescriptor descriptor,
234 @NotNull ClassTranslator classTranslator
235 ) {
236 JsFunction fun = new JsFunction(outerClassContext.scope(), new JsBlock());
237 JsNameRef outerClassRef = fun.getScope().declareName(Namer.OUTER_CLASS_NAME).makeRef();
238 UsageTracker usageTracker = new UsageTracker(descriptor, outerClassContext.usageTracker(), outerClass);
239 AliasingContext aliasingContext = outerClassContext.aliasingContext().inner(outerClass, outerClassRef);
240 TranslationContext funContext = outerClassContext.newFunctionBody(fun, aliasingContext, usageTracker);
241
242 fun.getBody().getStatements().add(new JsReturn(classTranslator.translate(funContext)));
243
244 JetClassBody body = declaration.getBody();
245 assert body != null;
246
247 JsNameRef define = funContext.define(getSuggestedName(funContext, descriptor), fun);
248 return new InnerObjectTranslator(funContext, fun).translate(define, usageTracker.isUsed() ? outerClassRef : null);
249 }
250 }