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