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.resolve.inline;
018    
019    import com.intellij.psi.PsiElement;
020    import com.intellij.psi.util.PsiTreeUtil;
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.psi.*;
026    import org.jetbrains.kotlin.resolve.BindingContext;
027    import org.jetbrains.kotlin.resolve.BindingTrace;
028    import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils;
029    import org.jetbrains.kotlin.resolve.calls.callUtil.CallUtilKt;
030    import org.jetbrains.kotlin.resolve.calls.model.ArgumentMapping;
031    import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatch;
032    import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
033    
034    public class InlineUtil {
035        public static boolean isInlineLambdaParameter(@NotNull ParameterDescriptor valueParameterOrReceiver) {
036            return !(valueParameterOrReceiver instanceof ValueParameterDescriptor
037                     && ((ValueParameterDescriptor) valueParameterOrReceiver).isNoinline()) &&
038                   KotlinBuiltIns.isExactFunctionOrExtensionFunctionType(valueParameterOrReceiver.getOriginal().getType());
039        }
040    
041        public static boolean isInline(@Nullable DeclarationDescriptor descriptor) {
042            return descriptor instanceof SimpleFunctionDescriptor && getInlineStrategy(descriptor).isInline();
043        }
044    
045        public static boolean isInlineOrContainingInline(@Nullable DeclarationDescriptor descriptor) {
046            if (isInline(descriptor)) return true;
047            if (descriptor == null) return false;
048            return isInlineOrContainingInline(descriptor.getContainingDeclaration());
049        }
050    
051        @NotNull
052        public static InlineStrategy getInlineStrategy(@NotNull DeclarationDescriptor descriptor) {
053            if (descriptor instanceof FunctionDescriptor &&
054                ((FunctionDescriptor) descriptor).isInline()) {
055                return InlineStrategy.AS_FUNCTION;
056            }
057    
058            return InlineStrategy.NOT_INLINE;
059        }
060    
061        public static boolean checkNonLocalReturnUsage(
062                @NotNull DeclarationDescriptor fromFunction,
063                @NotNull KtExpression startExpression,
064                @NotNull BindingTrace trace
065        ) {
066            PsiElement containingFunction = PsiTreeUtil.getParentOfType(startExpression, KtClassOrObject.class, KtDeclarationWithBody.class);
067            if (containingFunction == null) {
068                return false;
069            }
070    
071            DeclarationDescriptor containingFunctionDescriptor = trace.get(BindingContext.DECLARATION_TO_DESCRIPTOR, containingFunction);
072            if (containingFunctionDescriptor == null) {
073                return false;
074            }
075    
076            BindingContext bindingContext = trace.getBindingContext();
077    
078            while (canBeInlineArgument(containingFunction) && fromFunction != containingFunctionDescriptor) {
079                if (!isInlinedArgument((KtFunction) containingFunction, bindingContext, true)) {
080                    return false;
081                }
082    
083                containingFunctionDescriptor = getContainingClassOrFunctionDescriptor(containingFunctionDescriptor, true);
084    
085                containingFunction = containingFunctionDescriptor != null
086                                     ? DescriptorToSourceUtils.descriptorToDeclaration(containingFunctionDescriptor)
087                                     : null;
088            }
089    
090            return fromFunction == containingFunctionDescriptor;
091        }
092    
093        public static boolean isInlinedArgument(
094                @NotNull KtFunction argument,
095                @NotNull BindingContext bindingContext,
096                boolean checkNonLocalReturn
097        ) {
098            ValueParameterDescriptor descriptor = getInlineArgumentDescriptor(argument, bindingContext);
099            if (descriptor != null) {
100                return !checkNonLocalReturn || allowsNonLocalReturns(descriptor);
101            }
102    
103            return false;
104        }
105    
106        @Nullable
107        public static ValueParameterDescriptor getInlineArgumentDescriptor(
108                @NotNull KtFunction argument,
109                @NotNull BindingContext bindingContext
110        ) {
111            if (!canBeInlineArgument(argument)) return null;
112    
113            KtExpression call = KtPsiUtil.getParentCallIfPresent(argument);
114            if (call == null) return null;
115    
116            ResolvedCall<?> resolvedCall = CallUtilKt.getResolvedCall(call, bindingContext);
117            if (resolvedCall == null) return null;
118    
119            CallableDescriptor descriptor = resolvedCall.getResultingDescriptor();
120            if (!isInline(descriptor) && !isArrayConstructorWithLambda(descriptor)) return null;
121    
122            ValueArgument valueArgument = CallUtilKt.getValueArgumentForExpression(resolvedCall.getCall(), argument);
123            if (valueArgument == null) return null;
124    
125            ArgumentMapping mapping = resolvedCall.getArgumentMapping(valueArgument);
126            if (!(mapping instanceof ArgumentMatch)) return null;
127    
128            ValueParameterDescriptor parameter = ((ArgumentMatch) mapping).getValueParameter();
129            return isInlineLambdaParameter(parameter) ? parameter : null;
130        }
131    
132        public static boolean canBeInlineArgument(@Nullable PsiElement functionalExpression) {
133            return functionalExpression instanceof KtFunctionLiteral || functionalExpression instanceof KtNamedFunction;
134        }
135    
136        /**
137         * @return true if the descriptor is the constructor of one of 9 array classes (Array&lt;T&gt;, IntArray, FloatArray, ...)
138         * which takes the size and an initializer lambda as parameters. Such constructors are marked as 'inline' but they are not loaded
139         * as such because the 'inline' flag is not stored for constructors in the binary metadata. Therefore we pretend that they are inline
140         */
141        public static boolean isArrayConstructorWithLambda(@NotNull CallableDescriptor descriptor) {
142            return descriptor.getValueParameters().size() == 2 &&
143                   descriptor instanceof ConstructorDescriptor &&
144                   KotlinBuiltIns.isArrayOrPrimitiveArray(((ConstructorDescriptor) descriptor).getContainingDeclaration());
145        }
146    
147        @Nullable
148        public static DeclarationDescriptor getContainingClassOrFunctionDescriptor(@NotNull DeclarationDescriptor descriptor, boolean strict) {
149            DeclarationDescriptor current = strict ? descriptor.getContainingDeclaration() : descriptor;
150            while (current != null) {
151                if (current instanceof FunctionDescriptor || current instanceof ClassDescriptor) {
152                    return current;
153                }
154                current = current.getContainingDeclaration();
155            }
156    
157            return null;
158        }
159    
160        public static boolean allowsNonLocalReturns(@NotNull CallableDescriptor lambda) {
161            if (lambda instanceof ValueParameterDescriptor) {
162                if (((ValueParameterDescriptor) lambda).isCrossinline()) {
163                    //annotated
164                    return false;
165                }
166            }
167            return true;
168        }
169    
170        public static boolean containsReifiedTypeParameters(@NotNull CallableDescriptor descriptor) {
171            for (TypeParameterDescriptor typeParameterDescriptor : descriptor.getTypeParameters()) {
172                if (typeParameterDescriptor.isReified()) return true;
173            }
174    
175            return false;
176        }
177    }