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