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.google.dart.compiler.backend.js.ast.*;
020 import org.jetbrains.annotations.NotNull;
021 import org.jetbrains.annotations.Nullable;
022 import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
023 import org.jetbrains.kotlin.descriptors.*;
024 import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor;
025 import org.jetbrains.kotlin.js.translate.context.TemporaryConstVariable;
026 import org.jetbrains.kotlin.js.translate.context.TranslationContext;
027 import org.jetbrains.kotlin.js.translate.general.Translation;
028 import org.jetbrains.kotlin.psi.*;
029 import org.jetbrains.kotlin.resolve.DescriptorUtils;
030 import org.jetbrains.kotlin.types.JetType;
031
032 import java.util.ArrayList;
033 import java.util.Collections;
034 import java.util.List;
035
036 import static com.google.dart.compiler.backend.js.ast.JsBinaryOperator.*;
037 import static org.jetbrains.kotlin.js.translate.context.Namer.getKotlinBackingFieldName;
038 import static org.jetbrains.kotlin.js.translate.utils.BindingUtils.getCallableDescriptorForOperationExpression;
039 import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.assignment;
040 import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.createDataDescriptor;
041 import static org.jetbrains.kotlin.js.translate.utils.ManglingUtils.getMangledName;
042 import static org.jetbrains.kotlin.resolve.DescriptorUtils.isAnonymousObject;
043
044 public final class TranslationUtils {
045
046 private TranslationUtils() {
047 }
048
049 @NotNull
050 public static JsPropertyInitializer translateFunctionAsEcma5PropertyDescriptor(@NotNull JsFunction function,
051 @NotNull FunctionDescriptor descriptor,
052 @NotNull TranslationContext context) {
053 if (DescriptorUtils.isExtension(descriptor)) {
054 return translateExtensionFunctionAsEcma5DataDescriptor(function, descriptor, context);
055 }
056 else {
057 JsStringLiteral getOrSet = context.program().getStringLiteral(descriptor instanceof PropertyGetterDescriptor ? "get" : "set");
058 return new JsPropertyInitializer(getOrSet, function);
059 }
060 }
061
062 @NotNull
063 public static JsFunction simpleReturnFunction(@NotNull JsScope functionScope, @NotNull JsExpression returnExpression) {
064 return new JsFunction(functionScope, new JsBlock(new JsReturn(returnExpression)), "<simpleReturnFunction>");
065 }
066
067 @NotNull
068 private static JsPropertyInitializer translateExtensionFunctionAsEcma5DataDescriptor(@NotNull JsFunction function,
069 @NotNull FunctionDescriptor descriptor, @NotNull TranslationContext context) {
070 JsObjectLiteral meta = createDataDescriptor(function, descriptor.getModality().isOverridable(), false);
071 return new JsPropertyInitializer(context.getNameForDescriptor(descriptor).makeRef(), meta);
072 }
073
074 @NotNull
075 public static JsExpression translateExclForBinaryEqualLikeExpr(@NotNull JsBinaryOperation baseBinaryExpression) {
076 return new JsBinaryOperation(notOperator(baseBinaryExpression.getOperator()), baseBinaryExpression.getArg1(), baseBinaryExpression.getArg2());
077 }
078
079 public static boolean isEqualLikeOperator(@NotNull JsBinaryOperator operator) {
080 return notOperator(operator) != null;
081 }
082
083 @Nullable
084 private static JsBinaryOperator notOperator(@NotNull JsBinaryOperator operator) {
085 switch (operator) {
086 case REF_EQ:
087 return REF_NEQ;
088 case REF_NEQ:
089 return REF_EQ;
090 case EQ:
091 return NEQ;
092 case NEQ:
093 return EQ;
094 default:
095 return null;
096 }
097 }
098
099 @NotNull
100 public static JsBinaryOperation isNullCheck(@NotNull JsExpression expressionToCheck) {
101 return nullCheck(expressionToCheck, false);
102 }
103
104 @NotNull
105 public static JsBinaryOperation isNotNullCheck(@NotNull JsExpression expressionToCheck) {
106 return nullCheck(expressionToCheck, true);
107 }
108
109 @NotNull
110 public static JsBinaryOperation nullCheck(@NotNull JsExpression expressionToCheck, boolean isNegated) {
111 JsBinaryOperator operator = isNegated ? JsBinaryOperator.NEQ : JsBinaryOperator.EQ;
112 return new JsBinaryOperation(operator, expressionToCheck, JsLiteral.NULL);
113 }
114
115 @NotNull
116 public static JsConditional notNullConditional(
117 @NotNull JsExpression expression,
118 @NotNull JsExpression elseExpression,
119 @NotNull TranslationContext context
120 ) {
121 JsExpression testExpression;
122 JsExpression thenExpression;
123 if (isCacheNeeded(expression)) {
124 TemporaryConstVariable tempVar = context.getOrDeclareTemporaryConstVariable(expression);
125 testExpression = isNotNullCheck(tempVar.value());
126 thenExpression = tempVar.value();
127 }
128 else {
129 testExpression = isNotNullCheck(expression);
130 thenExpression = expression;
131 }
132
133 return new JsConditional(testExpression, thenExpression, elseExpression);
134 }
135
136 @NotNull
137 public static JsNameRef backingFieldReference(@NotNull TranslationContext context,
138 @NotNull PropertyDescriptor descriptor) {
139 JsName backingFieldName = context.getNameForDescriptor(descriptor);
140 if(!JsDescriptorUtils.isSimpleFinalProperty(descriptor)) {
141 String backingFieldMangledName;
142 if (!Visibilities.isPrivate(descriptor.getVisibility())) {
143 backingFieldMangledName = getMangledName(descriptor, getKotlinBackingFieldName(backingFieldName.getIdent()));
144 } else {
145 backingFieldMangledName = getKotlinBackingFieldName(backingFieldName.getIdent());
146 }
147 backingFieldName = context.declarePropertyOrPropertyAccessorName(descriptor, backingFieldMangledName, false);
148 }
149
150 DeclarationDescriptor containingDescriptor = descriptor.getContainingDeclaration();
151 JsExpression receiver;
152 if (containingDescriptor instanceof PackageFragmentDescriptor) {
153 // used inside package initializer
154 receiver = JsLiteral.THIS;
155 }
156 else {
157 receiver = context.getDispatchReceiver(JsDescriptorUtils.getReceiverParameterForDeclaration(containingDescriptor));
158 }
159 return new JsNameRef(backingFieldName, receiver);
160 }
161
162 @NotNull
163 public static JsExpression assignmentToBackingField(@NotNull TranslationContext context,
164 @NotNull PropertyDescriptor descriptor,
165 @NotNull JsExpression assignTo) {
166 JsNameRef backingFieldReference = backingFieldReference(context, descriptor);
167 return assignment(backingFieldReference, assignTo);
168 }
169
170 @Nullable
171 public static JsExpression translateInitializerForProperty(@NotNull JetProperty declaration,
172 @NotNull TranslationContext context) {
173 JsExpression jsInitExpression = null;
174 JetExpression initializer = declaration.getInitializer();
175 if (initializer != null) {
176 jsInitExpression = Translation.translateAsExpression(initializer, context);
177 }
178 return jsInitExpression;
179 }
180
181 @NotNull
182 public static JsExpression translateBaseExpression(@NotNull TranslationContext context,
183 @NotNull JetUnaryExpression expression) {
184 JetExpression baseExpression = PsiUtils.getBaseExpression(expression);
185 return Translation.translateAsExpression(baseExpression, context);
186 }
187
188 @NotNull
189 public static JsExpression translateLeftExpression(@NotNull TranslationContext context,
190 @NotNull JetBinaryExpression expression) {
191 return translateLeftExpression(context, expression, context.dynamicContext().jsBlock());
192 }
193
194 @NotNull
195 public static JsExpression translateLeftExpression(
196 @NotNull TranslationContext context,
197 @NotNull JetBinaryExpression expression,
198 @NotNull JsBlock block
199 ) {
200 JetExpression left = expression.getLeft();
201 assert left != null : "Binary expression should have a left expression: " + expression.getText();
202 return Translation.translateAsExpression(left, context, block);
203 }
204
205 @NotNull
206 public static JsExpression translateRightExpression(@NotNull TranslationContext context,
207 @NotNull JetBinaryExpression expression) {
208 return translateRightExpression(context, expression, context.dynamicContext().jsBlock());
209 }
210
211 @NotNull
212 public static JsExpression translateRightExpression(
213 @NotNull TranslationContext context,
214 @NotNull JetBinaryExpression expression,
215 @NotNull JsBlock block) {
216 JetExpression rightExpression = expression.getRight();
217 assert rightExpression != null : "Binary expression should have a right expression";
218 return Translation.translateAsExpression(rightExpression, context, block);
219 }
220
221 public static boolean hasCorrespondingFunctionIntrinsic(@NotNull TranslationContext context,
222 @NotNull JetOperationExpression expression) {
223 CallableDescriptor operationDescriptor = getCallableDescriptorForOperationExpression(context.bindingContext(), expression);
224
225 if (operationDescriptor == null || !(operationDescriptor instanceof FunctionDescriptor)) return true;
226
227 JetType returnType = operationDescriptor.getReturnType();
228 if (returnType != null && (KotlinBuiltIns.isChar(returnType) || KotlinBuiltIns.isLong(returnType))) return false;
229
230 if (context.intrinsics().getFunctionIntrinsic((FunctionDescriptor) operationDescriptor).exists()) return true;
231
232 return false;
233 }
234
235 @NotNull
236 public static List<JsExpression> generateInvocationArguments(@NotNull JsExpression receiver, @NotNull List<JsExpression> arguments) {
237 if (arguments.isEmpty()) {
238 return Collections.singletonList(receiver);
239 }
240
241 List<JsExpression> argumentList = new ArrayList<JsExpression>(1 + arguments.size());
242 argumentList.add(receiver);
243 argumentList.addAll(arguments);
244 return argumentList;
245 }
246
247 public static boolean isCacheNeeded(@NotNull JsExpression expression) {
248 return !(expression instanceof JsLiteral.JsValueLiteral) &&
249 !JsAstUtils.isEmptyExpression(expression) &&
250 (!(expression instanceof JsNameRef) || ((JsNameRef) expression).getQualifier() != null);
251 }
252
253 @NotNull
254 public static JsConditional sure(@NotNull JsExpression expression, @NotNull TranslationContext context) {
255 JsInvocation throwNPE = new JsInvocation(context.namer().throwNPEFunctionRef());
256 JsConditional ensureNotNull = notNullConditional(expression, throwNPE, context);
257
258 JsExpression thenExpression = ensureNotNull.getThenExpression();
259 if (thenExpression instanceof JsNameRef) {
260 JsName name = ((JsNameRef) thenExpression).getName();
261 if (name != null) {
262 // associate(cache) ensureNotNull expression to new TemporaryConstVariable with same name.
263 context.associateExpressionToLazyValue(ensureNotNull, new TemporaryConstVariable(name, ensureNotNull));
264 }
265 }
266
267 return ensureNotNull;
268 }
269
270 @NotNull
271 public static String getSuggestedNameForInnerDeclaration(TranslationContext context, DeclarationDescriptor descriptor) {
272 String suggestedName = "";
273 DeclarationDescriptor containingDeclaration = descriptor.getContainingDeclaration();
274 //noinspection ConstantConditions
275 if (containingDeclaration != null &&
276 !(containingDeclaration instanceof ClassOrPackageFragmentDescriptor) &&
277 !(containingDeclaration instanceof AnonymousFunctionDescriptor) &&
278 !(containingDeclaration instanceof ConstructorDescriptor && isAnonymousObject(containingDeclaration.getContainingDeclaration()))) {
279 suggestedName = context.getNameForDescriptor(containingDeclaration).getIdent();
280 }
281
282 if (!suggestedName.isEmpty() && !suggestedName.endsWith("$")) {
283 suggestedName += "$";
284 }
285
286 if (descriptor.getName().isSpecial()) {
287 suggestedName += "f";
288 }
289 else {
290 suggestedName += context.getNameForDescriptor(descriptor).getIdent();
291 }
292 return suggestedName;
293 }
294 }