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(@NotNull JetCallExpression expression,
190                @NotNull TranslationContext context) {
191            if (shouldBeInlined(expression, context) &&
192                BindingContextUtilPackage.isUsedAsExpression(expression, context.bindingContext())) {
193                TemporaryVariable temporaryVariable = context.declareTemporary(null);
194                JsExpression callResult = CallExpressionTranslator.translate(expression, null, context).source(expression);
195                context.addStatementToCurrentBlock(JsAstUtils.assignment(temporaryVariable.reference(), callResult).makeStmt());
196                return temporaryVariable.reference();
197            } else {
198                return CallExpressionTranslator.translate(expression, null, context).source(expression);
199            }
200        }
201    
202        @Override
203        @NotNull
204        public JsNode visitIfExpression(@NotNull JetIfExpression expression, @NotNull TranslationContext context) {
205            assert expression.getCondition() != null : "condition should not ne null: " + expression.getText();
206            JsExpression testExpression = Translation.translateAsExpression(expression.getCondition(), context);
207            if (JsAstUtils.isEmptyExpression(testExpression)) {
208                return testExpression;
209            }
210    
211            boolean isKotlinExpression = BindingContextUtilPackage.isUsedAsExpression(expression, context.bindingContext());
212    
213            JetExpression thenExpression = expression.getThen();
214            assert thenExpression != null : "then expression should not be null: " + expression.getText();
215            JetExpression elseExpression = expression.getElse();
216    
217            JsStatement thenStatement = Translation.translateAsStatementAndMergeInBlockIfNeeded(thenExpression, context);
218            JsStatement elseStatement = (elseExpression != null) ? Translation.translateAsStatementAndMergeInBlockIfNeeded(elseExpression,
219                                                                                                                           context) : null;
220    
221            if (isKotlinExpression) {
222                JsExpression jsThenExpression = JsAstUtils.extractExpressionFromStatement(thenStatement);
223                JsExpression jsElseExpression = JsAstUtils.extractExpressionFromStatement(elseStatement);
224                boolean canBeJsExpression = jsThenExpression != null && jsElseExpression != null;
225                if (canBeJsExpression) {
226                    return new JsConditional(testExpression, jsThenExpression, jsElseExpression).source(expression);
227                }
228            }
229            JsIf ifStatement = new JsIf(testExpression, thenStatement, elseStatement);
230            return ifStatement.source(expression);
231        }
232    
233        @Override
234        @NotNull
235        public JsExpression visitSimpleNameExpression(@NotNull JetSimpleNameExpression expression,
236                @NotNull TranslationContext context) {
237            return ReferenceTranslator.translateSimpleNameWithQualifier(expression, null, context).source(expression);
238        }
239    
240        @Override
241        @NotNull
242        public JsNode visitWhileExpression(@NotNull JetWhileExpression expression, @NotNull TranslationContext context) {
243            return LoopTranslatorPackage.createWhile(false, expression, context);
244        }
245    
246        @Override
247        @NotNull
248        public JsNode visitDoWhileExpression(@NotNull JetDoWhileExpression expression, @NotNull TranslationContext context) {
249            return LoopTranslatorPackage.createWhile(true, expression, context);
250        }
251    
252        @Override
253        @NotNull
254        public JsNode visitStringTemplateExpression(@NotNull JetStringTemplateExpression expression,
255                @NotNull TranslationContext context) {
256            JsStringLiteral stringLiteral = resolveAsStringConstant(expression, context);
257            if (stringLiteral != null) {
258                return stringLiteral;
259            }
260            return resolveAsTemplate(expression, context).source(expression);
261        }
262    
263        @NotNull
264        private static JsNode resolveAsTemplate(@NotNull JetStringTemplateExpression expression,
265                @NotNull TranslationContext context) {
266            return StringTemplateTranslator.translate(expression, context);
267        }
268    
269        @Nullable
270        private static JsStringLiteral resolveAsStringConstant(@NotNull JetExpression expression,
271                @NotNull TranslationContext context) {
272            Object value = getCompileTimeValue(context.bindingContext(), expression);
273            if (value == null) {
274                return null;
275            }
276            assert value instanceof String : "Compile time constant template should be a String constant.";
277            String constantString = (String) value;
278            return context.program().getStringLiteral(constantString);
279        }
280    
281        @Override
282        @NotNull
283        public JsNode visitDotQualifiedExpression(@NotNull JetDotQualifiedExpression expression,
284                @NotNull TranslationContext context) {
285            return QualifiedExpressionTranslator.translateQualifiedExpression(expression, context);
286        }
287    
288        @Override
289        public JsNode visitLabeledExpression(
290                @NotNull JetLabeledExpression expression, TranslationContext context
291        ) {
292            JetExpression baseExpression = expression.getBaseExpression();
293            assert baseExpression != null;
294            JsScope scope = context.scope();
295            assert scope instanceof JsFunctionScope: "Labeled statement is unexpected outside of function scope";
296            JsName name = ((JsFunctionScope) scope).declareNameUnsafe(getReferencedName(expression.getTargetLabel()));
297            JsStatement baseStatement = Translation.translateAsStatement(baseExpression, context);
298            return new JsLabel(name, baseStatement).source(expression);
299        }
300    
301        @Override
302        @NotNull
303        public JsNode visitPrefixExpression(
304                @NotNull JetPrefixExpression expression,
305                @NotNull TranslationContext context
306        ) {
307            return UnaryOperationTranslator.translate(expression, context).source(expression);
308        }
309    
310        @Override
311        @NotNull
312        public JsNode visitPostfixExpression(@NotNull JetPostfixExpression expression,
313                @NotNull TranslationContext context) {
314            return UnaryOperationTranslator.translate(expression, context).source(expression);
315        }
316    
317        @Override
318        @NotNull
319        public JsNode visitIsExpression(@NotNull JetIsExpression expression,
320                @NotNull TranslationContext context) {
321            return Translation.patternTranslator(context).translateIsExpression(expression);
322        }
323    
324        @Override
325        @NotNull
326        public JsNode visitSafeQualifiedExpression(@NotNull JetSafeQualifiedExpression expression,
327                @NotNull TranslationContext context) {
328            return QualifiedExpressionTranslator.translateQualifiedExpression(expression, context).source(expression);
329        }
330    
331        @Override
332        @Nullable
333        public JsNode visitWhenExpression(@NotNull JetWhenExpression expression,
334                @NotNull TranslationContext context) {
335            return WhenTranslator.translate(expression, context);
336        }
337    
338        @Override
339        @NotNull
340        public JsNode visitBinaryWithTypeRHSExpression(@NotNull JetBinaryExpressionWithTypeRHS expression,
341                @NotNull TranslationContext context) {
342            JsExpression jsExpression = Translation.translateAsExpression(expression.getLeft(), context);
343    
344            if (expression.getOperationReference().getReferencedNameElementType() != JetTokens.AS_KEYWORD)
345                return jsExpression.source(expression);
346    
347            JetTypeReference right = expression.getRight();
348            assert right != null;
349    
350            JetType rightType = BindingContextUtils.getNotNull(context.bindingContext(), BindingContext.TYPE, right);
351            JetType leftType = BindingContextUtils.getNotNull(context.bindingContext(), BindingContext.EXPRESSION_TYPE, expression.getLeft());
352            if (TypeUtils.isNullableType(rightType) || !TypeUtils.isNullableType(leftType)) {
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            return expression.getReferencedName()
363                    .replaceAll("^@", "")
364                    .replaceAll("(?:^`(.*)`$)", "$1") + "$";
365        }
366    
367        private static JsNameRef getTargetLabel(JetExpressionWithLabel expression, TranslationContext context) {
368            JetSimpleNameExpression labelElement = expression.getTargetLabel();
369            if (labelElement == null) {
370                return null;
371            }
372            else {
373                JsName name = context.scope().findName(getReferencedName(labelElement));
374                assert name != null;
375                return name.makeRef();
376            }
377        }
378    
379        @Override
380        @NotNull
381        public JsNode visitBreakExpression(@NotNull JetBreakExpression expression,
382                @NotNull TranslationContext context) {
383            return new JsBreak(getTargetLabel(expression, context)).source(expression);
384        }
385    
386        @Override
387        @NotNull
388        public JsNode visitContinueExpression(@NotNull JetContinueExpression expression,
389                @NotNull TranslationContext context) {
390            return new JsContinue(getTargetLabel(expression, context)).source(expression);
391        }
392    
393        @Override
394        @NotNull
395        public JsNode visitFunctionLiteralExpression(@NotNull JetFunctionLiteralExpression expression, @NotNull TranslationContext context) {
396            return new LiteralFunctionTranslator(context).translate(expression.getFunctionLiteral());
397        }
398    
399        @Override
400        @NotNull
401        public JsNode visitNamedFunction(@NotNull JetNamedFunction expression, @NotNull TranslationContext context) {
402            JsExpression alias = new LiteralFunctionTranslator(context).translate(expression);
403    
404            FunctionDescriptor descriptor = getFunctionDescriptor(context.bindingContext(), expression);
405            JsName name = context.getNameForDescriptor(descriptor);
406            if (InlineUtil.getInlineType(descriptor).isInline()) {
407                MetadataPackage.setStaticRef(name, alias);
408            }
409    
410            return new JsVars(new JsVars.JsVar(name, alias)).source(expression);
411        }
412    
413        @Override
414        @NotNull
415        public JsNode visitThisExpression(@NotNull JetThisExpression expression, @NotNull TranslationContext context) {
416            DeclarationDescriptor thisExpression =
417                    getDescriptorForReferenceExpression(context.bindingContext(), expression.getInstanceReference());
418            assert thisExpression != null : "This expression must reference a descriptor: " + expression.getText();
419    
420            return context.getDispatchReceiver(getReceiverParameterForDeclaration(thisExpression)).source(expression);
421        }
422    
423        @Override
424        @NotNull
425        public JsNode visitArrayAccessExpression(@NotNull JetArrayAccessExpression expression,
426                @NotNull TranslationContext context) {
427            return AccessTranslationUtils.translateAsGet(expression, context);
428        }
429    
430        @Override
431        @NotNull
432        public JsNode visitSuperExpression(@NotNull JetSuperExpression expression, @NotNull TranslationContext context) {
433            DeclarationDescriptor superClassDescriptor = context.bindingContext().get(BindingContext.REFERENCE_TARGET, expression.getInstanceReference());
434            assert superClassDescriptor != null: message(expression);
435            return translateAsFQReference(superClassDescriptor, context);
436        }
437    
438        @Override
439        @NotNull
440        public JsNode visitForExpression(@NotNull JetForExpression expression,
441                @NotNull TranslationContext context) {
442            return LoopTranslatorPackage.translateForExpression(expression, context).source(expression);
443        }
444    
445        @Override
446        @NotNull
447        public JsNode visitTryExpression(
448                @NotNull JetTryExpression expression,
449                @NotNull TranslationContext context
450        ) {
451            return new TryTranslator(expression, context).translate();
452        }
453    
454        @Override
455        @NotNull
456        public JsNode visitThrowExpression(@NotNull JetThrowExpression expression,
457                @NotNull TranslationContext context) {
458            JetExpression thrownExpression = expression.getThrownExpression();
459            assert thrownExpression != null : "Thrown expression must not be null";
460            return new JsThrow(translateAsExpression(thrownExpression, context)).source(expression);
461        }
462    
463        @Override
464        @NotNull
465        public JsNode visitObjectLiteralExpression(@NotNull JetObjectLiteralExpression expression,
466                @NotNull TranslationContext context) {
467            return ClassTranslator.generateObjectLiteral(expression.getObjectDeclaration(), context);
468        }
469    
470        @Override
471        @NotNull
472        public JsNode visitObjectDeclaration(@NotNull JetObjectDeclaration expression,
473                @NotNull TranslationContext context) {
474            DeclarationDescriptor descriptor = getDescriptorForElement(context.bindingContext(), expression);
475            JsName name = context.getNameForDescriptor(descriptor);
476            JsExpression value = ClassTranslator.generateClassCreation(expression, context);
477            return newVar(name, value).source(expression);
478        }
479    }