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 org.jetbrains.annotations.NotNull;
021 import org.jetbrains.annotations.Nullable;
022 import org.jetbrains.jet.lang.descriptors.*;
023 import org.jetbrains.jet.lang.psi.*;
024 import org.jetbrains.jet.lang.resolve.BindingContext;
025 import org.jetbrains.jet.lang.resolve.BindingContextUtils;
026 import org.jetbrains.jet.lang.resolve.bindingContextUtil.BindingContextUtilPackage;
027 import org.jetbrains.jet.lang.resolve.constants.CompileTimeConstant;
028 import org.jetbrains.jet.lang.resolve.constants.NullValue;
029 import org.jetbrains.jet.lang.types.JetType;
030 import org.jetbrains.jet.lang.types.TypeUtils;
031 import org.jetbrains.jet.lexer.JetTokens;
032 import org.jetbrains.k2js.translate.context.TemporaryVariable;
033 import org.jetbrains.k2js.translate.context.TranslationContext;
034 import org.jetbrains.k2js.translate.declaration.ClassTranslator;
035 import org.jetbrains.k2js.translate.expression.foreach.ForTranslator;
036 import org.jetbrains.k2js.translate.general.Translation;
037 import org.jetbrains.k2js.translate.general.TranslatorVisitor;
038 import org.jetbrains.k2js.translate.operation.BinaryOperationTranslator;
039 import org.jetbrains.k2js.translate.operation.UnaryOperationTranslator;
040 import org.jetbrains.k2js.translate.reference.*;
041 import org.jetbrains.k2js.translate.utils.*;
042 import org.jetbrains.k2js.translate.utils.mutator.AssignToExpressionMutator;
043
044 import java.util.List;
045
046 import static org.jetbrains.jet.lang.resolve.BindingContextUtils.isVarCapturedInClosure;
047 import static org.jetbrains.k2js.translate.context.Namer.getCapturedVarAccessor;
048 import static org.jetbrains.k2js.translate.general.Translation.translateAsExpression;
049 import static org.jetbrains.k2js.translate.reference.ReferenceTranslator.translateAsFQReference;
050 import static org.jetbrains.k2js.translate.utils.BindingUtils.*;
051 import static org.jetbrains.k2js.translate.utils.ErrorReportingUtils.message;
052 import static org.jetbrains.k2js.translate.utils.JsAstUtils.*;
053 import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.getReceiverParameterForDeclaration;
054 import static org.jetbrains.k2js.translate.utils.TranslationUtils.translateInitializerForProperty;
055 import static org.jetbrains.k2js.translate.utils.mutator.LastExpressionMutator.mutateLastExpression;
056
057 public final class ExpressionVisitor extends TranslatorVisitor<JsNode> {
058 @Override
059 @NotNull
060 public JsNode visitConstantExpression(@NotNull JetConstantExpression expression, @NotNull TranslationContext context) {
061 return translateConstantExpression(expression, context).source(expression);
062 }
063
064 @NotNull
065 private static JsNode translateConstantExpression(@NotNull JetConstantExpression expression, @NotNull TranslationContext context) {
066 CompileTimeConstant<?> compileTimeValue = context.bindingContext().get(BindingContext.COMPILE_TIME_VALUE, expression);
067
068 assert compileTimeValue != null : message(expression, "Expression is not compile time value: " + expression.getText() + " ");
069
070 if (compileTimeValue instanceof NullValue) {
071 return JsLiteral.NULL;
072 }
073
074 Object value = getCompileTimeValue(context.bindingContext(), expression, compileTimeValue);
075 if (value instanceof Integer || value instanceof Short || value instanceof Byte) {
076 return context.program().getNumberLiteral(((Number) value).intValue());
077 }
078 else if (value instanceof Number) {
079 return context.program().getNumberLiteral(((Number) value).doubleValue());
080 }
081 else if (value instanceof Boolean) {
082 return JsLiteral.getBoolean((Boolean) value);
083 }
084
085 //TODO: test
086 if (value instanceof String) {
087 return context.program().getStringLiteral((String) value);
088 }
089 if (value instanceof Character) {
090 return context.program().getStringLiteral(value.toString());
091 }
092
093 throw new AssertionError(message(expression, "Unsupported constant expression: " + expression.getText() + " "));
094 }
095
096 @Override
097 @NotNull
098 public JsNode visitBlockExpression(@NotNull JetBlockExpression jetBlock, @NotNull TranslationContext context) {
099 List<JetElement> statements = jetBlock.getStatements();
100 JsBlock jsBlock = new JsBlock();
101 TranslationContext blockContext = context.innerBlock(jsBlock);
102 for (JetElement statement : statements) {
103 assert statement instanceof JetExpression : "Elements in JetBlockExpression " +
104 "should be of type JetExpression";
105 JsNode jsNode = statement.accept(this, blockContext);
106 if (jsNode != null) {
107 jsBlock.getStatements().add(convertToStatement(jsNode));
108 }
109 }
110 return jsBlock;
111 }
112
113 @Override
114 public JsNode visitMultiDeclaration(@NotNull JetMultiDeclaration multiDeclaration, @NotNull TranslationContext context) {
115 JetExpression jetInitializer = multiDeclaration.getInitializer();
116 assert jetInitializer != null : "Initializer for multi declaration must be not null";
117 JsExpression initializer = Translation.translateAsExpression(jetInitializer, context);
118 return MultiDeclarationTranslator.translate(multiDeclaration, context.scope().declareTemporary(), initializer, context);
119 }
120
121 @Override
122 @NotNull
123 public JsNode visitReturnExpression(@NotNull JetReturnExpression jetReturnExpression,
124 @NotNull TranslationContext context) {
125 JetExpression returned = jetReturnExpression.getReturnedExpression();
126 return new JsReturn(returned != null ? translateAsExpression(returned, context) : null).source(jetReturnExpression);
127 }
128
129 @Override
130 @NotNull
131 public JsNode visitParenthesizedExpression(@NotNull JetParenthesizedExpression expression,
132 @NotNull TranslationContext context) {
133 JetExpression expressionInside = expression.getExpression();
134 if (expressionInside != null) {
135 JsNode translated = expressionInside.accept(this, context);
136 if (translated != null) {
137 return translated;
138 }
139 }
140 return context.program().getEmptyStatement();
141 }
142
143 @Override
144 @NotNull
145 public JsNode visitBinaryExpression(@NotNull JetBinaryExpression expression,
146 @NotNull TranslationContext context) {
147 return BinaryOperationTranslator.translate(expression, context);
148 }
149
150 @Override
151 @NotNull
152 // assume it is a local variable declaration
153 public JsNode visitProperty(@NotNull JetProperty expression, @NotNull TranslationContext context) {
154 VariableDescriptor descriptor = BindingContextUtils.getNotNull(context.bindingContext(), BindingContext.VARIABLE, expression);
155 JsExpression initializer = translateInitializerForProperty(expression, context);
156 JsName name = context.getNameForDescriptor(descriptor);
157 if (isVarCapturedInClosure(context.bindingContext(), descriptor)) {
158 JsNameRef alias = getCapturedVarAccessor(name.makeRef());
159 initializer = JsAstUtils.wrapValue(alias, initializer == null ? JsLiteral.NULL : initializer);
160 }
161
162 return newVar(name, initializer).source(expression);
163 }
164
165 @Override
166 @NotNull
167 public JsNode visitCallableReferenceExpression(@NotNull JetCallableReferenceExpression expression, @NotNull TranslationContext context) {
168 return CallableReferenceTranslator.INSTANCE$.translate(expression, context);
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 = BindingContextUtilPackage.isUsedAsStatement(expression, context.bindingContext());
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 JetTypeReference right = expression.getRight();
351 assert right != null;
352
353 JetType rightType = BindingContextUtils.getNotNull(context.bindingContext(), BindingContext.TYPE, right);
354 JetType leftType = BindingContextUtils.getNotNull(context.bindingContext(), BindingContext.EXPRESSION_TYPE, expression.getLeft());
355 if (TypeUtils.isNullableType(rightType) || !TypeUtils.isNullableType(leftType)) {
356 return jsExpression.source(expression);
357 }
358
359 // KT-2670
360 // we actually do not care for types in js
361 return TranslationUtils.sure(jsExpression, context).source(expression);
362 }
363
364 private static String getReferencedName(JetSimpleNameExpression expression) {
365 String name = expression.getReferencedName();
366 return name.charAt(0) == '@' ? name.substring(1) + '$' : name;
367 }
368
369 private static String getTargetLabel(JetExpressionWithLabel expression, TranslationContext context) {
370 JetSimpleNameExpression labelElement = expression.getTargetLabel();
371 if (labelElement == null) {
372 return null;
373 }
374 else {
375 JsName name = context.scope().findName(getReferencedName(labelElement));
376 assert name != null;
377 return name.getIdent();
378 }
379 }
380
381 @Override
382 @NotNull
383 public JsNode visitBreakExpression(@NotNull JetBreakExpression expression,
384 @NotNull TranslationContext context) {
385 return new JsBreak(getTargetLabel(expression, context)).source(expression);
386 }
387
388 @Override
389 @NotNull
390 public JsNode visitContinueExpression(@NotNull JetContinueExpression expression,
391 @NotNull TranslationContext context) {
392 return new JsContinue(getTargetLabel(expression, context)).source(expression);
393 }
394
395 @Override
396 @NotNull
397 public JsNode visitFunctionLiteralExpression(@NotNull JetFunctionLiteralExpression expression, @NotNull TranslationContext context) {
398 return new LiteralFunctionTranslator(context).translate(expression.getFunctionLiteral());
399 }
400
401 @Override
402 @NotNull
403 public JsNode visitNamedFunction(@NotNull JetNamedFunction expression, @NotNull TranslationContext context) {
404 JsExpression alias = new LiteralFunctionTranslator(context).translate(expression);
405
406 FunctionDescriptor descriptor = getFunctionDescriptor(context.bindingContext(), expression);
407 JsName name = context.getNameForDescriptor(descriptor);
408
409 return new JsVars(new JsVars.JsVar(name, alias)).source(expression);
410 }
411
412 @Override
413 @NotNull
414 public JsNode visitThisExpression(@NotNull JetThisExpression expression, @NotNull TranslationContext context) {
415 DeclarationDescriptor thisExpression =
416 getDescriptorForReferenceExpression(context.bindingContext(), expression.getInstanceReference());
417 assert thisExpression != null : "This expression must reference a descriptor: " + expression.getText();
418
419 return context.getThisObject(getReceiverParameterForDeclaration(thisExpression)).source(expression);
420 }
421
422 @Override
423 @NotNull
424 public JsNode visitArrayAccessExpression(@NotNull JetArrayAccessExpression expression,
425 @NotNull TranslationContext context) {
426 return AccessTranslationUtils.translateAsGet(expression, context);
427 }
428
429 @Override
430 @NotNull
431 public JsNode visitSuperExpression(@NotNull JetSuperExpression expression, @NotNull TranslationContext context) {
432 DeclarationDescriptor superClassDescriptor = context.bindingContext().get(BindingContext.REFERENCE_TARGET, expression.getInstanceReference());
433 assert superClassDescriptor != null: message(expression);
434 return translateAsFQReference(superClassDescriptor, context);
435 }
436
437 @Override
438 @NotNull
439 public JsNode visitForExpression(@NotNull JetForExpression expression,
440 @NotNull TranslationContext context) {
441 return ForTranslator.translate(expression, context).source(expression);
442 }
443
444 @Override
445 @NotNull
446 public JsNode visitTryExpression(@NotNull JetTryExpression expression,
447 @NotNull TranslationContext context) {
448 return TryTranslator.translate(expression, context).source(expression);
449 }
450
451 @Override
452 @NotNull
453 public JsNode visitThrowExpression(@NotNull JetThrowExpression expression,
454 @NotNull TranslationContext context) {
455 JetExpression thrownExpression = expression.getThrownExpression();
456 assert thrownExpression != null : "Thrown expression must not be null";
457 return new JsThrow(translateAsExpression(thrownExpression, context)).source(expression);
458 }
459
460 @Override
461 @NotNull
462 public JsNode visitObjectLiteralExpression(@NotNull JetObjectLiteralExpression expression,
463 @NotNull TranslationContext context) {
464 return ClassTranslator.generateObjectLiteral(expression.getObjectDeclaration(), context);
465 }
466
467 @Override
468 @NotNull
469 public JsNode visitObjectDeclaration(@NotNull JetObjectDeclaration expression,
470 @NotNull TranslationContext context) {
471 DeclarationDescriptor descriptor = getDescriptorForElement(context.bindingContext(), expression);
472 JsName name = context.getNameForDescriptor(descriptor);
473 JsExpression value = ClassTranslator.generateClassCreation(expression, context);
474 return newVar(name, value).source(expression);
475 }
476 }