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.intellij.openapi.util.text.StringUtil;
020    import com.intellij.util.Function;
021    import com.intellij.util.containers.ContainerUtil;
022    import kotlin.KotlinPackage;
023    import kotlin.jvm.functions.Function1;
024    import org.jetbrains.annotations.NotNull;
025    import org.jetbrains.kotlin.backend.common.CodegenUtil;
026    import org.jetbrains.kotlin.descriptors.*;
027    import org.jetbrains.kotlin.descriptors.annotations.Annotations;
028    import org.jetbrains.kotlin.descriptors.impl.ConstructorDescriptorImpl;
029    import org.jetbrains.kotlin.incremental.components.NoLookupLocation;
030    import org.jetbrains.kotlin.name.FqNameUnsafe;
031    import org.jetbrains.kotlin.name.Name;
032    import org.jetbrains.kotlin.resolve.DescriptorUtils;
033    import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter;
034    import org.jetbrains.kotlin.resolve.scopes.JetScope;
035    
036    import java.util.*;
037    
038    import static org.jetbrains.kotlin.js.descriptorUtils.DescriptorUtilsPackage.getJetTypeFqName;
039    import static org.jetbrains.kotlin.js.descriptorUtils.DescriptorUtilsPackage.hasPrimaryConstructor;
040    import static org.jetbrains.kotlin.resolve.DescriptorUtils.getFqName;
041    
042    public class ManglingUtils {
043        private ManglingUtils() {}
044    
045        public static final Comparator<CallableDescriptor> CALLABLE_COMPARATOR = new CallableComparator();
046    
047        @NotNull
048        public static String getMangledName(@NotNull PropertyDescriptor descriptor, @NotNull String suggestedName) {
049            return getStableMangledName(suggestedName, getFqName(descriptor).asString());
050        }
051    
052        @NotNull
053        public static String getSuggestedName(@NotNull DeclarationDescriptor descriptor) {
054            String suggestedName = descriptor.getName().asString();
055    
056            if (descriptor instanceof FunctionDescriptor ||
057                descriptor instanceof PropertyDescriptor && DescriptorUtils.isExtension((PropertyDescriptor) descriptor)
058            ) {
059                suggestedName = getMangledName((CallableMemberDescriptor) descriptor);
060            }
061    
062            return suggestedName;
063        }
064    
065        @NotNull
066        private static String getMangledName(@NotNull CallableMemberDescriptor descriptor) {
067            if (needsStableMangling(descriptor)) {
068                return getStableMangledName(descriptor);
069            }
070    
071            return getSimpleMangledName(descriptor);
072        }
073    
074        //TODO extend logic for nested/inner declarations
075        private static boolean needsStableMangling(CallableMemberDescriptor descriptor) {
076            // Use stable mangling for overrides because we use stable mangling when any function inside a overridable declaration
077            // for avoid clashing names when inheritance.
078            if (DescriptorUtils.isOverride(descriptor)) {
079                return true;
080            }
081    
082            DeclarationDescriptor containingDeclaration = descriptor.getContainingDeclaration();
083    
084            if (containingDeclaration instanceof PackageFragmentDescriptor) {
085                return descriptor.getVisibility().getIsPublicAPI();
086            }
087            else if (containingDeclaration instanceof ClassDescriptor) {
088                ClassDescriptor classDescriptor = (ClassDescriptor) containingDeclaration;
089    
090                // Use stable mangling when it's inside an overridable declaration to avoid clashing names on inheritance.
091                if (classDescriptor.getModality().isOverridable()) {
092                    return true;
093                }
094    
095                // valueOf() is created in the library with a mangled name for every enum class
096                if (descriptor instanceof FunctionDescriptor && CodegenUtil.isEnumValueOfMethod((FunctionDescriptor) descriptor)) {
097                    return true;
098                }
099    
100                // Don't use stable mangling when it inside a non-public API declaration.
101                if (!classDescriptor.getVisibility().getIsPublicAPI()) {
102                    return false;
103                }
104    
105                // Ignore the `protected` visibility because it can be use outside a containing declaration
106                // only when the containing declaration is overridable.
107                if (descriptor.getVisibility() == Visibilities.PUBLIC) {
108                    return true;
109                }
110    
111                return false;
112            }
113    
114            assert containingDeclaration instanceof CallableMemberDescriptor :
115                    "containingDeclaration for descriptor have unsupported type for mangling, " +
116                    "descriptor: " + descriptor + ", containingDeclaration: " + containingDeclaration;
117    
118            return false;
119        }
120    
121        @NotNull
122        public static String getMangledMemberNameForExplicitDelegation(
123                @NotNull String suggestedName,
124                @NotNull FqNameUnsafe classFqName,
125                @NotNull FqNameUnsafe typeFqName
126        ) {
127            String forCalculateId = classFqName.asString() + ":" + typeFqName.asString();
128            return getStableMangledName(suggestedName, forCalculateId);
129        }
130    
131        @NotNull
132        private static String getStableMangledName(@NotNull String suggestedName, String forCalculateId) {
133            int absHashCode = Math.abs(forCalculateId.hashCode());
134            String suffix = absHashCode == 0 ? "" : ("_" + Integer.toString(absHashCode, Character.MAX_RADIX) + "$");
135            return suggestedName + suffix;
136        }
137    
138        @NotNull
139        private static String getStableMangledName(@NotNull CallableDescriptor descriptor) {
140            String suggestedName = getSuggestedName(descriptor);
141            return getStableMangledName(suggestedName, getArgumentTypesAsString(descriptor));
142        }
143    
144        @NotNull
145        private static String getSuggestedName(@NotNull CallableDescriptor descriptor) {
146            if (descriptor instanceof ConstructorDescriptor && !((ConstructorDescriptor) descriptor).isPrimary()) {
147                DeclarationDescriptor classDescriptor = descriptor.getContainingDeclaration();
148                assert classDescriptor != null;
149                return classDescriptor.getName().asString();
150            }
151            else {
152                return descriptor.getName().asString();
153            }
154        }
155    
156        @NotNull
157        private static String getSimpleMangledName(@NotNull CallableMemberDescriptor descriptor) {
158            DeclarationDescriptor containingDeclaration = descriptor.getContainingDeclaration();
159    
160            JetScope jetScope = null;
161    
162            String nameToCompare = descriptor.getName().asString();
163    
164            if (containingDeclaration != null && descriptor instanceof ConstructorDescriptor) {
165                nameToCompare = containingDeclaration.getName().asString();
166                containingDeclaration = containingDeclaration.getContainingDeclaration();
167            }
168    
169            if (containingDeclaration instanceof PackageFragmentDescriptor) {
170                jetScope = ((PackageFragmentDescriptor) containingDeclaration).getMemberScope();
171            }
172            else if (containingDeclaration instanceof ClassDescriptor) {
173                jetScope = ((ClassDescriptor) containingDeclaration).getDefaultType().getMemberScope();
174            }
175    
176            int counter = 0;
177    
178            if (jetScope != null) {
179                final String finalNameToCompare = nameToCompare;
180    
181                Collection<DeclarationDescriptor> declarations = jetScope.getDescriptors(DescriptorKindFilter.CALLABLES, JetScope.ALL_NAME_FILTER);
182                List<CallableDescriptor> overloadedFunctions =
183                        KotlinPackage.flatMap(declarations, new Function1<DeclarationDescriptor, Iterable<? extends CallableDescriptor>>() {
184                    @Override
185                    public Iterable<? extends CallableDescriptor> invoke(DeclarationDescriptor declarationDescriptor) {
186                        if (declarationDescriptor instanceof ClassDescriptor && finalNameToCompare.equals(declarationDescriptor.getName().asString())) {
187                            ClassDescriptor classDescriptor = (ClassDescriptor) declarationDescriptor;
188                            Collection<ConstructorDescriptor> constructors = classDescriptor.getConstructors();
189    
190                            if (!hasPrimaryConstructor(classDescriptor)) {
191                                ConstructorDescriptorImpl fakePrimaryConstructor =
192                                        ConstructorDescriptorImpl.create(classDescriptor, Annotations.EMPTY, true, SourceElement.NO_SOURCE);
193                                return KotlinPackage.plus(constructors, fakePrimaryConstructor);
194                            }
195    
196                            return constructors;
197                        }
198    
199                        if (!(declarationDescriptor instanceof CallableMemberDescriptor)) return Collections.emptyList();
200    
201                        CallableMemberDescriptor callableMemberDescriptor = (CallableMemberDescriptor) declarationDescriptor;
202    
203                        String name = AnnotationsUtils.getNameForAnnotatedObjectWithOverrides(callableMemberDescriptor);
204    
205                        // when name == null it's mean that it's not native.
206                        if (name == null) {
207                            // skip functions without arguments, because we don't use mangling for them
208                            if (needsStableMangling(callableMemberDescriptor) && !callableMemberDescriptor.getValueParameters().isEmpty()) return Collections.emptyList();
209    
210                            // TODO add prefix for property: get_$name and set_$name
211                            name = callableMemberDescriptor.getName().asString();
212                        }
213    
214                        if (finalNameToCompare.equals(name)) return Collections.singletonList(callableMemberDescriptor);
215    
216                        return Collections.emptyList();
217                    }
218                });
219    
220                if (overloadedFunctions.size() > 1) {
221                    Collections.sort(overloadedFunctions, CALLABLE_COMPARATOR);
222                    counter = ContainerUtil.indexOfIdentity(overloadedFunctions, descriptor);
223                    assert counter >= 0;
224                }
225            }
226    
227            String name = getSuggestedName(descriptor);
228            return counter == 0 ? name : name + '_' + counter;
229        }
230    
231        private static String getArgumentTypesAsString(CallableDescriptor descriptor) {
232            StringBuilder argTypes = new StringBuilder();
233    
234            ReceiverParameterDescriptor receiverParameter = descriptor.getExtensionReceiverParameter();
235            if (receiverParameter != null) {
236                argTypes.append(getJetTypeFqName(receiverParameter.getType(), true)).append(".");
237            }
238    
239            argTypes.append(StringUtil.join(descriptor.getValueParameters(), new Function<ValueParameterDescriptor, String>() {
240                @Override
241                public String fun(ValueParameterDescriptor descriptor) {
242                    return getJetTypeFqName(descriptor.getType(), true);
243                }
244            }, ","));
245    
246            return argTypes.toString();
247        }
248    
249        @NotNull
250        public static String getStableMangledNameForDescriptor(@NotNull ClassDescriptor descriptor, @NotNull String functionName) {
251            Collection<FunctionDescriptor> functions =
252                    descriptor.getDefaultType().getMemberScope().getFunctions(Name.identifier(functionName), NoLookupLocation.FROM_BACKEND);
253            assert functions.size() == 1 : "Can't select a single function: " + functionName + " in " + descriptor;
254            return getSuggestedName((DeclarationDescriptor) functions.iterator().next());
255        }
256    
257        private static class CallableComparator implements Comparator<CallableDescriptor> {
258            @Override
259            public int compare(@NotNull CallableDescriptor a, @NotNull CallableDescriptor b) {
260                // primary constructors
261                if (a instanceof ConstructorDescriptor && ((ConstructorDescriptor) a).isPrimary()) {
262                    if (!(b instanceof ConstructorDescriptor) || !((ConstructorDescriptor) b).isPrimary()) return -1;
263                }
264                else if (b instanceof ConstructorDescriptor && ((ConstructorDescriptor) b).isPrimary()) {
265                    return 1;
266                }
267    
268                // native functions
269                if (isNativeOrOverrideNative(a)) {
270                    if (!isNativeOrOverrideNative(b)) return -1;
271                }
272                else if (isNativeOrOverrideNative(b)) {
273                    return 1;
274                }
275    
276                // be visibility
277                // Actually "internal" > "private", but we want to have less number for "internal", so compare b with a instead of a with b.
278                Integer result = Visibilities.compare(b.getVisibility(), a.getVisibility());
279                if (result != null && result != 0) return result;
280    
281                // by arity
282                int aArity = arity(a);
283                int bArity = arity(b);
284                if (aArity != bArity) return aArity - bArity;
285    
286                // by stringify argument types
287                String aArguments = getArgumentTypesAsString(a);
288                String bArguments = getArgumentTypesAsString(b);
289                assert aArguments != bArguments;
290    
291                return aArguments.compareTo(bArguments);
292            }
293    
294            private static int arity(CallableDescriptor descriptor) {
295                return descriptor.getValueParameters().size() + (descriptor.getExtensionReceiverParameter() == null ? 0 : 1);
296            }
297    
298            private static boolean isNativeOrOverrideNative(CallableDescriptor descriptor) {
299                if (!(descriptor instanceof CallableMemberDescriptor)) return false;
300    
301                if (AnnotationsUtils.isNativeObject(descriptor)) return true;
302    
303                Set<CallableMemberDescriptor> declarations = DescriptorUtils.getAllOverriddenDeclarations((CallableMemberDescriptor) descriptor);
304                for (CallableMemberDescriptor memberDescriptor : declarations) {
305                    if (AnnotationsUtils.isNativeObject(memberDescriptor)) return true;
306                }
307                return false;
308            }
309        }
310    }