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.types.expressions;
018    
019    import com.google.common.collect.Lists;
020    import com.intellij.openapi.project.Project;
021    import org.jetbrains.annotations.NotNull;
022    import org.jetbrains.annotations.Nullable;
023    import org.jetbrains.jet.lang.descriptors.FunctionDescriptor;
024    import org.jetbrains.jet.lang.descriptors.PropertyAccessorDescriptor;
025    import org.jetbrains.jet.lang.descriptors.PropertyDescriptor;
026    import org.jetbrains.jet.lang.descriptors.ValueParameterDescriptor;
027    import org.jetbrains.jet.lang.diagnostics.rendering.Renderers;
028    import org.jetbrains.jet.lang.psi.Call;
029    import org.jetbrains.jet.lang.psi.JetExpression;
030    import org.jetbrains.jet.lang.psi.JetReferenceExpression;
031    import org.jetbrains.jet.lang.psi.ValueArgument;
032    import org.jetbrains.jet.lang.resolve.BindingContext;
033    import org.jetbrains.jet.lang.resolve.BindingTrace;
034    import org.jetbrains.jet.lang.resolve.calls.autocasts.DataFlowInfo;
035    import org.jetbrains.jet.lang.resolve.calls.context.ExpressionPosition;
036    import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall;
037    import org.jetbrains.jet.lang.resolve.calls.results.OverloadResolutionResults;
038    import org.jetbrains.jet.lang.resolve.calls.util.CallMaker;
039    import org.jetbrains.jet.lang.resolve.name.Name;
040    import org.jetbrains.jet.lang.resolve.scopes.JetScope;
041    import org.jetbrains.jet.lang.resolve.scopes.receivers.ExpressionReceiver;
042    import org.jetbrains.jet.lang.types.DeferredType;
043    import org.jetbrains.jet.lang.types.JetType;
044    import org.jetbrains.jet.lang.types.TypeUtils;
045    import org.jetbrains.jet.lang.types.checker.JetTypeChecker;
046    import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
047    
048    import java.util.List;
049    
050    import static org.jetbrains.jet.lang.diagnostics.Errors.*;
051    import static org.jetbrains.jet.lang.psi.JetPsiFactory.createExpression;
052    import static org.jetbrains.jet.lang.psi.JetPsiFactory.createSimpleName;
053    import static org.jetbrains.jet.lang.resolve.BindingContext.*;
054    import static org.jetbrains.jet.lang.types.expressions.ExpressionTypingUtils.createFakeExpressionOfType;
055    
056    public class DelegatedPropertyUtils {
057    
058        @Nullable
059        public static JetType getDelegatedPropertyGetMethodReturnType(
060                @NotNull PropertyDescriptor propertyDescriptor,
061                @NotNull JetExpression delegateExpression,
062                @NotNull JetType delegateType,
063                @NotNull ExpressionTypingServices expressionTypingServices,
064                @NotNull BindingTrace trace,
065                @NotNull JetScope scope
066        ) {
067            resolveDelegatedPropertyConventionMethod(propertyDescriptor, delegateExpression, delegateType, expressionTypingServices, trace,
068                                                     scope, true);
069            return getDelegateGetMethodReturnType(trace.getBindingContext(), propertyDescriptor);
070        }
071    
072        @Nullable
073        private static JetType getDelegateGetMethodReturnType(@NotNull BindingContext context, @NotNull PropertyDescriptor descriptor) {
074            ResolvedCall<FunctionDescriptor> resolvedCall =
075                    context.get(DELEGATED_PROPERTY_RESOLVED_CALL, descriptor.getGetter());
076            return resolvedCall != null ? resolvedCall.getResultingDescriptor().getReturnType() : null;
077        }
078    
079        public static void resolveDelegatedPropertyGetMethod(
080                @NotNull PropertyDescriptor propertyDescriptor,
081                @NotNull JetExpression delegateExpression,
082                @NotNull JetType delegateType,
083                @NotNull ExpressionTypingServices expressionTypingServices,
084                @NotNull BindingTrace trace,
085                @NotNull JetScope scope
086        ) {
087            resolveDelegatedPropertyConventionMethod(propertyDescriptor, delegateExpression, delegateType, expressionTypingServices, trace,
088                                                     scope, true);
089            JetType returnType = getDelegateGetMethodReturnType(trace.getBindingContext(), propertyDescriptor);
090            JetType propertyType = propertyDescriptor.getType();
091    
092            /* Do not check return type of get() method of delegate for properties with DeferredType because property type is taken from it */
093            if (!(propertyType instanceof DeferredType) && returnType != null && !JetTypeChecker.INSTANCE.isSubtypeOf(returnType, propertyType)) {
094                Call call = trace.getBindingContext().get(DELEGATED_PROPERTY_CALL, propertyDescriptor.getGetter());
095                assert call != null : "Call should exists for " + propertyDescriptor.getGetter();
096                trace.report(DELEGATE_SPECIAL_FUNCTION_RETURN_TYPE_MISMATCH
097                                     .on(delegateExpression, renderCall(call, trace.getBindingContext()), propertyDescriptor.getType(), returnType));
098            }
099        }
100    
101        public static void resolveDelegatedPropertySetMethod(
102                @NotNull PropertyDescriptor propertyDescriptor,
103                @NotNull JetExpression delegateExpression,
104                @NotNull JetType delegateType,
105                @NotNull ExpressionTypingServices expressionTypingServices,
106                @NotNull BindingTrace trace,
107                @NotNull JetScope scope
108        ) {
109            resolveDelegatedPropertyConventionMethod(propertyDescriptor, delegateExpression, delegateType, expressionTypingServices, trace,
110                                                     scope, false);
111        }
112    
113        /* Resolve get() or set() methods from delegate */
114        private static void resolveDelegatedPropertyConventionMethod(
115                @NotNull PropertyDescriptor propertyDescriptor,
116                @NotNull JetExpression delegateExpression,
117                @NotNull JetType delegateType,
118                @NotNull ExpressionTypingServices expressionTypingServices,
119                @NotNull BindingTrace trace,
120                @NotNull JetScope scope,
121                boolean isGet
122        ) {
123            PropertyAccessorDescriptor accessor = isGet ? propertyDescriptor.getGetter() : propertyDescriptor.getSetter();
124            assert accessor != null : "Delegated property should have getter/setter " + propertyDescriptor + " " + delegateExpression.getText();
125    
126            if (trace.getBindingContext().get(DELEGATED_PROPERTY_CALL, accessor) != null) return;
127    
128            OverloadResolutionResults<FunctionDescriptor> functionResults = getDelegatedPropertyConventionMethod(
129                    propertyDescriptor, delegateExpression, delegateType, expressionTypingServices, trace, scope, isGet);
130            Call call = trace.getBindingContext().get(DELEGATED_PROPERTY_CALL, accessor);
131            assert call != null : "'getDelegatedPropertyConventionMethod' didn't record a call";
132    
133            if (!functionResults.isSuccess()) {
134                String expectedFunction = renderCall(call, trace.getBindingContext());
135                if (functionResults.isIncomplete()) {
136                    trace.report(DELEGATE_SPECIAL_FUNCTION_MISSING.on(delegateExpression, expectedFunction, delegateType));
137                }
138                else if (functionResults.isSingleResult() ||
139                         functionResults.getResultCode() == OverloadResolutionResults.Code.MANY_FAILED_CANDIDATES) {
140                    trace.report(DELEGATE_SPECIAL_FUNCTION_NONE_APPLICABLE
141                                                 .on(delegateExpression, expectedFunction, functionResults.getResultingCalls()));
142                }
143                else if (functionResults.isAmbiguity()) {
144                    trace.report(DELEGATE_SPECIAL_FUNCTION_AMBIGUITY
145                                                 .on(delegateExpression, expectedFunction, functionResults.getResultingCalls()));
146                }
147                else {
148                    trace.report(DELEGATE_SPECIAL_FUNCTION_MISSING.on(delegateExpression, expectedFunction, delegateType));
149                }
150                return;
151            }
152    
153            trace.record(DELEGATED_PROPERTY_RESOLVED_CALL, accessor, functionResults.getResultingCall());
154        }
155    
156        /* Resolve get() or set() methods from delegate */
157        public static OverloadResolutionResults<FunctionDescriptor> getDelegatedPropertyConventionMethod(
158                @NotNull PropertyDescriptor propertyDescriptor,
159                @NotNull JetExpression delegateExpression,
160                @NotNull JetType delegateType,
161                @NotNull ExpressionTypingServices expressionTypingServices,
162                @NotNull BindingTrace trace,
163                @NotNull JetScope scope,
164                boolean isGet
165        ) {
166            PropertyAccessorDescriptor accessor = isGet ? propertyDescriptor.getGetter() : propertyDescriptor.getSetter();
167            assert accessor != null : "Delegated property should have getter/setter " + propertyDescriptor + " " + delegateExpression.getText();
168    
169            ExpressionTypingContext context = ExpressionTypingContext.newContext(
170                    expressionTypingServices, trace, scope,
171                    DataFlowInfo.EMPTY, TypeUtils.NO_EXPECTED_TYPE, ExpressionPosition.FREE);
172            Project project = context.expressionTypingServices.getProject();
173    
174            boolean hasThis = propertyDescriptor.getReceiverParameter() != null || propertyDescriptor.getExpectedThisObject() != null;
175    
176            List<JetExpression> arguments = Lists.newArrayList();
177            arguments.add(createExpression(project, hasThis ? "this" : "null"));
178    
179            arguments.add(createExpression(project, KotlinBuiltIns.getInstance().getPropertyMetadataImpl().getName().asString() + "(\"" + propertyDescriptor.getName().asString() + "\")"));
180    
181            if (!isGet) {
182                JetReferenceExpression fakeArgument = (JetReferenceExpression) createFakeExpressionOfType(context.expressionTypingServices.getProject(), trace,
183                                                                                 "fakeArgument" + arguments.size(),
184                                                                                 propertyDescriptor.getType());
185                arguments.add(fakeArgument);
186                List<ValueParameterDescriptor> valueParameters = accessor.getValueParameters();
187                trace.record(REFERENCE_TARGET, fakeArgument, valueParameters.get(0));
188            }
189    
190            Name functionName = Name.identifier(isGet ? "get" : "set");
191            JetReferenceExpression fakeCalleeExpression = createSimpleName(project, functionName.asString());
192    
193            ExpressionReceiver receiver = new ExpressionReceiver(delegateExpression, delegateType);
194            Call call = CallMaker.makeCallWithExpressions(fakeCalleeExpression, receiver, null, fakeCalleeExpression, arguments, Call.CallType.DEFAULT);
195            trace.record(BindingContext.DELEGATED_PROPERTY_CALL, accessor, call);
196    
197            return context.resolveCallWithGivenName(call, fakeCalleeExpression, functionName);
198        }
199    
200        private static String renderCall(@NotNull Call call, @NotNull BindingContext context) {
201            JetExpression calleeExpression = call.getCalleeExpression();
202            assert calleeExpression != null : "CalleeExpression should exists for fake call of convention method";
203            StringBuilder builder = new StringBuilder(calleeExpression.getText());
204            builder.append("(");
205            List<JetType> argumentTypes = Lists.newArrayList();
206            for (ValueArgument argument : call.getValueArguments()) {
207                argumentTypes.add(context.get(EXPRESSION_TYPE, argument.getArgumentExpression()));
208    
209            }
210            builder.append(Renderers.RENDER_COLLECTION_OF_TYPES.render(argumentTypes));
211            builder.append(")");
212            return builder.toString();
213        }
214    
215        private DelegatedPropertyUtils() {
216        }
217    }