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