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.CompileTimeConstant;
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            CompileTimeConstant<?> 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        }
071    
072        private static boolean hasInlineOption(@NotNull ValueParameterDescriptor descriptor, @NotNull InlineOption option) {
073            AnnotationDescriptor annotation = descriptor.getAnnotations().findAnnotation(KotlinBuiltIns.FQ_NAMES.inlineOptions);
074            if (annotation != null) {
075                CompileTimeConstant<?> argument = firstOrNull(annotation.getAllValueArguments().values());
076                if (argument instanceof ArrayValue) {
077                    for (CompileTimeConstant<?> value : ((ArrayValue) argument).getValue()) {
078                        if (value instanceof EnumValue && ((EnumValue) value).getValue().getName().asString().equals(option.name())) {
079                            return true;
080                        }
081                    }
082                }
083            }
084    
085            return false;
086        }
087    
088        public static boolean checkNonLocalReturnUsage(
089                @NotNull DeclarationDescriptor fromFunction,
090                @NotNull JetExpression startExpression,
091                @NotNull BindingTrace trace
092        ) {
093            PsiElement containingFunction = PsiTreeUtil.getParentOfType(startExpression, JetClassOrObject.class, JetDeclarationWithBody.class);
094            if (containingFunction == null) {
095                return false;
096            }
097    
098            DeclarationDescriptor containingFunctionDescriptor = trace.get(BindingContext.DECLARATION_TO_DESCRIPTOR, containingFunction);
099            if (containingFunctionDescriptor == null) {
100                return false;
101            }
102    
103            BindingContext bindingContext = trace.getBindingContext();
104    
105            while (canBeInlineArgument(containingFunction) && fromFunction != containingFunctionDescriptor) {
106                if (!isInlinedArgument((JetFunction) containingFunction, bindingContext, true)) {
107                    return false;
108                }
109    
110                containingFunctionDescriptor = getContainingClassOrFunctionDescriptor(containingFunctionDescriptor, true);
111    
112                containingFunction = containingFunctionDescriptor != null
113                                     ? DescriptorToSourceUtils.descriptorToDeclaration(containingFunctionDescriptor)
114                                     : null;
115            }
116    
117            return fromFunction == containingFunctionDescriptor;
118        }
119    
120        public static boolean isInlinedArgument(
121                @NotNull JetFunction argument,
122                @NotNull BindingContext bindingContext,
123                boolean checkNonLocalReturn
124        ) {
125            if (!canBeInlineArgument(argument)) return false;
126    
127            JetExpression call = JetPsiUtil.getParentCallIfPresent(argument);
128            if (call != null) {
129                ResolvedCall<?> resolvedCall = CallUtilPackage.getResolvedCall(call, bindingContext);
130                if (resolvedCall != null && isInline(resolvedCall.getResultingDescriptor())) {
131                    ValueArgument valueArgument = CallUtilPackage.getValueArgumentForExpression(resolvedCall.getCall(), argument);
132                    if (valueArgument != null) {
133                        ArgumentMapping mapping = resolvedCall.getArgumentMapping(valueArgument);
134                        if (mapping instanceof ArgumentMatch) {
135                            ValueParameterDescriptor parameter = ((ArgumentMatch) mapping).getValueParameter();
136                            if (isInlineLambdaParameter(parameter)) {
137                                return !checkNonLocalReturn || allowsNonLocalReturns(parameter);
138                            }
139                        }
140                    }
141                }
142            }
143            return false;
144        }
145    
146        public static boolean canBeInlineArgument(@Nullable PsiElement functionalExpression) {
147            return functionalExpression instanceof JetFunctionLiteral || functionalExpression instanceof JetNamedFunction;
148        }
149    
150        @Nullable
151        public static DeclarationDescriptor getContainingClassOrFunctionDescriptor(@NotNull DeclarationDescriptor descriptor, boolean strict) {
152            DeclarationDescriptor current = strict ? descriptor.getContainingDeclaration() : descriptor;
153            while (current != null) {
154                if (current instanceof FunctionDescriptor || current instanceof ClassDescriptor) {
155                    return current;
156                }
157                current = current.getContainingDeclaration();
158            }
159    
160            return null;
161        }
162    
163        public static boolean allowsNonLocalReturns(@NotNull CallableDescriptor lambda) {
164            if (lambda instanceof ValueParameterDescriptor) {
165                if (hasOnlyLocalReturn((ValueParameterDescriptor) lambda)) {
166                    //annotated
167                    return false;
168                }
169            }
170            return true;
171        }
172    }