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