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