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.descriptors.annotations.AnnotationDescriptor;
026    import org.jetbrains.kotlin.psi.*;
027    import org.jetbrains.kotlin.resolve.BindingContext;
028    import org.jetbrains.kotlin.resolve.BindingTrace;
029    import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils;
030    import org.jetbrains.kotlin.resolve.calls.callUtil.CallUtilPackage;
031    import org.jetbrains.kotlin.resolve.calls.model.ArgumentMapping;
032    import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatch;
033    import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
034    import org.jetbrains.kotlin.resolve.constants.ArrayValue;
035    import org.jetbrains.kotlin.resolve.constants.ConstantValue;
036    import org.jetbrains.kotlin.resolve.constants.EnumValue;
037    
038    import static kotlin.KotlinPackage.firstOrNull;
039    
040    public class InlineUtil {
041        public static boolean isInlineLambdaParameter(@NotNull ParameterDescriptor valueParameterOrReceiver) {
042            return !KotlinBuiltIns.isNoinline(valueParameterOrReceiver) &&
043                   KotlinBuiltIns.isExactFunctionOrExtensionFunctionType(valueParameterOrReceiver.getOriginal().getType());
044        }
045    
046        public static boolean isInline(@Nullable DeclarationDescriptor descriptor) {
047            return descriptor instanceof SimpleFunctionDescriptor && getInlineStrategy(descriptor).isInline();
048        }
049    
050        @NotNull
051        public static InlineStrategy getInlineStrategy(@NotNull DeclarationDescriptor descriptor) {
052            AnnotationDescriptor annotation = descriptor.getAnnotations().findAnnotation(KotlinBuiltIns.FQ_NAMES.inline);
053            if (annotation == null) {
054                return InlineStrategy.NOT_INLINE;
055            }
056            ConstantValue<?> argument = firstOrNull(annotation.getAllValueArguments().values());
057            if (argument == null) {
058                return InlineStrategy.AS_FUNCTION;
059            }
060            assert argument instanceof EnumValue : "Inline annotation parameter should be enum entry but was: " + argument;
061            return InlineStrategy.valueOf(((EnumValue) argument).getValue().getName().asString());
062        }
063    
064        public static boolean hasOnlyLocalContinueAndBreak(@NotNull ValueParameterDescriptor descriptor) {
065            return hasInlineOption(descriptor, InlineOption.LOCAL_CONTINUE_AND_BREAK);
066        }
067    
068        public static boolean hasOnlyLocalReturn(@NotNull ValueParameterDescriptor descriptor) {
069            return hasInlineOption(descriptor, InlineOption.ONLY_LOCAL_RETURN)
070                   || descriptor.getAnnotations().findAnnotation(KotlinBuiltIns.FQ_NAMES.crossinline) != null;
071        }
072    
073        private static boolean hasInlineOption(@NotNull ValueParameterDescriptor descriptor, @NotNull InlineOption option) {
074            AnnotationDescriptor annotation = descriptor.getAnnotations().findAnnotation(KotlinBuiltIns.FQ_NAMES.inlineOptions);
075            if (annotation != null) {
076                ConstantValue<?> argument = firstOrNull(annotation.getAllValueArguments().values());
077                if (argument instanceof ArrayValue) {
078                    for (ConstantValue<?> value : ((ArrayValue) argument).getValue()) {
079                        if (value instanceof EnumValue && ((EnumValue) value).getValue().getName().asString().equals(option.name())) {
080                            return true;
081                        }
082                    }
083                }
084            }
085    
086            return false;
087        }
088    
089        public static boolean checkNonLocalReturnUsage(
090                @NotNull DeclarationDescriptor fromFunction,
091                @NotNull JetExpression startExpression,
092                @NotNull BindingTrace trace
093        ) {
094            PsiElement containingFunction = PsiTreeUtil.getParentOfType(startExpression, JetClassOrObject.class, JetDeclarationWithBody.class);
095            if (containingFunction == null) {
096                return false;
097            }
098    
099            DeclarationDescriptor containingFunctionDescriptor = trace.get(BindingContext.DECLARATION_TO_DESCRIPTOR, containingFunction);
100            if (containingFunctionDescriptor == null) {
101                return false;
102            }
103    
104            BindingContext bindingContext = trace.getBindingContext();
105    
106            while (canBeInlineArgument(containingFunction) && fromFunction != containingFunctionDescriptor) {
107                if (!isInlinedArgument((JetFunction) containingFunction, bindingContext, true)) {
108                    return false;
109                }
110    
111                containingFunctionDescriptor = getContainingClassOrFunctionDescriptor(containingFunctionDescriptor, true);
112    
113                containingFunction = containingFunctionDescriptor != null
114                                     ? DescriptorToSourceUtils.descriptorToDeclaration(containingFunctionDescriptor)
115                                     : null;
116            }
117    
118            return fromFunction == containingFunctionDescriptor;
119        }
120    
121        public static boolean isInlinedArgument(
122                @NotNull JetFunction argument,
123                @NotNull BindingContext bindingContext,
124                boolean checkNonLocalReturn
125        ) {
126            if (!canBeInlineArgument(argument)) return false;
127    
128            JetExpression call = JetPsiUtil.getParentCallIfPresent(argument);
129            if (call != null) {
130                ResolvedCall<?> resolvedCall = CallUtilPackage.getResolvedCall(call, bindingContext);
131                if (resolvedCall != null && isInline(resolvedCall.getResultingDescriptor())) {
132                    ValueArgument valueArgument = CallUtilPackage.getValueArgumentForExpression(resolvedCall.getCall(), argument);
133                    if (valueArgument != null) {
134                        ArgumentMapping mapping = resolvedCall.getArgumentMapping(valueArgument);
135                        if (mapping instanceof ArgumentMatch) {
136                            ValueParameterDescriptor parameter = ((ArgumentMatch) mapping).getValueParameter();
137                            if (isInlineLambdaParameter(parameter)) {
138                                return !checkNonLocalReturn || allowsNonLocalReturns(parameter);
139                            }
140                        }
141                    }
142                }
143            }
144            return false;
145        }
146    
147        public static boolean canBeInlineArgument(@Nullable PsiElement functionalExpression) {
148            return functionalExpression instanceof JetFunctionLiteral || functionalExpression instanceof JetNamedFunction;
149        }
150    
151        @Nullable
152        public static DeclarationDescriptor getContainingClassOrFunctionDescriptor(@NotNull DeclarationDescriptor descriptor, boolean strict) {
153            DeclarationDescriptor current = strict ? descriptor.getContainingDeclaration() : descriptor;
154            while (current != null) {
155                if (current instanceof FunctionDescriptor || current instanceof ClassDescriptor) {
156                    return current;
157                }
158                current = current.getContainingDeclaration();
159            }
160    
161            return null;
162        }
163    
164        public static boolean allowsNonLocalReturns(@NotNull CallableDescriptor lambda) {
165            if (lambda instanceof ValueParameterDescriptor) {
166                if (hasOnlyLocalReturn((ValueParameterDescriptor) lambda)) {
167                    //annotated
168                    return false;
169                }
170            }
171            return true;
172        }
173    }