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.descriptors.impl.AnonymousFunctionDescriptor;
028 import org.jetbrains.jet.lang.psi.*;
029 import org.jetbrains.jet.lang.resolve.BindingContextUtils;
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.getFunctionDescriptorForOperationExpression;
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(getJetTypeName(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 getJetTypeName(descriptor.getType());
277 }
278 }, ","));
279
280 return argTypes.toString();
281 }
282
283 @NotNull
284 private static String getJetTypeName(@NotNull JetType jetType) {
285 ClassifierDescriptor declaration = jetType.getConstructor().getDeclarationDescriptor();
286 assert declaration != null;
287
288 if (declaration instanceof TypeParameterDescriptor) {
289 return getJetTypeName(((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 FunctionDescriptor operationDescriptor = getFunctionDescriptorForOperationExpression(context.bindingContext(), expression);
356
357 if (operationDescriptor == null) return true;
358 if (context.intrinsics().getFunctionIntrinsics().getIntrinsic(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 = BindingContextUtils.getAllOverriddenDeclarations(descriptor);
469 for (FunctionDescriptor memberDescriptor : declarations) {
470 if (AnnotationsUtils.isNativeObject(memberDescriptor)) return true;
471 }
472 return false;
473 }
474 }
475 }