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.utils;
018    
019    import com.google.dart.compiler.backend.js.ast.*;
020    import com.google.dart.compiler.backend.js.ast.JsBinaryOperator;
021    import org.jetbrains.annotations.NotNull;
022    import org.jetbrains.annotations.Nullable;
023    import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
024    import org.jetbrains.kotlin.descriptors.*;
025    import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor;
026    import org.jetbrains.kotlin.js.translate.context.Namer;
027    import org.jetbrains.kotlin.js.translate.context.StaticContext;
028    import org.jetbrains.kotlin.js.translate.context.TemporaryConstVariable;
029    import org.jetbrains.kotlin.js.translate.context.TranslationContext;
030    import org.jetbrains.kotlin.js.translate.general.Translation;
031    import org.jetbrains.kotlin.psi.*;
032    import org.jetbrains.kotlin.resolve.DescriptorUtils;
033    import org.jetbrains.kotlin.types.KotlinType;
034    
035    import java.util.ArrayList;
036    import java.util.List;
037    
038    import static com.google.dart.compiler.backend.js.ast.JsBinaryOperator.*;
039    import static org.jetbrains.kotlin.js.translate.utils.BindingUtils.getCallableDescriptorForOperationExpression;
040    import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.assignment;
041    import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.createDataDescriptor;
042    import static org.jetbrains.kotlin.resolve.DescriptorUtils.isAnonymousObject;
043    
044    public final class TranslationUtils {
045    
046        private TranslationUtils() {
047        }
048    
049        @NotNull
050        public static JsPropertyInitializer translateFunctionAsEcma5PropertyDescriptor(@NotNull JsFunction function,
051                @NotNull FunctionDescriptor descriptor,
052                @NotNull TranslationContext context) {
053            if (DescriptorUtils.isExtension(descriptor) ||
054                descriptor instanceof PropertyAccessorDescriptor &&
055                shouldGenerateAccessors(((PropertyAccessorDescriptor) descriptor).getCorrespondingProperty())
056            ) {
057                return translateExtensionFunctionAsEcma5DataDescriptor(function, descriptor, context);
058            }
059            else {
060                JsStringLiteral getOrSet = context.program().getStringLiteral(descriptor instanceof PropertyGetterDescriptor ? "get" : "set");
061                return new JsPropertyInitializer(getOrSet, function);
062            }
063        }
064    
065        @NotNull
066        public static JsFunction simpleReturnFunction(@NotNull JsScope functionScope, @NotNull JsExpression returnExpression) {
067            return new JsFunction(functionScope, new JsBlock(new JsReturn(returnExpression)), "<simpleReturnFunction>");
068        }
069    
070        @NotNull
071        private static JsPropertyInitializer translateExtensionFunctionAsEcma5DataDescriptor(@NotNull JsFunction function,
072                @NotNull FunctionDescriptor descriptor, @NotNull TranslationContext context) {
073            JsObjectLiteral meta = createDataDescriptor(function, ModalityKt.isOverridable(descriptor), false);
074            return new JsPropertyInitializer(context.getNameForDescriptor(descriptor).makeRef(), meta);
075        }
076    
077        @NotNull
078        public static JsExpression translateExclForBinaryEqualLikeExpr(@NotNull JsBinaryOperation baseBinaryExpression) {
079            return new JsBinaryOperation(notOperator(baseBinaryExpression.getOperator()), baseBinaryExpression.getArg1(),
080                                         baseBinaryExpression.getArg2());
081        }
082    
083        public static boolean isEqualLikeOperator(@NotNull JsBinaryOperator operator) {
084            return notOperator(operator) != null;
085        }
086    
087        @Nullable
088        private static JsBinaryOperator notOperator(@NotNull JsBinaryOperator operator) {
089            switch (operator) {
090                case REF_EQ:
091                    return REF_NEQ;
092                case REF_NEQ:
093                    return REF_EQ;
094                case EQ:
095                    return NEQ;
096                case NEQ:
097                    return EQ;
098                default:
099                    return null;
100            }
101        }
102    
103        @NotNull
104        public static JsBinaryOperation isNullCheck(@NotNull JsExpression expressionToCheck) {
105            return nullCheck(expressionToCheck, false);
106        }
107    
108        @NotNull
109        public static JsBinaryOperation isNotNullCheck(@NotNull JsExpression expressionToCheck) {
110            return nullCheck(expressionToCheck, true);
111        }
112    
113        @NotNull
114        public static JsBinaryOperation nullCheck(@NotNull JsExpression expressionToCheck, boolean isNegated) {
115            JsBinaryOperator operator = isNegated ? JsBinaryOperator.NEQ : JsBinaryOperator.EQ;
116            return new JsBinaryOperation(operator, expressionToCheck, JsLiteral.NULL);
117        }
118    
119        @NotNull
120        public static JsConditional notNullConditional(
121                @NotNull JsExpression expression,
122                @NotNull JsExpression elseExpression,
123                @NotNull TranslationContext context
124        ) {
125            JsExpression testExpression;
126            JsExpression thenExpression;
127            if (isCacheNeeded(expression)) {
128                TemporaryConstVariable tempVar = context.getOrDeclareTemporaryConstVariable(expression);
129                testExpression = isNotNullCheck(tempVar.value());
130                thenExpression = tempVar.value();
131            }
132            else {
133                testExpression = isNotNullCheck(expression);
134                thenExpression = expression;
135            }
136    
137            return new JsConditional(testExpression, thenExpression, elseExpression);
138        }
139    
140        @NotNull
141        public static JsNameRef backingFieldReference(@NotNull TranslationContext context,
142                @NotNull PropertyDescriptor descriptor) {
143            JsName backingFieldName = context.getNameForDescriptor(descriptor);
144            if(!JsDescriptorUtils.isSimpleFinalProperty(descriptor)) {
145                JsName backingFieldMangledName = context.getNameForBackingField(descriptor);
146                backingFieldName = context.declarePropertyOrPropertyAccessorName(descriptor, backingFieldMangledName.getIdent(), false);
147            }
148    
149            DeclarationDescriptor containingDescriptor = descriptor.getContainingDeclaration();
150            JsExpression receiver;
151            if (containingDescriptor instanceof PackageFragmentDescriptor) {
152                // used inside package initializer
153                receiver = JsLiteral.THIS;
154            }
155            else {
156                receiver = context.getDispatchReceiver(JsDescriptorUtils.getReceiverParameterForDeclaration(containingDescriptor));
157            }
158            return new JsNameRef(backingFieldName, receiver);
159        }
160    
161        @NotNull
162        public static JsExpression assignmentToBackingField(@NotNull TranslationContext context,
163                @NotNull PropertyDescriptor descriptor,
164                @NotNull JsExpression assignTo) {
165            JsNameRef backingFieldReference = backingFieldReference(context, descriptor);
166            return assignment(backingFieldReference, assignTo);
167        }
168    
169        @Nullable
170        public static JsExpression translateInitializerForProperty(@NotNull KtProperty declaration,
171                @NotNull TranslationContext context) {
172            JsExpression jsInitExpression = null;
173            KtExpression initializer = declaration.getInitializer();
174            if (initializer != null) {
175                jsInitExpression = Translation.translateAsExpression(initializer, context);
176            }
177            return jsInitExpression;
178        }
179    
180        @NotNull
181        public static JsExpression translateBaseExpression(@NotNull TranslationContext context,
182                @NotNull KtUnaryExpression expression) {
183            KtExpression baseExpression = PsiUtils.getBaseExpression(expression);
184            return Translation.translateAsExpression(baseExpression, context);
185        }
186    
187        @NotNull
188        public static JsExpression translateLeftExpression(
189                @NotNull TranslationContext context,
190                @NotNull KtBinaryExpression expression,
191                @NotNull JsBlock block
192        ) {
193            KtExpression left = expression.getLeft();
194            assert left != null : "Binary expression should have a left expression: " + expression.getText();
195            return Translation.translateAsExpression(left, context, block);
196        }
197    
198        @NotNull
199        public static JsExpression translateRightExpression(@NotNull TranslationContext context,
200                @NotNull KtBinaryExpression expression) {
201            return translateRightExpression(context, expression, context.dynamicContext().jsBlock());
202        }
203    
204        @NotNull
205        public static JsExpression translateRightExpression(
206                @NotNull TranslationContext context,
207                @NotNull KtBinaryExpression expression,
208                @NotNull JsBlock block) {
209            KtExpression rightExpression = expression.getRight();
210            assert rightExpression != null : "Binary expression should have a right expression";
211            return Translation.translateAsExpression(rightExpression, context, block);
212        }
213    
214        public static boolean hasCorrespondingFunctionIntrinsic(@NotNull TranslationContext context,
215                @NotNull KtOperationExpression expression) {
216            CallableDescriptor operationDescriptor = getCallableDescriptorForOperationExpression(context.bindingContext(), expression);
217    
218            if (operationDescriptor == null || !(operationDescriptor instanceof FunctionDescriptor)) return true;
219    
220            KotlinType returnType = operationDescriptor.getReturnType();
221            if (returnType != null && (KotlinBuiltIns.isChar(returnType) || KotlinBuiltIns.isLong(returnType))) return false;
222    
223            if (context.intrinsics().getFunctionIntrinsic((FunctionDescriptor) operationDescriptor).exists()) return true;
224    
225            return false;
226        }
227    
228        @NotNull
229        public static List<JsExpression> generateInvocationArguments(@NotNull JsExpression receiver, @NotNull List<JsExpression> arguments) {
230            List<JsExpression> argumentList = new ArrayList<JsExpression>(1 + arguments.size());
231            argumentList.add(receiver);
232            argumentList.addAll(arguments);
233            return argumentList;
234        }
235    
236        public static boolean isCacheNeeded(@NotNull JsExpression expression) {
237            return !(expression instanceof JsLiteral.JsValueLiteral) &&
238                   (!(expression instanceof JsNameRef) || ((JsNameRef) expression).getQualifier() != null);
239        }
240    
241        @NotNull
242        public static JsConditional sure(@NotNull JsExpression expression, @NotNull TranslationContext context) {
243            JsInvocation throwNPE = new JsInvocation(Namer.throwNPEFunctionRef());
244            JsConditional ensureNotNull = notNullConditional(expression, throwNPE, context);
245    
246            JsExpression thenExpression = ensureNotNull.getThenExpression();
247            if (thenExpression instanceof JsNameRef) {
248                JsName name = ((JsNameRef) thenExpression).getName();
249                if (name != null) {
250                    // associate(cache) ensureNotNull expression to new TemporaryConstVariable with same name.
251                    context.associateExpressionToLazyValue(ensureNotNull, new TemporaryConstVariable(name, ensureNotNull));
252                }
253            }
254    
255            return ensureNotNull;
256        }
257    
258        @NotNull
259        public static String getSuggestedNameForInnerDeclaration(StaticContext context, DeclarationDescriptor descriptor) {
260            String suggestedName = "";
261            DeclarationDescriptor containingDeclaration = descriptor.getContainingDeclaration();
262            //noinspection ConstantConditions
263            if (containingDeclaration != null &&
264                !(containingDeclaration instanceof ClassOrPackageFragmentDescriptor) &&
265                !(containingDeclaration instanceof AnonymousFunctionDescriptor) &&
266                !(containingDeclaration instanceof ConstructorDescriptor && isAnonymousObject(containingDeclaration.getContainingDeclaration()))) {
267                suggestedName = context.getNameForDescriptor(containingDeclaration).getIdent();
268            }
269    
270            if (!suggestedName.isEmpty() && !suggestedName.endsWith("$")) {
271                suggestedName += "$";
272            }
273    
274            if (descriptor.getName().isSpecial()) {
275                suggestedName += "f";
276            }
277            else {
278                suggestedName += context.getNameForDescriptor(descriptor).getIdent();
279            }
280            return suggestedName;
281        }
282    
283        public static boolean shouldGenerateAccessors(@NotNull CallableDescriptor descriptor) {
284            if (descriptor instanceof PropertyDescriptor) {
285                return shouldGenerateAccessors((PropertyDescriptor) descriptor);
286            }
287            else if (descriptor instanceof PropertyAccessorDescriptor) {
288                return shouldGenerateAccessors(((PropertyAccessorDescriptor) descriptor).getCorrespondingProperty());
289            }
290            else {
291                return false;
292            }
293        }
294    
295        private static boolean shouldGenerateAccessors(@NotNull PropertyDescriptor property) {
296            if (AnnotationsUtils.hasJsNameInAccessors(property)) return true;
297            for (PropertyDescriptor overriddenProperty : property.getOverriddenDescriptors()) {
298                if (shouldGenerateAccessors(overriddenProperty)) return true;
299            }
300            return false;
301        }
302    }