001    /*
002     * Copyright 2010-2013 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.k2js.translate.utils;
018    
019    import com.google.dart.compiler.backend.js.ast.*;
020    import com.intellij.openapi.util.Pair;
021    import com.intellij.openapi.util.text.StringUtil;
022    import com.intellij.util.Function;
023    import com.intellij.util.containers.ContainerUtil;
024    import org.jetbrains.annotations.NotNull;
025    import org.jetbrains.annotations.Nullable;
026    import org.jetbrains.jet.lang.descriptors.*;
027    import org.jetbrains.jet.lang.psi.*;
028    import org.jetbrains.jet.lang.resolve.BindingContextUtils;
029    import org.jetbrains.jet.lang.resolve.scopes.JetScope;
030    import org.jetbrains.jet.lang.types.JetType;
031    import org.jetbrains.k2js.translate.context.TemporaryConstVariable;
032    import org.jetbrains.k2js.translate.context.TranslationContext;
033    import org.jetbrains.k2js.translate.general.Translation;
034    
035    import java.util.*;
036    
037    import static com.google.dart.compiler.backend.js.ast.JsBinaryOperator.*;
038    import static org.jetbrains.jet.lang.resolve.DescriptorUtils.getFqName;
039    import static org.jetbrains.k2js.translate.context.Namer.getKotlinBackingFieldName;
040    import static org.jetbrains.k2js.translate.utils.BindingUtils.getFunctionDescriptorForOperationExpression;
041    import static org.jetbrains.k2js.translate.utils.JsAstUtils.assignment;
042    import static org.jetbrains.k2js.translate.utils.JsAstUtils.createDataDescriptor;
043    
044    public final class TranslationUtils {
045        public static final Comparator<FunctionDescriptor> OVERLOADED_FUNCTION_COMPARATOR = new OverloadedFunctionComparator();
046        // TODO drop after KT-4517 will be fixed.
047        public static final Set<String> ANY_METHODS = ContainerUtil.set("equals", "hashCode", "toString");
048    
049        private TranslationUtils() {
050        }
051    
052        @NotNull
053        public static JsPropertyInitializer translateFunctionAsEcma5PropertyDescriptor(@NotNull JsFunction function,
054                @NotNull FunctionDescriptor descriptor,
055                @NotNull TranslationContext context) {
056            if (JsDescriptorUtils.isExtension(descriptor)) {
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)));
068        }
069    
070        @NotNull
071        private static JsPropertyInitializer translateExtensionFunctionAsEcma5DataDescriptor(@NotNull JsFunction function,
072                @NotNull FunctionDescriptor descriptor, @NotNull TranslationContext context) {
073            JsObjectLiteral meta = createDataDescriptor(function, descriptor.getModality().isOverridable(), 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(), baseBinaryExpression.getArg2());
080        }
081    
082        public static boolean isEqualLikeOperator(@NotNull JsBinaryOperator operator) {
083            return notOperator(operator) != null;
084        }
085    
086        @Nullable
087        private static JsBinaryOperator notOperator(@NotNull JsBinaryOperator operator) {
088            switch (operator) {
089                case REF_EQ:
090                    return REF_NEQ;
091                case REF_NEQ:
092                    return REF_EQ;
093                case EQ:
094                    return NEQ;
095                case NEQ:
096                    return EQ;
097                default:
098                    return null;
099            }
100        }
101    
102        @NotNull
103        public static JsBinaryOperation isNullCheck(@NotNull JsExpression expressionToCheck) {
104            return nullCheck(expressionToCheck, false);
105        }
106    
107        @NotNull
108        public static JsBinaryOperation isNotNullCheck(@NotNull JsExpression expressionToCheck) {
109            return nullCheck(expressionToCheck, true);
110        }
111    
112        @NotNull
113        public static JsBinaryOperation nullCheck(@NotNull JsExpression expressionToCheck, boolean isNegated) {
114            JsBinaryOperator operator = isNegated ? JsBinaryOperator.NEQ : JsBinaryOperator.EQ;
115            return new JsBinaryOperation(operator, expressionToCheck, JsLiteral.NULL);
116        }
117    
118        @NotNull
119        public static JsConditional notNullConditional(
120                @NotNull JsExpression expression,
121                @NotNull JsExpression elseExpression,
122                @NotNull TranslationContext context
123        ) {
124            JsExpression testExpression;
125            JsExpression thenExpression;
126            if (isCacheNeeded(expression)) {
127                TemporaryConstVariable tempVar = context.getOrDeclareTemporaryConstVariable(expression);
128                testExpression = isNotNullCheck(tempVar.value());
129                thenExpression = tempVar.value();
130            }
131            else {
132                testExpression = isNotNullCheck(expression);
133                thenExpression = expression;
134            }
135    
136            return new JsConditional(testExpression, thenExpression, elseExpression);
137        }
138    
139        @NotNull
140        public static String getMangledName(@NotNull PropertyDescriptor descriptor, @NotNull String suggestedName) {
141            return getStableMangledName(suggestedName, getFqName(descriptor).asString());
142        }
143    
144        @NotNull
145        public static String getMangledName(@NotNull FunctionDescriptor descriptor) {
146            if (needsStableMangling(descriptor)) {
147                return getStableMangledName(descriptor);
148            }
149    
150            return getSimpleMangledName(descriptor);
151        }
152    
153        //TODO extend logic for nested/inner declarations
154        private static boolean needsStableMangling(FunctionDescriptor descriptor) {
155            // Use stable mangling for overrides because we use stable mangling when any function inside a overridable declaration
156            // for avoid clashing names when inheritance.
157            if (JsDescriptorUtils.isOverride(descriptor)) {
158                return true;
159            }
160    
161            DeclarationDescriptor containingDeclaration = descriptor.getContainingDeclaration();
162    
163            if (containingDeclaration instanceof PackageFragmentDescriptor) {
164                return descriptor.getVisibility().isPublicAPI();
165            }
166            else if (containingDeclaration instanceof ClassDescriptor) {
167                ClassDescriptor classDescriptor = (ClassDescriptor) containingDeclaration;
168    
169                // TODO drop this temporary workaround and uncomment test cases in manglingAnyMethods.kt after KT-4517 will be fixed.
170                if (ANY_METHODS.contains(descriptor.getName().asString())) {
171                    return true;
172                }
173    
174                // Use stable mangling when it inside a overridable declaration for avoid clashing names when inheritance.
175                if (classDescriptor.getModality().isOverridable()) {
176                    return true;
177                }
178    
179                // Don't use stable mangling when it inside a non-public API declaration.
180                if (!classDescriptor.getVisibility().isPublicAPI()) {
181                    return false;
182                }
183    
184                // Ignore the `protected` visibility because it can be use outside a containing declaration
185                // only when the containing declaration is overridable.
186                if (descriptor.getVisibility() == Visibilities.PUBLIC) {
187                    return true;
188                }
189    
190                return false;
191            }
192    
193            assert containingDeclaration instanceof CallableMemberDescriptor :
194                    "containingDeclaration for descriptor have unsupported type for mangling, " +
195                    "descriptor: " + descriptor + ", containingDeclaration: " + containingDeclaration;
196    
197            return false;
198        }
199    
200        @NotNull
201        private static String getStableMangledName(@NotNull String suggestedName, String forCalculateId) {
202            int absHashCode = Math.abs(forCalculateId.hashCode());
203            String suffix = absHashCode == 0 ? "" : ("_" + Integer.toString(absHashCode, Character.MAX_RADIX) + "$");
204            return suggestedName + suffix;
205        }
206    
207        @NotNull
208        private static String getStableMangledName(@NotNull FunctionDescriptor descriptor) {
209            return getStableMangledName(descriptor.getName().asString(), getArgumentTypesAsString(descriptor));
210        }
211    
212        @NotNull
213        private static String getSimpleMangledName(@NotNull final FunctionDescriptor descriptor) {
214            DeclarationDescriptor declaration = descriptor.getContainingDeclaration();
215    
216            JetScope jetScope = null;
217            if (declaration instanceof PackageFragmentDescriptor) {
218                jetScope = ((PackageFragmentDescriptor) declaration).getMemberScope();
219            }
220            else if (declaration instanceof ClassDescriptor) {
221                jetScope = ((ClassDescriptor) declaration).getDefaultType().getMemberScope();
222            }
223    
224            int counter = 0;
225    
226            if (jetScope != null) {
227                Collection<DeclarationDescriptor> declarations = jetScope.getAllDescriptors();
228                List<FunctionDescriptor> overloadedFunctions = ContainerUtil.mapNotNull(declarations, new Function<DeclarationDescriptor, FunctionDescriptor>() {
229                    @Override
230                    public FunctionDescriptor fun(DeclarationDescriptor declarationDescriptor) {
231                        if (!(declarationDescriptor instanceof FunctionDescriptor)) return null;
232    
233                        FunctionDescriptor functionDescriptor = (FunctionDescriptor) declarationDescriptor;
234    
235                        String name = AnnotationsUtils.getNameForAnnotatedObjectWithOverrides(functionDescriptor);
236    
237                        if (name == null) {
238                            // when name == null it's mean that it's not native
239                            if (needsStableMangling(functionDescriptor)) return null;
240    
241                            name = declarationDescriptor.getName().asString();
242                        }
243    
244                        return descriptor.getName().asString().equals(name) ? functionDescriptor : null;
245                    }
246                });
247    
248                if (overloadedFunctions.size() > 1) {
249                    Collections.sort(overloadedFunctions, OVERLOADED_FUNCTION_COMPARATOR);
250                    counter = ContainerUtil.indexOfIdentity(overloadedFunctions, descriptor);
251                    assert counter >= 0;
252                }
253            }
254    
255            String name = descriptor.getName().asString();
256            return counter == 0 ? name : name + '_' + counter;
257        }
258    
259        private static String getArgumentTypesAsString(FunctionDescriptor descriptor) {
260            StringBuilder argTypes = new StringBuilder();
261    
262            ReceiverParameterDescriptor receiverParameter = descriptor.getReceiverParameter();
263            if (receiverParameter != null) {
264                argTypes.append(getJetTypeName(receiverParameter.getType())).append(".");
265            }
266    
267            argTypes.append(StringUtil.join(descriptor.getValueParameters(), new Function<ValueParameterDescriptor, String>() {
268                @Override
269                public String fun(ValueParameterDescriptor descriptor) {
270                    return getJetTypeName(descriptor.getType());
271                }
272            }, ","));
273    
274            return argTypes.toString();
275        }
276    
277        @NotNull
278        private static String getJetTypeName(@NotNull JetType jetType) {
279            ClassifierDescriptor declaration = jetType.getConstructor().getDeclarationDescriptor();
280            assert declaration != null;
281    
282            if (declaration instanceof TypeParameterDescriptor) {
283                return getJetTypeName(((TypeParameterDescriptor) declaration).getUpperBoundsAsType());
284            }
285    
286            return getFqName(declaration).asString();
287        }
288    
289        @NotNull
290        public static JsNameRef backingFieldReference(@NotNull TranslationContext context,
291                @NotNull PropertyDescriptor descriptor) {
292            JsName backingFieldName = context.getNameForDescriptor(descriptor);
293            if(!JsDescriptorUtils.isSimpleFinalProperty(descriptor)) {
294                String backingFieldMangledName;
295                if (descriptor.getVisibility() != Visibilities.PRIVATE) {
296                    backingFieldMangledName = getMangledName(descriptor, getKotlinBackingFieldName(backingFieldName.getIdent()));
297                } else {
298                    backingFieldMangledName = getKotlinBackingFieldName(backingFieldName.getIdent());
299                }
300                backingFieldName = context.declarePropertyOrPropertyAccessorName(descriptor, backingFieldMangledName, false);
301            }
302            return new JsNameRef(backingFieldName, JsLiteral.THIS);
303        }
304    
305        @NotNull
306        public static JsExpression assignmentToBackingField(@NotNull TranslationContext context,
307                @NotNull PropertyDescriptor descriptor,
308                @NotNull JsExpression assignTo) {
309            JsNameRef backingFieldReference = backingFieldReference(context, descriptor);
310            return assignment(backingFieldReference, assignTo);
311        }
312    
313        @Nullable
314        public static JsExpression translateInitializerForProperty(@NotNull JetProperty declaration,
315                @NotNull TranslationContext context) {
316            JsExpression jsInitExpression = null;
317            JetExpression initializer = declaration.getInitializer();
318            if (initializer != null) {
319                jsInitExpression = Translation.translateAsExpression(initializer, context);
320            }
321            return jsInitExpression;
322        }
323    
324        @NotNull
325        public static List<JsExpression> translateExpressionList(@NotNull TranslationContext context,
326                @NotNull List<JetExpression> expressions) {
327            List<JsExpression> result = new ArrayList<JsExpression>();
328            for (JetExpression expression : expressions) {
329                result.add(Translation.translateAsExpression(expression, context));
330            }
331            return result;
332        }
333    
334        @NotNull
335        public static JsExpression translateBaseExpression(@NotNull TranslationContext context,
336                @NotNull JetUnaryExpression expression) {
337            JetExpression baseExpression = PsiUtils.getBaseExpression(expression);
338            return Translation.translateAsExpression(baseExpression, context);
339        }
340    
341        @NotNull
342        public static JsExpression translateLeftExpression(@NotNull TranslationContext context,
343                @NotNull JetBinaryExpression expression) {
344            JetExpression left = expression.getLeft();
345            assert left != null : "Binary expression should have a left expression: " + expression.getText();
346            return Translation.translateAsExpression(left, context);
347        }
348    
349        @NotNull
350        public static JsExpression translateRightExpression(@NotNull TranslationContext context,
351                @NotNull JetBinaryExpression expression) {
352            JetExpression rightExpression = expression.getRight();
353            assert rightExpression != null : "Binary expression should have a right expression";
354            return Translation.translateAsExpression(rightExpression, context);
355        }
356    
357        public static boolean hasCorrespondingFunctionIntrinsic(@NotNull TranslationContext context,
358                @NotNull JetOperationExpression expression) {
359            FunctionDescriptor operationDescriptor = getFunctionDescriptorForOperationExpression(context.bindingContext(), expression);
360    
361            if (operationDescriptor == null) return true;
362            if (context.intrinsics().getFunctionIntrinsics().getIntrinsic(operationDescriptor).exists()) return true;
363    
364            return false;
365        }
366    
367        @NotNull
368        public static List<JsExpression> generateInvocationArguments(@NotNull JsExpression receiver, @NotNull List<JsExpression> arguments) {
369            if (arguments.isEmpty()) {
370                return Collections.singletonList(receiver);
371            }
372    
373            List<JsExpression> argumentList = new ArrayList<JsExpression>(1 + arguments.size());
374            argumentList.add(receiver);
375            argumentList.addAll(arguments);
376            return argumentList;
377        }
378    
379        public static boolean isCacheNeeded(@NotNull JsExpression expression) {
380            return !(expression instanceof JsLiteral) &&
381                   (!(expression instanceof JsNameRef) || ((JsNameRef) expression).getQualifier() != null);
382        }
383    
384        @NotNull
385        public static Pair<JsVars.JsVar, JsExpression> createTemporaryIfNeed(
386                @NotNull JsExpression expression,
387                @NotNull TranslationContext context
388        ) {
389            // don't create temp variable for simple expression
390            if (isCacheNeeded(expression)) {
391                return context.dynamicContext().createTemporary(expression);
392            }
393            else {
394                return Pair.create(null, expression);
395            }
396        }
397    
398        @NotNull
399        public static JsConditional sure(@NotNull JsExpression expression, @NotNull TranslationContext context) {
400            JsInvocation throwNPE = new JsInvocation(context.namer().throwNPEFunctionRef());
401            JsConditional ensureNotNull = notNullConditional(expression, throwNPE, context);
402    
403            JsExpression thenExpression = ensureNotNull.getThenExpression();
404            if (thenExpression instanceof JsNameRef) {
405                // associate (cache) ensureNotNull expression to new TemporaryConstVariable with same name.
406                context.associateExpressionToLazyValue(ensureNotNull,
407                                                       new TemporaryConstVariable(((JsNameRef) thenExpression).getName(), ensureNotNull));
408            }
409    
410            return ensureNotNull;
411        }
412    
413        private static class OverloadedFunctionComparator implements Comparator<FunctionDescriptor> {
414            @Override
415            public int compare(@NotNull FunctionDescriptor a, @NotNull FunctionDescriptor b) {
416                // native functions first
417                if (isNativeOrOverrideNative(a)) {
418                    if (!isNativeOrOverrideNative(b)) return -1;
419                }
420                else if (isNativeOrOverrideNative(b)) {
421                    return 1;
422                }
423    
424                // be visibility
425                // Actually "internal" > "private", but we want to have less number for "internal", so compare b with a instead of a with b.
426                Integer result = Visibilities.compare(b.getVisibility(), a.getVisibility());
427                if (result != null && result != 0) return result;
428    
429                // by arity
430                int aArity = arity(a);
431                int bArity = arity(b);
432                if (aArity != bArity) return aArity - bArity;
433    
434                // by stringify argument types
435                String aArguments = getArgumentTypesAsString(a);
436                String bArguments = getArgumentTypesAsString(b);
437                assert aArguments != bArguments;
438    
439                return aArguments.compareTo(bArguments);
440            }
441    
442            private static int arity(FunctionDescriptor descriptor) {
443                return descriptor.getValueParameters().size() + (descriptor.getReceiverParameter() == null ? 0 : 1);
444            }
445    
446            private static boolean isNativeOrOverrideNative(FunctionDescriptor descriptor) {
447                if (AnnotationsUtils.isNativeObject(descriptor)) return true;
448    
449                Set<FunctionDescriptor> declarations = BindingContextUtils.getAllOverriddenDeclarations(descriptor);
450                for (FunctionDescriptor memberDescriptor : declarations) {
451                    if (AnnotationsUtils.isNativeObject(memberDescriptor)) return true;
452                }
453                return false;
454            }
455        }
456    }