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 @NotNull
296 public JsNode visitPrefixExpression(
297 @NotNull JetPrefixExpression expression,
298 @NotNull TranslationContext context
299 ) {
300 JetSimpleNameExpression operationReference = expression.getOperationReference();
301 IElementType operationToken = operationReference.getReferencedNameElementType();
302 JsNode result;
303 if (JetTokens.LABELS.contains(operationToken)) {
304 JetExpression baseExpression = expression.getBaseExpression();
305 assert baseExpression != null;
306 result = new JsLabel(context.scope().declareName(getReferencedName(operationReference)),
307 convertToStatement(baseExpression.accept(this, context)));
308 }
309 else {
310 result = UnaryOperationTranslator.translate(expression, context);
311 }
312 return result.source(expression);
313 }
314
315 @Override
316 @NotNull
317 public JsNode visitPostfixExpression(@NotNull JetPostfixExpression expression,
318 @NotNull TranslationContext context) {
319 return UnaryOperationTranslator.translate(expression, context).source(expression);
320 }
321
322 @Override
323 @NotNull
324 public JsNode visitIsExpression(@NotNull JetIsExpression expression,
325 @NotNull TranslationContext context) {
326 return Translation.patternTranslator(context).translateIsExpression(expression);
327 }
328
329 @Override
330 @NotNull
331 public JsNode visitSafeQualifiedExpression(@NotNull JetSafeQualifiedExpression expression,
332 @NotNull TranslationContext context) {
333 return QualifiedExpressionTranslator.translateQualifiedExpression(expression, context).source(expression);
334 }
335
336 @Override
337 @Nullable
338 public JsNode visitWhenExpression(@NotNull JetWhenExpression expression,
339 @NotNull TranslationContext context) {
340 return WhenTranslator.translate(expression, context);
341 }
342
343 @Override
344 @NotNull
345 public JsNode visitBinaryWithTypeRHSExpression(@NotNull JetBinaryExpressionWithTypeRHS expression,
346 @NotNull TranslationContext context) {
347 JsExpression jsExpression = Translation.translateAsExpression(expression.getLeft(), context);
348
349 if (expression.getOperationReference().getReferencedNameElementType() != JetTokens.AS_KEYWORD)
350 return jsExpression.source(expression);
351
352 JetType rightType = BindingContextUtils.getNotNull(context.bindingContext(), BindingContext.TYPE, expression.getRight());
353 JetType leftType = BindingContextUtils.getNotNull(context.bindingContext(), BindingContext.EXPRESSION_TYPE, expression.getLeft());
354 if (rightType.isNullable() || !leftType.isNullable()) {
355 return jsExpression.source(expression);
356 }
357
358 // KT-2670
359 // we actually do not care for types in js
360 return TranslationUtils.sure(jsExpression, context).source(expression);
361 }
362
363 private static String getReferencedName(JetSimpleNameExpression expression) {
364 String name = expression.getReferencedName();
365 return name.charAt(0) == '@' ? name.substring(1) + '$' : name;
366 }
367
368 private static String getTargetLabel(JetLabelQualifiedExpression expression, TranslationContext context) {
369 JetSimpleNameExpression labelElement = expression.getTargetLabel();
370 if (labelElement == null) {
371 return null;
372 }
373 else {
374 JsName name = context.scope().findName(getReferencedName(labelElement));
375 assert name != null;
376 return name.getIdent();
377 }
378 }
379
380 @Override
381 @NotNull
382 public JsNode visitBreakExpression(@NotNull JetBreakExpression expression,
383 @NotNull TranslationContext context) {
384 return new JsBreak(getTargetLabel(expression, context)).source(expression);
385 }
386
387 @Override
388 @NotNull
389 public JsNode visitContinueExpression(@NotNull JetContinueExpression expression,
390 @NotNull TranslationContext context) {
391 return new JsContinue(getTargetLabel(expression, context)).source(expression);
392 }
393
394 @Override
395 @NotNull
396 public JsNode visitFunctionLiteralExpression(@NotNull JetFunctionLiteralExpression expression, @NotNull TranslationContext context) {
397 return new LiteralFunctionTranslator(context).translate(expression.getFunctionLiteral());
398 }
399
400 @Override
401 @NotNull
402 public JsNode visitNamedFunction(@NotNull JetNamedFunction expression, @NotNull TranslationContext context) {
403 JsExpression alias = new LiteralFunctionTranslator(context).translate(expression);
404
405 FunctionDescriptor descriptor = getFunctionDescriptor(context.bindingContext(), expression);
406 JsName name = context.getNameForDescriptor(descriptor);
407
408 return new JsVars(new JsVars.JsVar(name, alias)).source(expression);
409 }
410
411 @Override
412 @NotNull
413 public JsNode visitThisExpression(@NotNull JetThisExpression expression, @NotNull TranslationContext context) {
414 DeclarationDescriptor thisExpression =
415 getDescriptorForReferenceExpression(context.bindingContext(), expression.getInstanceReference());
416 assert thisExpression != null : "This expression must reference a descriptor: " + expression.getText();
417
418 return context.getThisObject(getReceiverParameterForDeclaration(thisExpression)).source(expression);
419 }
420
421 @Override
422 @NotNull
423 public JsNode visitArrayAccessExpression(@NotNull JetArrayAccessExpression expression,
424 @NotNull TranslationContext context) {
425 return AccessTranslationUtils.translateAsGet(expression, context);
426 }
427
428 @Override
429 @NotNull
430 public JsNode visitSuperExpression(@NotNull JetSuperExpression expression, @NotNull TranslationContext context) {
431 DeclarationDescriptor superClassDescriptor = context.bindingContext().get(BindingContext.REFERENCE_TARGET, expression.getInstanceReference());
432 assert superClassDescriptor != null: message(expression);
433 return translateAsFQReference(superClassDescriptor, context);
434 }
435
436 @Override
437 @NotNull
438 public JsNode visitForExpression(@NotNull JetForExpression expression,
439 @NotNull TranslationContext context) {
440 return ForTranslator.translate(expression, context).source(expression);
441 }
442
443 @Override
444 @NotNull
445 public JsNode visitTryExpression(@NotNull JetTryExpression expression,
446 @NotNull TranslationContext context) {
447 return TryTranslator.translate(expression, context).source(expression);
448 }
449
450 @Override
451 @NotNull
452 public JsNode visitThrowExpression(@NotNull JetThrowExpression expression,
453 @NotNull TranslationContext context) {
454 JetExpression thrownExpression = expression.getThrownExpression();
455 assert thrownExpression != null : "Thrown expression must not be null";
456 return new JsThrow(translateAsExpression(thrownExpression, context)).source(expression);
457 }
458
459 @Override
460 @NotNull
461 public JsNode visitObjectLiteralExpression(@NotNull JetObjectLiteralExpression expression,
462 @NotNull TranslationContext context) {
463 return ClassTranslator.generateObjectLiteral(expression.getObjectDeclaration(), context);
464 }
465
466 @Override
467 @NotNull
468 public JsNode visitObjectDeclaration(@NotNull JetObjectDeclaration expression,
469 @NotNull TranslationContext context) {
470 DeclarationDescriptor descriptor = getDescriptorForElement(context.bindingContext(), expression);
471 JsName name = context.getNameForDescriptor(descriptor);
472 JsExpression value = ClassTranslator.generateClassCreation(expression, context);
473 return newVar(name, value).source(expression);
474 }
475 }