001    /*
002     * Copyright 2010-2016 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 org.jetbrains.annotations.NotNull;
020    import org.jetbrains.annotations.Nullable;
021    import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
022    import org.jetbrains.kotlin.descriptors.*;
023    import org.jetbrains.kotlin.descriptors.impl.LocalVariableAccessorDescriptor;
024    import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor;
025    import org.jetbrains.kotlin.incremental.components.NoLookupLocation;
026    import org.jetbrains.kotlin.js.backend.ast.*;
027    import org.jetbrains.kotlin.js.backend.ast.JsBinaryOperator;
028    import org.jetbrains.kotlin.js.translate.context.Namer;
029    import org.jetbrains.kotlin.js.translate.context.TemporaryConstVariable;
030    import org.jetbrains.kotlin.js.translate.context.TranslationContext;
031    import org.jetbrains.kotlin.js.translate.expression.InlineMetadata;
032    import org.jetbrains.kotlin.js.translate.general.Translation;
033    import org.jetbrains.kotlin.js.translate.reference.ReferenceTranslator;
034    import org.jetbrains.kotlin.name.ClassId;
035    import org.jetbrains.kotlin.name.FqName;
036    import org.jetbrains.kotlin.name.Name;
037    import org.jetbrains.kotlin.psi.*;
038    import org.jetbrains.kotlin.resolve.BindingContext;
039    import org.jetbrains.kotlin.resolve.BindingContextUtils;
040    import org.jetbrains.kotlin.resolve.DescriptorUtils;
041    import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
042    import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt;
043    import org.jetbrains.kotlin.resolve.inline.InlineUtil;
044    import org.jetbrains.kotlin.serialization.deserialization.FindClassInModuleKt;
045    import org.jetbrains.kotlin.types.KotlinType;
046    import org.jetbrains.kotlin.types.TypeUtils;
047    
048    import java.util.ArrayList;
049    import java.util.List;
050    
051    import static org.jetbrains.kotlin.js.backend.ast.JsBinaryOperator.*;
052    import static org.jetbrains.kotlin.js.translate.utils.BindingUtils.getCallableDescriptorForOperationExpression;
053    import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.assignment;
054    import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.createDataDescriptor;
055    
056    public final class TranslationUtils {
057    
058        private TranslationUtils() {
059        }
060    
061        @NotNull
062        public static JsPropertyInitializer translateFunctionAsEcma5PropertyDescriptor(@NotNull JsFunction function,
063                @NotNull FunctionDescriptor descriptor,
064                @NotNull TranslationContext context) {
065            JsExpression functionExpression = function;
066            if (InlineUtil.isInline(descriptor)) {
067                InlineMetadata metadata = InlineMetadata.compose(function, descriptor);
068                functionExpression = metadata.getFunctionWithMetadata();
069            }
070    
071            if (DescriptorUtils.isExtension(descriptor) ||
072                descriptor instanceof PropertyAccessorDescriptor &&
073                shouldAccessViaFunctions(((PropertyAccessorDescriptor) descriptor).getCorrespondingProperty())
074            ) {
075                return translateExtensionFunctionAsEcma5DataDescriptor(functionExpression, descriptor, context);
076            }
077            else {
078                JsStringLiteral getOrSet = context.program().getStringLiteral(getAccessorFunctionName(descriptor));
079                return new JsPropertyInitializer(getOrSet, functionExpression);
080            }
081        }
082    
083        @NotNull
084        public static String getAccessorFunctionName(@NotNull FunctionDescriptor descriptor) {
085            boolean isGetter = descriptor instanceof PropertyGetterDescriptor || descriptor instanceof LocalVariableAccessorDescriptor.Getter;
086            return isGetter ? "get" : "set";
087        }
088    
089        @NotNull
090        public static JsFunction simpleReturnFunction(@NotNull JsScope functionScope, @NotNull JsExpression returnExpression) {
091            return new JsFunction(functionScope, new JsBlock(new JsReturn(returnExpression)), "<simpleReturnFunction>");
092        }
093    
094        @NotNull
095        private static JsPropertyInitializer translateExtensionFunctionAsEcma5DataDescriptor(@NotNull JsExpression functionExpression,
096                @NotNull FunctionDescriptor descriptor, @NotNull TranslationContext context) {
097            JsObjectLiteral meta = createDataDescriptor(functionExpression, ModalityKt.isOverridable(descriptor), false);
098            return new JsPropertyInitializer(context.getNameForDescriptor(descriptor).makeRef(), meta);
099        }
100    
101        @NotNull
102        public static JsExpression translateExclForBinaryEqualLikeExpr(@NotNull JsBinaryOperation baseBinaryExpression) {
103            JsBinaryOperator negatedOperator = notOperator(baseBinaryExpression.getOperator());
104            assert negatedOperator != null : "Can't negate operator: " + baseBinaryExpression.getOperator();
105            return new JsBinaryOperation(negatedOperator, baseBinaryExpression.getArg1(), baseBinaryExpression.getArg2());
106        }
107    
108        public static boolean isEqualLikeOperator(@NotNull JsBinaryOperator operator) {
109            return notOperator(operator) != null;
110        }
111    
112        @Nullable
113        private static JsBinaryOperator notOperator(@NotNull JsBinaryOperator operator) {
114            switch (operator) {
115                case REF_EQ:
116                    return REF_NEQ;
117                case REF_NEQ:
118                    return REF_EQ;
119                case EQ:
120                    return NEQ;
121                case NEQ:
122                    return EQ;
123                default:
124                    return null;
125            }
126        }
127    
128        @NotNull
129        public static JsBinaryOperation isNullCheck(@NotNull JsExpression expressionToCheck) {
130            return nullCheck(expressionToCheck, false);
131        }
132    
133        @NotNull
134        private static JsBinaryOperation isNotNullCheck(@NotNull JsExpression expressionToCheck) {
135            return nullCheck(expressionToCheck, true);
136        }
137    
138        @NotNull
139        public static JsBinaryOperation nullCheck(@NotNull JsExpression expressionToCheck, boolean isNegated) {
140            JsBinaryOperator operator = isNegated ? JsBinaryOperator.NEQ : JsBinaryOperator.EQ;
141            return new JsBinaryOperation(operator, expressionToCheck, JsLiteral.NULL);
142        }
143    
144        @NotNull
145        public static JsConditional notNullConditional(
146                @NotNull JsExpression expression,
147                @NotNull JsExpression elseExpression,
148                @NotNull TranslationContext context
149        ) {
150            JsExpression testExpression;
151            JsExpression thenExpression;
152            if (isCacheNeeded(expression)) {
153                TemporaryConstVariable tempVar = context.getOrDeclareTemporaryConstVariable(expression);
154                testExpression = isNotNullCheck(tempVar.value());
155                thenExpression = tempVar.value();
156            }
157            else {
158                testExpression = isNotNullCheck(expression);
159                thenExpression = expression;
160            }
161    
162            return new JsConditional(testExpression, thenExpression, elseExpression);
163        }
164    
165        @NotNull
166        public static JsNameRef backingFieldReference(@NotNull TranslationContext context, @NotNull PropertyDescriptor descriptor) {
167            DeclarationDescriptor containingDescriptor = descriptor.getContainingDeclaration();
168            JsName backingFieldName = containingDescriptor instanceof PackageFragmentDescriptor ?
169                                      context.getInnerNameForDescriptor(descriptor) :
170                                      context.getNameForDescriptor(descriptor);
171    
172            if (!JsDescriptorUtils.isSimpleFinalProperty(descriptor) && !(containingDescriptor instanceof PackageFragmentDescriptor)) {
173                backingFieldName = context.getNameForBackingField(descriptor);
174            }
175    
176            JsExpression receiver;
177            if (containingDescriptor instanceof PackageFragmentDescriptor) {
178                receiver = null;
179            }
180            else {
181                receiver = context.getDispatchReceiver(JsDescriptorUtils.getReceiverParameterForDeclaration(containingDescriptor));
182            }
183            return new JsNameRef(backingFieldName, receiver);
184        }
185    
186        @NotNull
187        public static JsExpression assignmentToBackingField(@NotNull TranslationContext context,
188                @NotNull PropertyDescriptor descriptor,
189                @NotNull JsExpression assignTo) {
190            JsNameRef backingFieldReference = backingFieldReference(context, descriptor);
191            return assignment(backingFieldReference, assignTo);
192        }
193    
194        @Nullable
195        public static JsExpression translateInitializerForProperty(@NotNull KtProperty declaration,
196                @NotNull TranslationContext context) {
197            JsExpression jsInitExpression = null;
198            KtExpression initializer = declaration.getInitializer();
199            if (initializer != null) {
200                jsInitExpression = Translation.translateAsExpression(initializer, context);
201    
202                KotlinType propertyType = BindingContextUtils.getNotNull(context.bindingContext(), BindingContext.VARIABLE, declaration).getType();
203                KotlinType initType = context.bindingContext().getType(initializer);
204    
205                if (initType != null && KotlinBuiltIns.isCharOrNullableChar(initType) && !KotlinBuiltIns.isCharOrNullableChar(propertyType)) {
206                    jsInitExpression = JsAstUtils.charToBoxedChar(jsInitExpression);
207                }
208            }
209            return jsInitExpression;
210        }
211    
212        @NotNull
213        public static JsExpression translateBaseExpression(@NotNull TranslationContext context,
214                @NotNull KtUnaryExpression expression) {
215            KtExpression baseExpression = PsiUtils.getBaseExpression(expression);
216            return Translation.translateAsExpression(baseExpression, context);
217        }
218    
219        @NotNull
220        public static JsExpression translateLeftExpression(
221                @NotNull TranslationContext context,
222                @NotNull KtBinaryExpression expression,
223                @NotNull JsBlock block
224        ) {
225            KtExpression left = expression.getLeft();
226            assert left != null : "Binary expression should have a left expression: " + expression.getText();
227            return Translation.translateAsExpression(left, context, block);
228        }
229    
230        @NotNull
231        public static JsExpression translateRightExpression(@NotNull TranslationContext context,
232                @NotNull KtBinaryExpression expression) {
233            return translateRightExpression(context, expression, context.dynamicContext().jsBlock());
234        }
235    
236        @NotNull
237        public static JsExpression translateRightExpression(
238                @NotNull TranslationContext context,
239                @NotNull KtBinaryExpression expression,
240                @NotNull JsBlock block) {
241            KtExpression rightExpression = expression.getRight();
242            assert rightExpression != null : "Binary expression should have a right expression";
243            return Translation.translateAsExpression(rightExpression, context, block);
244        }
245    
246        public static boolean hasCorrespondingFunctionIntrinsic(@NotNull TranslationContext context,
247                @NotNull KtOperationExpression expression) {
248            CallableDescriptor operationDescriptor = getCallableDescriptorForOperationExpression(context.bindingContext(), expression);
249    
250            if (operationDescriptor == null || !(operationDescriptor instanceof FunctionDescriptor)) return true;
251    
252            KotlinType returnType = operationDescriptor.getReturnType();
253            if (returnType != null &&
254                (KotlinBuiltIns.isChar(returnType) || KotlinBuiltIns.isLong(returnType) || KotlinBuiltIns.isInt(returnType))) {
255                return false;
256            }
257    
258            if (context.intrinsics().getFunctionIntrinsic((FunctionDescriptor) operationDescriptor).exists()) return true;
259    
260            return false;
261        }
262    
263        @NotNull
264        public static List<JsExpression> generateInvocationArguments(
265                @NotNull JsExpression receiver,
266                @NotNull List<? extends JsExpression> arguments
267        ) {
268            List<JsExpression> argumentList = new ArrayList<JsExpression>(1 + arguments.size());
269            argumentList.add(receiver);
270            argumentList.addAll(arguments);
271            return argumentList;
272        }
273    
274        public static boolean isCacheNeeded(@NotNull JsExpression expression) {
275            if (expression instanceof JsLiteral.JsValueLiteral) return false;
276            if (expression instanceof JsNameRef && ((JsNameRef) expression).getQualifier() == null) return false;
277            if (expression instanceof JsBinaryOperation) {
278                JsBinaryOperation operation = (JsBinaryOperation) expression;
279                JsBinaryOperator operator = operation.getOperator();
280                if (operator.isAssignment() || operator == COMMA) return true;
281                return isCacheNeeded(operation.getArg1()) || isCacheNeeded(operation.getArg2());
282            }
283            if (expression instanceof JsUnaryOperation) {
284                JsUnaryOperation operation = (JsUnaryOperation) expression;
285                JsUnaryOperator operator = operation.getOperator();
286                switch (operator) {
287                    case BIT_NOT:
288                    case NEG:
289                    case POS:
290                    case NOT:
291                    case TYPEOF:
292                    case VOID:
293                        return isCacheNeeded(operation.getArg());
294                    default:
295                        return true;
296                }
297            }
298    
299            return true;
300        }
301    
302        @NotNull
303        public static JsConditional sure(@NotNull JsExpression expression, @NotNull TranslationContext context) {
304            JsInvocation throwNPE = new JsInvocation(Namer.throwNPEFunctionRef());
305            JsConditional ensureNotNull = notNullConditional(expression, throwNPE, context);
306    
307            JsExpression thenExpression = ensureNotNull.getThenExpression();
308            if (thenExpression instanceof JsNameRef) {
309                JsName name = ((JsNameRef) thenExpression).getName();
310                if (name != null) {
311                    // associate(cache) ensureNotNull expression to new TemporaryConstVariable with same name.
312                    context.associateExpressionToLazyValue(ensureNotNull, new TemporaryConstVariable(name, ensureNotNull));
313                }
314            }
315    
316            return ensureNotNull;
317        }
318    
319        public static boolean isSimpleNameExpressionNotDelegatedLocalVar(@Nullable KtExpression expression, @NotNull TranslationContext context) {
320            if (!(expression instanceof KtSimpleNameExpression)) {
321                return false;
322            }
323            DeclarationDescriptor descriptor = context.bindingContext().get(BindingContext.REFERENCE_TARGET, ((KtSimpleNameExpression) expression));
324            return !((descriptor instanceof LocalVariableDescriptor) && ((LocalVariableDescriptor) descriptor).isDelegated()) &&
325                    !((descriptor instanceof PropertyDescriptor) && propertyAccessedByFunctionsInternally((PropertyDescriptor) descriptor, context));
326        }
327    
328        private static boolean propertyAccessedByFunctionsInternally(@NotNull PropertyDescriptor p, @NotNull TranslationContext context) {
329            return !JsDescriptorUtils.isSimpleFinalProperty(p) && context.isFromCurrentModule(p) || shouldAccessViaFunctions(p);
330        }
331    
332        public static boolean shouldAccessViaFunctions(@NotNull CallableDescriptor descriptor) {
333            if (descriptor instanceof PropertyDescriptor) {
334                return shouldAccessViaFunctions((PropertyDescriptor) descriptor);
335            }
336            else if (descriptor instanceof PropertyAccessorDescriptor) {
337                return shouldAccessViaFunctions(((PropertyAccessorDescriptor) descriptor).getCorrespondingProperty());
338            }
339            else {
340                return false;
341            }
342        }
343    
344        private static boolean shouldAccessViaFunctions(@NotNull PropertyDescriptor property) {
345            if (AnnotationsUtils.hasJsNameInAccessors(property)) return true;
346            for (PropertyDescriptor overriddenProperty : property.getOverriddenDescriptors()) {
347                if (shouldAccessViaFunctions(overriddenProperty)) return true;
348            }
349            return false;
350        }
351    
352        @NotNull
353        public static JsExpression translateContinuationArgument(@NotNull TranslationContext context, @NotNull ResolvedCall<?> resolvedCall) {
354            CallableDescriptor continuationDescriptor =
355                    context.bindingContext().get(BindingContext.ENCLOSING_SUSPEND_FUNCTION_FOR_SUSPEND_FUNCTION_CALL, resolvedCall.getCall());
356    
357            if (continuationDescriptor == null) {
358                continuationDescriptor = getEnclosingContinuationParameter(context);
359            }
360    
361            return ReferenceTranslator.translateAsValueReference(continuationDescriptor, context);
362        }
363    
364        @NotNull
365        public static VariableDescriptor getEnclosingContinuationParameter(@NotNull TranslationContext context) {
366            VariableDescriptor result = context.getContinuationParameterDescriptor();
367            if (result == null) {
368                assert context.getParent() != null;
369                result = getEnclosingContinuationParameter(context.getParent());
370            }
371            return result;
372        }
373    
374        @NotNull
375        public static ClassDescriptor getCoroutineBaseClass(@NotNull TranslationContext context) {
376            FqName className = DescriptorUtils.COROUTINES_PACKAGE_FQ_NAME.child(Name.identifier("CoroutineImpl"));
377            ClassDescriptor descriptor = FindClassInModuleKt.findClassAcrossModuleDependencies(
378                    context.getCurrentModule(), ClassId.topLevel(className));
379            assert descriptor != null;
380            return descriptor;
381        }
382    
383        @NotNull
384        public static PropertyDescriptor getCoroutineProperty(@NotNull TranslationContext context, @NotNull String name) {
385            return getCoroutineBaseClass(context).getUnsubstitutedMemberScope()
386                    .getContributedVariables(Name.identifier(name), NoLookupLocation.FROM_DESERIALIZATION)
387                    .iterator().next();
388        }
389    
390    
391        @NotNull
392        public static FunctionDescriptor getCoroutineDoResumeFunction(@NotNull TranslationContext context) {
393            return getCoroutineBaseClass(context).getUnsubstitutedMemberScope()
394                    .getContributedFunctions(Name.identifier("doResume"), NoLookupLocation.FROM_DESERIALIZATION)
395                    .iterator().next();
396        }
397    
398        @NotNull
399        public static FunctionDescriptor getCoroutineResumeFunction(@NotNull TranslationContext context) {
400            return getCoroutineBaseClass(context).getUnsubstitutedMemberScope()
401                    .getContributedFunctions(Name.identifier("resume"), NoLookupLocation.FROM_DESERIALIZATION)
402                    .iterator().next();
403        }
404    
405        public static boolean isOverridableFunctionWithDefaultParameters(@NotNull FunctionDescriptor descriptor) {
406            return DescriptorUtilsKt.hasOrInheritsParametersWithDefaultValue(descriptor) &&
407                   !(descriptor instanceof ConstructorDescriptor) &&
408                   descriptor.getContainingDeclaration() instanceof ClassDescriptor &&
409                   ModalityKt.isOverridable(descriptor);
410        }
411    
412        private static boolean overridesReturnAny(CallableDescriptor c) {
413            KotlinType returnType = c.getOriginal().getReturnType();
414            assert returnType != null;
415            if (KotlinBuiltIns.isAnyOrNullableAny(returnType) || TypeUtils.isTypeParameter(returnType)) return true;
416            for (CallableDescriptor o : c.getOverriddenDescriptors()) {
417                if (overridesReturnAny(o)) return true;
418            }
419            return false;
420        }
421    
422    
423        public static boolean shouldBoxReturnValue(CallableDescriptor c) {
424            return overridesReturnAny(c) || c instanceof CallableMemberDescriptor && ModalityKt.isOverridable((CallableMemberDescriptor)c);
425        }
426    }