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    }