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