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