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 }