001    /*
002     * Copyright 2010-2013 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.jet.lang.resolve.calls;
018    
019    import com.intellij.psi.PsiElement;
020    import org.jetbrains.annotations.NotNull;
021    import org.jetbrains.annotations.Nullable;
022    import org.jetbrains.jet.lang.descriptors.*;
023    import org.jetbrains.jet.lang.diagnostics.Errors;
024    import org.jetbrains.jet.lang.psi.*;
025    import org.jetbrains.jet.lang.resolve.BindingContext;
026    import org.jetbrains.jet.lang.resolve.DescriptorUtils;
027    import org.jetbrains.jet.lang.resolve.calls.context.BasicCallResolutionContext;
028    import org.jetbrains.jet.lang.resolve.calls.model.DefaultValueArgument;
029    import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall;
030    import org.jetbrains.jet.lang.resolve.calls.model.ResolvedValueArgument;
031    import org.jetbrains.jet.lang.resolve.calls.model.VarargValueArgument;
032    import org.jetbrains.jet.lang.resolve.scopes.receivers.ExpressionReceiver;
033    import org.jetbrains.jet.lang.resolve.scopes.receivers.ExtensionReceiver;
034    import org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverValue;
035    import org.jetbrains.jet.lang.types.JetType;
036    import org.jetbrains.jet.lang.types.lang.InlineUtil;
037    import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
038    import org.jetbrains.jet.lexer.JetToken;
039    import org.jetbrains.jet.lexer.JetTokens;
040    
041    import java.util.HashSet;
042    import java.util.Set;
043    
044    public class InlineCallResolverExtension implements CallResolverExtension {
045    
046        private final SimpleFunctionDescriptor descriptor;
047    
048        private final Set<CallableDescriptor> inlinableParameters = new HashSet<CallableDescriptor>();
049    
050        private final boolean isEffectivelyPublicApiFunction;
051    
052        public InlineCallResolverExtension(@NotNull SimpleFunctionDescriptor descriptor) {
053            assert descriptor.isInline() : "This extension should be created only for inline functions but not " + descriptor;
054            this.descriptor = descriptor;
055            this.isEffectivelyPublicApiFunction = isEffectivelyPublicApi(descriptor);
056    
057            for (ValueParameterDescriptor param : descriptor.getValueParameters()) {
058                if (isInlinableParameter(param)) {
059                    inlinableParameters.add(param);
060                }
061            }
062    
063            //add extension receiver as inlinable
064            ReceiverParameterDescriptor receiverParameter = descriptor.getReceiverParameter();
065            if (receiverParameter != null) {
066                if (isInlinableParameter(receiverParameter)) {
067                    inlinableParameters.add(receiverParameter);
068                }
069            }
070        }
071    
072        @Override
073        public <F extends CallableDescriptor> void run(
074                @NotNull ResolvedCall<F> resolvedCall, @NotNull BasicCallResolutionContext context
075        ) {
076            JetExpression expression = context.call.getCalleeExpression();
077            if (expression == null) {
078                return;
079            }
080    
081            //checking that only invoke or inlinable extension called on function parameter
082            CallableDescriptor targetDescriptor = resolvedCall.getResultingDescriptor();
083            checkCallWithReceiver(context, targetDescriptor, resolvedCall.getThisObject(), expression);
084            checkCallWithReceiver(context, targetDescriptor, resolvedCall.getReceiverArgument(), expression);
085    
086            if (inlinableParameters.contains(targetDescriptor)) {
087                if (!couldAccessVariable(expression)) {
088                    context.trace.report(Errors.USAGE_IS_NOT_INLINABLE.on(expression, expression, descriptor));
089                }
090            }
091    
092            for (ResolvedValueArgument value : resolvedCall.getValueArguments().values()) {
093                if (!(value instanceof DefaultValueArgument)) {
094                    for (ValueArgument argument : value.getArguments()) {
095                        checkValueParameter(context, targetDescriptor, argument, value instanceof VarargValueArgument);
096                    }
097                }
098            }
099    
100            checkVisibility(targetDescriptor, expression, context);
101            checkRecursion(targetDescriptor, expression, context);
102        }
103    
104        private static boolean couldAccessVariable(JetExpression expression) {
105            PsiElement parent = expression.getParent();
106            while (parent != null) {
107                if (parent instanceof JetValueArgument ||
108                    parent instanceof JetBinaryExpression ||
109                    parent instanceof JetUnaryExpression ||
110                    parent instanceof JetDotQualifiedExpression ||
111                    parent instanceof JetCallExpression ||
112                    parent instanceof JetArrayAccessExpression ||
113                    parent instanceof JetMultiDeclaration) {
114    
115                    if (parent instanceof JetPrefixExpression) {
116                        if (JetPsiUtil.isLabeledExpression((JetPrefixExpression) parent)) {
117                            parent = parent.getParent();
118                            continue;
119                        }
120                    }
121                    else if (parent instanceof JetBinaryExpression) {
122                        JetToken token = JetPsiUtil.getOperationToken((JetOperationExpression) parent);
123                        if (token == JetTokens.EQ || token == JetTokens.ANDAND || token == JetTokens.OROR) {
124                            //assignment
125                            return false;
126                        }
127                    }
128    
129                    //check that it's in inlineable call would be in resolve call of parent
130                    return true;
131                }
132                else if (parent instanceof JetParenthesizedExpression || parent instanceof JetBinaryExpressionWithTypeRHS) {
133                    parent = parent.getParent();
134                }
135                else {
136                    return false;
137                }
138            }
139            return false;
140        }
141    
142        private void checkValueParameter(BasicCallResolutionContext context, CallableDescriptor targetDescriptor, ValueArgument argument, boolean isVararg) {
143            JetExpression jetExpression = argument.getArgumentExpression();
144            if (jetExpression == null) {
145                return;
146            }
147            CallableDescriptor varDescriptor = getDescriptor(context, jetExpression);
148    
149            if (varDescriptor != null && inlinableParameters.contains(varDescriptor)) {
150                checkFunctionCall(context, targetDescriptor, jetExpression, isVararg);
151            }
152        }
153    
154        private void checkCallWithReceiver(
155                @NotNull BasicCallResolutionContext context,
156                @NotNull CallableDescriptor targetDescriptor,
157                @NotNull ReceiverValue receiver,
158                @Nullable JetExpression expression
159        ) {
160            if (!receiver.exists()) return;
161    
162            CallableDescriptor varDescriptor = null;
163            JetExpression receiverExpression = null;
164            if (receiver instanceof ExpressionReceiver) {
165                receiverExpression = ((ExpressionReceiver) receiver).getExpression();
166                varDescriptor = getDescriptor(context, receiverExpression);
167            }
168            else if (receiver instanceof ExtensionReceiver) {
169                ExtensionReceiver extensionReceiver = (ExtensionReceiver) receiver;
170                CallableDescriptor extension = extensionReceiver.getDeclarationDescriptor();
171    
172                varDescriptor = extension.getReceiverParameter();
173                assert varDescriptor != null : "Extension should have receiverParameterDescriptor: " + extension;
174    
175                receiverExpression = expression;
176            }
177    
178            if (inlinableParameters.contains(varDescriptor)) {
179                //check that it's invoke or inlinable extension
180                checkFunctionCall(context, targetDescriptor, receiverExpression, false);
181            }
182        }
183    
184        @Nullable
185        private static CallableDescriptor getDescriptor(
186                @NotNull BasicCallResolutionContext context,
187                @NotNull JetExpression expression
188        ) {
189            ResolvedCall<? extends CallableDescriptor> thisCall = context.trace.get(BindingContext.RESOLVED_CALL, expression);
190            return thisCall != null ? thisCall.getResultingDescriptor() : null;
191        }
192    
193        private void checkFunctionCall(
194                BasicCallResolutionContext context,
195                CallableDescriptor targetDescriptor,
196                JetExpression receiverExpresssion,
197                boolean isVararg
198        ) {
199            boolean inlinableCall = isInvokeOrInlineExtension(targetDescriptor);
200            if (!inlinableCall || isVararg) {
201                context.trace.report(Errors.USAGE_IS_NOT_INLINABLE.on(receiverExpresssion, receiverExpresssion, descriptor));
202            }
203        }
204    
205        public void checkRecursion(
206                @NotNull CallableDescriptor targetDescriptor,
207                @NotNull JetElement expression,
208                @NotNull BasicCallResolutionContext context
209        ) {
210            if (targetDescriptor.getOriginal() == descriptor) {
211                context.trace.report(Errors.RECURSION_IN_INLINE.on(expression, expression, descriptor));
212            }
213        }
214    
215        private static boolean isInlinableParameter(@NotNull CallableDescriptor descriptor) {
216            JetType type = descriptor.getReturnType();
217            return type != null &&
218                   KotlinBuiltIns.getInstance().isExactFunctionOrExtensionFunctionType(type) &&
219                   !type.isNullable() &&
220                   !InlineUtil.hasNoinlineAnnotation(descriptor);
221        }
222    
223        private static boolean isInvokeOrInlineExtension(@NotNull CallableDescriptor descriptor) {
224            if (!(descriptor instanceof SimpleFunctionDescriptor)) {
225                return false;
226            }
227    
228            DeclarationDescriptor containingDeclaration = descriptor.getContainingDeclaration();
229            boolean isInvoke = descriptor.getName().asString().equals("invoke") &&
230                               containingDeclaration instanceof ClassDescriptor &&
231                               KotlinBuiltIns.getInstance().isExactFunctionOrExtensionFunctionType(((ClassDescriptor) containingDeclaration).getDefaultType());
232    
233            return isInvoke ||
234                   //or inline extension
235                   ((SimpleFunctionDescriptor) descriptor).isInline();
236        }
237    
238        private void checkVisibility(@NotNull CallableDescriptor declarationDescriptor, @NotNull JetElement expression, @NotNull BasicCallResolutionContext context){
239            if (isEffectivelyPublicApiFunction && !isEffectivelyPublicApi(declarationDescriptor) && declarationDescriptor.getVisibility() != Visibilities.LOCAL) {
240                context.trace.report(Errors.INVISIBLE_MEMBER_FROM_INLINE.on(expression, declarationDescriptor, descriptor));
241            }
242        }
243    
244        private static boolean isEffectivelyPublicApi(DeclarationDescriptorWithVisibility descriptor) {
245            DeclarationDescriptorWithVisibility parent = descriptor;
246            while (parent != null) {
247                if (!parent.getVisibility().isPublicAPI()) {
248                    return false;
249                }
250                parent = DescriptorUtils.getParentOfType(parent, DeclarationDescriptorWithVisibility.class);
251            }
252            return true;
253        }
254    }