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
017package org.jetbrains.jet.lang.types.expressions;
018
019import com.google.common.collect.Lists;
020import com.intellij.openapi.project.Project;
021import org.jetbrains.annotations.NotNull;
022import org.jetbrains.annotations.Nullable;
023import org.jetbrains.jet.lang.descriptors.FunctionDescriptor;
024import org.jetbrains.jet.lang.descriptors.PropertyAccessorDescriptor;
025import org.jetbrains.jet.lang.descriptors.PropertyDescriptor;
026import org.jetbrains.jet.lang.descriptors.ValueParameterDescriptor;
027import org.jetbrains.jet.lang.diagnostics.rendering.Renderers;
028import org.jetbrains.jet.lang.psi.Call;
029import org.jetbrains.jet.lang.psi.JetExpression;
030import org.jetbrains.jet.lang.psi.JetReferenceExpression;
031import org.jetbrains.jet.lang.psi.ValueArgument;
032import org.jetbrains.jet.lang.resolve.BindingContext;
033import org.jetbrains.jet.lang.resolve.BindingTrace;
034import org.jetbrains.jet.lang.resolve.calls.autocasts.DataFlowInfo;
035import org.jetbrains.jet.lang.resolve.calls.context.ExpressionPosition;
036import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall;
037import org.jetbrains.jet.lang.resolve.calls.results.OverloadResolutionResults;
038import org.jetbrains.jet.lang.resolve.calls.util.CallMaker;
039import org.jetbrains.jet.lang.resolve.name.Name;
040import org.jetbrains.jet.lang.resolve.scopes.JetScope;
041import org.jetbrains.jet.lang.resolve.scopes.receivers.ExpressionReceiver;
042import org.jetbrains.jet.lang.types.DeferredType;
043import org.jetbrains.jet.lang.types.JetType;
044import org.jetbrains.jet.lang.types.TypeUtils;
045import org.jetbrains.jet.lang.types.checker.JetTypeChecker;
046import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
047
048import java.util.List;
049
050import static org.jetbrains.jet.lang.diagnostics.Errors.*;
051import static org.jetbrains.jet.lang.psi.JetPsiFactory.createExpression;
052import static org.jetbrains.jet.lang.psi.JetPsiFactory.createSimpleName;
053import static org.jetbrains.jet.lang.resolve.BindingContext.*;
054import static org.jetbrains.jet.lang.types.expressions.ExpressionTypingUtils.createFakeExpressionOfType;
055
056public 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}