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 org.jetbrains.annotations.NotNull;
023 import org.jetbrains.annotations.Nullable;
024 import org.jetbrains.jet.lang.descriptors.*;
025 import org.jetbrains.jet.lang.descriptors.impl.AnonymousFunctionDescriptor;
026 import org.jetbrains.jet.lang.psi.*;
027 import org.jetbrains.jet.lang.types.JetType;
028 import org.jetbrains.jet.lang.types.TypeProjection;
029 import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
030 import org.jetbrains.k2js.translate.context.TemporaryConstVariable;
031 import org.jetbrains.k2js.translate.context.TranslationContext;
032 import org.jetbrains.k2js.translate.general.Translation;
033
034 import java.util.ArrayList;
035 import java.util.Collections;
036 import java.util.List;
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.jet.lang.resolve.DescriptorUtils.isAnonymousObject;
041 import static org.jetbrains.k2js.translate.context.Namer.getKotlinBackingFieldName;
042 import static org.jetbrains.k2js.translate.utils.BindingUtils.getCallableDescriptorForOperationExpression;
043 import static org.jetbrains.k2js.translate.utils.JsAstUtils.assignment;
044 import static org.jetbrains.k2js.translate.utils.JsAstUtils.createDataDescriptor;
045 import static org.jetbrains.k2js.translate.utils.ManglingUtils.getMangledName;
046
047 public final class TranslationUtils {
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)), "<simpleReturnFunction>");
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 getJetTypeFqName(@NotNull JetType jetType, final boolean printTypeArguments) {
141 ClassifierDescriptor declaration = jetType.getConstructor().getDeclarationDescriptor();
142 assert declaration != null;
143
144 if (declaration instanceof TypeParameterDescriptor) {
145 return StringUtil.join(((TypeParameterDescriptor) declaration).getUpperBounds(), new Function<JetType, String>() {
146 @Override
147 public String fun(JetType type) {
148 return getJetTypeFqName(type, printTypeArguments);
149 }
150 }, "&");
151 }
152
153 List<TypeProjection> typeArguments = jetType.getArguments();
154
155 String typeArgumentsAsString;
156
157 if (printTypeArguments && !typeArguments.isEmpty()) {
158 String joinedTypeArguments = StringUtil.join(typeArguments, new Function<TypeProjection, String>() {
159 @Override
160 public String fun(TypeProjection typeProjection) {
161 return getJetTypeFqName(typeProjection.getType(), false);
162 }
163 }, ", ");
164
165 typeArgumentsAsString = "<" + joinedTypeArguments + ">";
166 }
167 else {
168 typeArgumentsAsString = "";
169 }
170
171 return getFqName(declaration).asString() + typeArgumentsAsString;
172 }
173
174 @NotNull
175 public static JsNameRef backingFieldReference(@NotNull TranslationContext context,
176 @NotNull PropertyDescriptor descriptor) {
177 JsName backingFieldName = context.getNameForDescriptor(descriptor);
178 if(!JsDescriptorUtils.isSimpleFinalProperty(descriptor)) {
179 String backingFieldMangledName;
180 if (descriptor.getVisibility() != Visibilities.PRIVATE) {
181 backingFieldMangledName = getMangledName(descriptor, getKotlinBackingFieldName(backingFieldName.getIdent()));
182 } else {
183 backingFieldMangledName = getKotlinBackingFieldName(backingFieldName.getIdent());
184 }
185 backingFieldName = context.declarePropertyOrPropertyAccessorName(descriptor, backingFieldMangledName, false);
186 }
187 return new JsNameRef(backingFieldName, JsLiteral.THIS);
188 }
189
190 @NotNull
191 public static JsExpression assignmentToBackingField(@NotNull TranslationContext context,
192 @NotNull PropertyDescriptor descriptor,
193 @NotNull JsExpression assignTo) {
194 JsNameRef backingFieldReference = backingFieldReference(context, descriptor);
195 return assignment(backingFieldReference, assignTo);
196 }
197
198 @Nullable
199 public static JsExpression translateInitializerForProperty(@NotNull JetProperty declaration,
200 @NotNull TranslationContext context) {
201 JsExpression jsInitExpression = null;
202 JetExpression initializer = declaration.getInitializer();
203 if (initializer != null) {
204 jsInitExpression = Translation.translateAsExpression(initializer, context);
205 }
206 return jsInitExpression;
207 }
208
209 @NotNull
210 public static JsExpression translateBaseExpression(@NotNull TranslationContext context,
211 @NotNull JetUnaryExpression expression) {
212 JetExpression baseExpression = PsiUtils.getBaseExpression(expression);
213 return Translation.translateAsExpression(baseExpression, context);
214 }
215
216 @NotNull
217 public static JsExpression translateLeftExpression(@NotNull TranslationContext context,
218 @NotNull JetBinaryExpression expression) {
219 return translateLeftExpression(context, expression, context.dynamicContext().jsBlock());
220 }
221
222 @NotNull
223 public static JsExpression translateLeftExpression(
224 @NotNull TranslationContext context,
225 @NotNull JetBinaryExpression expression,
226 @NotNull JsBlock block
227 ) {
228 JetExpression left = expression.getLeft();
229 assert left != null : "Binary expression should have a left expression: " + expression.getText();
230 return Translation.translateAsExpression(left, context, block);
231 }
232
233 @NotNull
234 public static JsExpression translateRightExpression(@NotNull TranslationContext context,
235 @NotNull JetBinaryExpression expression) {
236 return translateRightExpression(context, expression, context.dynamicContext().jsBlock());
237 }
238
239 @NotNull
240 public static JsExpression translateRightExpression(
241 @NotNull TranslationContext context,
242 @NotNull JetBinaryExpression expression,
243 @NotNull JsBlock block) {
244 JetExpression rightExpression = expression.getRight();
245 assert rightExpression != null : "Binary expression should have a right expression";
246 return Translation.translateAsExpression(rightExpression, context, block);
247 }
248
249 public static boolean hasCorrespondingFunctionIntrinsic(@NotNull TranslationContext context,
250 @NotNull JetOperationExpression expression) {
251 CallableDescriptor operationDescriptor = getCallableDescriptorForOperationExpression(context.bindingContext(), expression);
252
253 if (operationDescriptor == null || !(operationDescriptor instanceof FunctionDescriptor)) return true;
254
255 JetType returnType = operationDescriptor.getReturnType();
256 if (returnType != null &&
257 KotlinBuiltIns.getInstance().getCharType().equals(returnType) || (KotlinBuiltIns.getInstance().getLongType().equals(returnType))) return false;
258
259 if (context.intrinsics().getFunctionIntrinsics().getIntrinsic((FunctionDescriptor) operationDescriptor).exists()) return true;
260
261 return false;
262 }
263
264 @NotNull
265 public static List<JsExpression> generateInvocationArguments(@NotNull JsExpression receiver, @NotNull List<JsExpression> arguments) {
266 if (arguments.isEmpty()) {
267 return Collections.singletonList(receiver);
268 }
269
270 List<JsExpression> argumentList = new ArrayList<JsExpression>(1 + arguments.size());
271 argumentList.add(receiver);
272 argumentList.addAll(arguments);
273 return argumentList;
274 }
275
276 public static boolean isCacheNeeded(@NotNull JsExpression expression) {
277 return !(expression instanceof JsLiteral.JsValueLiteral) &&
278 !JsAstUtils.isEmptyExpression(expression) &&
279 (!(expression instanceof JsNameRef) || ((JsNameRef) expression).getQualifier() != null);
280 }
281
282 @NotNull
283 public static JsConditional sure(@NotNull JsExpression expression, @NotNull TranslationContext context) {
284 JsInvocation throwNPE = new JsInvocation(context.namer().throwNPEFunctionRef());
285 JsConditional ensureNotNull = notNullConditional(expression, throwNPE, context);
286
287 JsExpression thenExpression = ensureNotNull.getThenExpression();
288 if (thenExpression instanceof JsNameRef) {
289 // associate (cache) ensureNotNull expression to new TemporaryConstVariable with same name.
290 context.associateExpressionToLazyValue(ensureNotNull,
291 new TemporaryConstVariable(((JsNameRef) thenExpression).getName(), ensureNotNull));
292 }
293
294 return ensureNotNull;
295 }
296
297 @NotNull
298 public static String getSuggestedNameForInnerDeclaration(TranslationContext context, DeclarationDescriptor descriptor) {
299 String suggestedName = "";
300 DeclarationDescriptor containingDeclaration = descriptor.getContainingDeclaration();
301 //noinspection ConstantConditions
302 if (containingDeclaration != null &&
303 !(containingDeclaration instanceof ClassOrPackageFragmentDescriptor) &&
304 !(containingDeclaration instanceof AnonymousFunctionDescriptor) &&
305 !(containingDeclaration instanceof ConstructorDescriptor && isAnonymousObject(containingDeclaration.getContainingDeclaration()))) {
306 suggestedName = context.getNameForDescriptor(containingDeclaration).getIdent();
307 }
308
309 if (!suggestedName.isEmpty() && !suggestedName.endsWith("$")) {
310 suggestedName += "$";
311 }
312
313 if (descriptor.getName().isSpecial()) {
314 suggestedName += "f";
315 }
316 else {
317 suggestedName += context.getNameForDescriptor(descriptor).getIdent();
318 }
319 return suggestedName;
320 }
321 }