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.psi.tree.IElementType;
021 import org.jetbrains.annotations.NotNull;
022 import org.jetbrains.annotations.Nullable;
023 import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
024 import org.jetbrains.jet.lang.descriptors.FunctionDescriptor;
025 import org.jetbrains.jet.lang.descriptors.VariableDescriptor;
026 import org.jetbrains.jet.lang.psi.*;
027 import org.jetbrains.jet.lang.resolve.BindingContext;
028 import org.jetbrains.jet.lang.resolve.BindingContextUtils;
029 import org.jetbrains.jet.lang.resolve.constants.CompileTimeConstant;
030 import org.jetbrains.jet.lang.resolve.constants.NullValue;
031 import org.jetbrains.jet.lang.types.JetType;
032 import org.jetbrains.jet.lexer.JetTokens;
033 import org.jetbrains.k2js.translate.context.TemporaryVariable;
034 import org.jetbrains.k2js.translate.context.TranslationContext;
035 import org.jetbrains.k2js.translate.declaration.ClassTranslator;
036 import org.jetbrains.k2js.translate.expression.foreach.ForTranslator;
037 import org.jetbrains.k2js.translate.general.Translation;
038 import org.jetbrains.k2js.translate.general.TranslatorVisitor;
039 import org.jetbrains.k2js.translate.operation.BinaryOperationTranslator;
040 import org.jetbrains.k2js.translate.operation.UnaryOperationTranslator;
041 import org.jetbrains.k2js.translate.reference.AccessTranslationUtils;
042 import org.jetbrains.k2js.translate.reference.CallExpressionTranslator;
043 import org.jetbrains.k2js.translate.reference.QualifiedExpressionTranslator;
044 import org.jetbrains.k2js.translate.reference.ReferenceTranslator;
045 import org.jetbrains.k2js.translate.utils.BindingUtils;
046 import org.jetbrains.k2js.translate.utils.JsAstUtils;
047 import org.jetbrains.k2js.translate.utils.TranslationUtils;
048 import org.jetbrains.k2js.translate.utils.mutator.AssignToExpressionMutator;
049
050 import java.util.List;
051
052 import static org.jetbrains.jet.lang.resolve.BindingContextUtils.isVarCapturedInClosure;
053 import static org.jetbrains.k2js.translate.context.Namer.getCapturedVarAccessor;
054 import static org.jetbrains.k2js.translate.general.Translation.translateAsExpression;
055 import static org.jetbrains.k2js.translate.reference.ReferenceTranslator.translateAsFQReference;
056 import static org.jetbrains.k2js.translate.utils.BindingUtils.*;
057 import static org.jetbrains.k2js.translate.utils.ErrorReportingUtils.message;
058 import static org.jetbrains.k2js.translate.utils.JsAstUtils.*;
059 import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.getReceiverParameterForDeclaration;
060 import static org.jetbrains.k2js.translate.utils.TranslationUtils.translateInitializerForProperty;
061 import static org.jetbrains.k2js.translate.utils.mutator.LastExpressionMutator.mutateLastExpression;
062
063 public final class ExpressionVisitor extends TranslatorVisitor<JsNode> {
064 @Override
065 @NotNull
066 public JsNode visitConstantExpression(@NotNull JetConstantExpression expression, @NotNull TranslationContext context) {
067 return translateConstantExpression(expression, context).source(expression);
068 }
069
070 @NotNull
071 private static JsNode translateConstantExpression(@NotNull JetConstantExpression expression, @NotNull TranslationContext context) {
072 CompileTimeConstant<?> compileTimeValue = context.bindingContext().get(BindingContext.COMPILE_TIME_VALUE, expression);
073
074 assert compileTimeValue != null : message(expression, "Expression is not compile time value: " + expression.getText() + " ");
075
076 if (compileTimeValue instanceof NullValue) {
077 return JsLiteral.NULL;
078 }
079
080 Object value = getCompileTimeValue(context.bindingContext(), expression, compileTimeValue);
081 if (value instanceof Integer || value instanceof Short || value instanceof Byte) {
082 return context.program().getNumberLiteral(((Number) value).intValue());
083 }
084 else if (value instanceof Number) {
085 return context.program().getNumberLiteral(((Number) value).doubleValue());
086 }
087 else if (value instanceof Boolean) {
088 return JsLiteral.getBoolean((Boolean) value);
089 }
090
091 //TODO: test
092 if (value instanceof String) {
093 return context.program().getStringLiteral((String) value);
094 }
095 if (value instanceof Character) {
096 return context.program().getStringLiteral(value.toString());
097 }
098
099 throw new AssertionError(message(expression, "Unsupported constant expression: " + expression.getText() + " "));
100 }
101
102 @Override
103 @NotNull
104 public JsNode visitBlockExpression(@NotNull JetBlockExpression jetBlock, @NotNull TranslationContext context) {
105 List<JetElement> statements = jetBlock.getStatements();
106 JsBlock jsBlock = new JsBlock();
107 TranslationContext blockContext = context.innerBlock(jsBlock);
108 for (JetElement statement : statements) {
109 assert statement instanceof JetExpression : "Elements in JetBlockExpression " +
110 "should be of type JetExpression";
111 JsNode jsNode = statement.accept(this, blockContext);
112 if (jsNode != null) {
113 jsBlock.getStatements().add(convertToStatement(jsNode));
114 }
115 }
116 return jsBlock;
117 }
118
119 @Override
120 public JsNode visitMultiDeclaration(@NotNull JetMultiDeclaration multiDeclaration, @NotNull TranslationContext context) {
121 JetExpression jetInitializer = multiDeclaration.getInitializer();
122 assert jetInitializer != null : "Initializer for multi declaration must be not null";
123 JsExpression initializer = Translation.translateAsExpression(jetInitializer, context);
124 return MultiDeclarationTranslator.translate(multiDeclaration, context.scope().declareTemporary(), initializer, context);
125 }
126
127 @Override
128 @NotNull
129 public JsNode visitReturnExpression(@NotNull JetReturnExpression jetReturnExpression,
130 @NotNull TranslationContext context) {
131 JetExpression returned = jetReturnExpression.getReturnedExpression();
132 return new JsReturn(returned != null ? translateAsExpression(returned, context) : null).source(jetReturnExpression);
133 }
134
135 @Override
136 @NotNull
137 public JsNode visitParenthesizedExpression(@NotNull JetParenthesizedExpression expression,
138 @NotNull TranslationContext context) {
139 JetExpression expressionInside = expression.getExpression();
140 if (expressionInside != null) {
141 JsNode translated = expressionInside.accept(this, context);
142 if (translated != null) {
143 return translated;
144 }
145 }
146 return context.program().getEmptyStatement();
147 }
148
149 @Override
150 @NotNull
151 public JsNode visitBinaryExpression(@NotNull JetBinaryExpression expression,
152 @NotNull TranslationContext context) {
153 return BinaryOperationTranslator.translate(expression, context);
154 }
155
156 @Override
157 @NotNull
158 // assume it is a local variable declaration
159 public JsNode visitProperty(@NotNull JetProperty expression, @NotNull TranslationContext context) {
160 VariableDescriptor descriptor = BindingContextUtils.getNotNull(context.bindingContext(), BindingContext.VARIABLE, expression);
161 JsExpression initializer = translateInitializerForProperty(expression, context);
162 JsName name = context.getNameForDescriptor(descriptor);
163 if (isVarCapturedInClosure(context.bindingContext(), descriptor)) {
164 JsNameRef alias = getCapturedVarAccessor(name.makeRef());
165 initializer = JsAstUtils.wrapValue(alias, initializer == null ? JsLiteral.NULL : initializer);
166 }
167
168 return newVar(name, initializer).source(expression);
169 }
170
171 @Override
172 @NotNull
173 public JsNode visitCallExpression(@NotNull JetCallExpression expression,
174 @NotNull TranslationContext context) {
175 return CallExpressionTranslator.translate(expression, null, context).source(expression);
176 }
177
178 @Override
179 @NotNull
180 public JsNode visitIfExpression(@NotNull JetIfExpression expression, @NotNull TranslationContext context) {
181 JsExpression testExpression = translateConditionExpression(expression.getCondition(), context);
182 JetExpression thenExpression = expression.getThen();
183 JetExpression elseExpression = expression.getElse();
184 assert thenExpression != null;
185 JsNode thenNode = thenExpression.accept(this, context);
186 JsNode elseNode = elseExpression == null ? null : elseExpression.accept(this, context);
187
188 boolean isKotlinStatement = BindingUtils.isStatement(context.bindingContext(), expression);
189 boolean canBeJsExpression = thenNode instanceof JsExpression && elseNode instanceof JsExpression;
190 if (!isKotlinStatement && canBeJsExpression) {
191 return new JsConditional(testExpression, convertToExpression(thenNode), convertToExpression(elseNode)).source(expression);
192 }
193 else {
194 JsIf ifStatement = new JsIf(testExpression, convertToStatement(thenNode), elseNode == null ? null : convertToStatement(elseNode));
195 ifStatement.source(expression);
196 if (isKotlinStatement) {
197 return ifStatement;
198 }
199
200 TemporaryVariable result = context.declareTemporary(null);
201 AssignToExpressionMutator saveResultToTemporaryMutator = new AssignToExpressionMutator(result.reference());
202 context.addStatementToCurrentBlock(mutateLastExpression(ifStatement, saveResultToTemporaryMutator));
203 return result.reference();
204 }
205 }
206
207 @Override
208 @NotNull
209 public JsExpression visitSimpleNameExpression(@NotNull JetSimpleNameExpression expression,
210 @NotNull TranslationContext context) {
211 return ReferenceTranslator.translateSimpleNameWithQualifier(expression, null, context).source(expression);
212 }
213
214 @NotNull
215 private JsStatement translateNullableExpressionAsNotNullStatement(@Nullable JetExpression nullableExpression,
216 @NotNull TranslationContext context) {
217 if (nullableExpression == null) {
218 return context.program().getEmptyStatement();
219 }
220 return convertToStatement(nullableExpression.accept(this, context));
221 }
222
223 @NotNull
224 private JsExpression translateConditionExpression(@Nullable JetExpression expression,
225 @NotNull TranslationContext context) {
226 JsExpression jsCondition = translateNullableExpression(expression, context);
227 assert (jsCondition != null) : "Condition should not be empty";
228 return convertToExpression(jsCondition);
229 }
230
231 @Nullable
232 private JsExpression translateNullableExpression(@Nullable JetExpression expression,
233 @NotNull TranslationContext context) {
234 if (expression == null) {
235 return null;
236 }
237 return convertToExpression(expression.accept(this, context));
238 }
239
240 @Override
241 @NotNull
242 public JsNode visitWhileExpression(@NotNull JetWhileExpression expression, @NotNull TranslationContext context) {
243 return createWhile(new JsWhile(), expression, context);
244 }
245
246 @Override
247 @NotNull
248 public JsNode visitDoWhileExpression(@NotNull JetDoWhileExpression expression, @NotNull TranslationContext context) {
249 return createWhile(new JsDoWhile(), expression, context);
250 }
251
252 private JsNode createWhile(@NotNull JsWhile result, @NotNull JetWhileExpressionBase expression, @NotNull TranslationContext context) {
253 result.setCondition(translateConditionExpression(expression.getCondition(), context));
254 result.setBody(translateNullableExpressionAsNotNullStatement(expression.getBody(), context));
255 return result.source(expression);
256 }
257
258 @Override
259 @NotNull
260 public JsNode visitStringTemplateExpression(@NotNull JetStringTemplateExpression expression,
261 @NotNull TranslationContext context) {
262 JsStringLiteral stringLiteral = resolveAsStringConstant(expression, context);
263 if (stringLiteral != null) {
264 return stringLiteral;
265 }
266 return resolveAsTemplate(expression, context).source(expression);
267 }
268
269 @NotNull
270 private static JsNode resolveAsTemplate(@NotNull JetStringTemplateExpression expression,
271 @NotNull TranslationContext context) {
272 return StringTemplateTranslator.translate(expression, context);
273 }
274
275 @Nullable
276 private static JsStringLiteral resolveAsStringConstant(@NotNull JetExpression expression,
277 @NotNull TranslationContext context) {
278 Object value = getCompileTimeValue(context.bindingContext(), expression);
279 if (value == null) {
280 return null;
281 }
282 assert value instanceof String : "Compile time constant template should be a String constant.";
283 String constantString = (String) value;
284 return context.program().getStringLiteral(constantString);
285 }
286
287 @Override
288 @NotNull
289 public JsNode visitDotQualifiedExpression(@NotNull JetDotQualifiedExpression expression,
290 @NotNull TranslationContext context) {
291 return QualifiedExpressionTranslator.translateQualifiedExpression(expression, context);
292 }
293
294 @Override
295 public JsNode visitLabeledExpression(
296 @NotNull JetLabeledExpression expression, TranslationContext context
297 ) {
298 JetExpression baseExpression = expression.getBaseExpression();
299 assert baseExpression != null;
300 return new JsLabel(context.scope().declareName(getReferencedName(expression.getTargetLabel())),
301 convertToStatement(baseExpression.accept(this, context))).source(expression);
302 }
303
304 @Override
305 @NotNull
306 public JsNode visitPrefixExpression(
307 @NotNull JetPrefixExpression expression,
308 @NotNull TranslationContext context
309 ) {
310 return UnaryOperationTranslator.translate(expression, context).source(expression);
311 }
312
313 @Override
314 @NotNull
315 public JsNode visitPostfixExpression(@NotNull JetPostfixExpression expression,
316 @NotNull TranslationContext context) {
317 return UnaryOperationTranslator.translate(expression, context).source(expression);
318 }
319
320 @Override
321 @NotNull
322 public JsNode visitIsExpression(@NotNull JetIsExpression expression,
323 @NotNull TranslationContext context) {
324 return Translation.patternTranslator(context).translateIsExpression(expression);
325 }
326
327 @Override
328 @NotNull
329 public JsNode visitSafeQualifiedExpression(@NotNull JetSafeQualifiedExpression expression,
330 @NotNull TranslationContext context) {
331 return QualifiedExpressionTranslator.translateQualifiedExpression(expression, context).source(expression);
332 }
333
334 @Override
335 @Nullable
336 public JsNode visitWhenExpression(@NotNull JetWhenExpression expression,
337 @NotNull TranslationContext context) {
338 return WhenTranslator.translate(expression, context);
339 }
340
341 @Override
342 @NotNull
343 public JsNode visitBinaryWithTypeRHSExpression(@NotNull JetBinaryExpressionWithTypeRHS expression,
344 @NotNull TranslationContext context) {
345 JsExpression jsExpression = Translation.translateAsExpression(expression.getLeft(), context);
346
347 if (expression.getOperationReference().getReferencedNameElementType() != JetTokens.AS_KEYWORD)
348 return jsExpression.source(expression);
349
350 JetType rightType = BindingContextUtils.getNotNull(context.bindingContext(), BindingContext.TYPE, expression.getRight());
351 JetType leftType = BindingContextUtils.getNotNull(context.bindingContext(), BindingContext.EXPRESSION_TYPE, expression.getLeft());
352 if (rightType.isNullable() || !leftType.isNullable()) {
353 return jsExpression.source(expression);
354 }
355
356 // KT-2670
357 // we actually do not care for types in js
358 return TranslationUtils.sure(jsExpression, context).source(expression);
359 }
360
361 private static String getReferencedName(JetSimpleNameExpression expression) {
362 String name = expression.getReferencedName();
363 return name.charAt(0) == '@' ? name.substring(1) + '$' : name;
364 }
365
366 private static String getTargetLabel(JetExpressionWithLabel expression, TranslationContext context) {
367 JetSimpleNameExpression labelElement = expression.getTargetLabel();
368 if (labelElement == null) {
369 return null;
370 }
371 else {
372 JsName name = context.scope().findName(getReferencedName(labelElement));
373 assert name != null;
374 return name.getIdent();
375 }
376 }
377
378 @Override
379 @NotNull
380 public JsNode visitBreakExpression(@NotNull JetBreakExpression expression,
381 @NotNull TranslationContext context) {
382 return new JsBreak(getTargetLabel(expression, context)).source(expression);
383 }
384
385 @Override
386 @NotNull
387 public JsNode visitContinueExpression(@NotNull JetContinueExpression expression,
388 @NotNull TranslationContext context) {
389 return new JsContinue(getTargetLabel(expression, context)).source(expression);
390 }
391
392 @Override
393 @NotNull
394 public JsNode visitFunctionLiteralExpression(@NotNull JetFunctionLiteralExpression expression, @NotNull TranslationContext context) {
395 return new LiteralFunctionTranslator(context).translate(expression.getFunctionLiteral());
396 }
397
398 @Override
399 @NotNull
400 public JsNode visitNamedFunction(@NotNull JetNamedFunction expression, @NotNull TranslationContext context) {
401 JsExpression alias = new LiteralFunctionTranslator(context).translate(expression);
402
403 FunctionDescriptor descriptor = getFunctionDescriptor(context.bindingContext(), expression);
404 JsName name = context.getNameForDescriptor(descriptor);
405
406 return new JsVars(new JsVars.JsVar(name, alias)).source(expression);
407 }
408
409 @Override
410 @NotNull
411 public JsNode visitThisExpression(@NotNull JetThisExpression expression, @NotNull TranslationContext context) {
412 DeclarationDescriptor thisExpression =
413 getDescriptorForReferenceExpression(context.bindingContext(), expression.getInstanceReference());
414 assert thisExpression != null : "This expression must reference a descriptor: " + expression.getText();
415
416 return context.getThisObject(getReceiverParameterForDeclaration(thisExpression)).source(expression);
417 }
418
419 @Override
420 @NotNull
421 public JsNode visitArrayAccessExpression(@NotNull JetArrayAccessExpression expression,
422 @NotNull TranslationContext context) {
423 return AccessTranslationUtils.translateAsGet(expression, context);
424 }
425
426 @Override
427 @NotNull
428 public JsNode visitSuperExpression(@NotNull JetSuperExpression expression, @NotNull TranslationContext context) {
429 DeclarationDescriptor superClassDescriptor = context.bindingContext().get(BindingContext.REFERENCE_TARGET, expression.getInstanceReference());
430 assert superClassDescriptor != null: message(expression);
431 return translateAsFQReference(superClassDescriptor, context);
432 }
433
434 @Override
435 @NotNull
436 public JsNode visitForExpression(@NotNull JetForExpression expression,
437 @NotNull TranslationContext context) {
438 return ForTranslator.translate(expression, context).source(expression);
439 }
440
441 @Override
442 @NotNull
443 public JsNode visitTryExpression(@NotNull JetTryExpression expression,
444 @NotNull TranslationContext context) {
445 return TryTranslator.translate(expression, context).source(expression);
446 }
447
448 @Override
449 @NotNull
450 public JsNode visitThrowExpression(@NotNull JetThrowExpression expression,
451 @NotNull TranslationContext context) {
452 JetExpression thrownExpression = expression.getThrownExpression();
453 assert thrownExpression != null : "Thrown expression must not be null";
454 return new JsThrow(translateAsExpression(thrownExpression, context)).source(expression);
455 }
456
457 @Override
458 @NotNull
459 public JsNode visitObjectLiteralExpression(@NotNull JetObjectLiteralExpression expression,
460 @NotNull TranslationContext context) {
461 return ClassTranslator.generateObjectLiteral(expression.getObjectDeclaration(), context);
462 }
463
464 @Override
465 @NotNull
466 public JsNode visitObjectDeclaration(@NotNull JetObjectDeclaration expression,
467 @NotNull TranslationContext context) {
468 DeclarationDescriptor descriptor = getDescriptorForElement(context.bindingContext(), expression);
469 JsName name = context.getNameForDescriptor(descriptor);
470 JsExpression value = ClassTranslator.generateClassCreation(expression, context);
471 return newVar(name, value).source(expression);
472 }
473 }