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.google.dart.compiler.backend.js.ast.metadata.MetadataPackage;
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.bindingContextUtil.BindingContextUtilPackage;
030    import org.jetbrains.jet.lang.resolve.constants.CompileTimeConstant;
031    import org.jetbrains.jet.lang.resolve.constants.NullValue;
032    import org.jetbrains.jet.lang.types.JetType;
033    import org.jetbrains.jet.lang.types.TypeUtils;
034    import org.jetbrains.jet.lang.types.lang.InlineUtil;
035    import org.jetbrains.jet.lexer.JetTokens;
036    import org.jetbrains.k2js.translate.context.TemporaryVariable;
037    import org.jetbrains.k2js.translate.context.TranslationContext;
038    import org.jetbrains.k2js.translate.declaration.ClassTranslator;
039    import org.jetbrains.k2js.translate.expression.loopTranslator.LoopTranslatorPackage;
040    import org.jetbrains.k2js.translate.general.Translation;
041    import org.jetbrains.k2js.translate.general.TranslatorVisitor;
042    import org.jetbrains.k2js.translate.operation.BinaryOperationTranslator;
043    import org.jetbrains.k2js.translate.operation.UnaryOperationTranslator;
044    import org.jetbrains.k2js.translate.reference.*;
045    import org.jetbrains.k2js.translate.utils.JsAstUtils;
046    import org.jetbrains.k2js.translate.utils.TranslationUtils;
047    
048    import java.util.List;
049    
050    import static org.jetbrains.jet.lang.resolve.BindingContextUtils.isVarCapturedInClosure;
051    import static org.jetbrains.k2js.translate.context.Namer.getCapturedVarAccessor;
052    import static org.jetbrains.k2js.translate.general.Translation.translateAsExpression;
053    import static org.jetbrains.k2js.translate.reference.CallExpressionTranslator.shouldBeInlined;
054    import static org.jetbrains.k2js.translate.reference.ReferenceTranslator.translateAsFQReference;
055    import static org.jetbrains.k2js.translate.utils.BindingUtils.*;
056    import static org.jetbrains.k2js.translate.utils.ErrorReportingUtils.message;
057    import static org.jetbrains.k2js.translate.utils.JsAstUtils.convertToStatement;
058    import static org.jetbrains.k2js.translate.utils.JsAstUtils.newVar;
059    import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.getReceiverParameterForDeclaration;
060    import static org.jetbrains.k2js.translate.utils.TranslationUtils.translateInitializerForProperty;
061    
062    public final class ExpressionVisitor extends TranslatorVisitor<JsNode> {
063        @Override
064        @NotNull
065        public JsNode visitConstantExpression(@NotNull JetConstantExpression expression, @NotNull TranslationContext context) {
066            return translateConstantExpression(expression, context).source(expression);
067        }
068    
069        @NotNull
070        private static JsNode translateConstantExpression(@NotNull JetConstantExpression expression, @NotNull TranslationContext context) {
071            CompileTimeConstant<?> compileTimeValue = context.bindingContext().get(BindingContext.COMPILE_TIME_VALUE, expression);
072    
073            assert compileTimeValue != null : message(expression, "Expression is not compile time value: " + expression.getText() + " ");
074    
075            if (compileTimeValue instanceof NullValue) {
076                return JsLiteral.NULL;
077            }
078    
079            Object value = getCompileTimeValue(context.bindingContext(), expression, compileTimeValue);
080            if (value instanceof Integer || value instanceof Short || value instanceof Byte) {
081                return context.program().getNumberLiteral(((Number) value).intValue());
082            }
083            else if (value instanceof Long) {
084                return JsAstUtils.newLong((Long) value, context);
085            }
086            else if (value instanceof Number) {
087                return context.program().getNumberLiteral(((Number) value).doubleValue());
088            }
089            else if (value instanceof Boolean) {
090                return JsLiteral.getBoolean((Boolean) value);
091            }
092    
093            //TODO: test
094            if (value instanceof String) {
095                return context.program().getStringLiteral((String) value);
096            }
097            if (value instanceof Character) {
098                return context.program().getStringLiteral(value.toString());
099            }
100    
101            throw new AssertionError(message(expression, "Unsupported constant expression: " + expression.getText() + " "));
102        }
103    
104        @Override
105        @NotNull
106        public JsNode visitBlockExpression(@NotNull JetBlockExpression jetBlock, @NotNull TranslationContext context) {
107            List<JetElement> statements = jetBlock.getStatements();
108            JsBlock jsBlock = new JsBlock();
109            for (JetElement statement : statements) {
110                assert statement instanceof JetExpression : "Elements in JetBlockExpression " +
111                                                            "should be of type JetExpression";
112                JsNode jsNode = Translation.translateExpression((JetExpression)statement, context, jsBlock);
113                JsStatement jsStatement = convertToStatement(jsNode);
114                if (!JsAstUtils.isEmptyStatement(jsStatement)) {
115                    jsBlock.getStatements().add(jsStatement);
116                }
117            }
118            return jsBlock;
119        }
120    
121        @Override
122        public JsNode visitMultiDeclaration(@NotNull JetMultiDeclaration multiDeclaration, @NotNull TranslationContext context) {
123            JetExpression jetInitializer = multiDeclaration.getInitializer();
124            assert jetInitializer != null : "Initializer for multi declaration must be not null";
125            JsExpression initializer = Translation.translateAsExpression(jetInitializer, context);
126            return MultiDeclarationTranslator.translate(multiDeclaration, context.scope().declareTemporary(), initializer, context);
127        }
128    
129        @Override
130        @NotNull
131        public JsNode visitReturnExpression(@NotNull JetReturnExpression jetReturnExpression,
132                @NotNull TranslationContext context) {
133            JetExpression returned = jetReturnExpression.getReturnedExpression();
134            if (returned == null) {
135                return new JsReturn(null).source(jetReturnExpression);
136            }
137            JsExpression jsReturnExpression = translateAsExpression(returned, context);
138            if (JsAstUtils.isEmptyExpression(jsReturnExpression)) {
139                return context.getEmptyExpression();
140            }
141            return new JsReturn(jsReturnExpression).source(jetReturnExpression);
142        }
143    
144        @Override
145        @NotNull
146        public JsNode visitParenthesizedExpression(@NotNull JetParenthesizedExpression expression,
147                @NotNull TranslationContext context) {
148            JetExpression expressionInside = expression.getExpression();
149            if (expressionInside != null) {
150                return Translation.translateExpression(expressionInside, context);
151            }
152            return context.getEmptyStatement();
153        }
154    
155        @Override
156        @NotNull
157        public JsNode visitBinaryExpression(@NotNull JetBinaryExpression expression,
158                @NotNull TranslationContext context) {
159            return BinaryOperationTranslator.translate(expression, context);
160        }
161    
162        @Override
163        @NotNull
164        // assume it is a local variable declaration
165        public JsNode visitProperty(@NotNull JetProperty expression, @NotNull TranslationContext context) {
166            VariableDescriptor descriptor = BindingContextUtils.getNotNull(context.bindingContext(), BindingContext.VARIABLE, expression);
167            JsExpression initializer = translateInitializerForProperty(expression, context);
168            if (initializer != null && JsAstUtils.isEmptyExpression(initializer)) {
169                return context.getEmptyExpression();
170            }
171    
172            JsName name = context.getNameForDescriptor(descriptor);
173            if (isVarCapturedInClosure(context.bindingContext(), descriptor)) {
174                JsNameRef alias = getCapturedVarAccessor(name.makeRef());
175                initializer = JsAstUtils.wrapValue(alias, initializer == null ? JsLiteral.NULL : initializer);
176            }
177    
178            return newVar(name, initializer).source(expression);
179        }
180    
181        @Override
182        @NotNull
183        public JsNode visitCallableReferenceExpression(@NotNull JetCallableReferenceExpression expression, @NotNull TranslationContext context) {
184            return CallableReferenceTranslator.INSTANCE$.translate(expression, context);
185        }
186    
187        @Override
188        @NotNull
189        public JsNode visitCallExpression(
190                @NotNull JetCallExpression expression,
191                @NotNull TranslationContext context
192        ) {
193            if (shouldBeInlined(expression, context) &&
194                BindingContextUtilPackage.isUsedAsExpression(expression, context.bindingContext())) {
195                TemporaryVariable temporaryVariable = context.declareTemporary(null);
196    
197                JsNode callResult = CallExpressionTranslator.translate(expression, null, context).source(expression);
198                assert callResult instanceof JsExpression;
199    
200                JsExpression assignment = JsAstUtils.assignment(temporaryVariable.reference(), (JsExpression) callResult);
201                context.addStatementToCurrentBlock(assignment.makeStmt());
202                return temporaryVariable.reference();
203            } else {
204                return CallExpressionTranslator.translate(expression, null, context).source(expression);
205            }
206        }
207    
208        @Override
209        @NotNull
210        public JsNode visitIfExpression(@NotNull JetIfExpression expression, @NotNull TranslationContext context) {
211            assert expression.getCondition() != null : "condition should not ne null: " + expression.getText();
212            JsExpression testExpression = Translation.translateAsExpression(expression.getCondition(), context);
213            if (JsAstUtils.isEmptyExpression(testExpression)) {
214                return testExpression;
215            }
216    
217            boolean isKotlinExpression = BindingContextUtilPackage.isUsedAsExpression(expression, context.bindingContext());
218    
219            JetExpression thenExpression = expression.getThen();
220            assert thenExpression != null : "then expression should not be null: " + expression.getText();
221            JetExpression elseExpression = expression.getElse();
222    
223            JsStatement thenStatement = Translation.translateAsStatementAndMergeInBlockIfNeeded(thenExpression, context);
224            JsStatement elseStatement = (elseExpression != null) ? Translation.translateAsStatementAndMergeInBlockIfNeeded(elseExpression,
225                                                                                                                           context) : null;
226    
227            if (isKotlinExpression) {
228                JsExpression jsThenExpression = JsAstUtils.extractExpressionFromStatement(thenStatement);
229                JsExpression jsElseExpression = JsAstUtils.extractExpressionFromStatement(elseStatement);
230                boolean canBeJsExpression = jsThenExpression != null && jsElseExpression != null;
231                if (canBeJsExpression) {
232                    return new JsConditional(testExpression, jsThenExpression, jsElseExpression).source(expression);
233                }
234            }
235            JsIf ifStatement = new JsIf(testExpression, thenStatement, elseStatement);
236            return ifStatement.source(expression);
237        }
238    
239        @Override
240        @NotNull
241        public JsExpression visitSimpleNameExpression(@NotNull JetSimpleNameExpression expression,
242                @NotNull TranslationContext context) {
243            return ReferenceTranslator.translateSimpleNameWithQualifier(expression, null, context).source(expression);
244        }
245    
246        @Override
247        @NotNull
248        public JsNode visitWhileExpression(@NotNull JetWhileExpression expression, @NotNull TranslationContext context) {
249            return LoopTranslatorPackage.createWhile(false, expression, context);
250        }
251    
252        @Override
253        @NotNull
254        public JsNode visitDoWhileExpression(@NotNull JetDoWhileExpression expression, @NotNull TranslationContext context) {
255            return LoopTranslatorPackage.createWhile(true, expression, context);
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            JsScope scope = context.scope();
301            assert scope instanceof JsFunctionScope: "Labeled statement is unexpected outside of function scope";
302            JsFunctionScope functionScope = (JsFunctionScope) scope;
303            String labelIdent = getReferencedName(expression.getTargetLabel());
304            JsName labelName = functionScope.enterLabel(labelIdent);
305            JsStatement baseStatement = Translation.translateAsStatement(baseExpression, context);
306            functionScope.exitLabel();
307            return new JsLabel(labelName, baseStatement).source(expression);
308        }
309    
310        @Override
311        @NotNull
312        public JsNode visitPrefixExpression(
313                @NotNull JetPrefixExpression expression,
314                @NotNull TranslationContext context
315        ) {
316            return UnaryOperationTranslator.translate(expression, context).source(expression);
317        }
318    
319        @Override
320        @NotNull
321        public JsNode visitPostfixExpression(@NotNull JetPostfixExpression expression,
322                @NotNull TranslationContext context) {
323            return UnaryOperationTranslator.translate(expression, context).source(expression);
324        }
325    
326        @Override
327        @NotNull
328        public JsNode visitIsExpression(@NotNull JetIsExpression expression,
329                @NotNull TranslationContext context) {
330            return Translation.patternTranslator(context).translateIsExpression(expression);
331        }
332    
333        @Override
334        @NotNull
335        public JsNode visitSafeQualifiedExpression(@NotNull JetSafeQualifiedExpression expression,
336                @NotNull TranslationContext context) {
337            return QualifiedExpressionTranslator.translateQualifiedExpression(expression, context).source(expression);
338        }
339    
340        @Override
341        @Nullable
342        public JsNode visitWhenExpression(@NotNull JetWhenExpression expression,
343                @NotNull TranslationContext context) {
344            return WhenTranslator.translate(expression, context);
345        }
346    
347        @Override
348        @NotNull
349        public JsNode visitBinaryWithTypeRHSExpression(@NotNull JetBinaryExpressionWithTypeRHS expression,
350                @NotNull TranslationContext context) {
351            JsExpression jsExpression = Translation.translateAsExpression(expression.getLeft(), context);
352    
353            if (expression.getOperationReference().getReferencedNameElementType() != JetTokens.AS_KEYWORD)
354                return jsExpression.source(expression);
355    
356            JetTypeReference right = expression.getRight();
357            assert right != null;
358    
359            JetType rightType = BindingContextUtils.getNotNull(context.bindingContext(), BindingContext.TYPE, right);
360            JetType leftType = BindingContextUtils.getNotNull(context.bindingContext(), BindingContext.EXPRESSION_TYPE, expression.getLeft());
361            if (TypeUtils.isNullableType(rightType) || !TypeUtils.isNullableType(leftType)) {
362                return jsExpression.source(expression);
363            }
364    
365            // KT-2670
366            // we actually do not care for types in js
367            return TranslationUtils.sure(jsExpression, context).source(expression);
368        }
369    
370        private static String getReferencedName(JetSimpleNameExpression expression) {
371            return expression.getReferencedName()
372                    .replaceAll("^@", "")
373                    .replaceAll("(?:^`(.*)`$)", "$1");
374        }
375    
376        private static JsNameRef getTargetLabel(JetExpressionWithLabel expression, TranslationContext context) {
377            JetSimpleNameExpression labelElement = expression.getTargetLabel();
378            if (labelElement == null) {
379                return null;
380            }
381    
382            String labelIdent = getReferencedName(labelElement);
383            JsScope scope = context.scope();
384            assert scope instanceof JsFunctionScope: "Labeled statement is unexpected outside of function scope";
385            JsName labelName = ((JsFunctionScope) scope).findLabel(labelIdent);
386            assert labelName != null;
387            return labelName.makeRef();
388        }
389    
390        @Override
391        @NotNull
392        public JsNode visitBreakExpression(@NotNull JetBreakExpression expression,
393                @NotNull TranslationContext context) {
394            return new JsBreak(getTargetLabel(expression, context)).source(expression);
395        }
396    
397        @Override
398        @NotNull
399        public JsNode visitContinueExpression(@NotNull JetContinueExpression expression,
400                @NotNull TranslationContext context) {
401            return new JsContinue(getTargetLabel(expression, context)).source(expression);
402        }
403    
404        @Override
405        @NotNull
406        public JsNode visitFunctionLiteralExpression(@NotNull JetFunctionLiteralExpression expression, @NotNull TranslationContext context) {
407            return new LiteralFunctionTranslator(context).translate(expression.getFunctionLiteral());
408        }
409    
410        @Override
411        @NotNull
412        public JsNode visitNamedFunction(@NotNull JetNamedFunction expression, @NotNull TranslationContext context) {
413            JsExpression alias = new LiteralFunctionTranslator(context).translate(expression);
414    
415            FunctionDescriptor descriptor = getFunctionDescriptor(context.bindingContext(), expression);
416            JsName name = context.getNameForDescriptor(descriptor);
417            if (InlineUtil.getInlineType(descriptor).isInline()) {
418                MetadataPackage.setStaticRef(name, alias);
419            }
420    
421            return new JsVars(new JsVars.JsVar(name, alias)).source(expression);
422        }
423    
424        @Override
425        @NotNull
426        public JsNode visitThisExpression(@NotNull JetThisExpression expression, @NotNull TranslationContext context) {
427            DeclarationDescriptor thisExpression =
428                    getDescriptorForReferenceExpression(context.bindingContext(), expression.getInstanceReference());
429            assert thisExpression != null : "This expression must reference a descriptor: " + expression.getText();
430    
431            return context.getDispatchReceiver(getReceiverParameterForDeclaration(thisExpression)).source(expression);
432        }
433    
434        @Override
435        @NotNull
436        public JsNode visitArrayAccessExpression(@NotNull JetArrayAccessExpression expression,
437                @NotNull TranslationContext context) {
438            return AccessTranslationUtils.translateAsGet(expression, context);
439        }
440    
441        @Override
442        @NotNull
443        public JsNode visitSuperExpression(@NotNull JetSuperExpression expression, @NotNull TranslationContext context) {
444            DeclarationDescriptor superClassDescriptor = context.bindingContext().get(BindingContext.REFERENCE_TARGET, expression.getInstanceReference());
445            assert superClassDescriptor != null: message(expression);
446            return translateAsFQReference(superClassDescriptor, context);
447        }
448    
449        @Override
450        @NotNull
451        public JsNode visitForExpression(@NotNull JetForExpression expression,
452                @NotNull TranslationContext context) {
453            return LoopTranslatorPackage.translateForExpression(expression, context).source(expression);
454        }
455    
456        @Override
457        @NotNull
458        public JsNode visitTryExpression(
459                @NotNull JetTryExpression expression,
460                @NotNull TranslationContext context
461        ) {
462            return new TryTranslator(expression, context).translate();
463        }
464    
465        @Override
466        @NotNull
467        public JsNode visitThrowExpression(@NotNull JetThrowExpression expression,
468                @NotNull TranslationContext context) {
469            JetExpression thrownExpression = expression.getThrownExpression();
470            assert thrownExpression != null : "Thrown expression must not be null";
471            return new JsThrow(translateAsExpression(thrownExpression, context)).source(expression);
472        }
473    
474        @Override
475        @NotNull
476        public JsNode visitObjectLiteralExpression(@NotNull JetObjectLiteralExpression expression,
477                @NotNull TranslationContext context) {
478            return ClassTranslator.generateObjectLiteral(expression.getObjectDeclaration(), context);
479        }
480    
481        @Override
482        @NotNull
483        public JsNode visitObjectDeclaration(@NotNull JetObjectDeclaration expression,
484                @NotNull TranslationContext context) {
485            DeclarationDescriptor descriptor = getDescriptorForElement(context.bindingContext(), expression);
486            JsName name = context.getNameForDescriptor(descriptor);
487            JsExpression value = ClassTranslator.generateClassCreation(expression, context);
488            return newVar(name, value).source(expression);
489        }
490    }