001    /*
002     * Copyright 2010-2014 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.descriptors.impl.AnonymousFunctionDescriptor;
028    import org.jetbrains.jet.lang.psi.*;
029    import org.jetbrains.jet.lang.resolve.OverrideResolver;
030    import org.jetbrains.jet.lang.resolve.scopes.JetScope;
031    import org.jetbrains.jet.lang.types.JetType;
032    import org.jetbrains.k2js.translate.context.TemporaryConstVariable;
033    import org.jetbrains.k2js.translate.context.TranslationContext;
034    import org.jetbrains.k2js.translate.general.Translation;
035    
036    import java.util.*;
037    
038    import static com.google.dart.compiler.backend.js.ast.JsBinaryOperator.*;
039    import static org.jetbrains.jet.lang.resolve.DescriptorUtils.getFqName;
040    import static org.jetbrains.k2js.translate.context.Namer.getKotlinBackingFieldName;
041    import static org.jetbrains.k2js.translate.utils.BindingUtils.getCallableDescriptorForOperationExpression;
042    import static org.jetbrains.k2js.translate.utils.JsAstUtils.assignment;
043    import static org.jetbrains.k2js.translate.utils.JsAstUtils.createDataDescriptor;
044    
045    public final class TranslationUtils {
046        public static final Comparator<FunctionDescriptor> OVERLOADED_FUNCTION_COMPARATOR = new OverloadedFunctionComparator();
047    
048        private TranslationUtils() {
049        }
050    
051        @NotNull
052        public static JsPropertyInitializer translateFunctionAsEcma5PropertyDescriptor(@NotNull JsFunction function,
053                @NotNull FunctionDescriptor descriptor,
054                @NotNull TranslationContext context) {
055            if (JsDescriptorUtils.isExtension(descriptor)) {
056                return translateExtensionFunctionAsEcma5DataDescriptor(function, descriptor, context);
057            }
058            else {
059                JsStringLiteral getOrSet = context.program().getStringLiteral(descriptor instanceof PropertyGetterDescriptor ? "get" : "set");
060                return new JsPropertyInitializer(getOrSet, function);
061            }
062        }
063    
064        @NotNull
065        public static JsFunction simpleReturnFunction(@NotNull JsScope functionScope, @NotNull JsExpression returnExpression) {
066            return new JsFunction(functionScope, new JsBlock(new JsReturn(returnExpression)));
067        }
068    
069        @NotNull
070        private static JsPropertyInitializer translateExtensionFunctionAsEcma5DataDescriptor(@NotNull JsFunction function,
071                @NotNull FunctionDescriptor descriptor, @NotNull TranslationContext context) {
072            JsObjectLiteral meta = createDataDescriptor(function, descriptor.getModality().isOverridable(), false);
073            return new JsPropertyInitializer(context.getNameForDescriptor(descriptor).makeRef(), meta);
074        }
075    
076        @NotNull
077        public static JsExpression translateExclForBinaryEqualLikeExpr(@NotNull JsBinaryOperation baseBinaryExpression) {
078            return new JsBinaryOperation(notOperator(baseBinaryExpression.getOperator()), baseBinaryExpression.getArg1(), 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 String getMangledName(@NotNull PropertyDescriptor descriptor, @NotNull String suggestedName) {
140            return getStableMangledName(suggestedName, getFqName(descriptor).asString());
141        }
142    
143        @NotNull
144        public static String getSuggestedName(@NotNull DeclarationDescriptor descriptor) {
145            String suggestedName = descriptor.getName().asString();
146    
147            if (descriptor instanceof FunctionDescriptor) {
148                suggestedName = getMangledName((FunctionDescriptor) descriptor);
149            }
150    
151            return suggestedName;
152        }
153    
154        @NotNull
155        private static String getMangledName(@NotNull FunctionDescriptor descriptor) {
156            if (needsStableMangling(descriptor)) {
157                return getStableMangledName(descriptor);
158            }
159    
160            return getSimpleMangledName(descriptor);
161        }
162    
163        //TODO extend logic for nested/inner declarations
164        private static boolean needsStableMangling(FunctionDescriptor descriptor) {
165            // Use stable mangling for overrides because we use stable mangling when any function inside a overridable declaration
166            // for avoid clashing names when inheritance.
167            if (JsDescriptorUtils.isOverride(descriptor)) {
168                return true;
169            }
170    
171            DeclarationDescriptor containingDeclaration = descriptor.getContainingDeclaration();
172    
173            if (containingDeclaration instanceof PackageFragmentDescriptor) {
174                return descriptor.getVisibility().isPublicAPI();
175            }
176            else if (containingDeclaration instanceof ClassDescriptor) {
177                ClassDescriptor classDescriptor = (ClassDescriptor) containingDeclaration;
178    
179                // Use stable mangling when it inside a overridable declaration for avoid clashing names when inheritance.
180                if (classDescriptor.getModality().isOverridable()) {
181                    return true;
182                }
183    
184                // Don't use stable mangling when it inside a non-public API declaration.
185                if (!classDescriptor.getVisibility().isPublicAPI()) {
186                    return false;
187                }
188    
189                // Ignore the `protected` visibility because it can be use outside a containing declaration
190                // only when the containing declaration is overridable.
191                if (descriptor.getVisibility() == Visibilities.PUBLIC) {
192                    return true;
193                }
194    
195                return false;
196            }
197    
198            assert containingDeclaration instanceof CallableMemberDescriptor :
199                    "containingDeclaration for descriptor have unsupported type for mangling, " +
200                    "descriptor: " + descriptor + ", containingDeclaration: " + containingDeclaration;
201    
202            return false;
203        }
204    
205        @NotNull
206        private static String getStableMangledName(@NotNull String suggestedName, String forCalculateId) {
207            int absHashCode = Math.abs(forCalculateId.hashCode());
208            String suffix = absHashCode == 0 ? "" : ("_" + Integer.toString(absHashCode, Character.MAX_RADIX) + "$");
209            return suggestedName + suffix;
210        }
211    
212        @NotNull
213        private static String getStableMangledName(@NotNull FunctionDescriptor descriptor) {
214            return getStableMangledName(descriptor.getName().asString(), getArgumentTypesAsString(descriptor));
215        }
216    
217        @NotNull
218        private static String getSimpleMangledName(@NotNull final FunctionDescriptor descriptor) {
219            DeclarationDescriptor declaration = descriptor.getContainingDeclaration();
220    
221            JetScope jetScope = null;
222            if (declaration instanceof PackageFragmentDescriptor) {
223                jetScope = ((PackageFragmentDescriptor) declaration).getMemberScope();
224            }
225            else if (declaration instanceof ClassDescriptor) {
226                jetScope = ((ClassDescriptor) declaration).getDefaultType().getMemberScope();
227            }
228    
229            int counter = 0;
230    
231            if (jetScope != null) {
232                Collection<DeclarationDescriptor> declarations = jetScope.getAllDescriptors();
233                List<FunctionDescriptor> overloadedFunctions = ContainerUtil.mapNotNull(declarations, new Function<DeclarationDescriptor, FunctionDescriptor>() {
234                    @Override
235                    public FunctionDescriptor fun(DeclarationDescriptor declarationDescriptor) {
236                        if (!(declarationDescriptor instanceof FunctionDescriptor)) return null;
237    
238                        FunctionDescriptor functionDescriptor = (FunctionDescriptor) declarationDescriptor;
239    
240                        String name = AnnotationsUtils.getNameForAnnotatedObjectWithOverrides(functionDescriptor);
241    
242                        // when name == null it's mean that it's not native.
243                        if (name == null) {
244                            // skip functions without arguments, because we don't use mangling for them
245                            if (needsStableMangling(functionDescriptor) && !functionDescriptor.getValueParameters().isEmpty()) return null;
246    
247                            name = declarationDescriptor.getName().asString();
248                        }
249    
250                        return descriptor.getName().asString().equals(name) ? functionDescriptor : null;
251                    }
252                });
253    
254                if (overloadedFunctions.size() > 1) {
255                    Collections.sort(overloadedFunctions, OVERLOADED_FUNCTION_COMPARATOR);
256                    counter = ContainerUtil.indexOfIdentity(overloadedFunctions, descriptor);
257                    assert counter >= 0;
258                }
259            }
260    
261            String name = descriptor.getName().asString();
262            return counter == 0 ? name : name + '_' + counter;
263        }
264    
265        private static String getArgumentTypesAsString(FunctionDescriptor descriptor) {
266            StringBuilder argTypes = new StringBuilder();
267    
268            ReceiverParameterDescriptor receiverParameter = descriptor.getReceiverParameter();
269            if (receiverParameter != null) {
270                argTypes.append(getJetTypeFqName(receiverParameter.getType())).append(".");
271            }
272    
273            argTypes.append(StringUtil.join(descriptor.getValueParameters(), new Function<ValueParameterDescriptor, String>() {
274                @Override
275                public String fun(ValueParameterDescriptor descriptor) {
276                    return getJetTypeFqName(descriptor.getType());
277                }
278            }, ","));
279    
280            return argTypes.toString();
281        }
282    
283        @NotNull
284        public static String getJetTypeFqName(@NotNull JetType jetType) {
285            ClassifierDescriptor declaration = jetType.getConstructor().getDeclarationDescriptor();
286            assert declaration != null;
287    
288            if (declaration instanceof TypeParameterDescriptor) {
289                return getJetTypeFqName(((TypeParameterDescriptor) declaration).getUpperBoundsAsType());
290            }
291    
292            return getFqName(declaration).asString();
293        }
294    
295        @NotNull
296        public static JsNameRef backingFieldReference(@NotNull TranslationContext context,
297                @NotNull PropertyDescriptor descriptor) {
298            JsName backingFieldName = context.getNameForDescriptor(descriptor);
299            if(!JsDescriptorUtils.isSimpleFinalProperty(descriptor)) {
300                String backingFieldMangledName;
301                if (descriptor.getVisibility() != Visibilities.PRIVATE) {
302                    backingFieldMangledName = getMangledName(descriptor, getKotlinBackingFieldName(backingFieldName.getIdent()));
303                } else {
304                    backingFieldMangledName = getKotlinBackingFieldName(backingFieldName.getIdent());
305                }
306                backingFieldName = context.declarePropertyOrPropertyAccessorName(descriptor, backingFieldMangledName, false);
307            }
308            return new JsNameRef(backingFieldName, JsLiteral.THIS);
309        }
310    
311        @NotNull
312        public static JsExpression assignmentToBackingField(@NotNull TranslationContext context,
313                @NotNull PropertyDescriptor descriptor,
314                @NotNull JsExpression assignTo) {
315            JsNameRef backingFieldReference = backingFieldReference(context, descriptor);
316            return assignment(backingFieldReference, assignTo);
317        }
318    
319        @Nullable
320        public static JsExpression translateInitializerForProperty(@NotNull JetProperty declaration,
321                @NotNull TranslationContext context) {
322            JsExpression jsInitExpression = null;
323            JetExpression initializer = declaration.getInitializer();
324            if (initializer != null) {
325                jsInitExpression = Translation.translateAsExpression(initializer, context);
326            }
327            return jsInitExpression;
328        }
329    
330        @NotNull
331        public static JsExpression translateBaseExpression(@NotNull TranslationContext context,
332                @NotNull JetUnaryExpression expression) {
333            JetExpression baseExpression = PsiUtils.getBaseExpression(expression);
334            return Translation.translateAsExpression(baseExpression, context);
335        }
336    
337        @NotNull
338        public static JsExpression translateLeftExpression(@NotNull TranslationContext context,
339                @NotNull JetBinaryExpression expression) {
340            JetExpression left = expression.getLeft();
341            assert left != null : "Binary expression should have a left expression: " + expression.getText();
342            return Translation.translateAsExpression(left, context);
343        }
344    
345        @NotNull
346        public static JsExpression translateRightExpression(@NotNull TranslationContext context,
347                @NotNull JetBinaryExpression expression) {
348            JetExpression rightExpression = expression.getRight();
349            assert rightExpression != null : "Binary expression should have a right expression";
350            return Translation.translateAsExpression(rightExpression, context);
351        }
352    
353        public static boolean hasCorrespondingFunctionIntrinsic(@NotNull TranslationContext context,
354                @NotNull JetOperationExpression expression) {
355            CallableDescriptor operationDescriptor = getCallableDescriptorForOperationExpression(context.bindingContext(), expression);
356    
357            if (operationDescriptor == null || !(operationDescriptor instanceof FunctionDescriptor)) return true;
358            if (context.intrinsics().getFunctionIntrinsics().getIntrinsic((FunctionDescriptor) operationDescriptor).exists()) return true;
359    
360            return false;
361        }
362    
363        @NotNull
364        public static List<JsExpression> generateInvocationArguments(@NotNull JsExpression receiver, @NotNull List<JsExpression> arguments) {
365            if (arguments.isEmpty()) {
366                return Collections.singletonList(receiver);
367            }
368    
369            List<JsExpression> argumentList = new ArrayList<JsExpression>(1 + arguments.size());
370            argumentList.add(receiver);
371            argumentList.addAll(arguments);
372            return argumentList;
373        }
374    
375        public static boolean isCacheNeeded(@NotNull JsExpression expression) {
376            return !(expression instanceof JsLiteral) &&
377                   (!(expression instanceof JsNameRef) || ((JsNameRef) expression).getQualifier() != null);
378        }
379    
380        @NotNull
381        public static Pair<JsVars.JsVar, JsExpression> createTemporaryIfNeed(
382                @NotNull JsExpression expression,
383                @NotNull TranslationContext context
384        ) {
385            // don't create temp variable for simple expression
386            if (isCacheNeeded(expression)) {
387                return context.dynamicContext().createTemporary(expression);
388            }
389            else {
390                return Pair.create(null, expression);
391            }
392        }
393    
394        @NotNull
395        public static JsConditional sure(@NotNull JsExpression expression, @NotNull TranslationContext context) {
396            JsInvocation throwNPE = new JsInvocation(context.namer().throwNPEFunctionRef());
397            JsConditional ensureNotNull = notNullConditional(expression, throwNPE, context);
398    
399            JsExpression thenExpression = ensureNotNull.getThenExpression();
400            if (thenExpression instanceof JsNameRef) {
401                // associate (cache) ensureNotNull expression to new TemporaryConstVariable with same name.
402                context.associateExpressionToLazyValue(ensureNotNull,
403                                                       new TemporaryConstVariable(((JsNameRef) thenExpression).getName(), ensureNotNull));
404            }
405    
406            return ensureNotNull;
407        }
408    
409        @NotNull
410        public static String getSuggestedNameForInnerDeclaration(TranslationContext context, DeclarationDescriptor descriptor) {
411            String suggestedName = "";
412            DeclarationDescriptor containingDeclaration = descriptor.getContainingDeclaration();
413            if (containingDeclaration != null &&
414                !(containingDeclaration instanceof ClassOrPackageFragmentDescriptor) &&
415                !(containingDeclaration instanceof AnonymousFunctionDescriptor)) {
416                suggestedName = context.getNameForDescriptor(containingDeclaration).getIdent();
417            }
418    
419            if (!suggestedName.isEmpty() && !suggestedName.endsWith("$")) {
420                suggestedName += "$";
421            }
422    
423            if (descriptor.getName().isSpecial()) {
424                suggestedName += "f";
425            }
426            else {
427                suggestedName += context.getNameForDescriptor(descriptor).getIdent();
428            }
429            return suggestedName;
430        }
431    
432        private static class OverloadedFunctionComparator implements Comparator<FunctionDescriptor> {
433            @Override
434            public int compare(@NotNull FunctionDescriptor a, @NotNull FunctionDescriptor b) {
435                // native functions first
436                if (isNativeOrOverrideNative(a)) {
437                    if (!isNativeOrOverrideNative(b)) return -1;
438                }
439                else if (isNativeOrOverrideNative(b)) {
440                    return 1;
441                }
442    
443                // be visibility
444                // Actually "internal" > "private", but we want to have less number for "internal", so compare b with a instead of a with b.
445                Integer result = Visibilities.compare(b.getVisibility(), a.getVisibility());
446                if (result != null && result != 0) return result;
447    
448                // by arity
449                int aArity = arity(a);
450                int bArity = arity(b);
451                if (aArity != bArity) return aArity - bArity;
452    
453                // by stringify argument types
454                String aArguments = getArgumentTypesAsString(a);
455                String bArguments = getArgumentTypesAsString(b);
456                assert aArguments != bArguments;
457    
458                return aArguments.compareTo(bArguments);
459            }
460    
461            private static int arity(FunctionDescriptor descriptor) {
462                return descriptor.getValueParameters().size() + (descriptor.getReceiverParameter() == null ? 0 : 1);
463            }
464    
465            private static boolean isNativeOrOverrideNative(FunctionDescriptor descriptor) {
466                if (AnnotationsUtils.isNativeObject(descriptor)) return true;
467    
468                Set<FunctionDescriptor> declarations = OverrideResolver.getAllOverriddenDeclarations(descriptor);
469                for (FunctionDescriptor memberDescriptor : declarations) {
470                    if (AnnotationsUtils.isNativeObject(memberDescriptor)) return true;
471                }
472                return false;
473            }
474        }
475    }