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