001    /*
002     * Copyright 2010-2014 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.intellij.openapi.project.Project;
020    import com.intellij.openapi.util.Pair;
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.VariableDescriptor;
025    import org.jetbrains.jet.lang.diagnostics.DiagnosticFactory1;
026    import org.jetbrains.jet.lang.psi.Call;
027    import org.jetbrains.jet.lang.psi.JetExpression;
028    import org.jetbrains.jet.lang.resolve.BindingTraceContext;
029    import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall;
030    import org.jetbrains.jet.lang.resolve.calls.results.OverloadResolutionResults;
031    import org.jetbrains.jet.lang.resolve.calls.smartcasts.DataFlowInfo;
032    import org.jetbrains.jet.lang.resolve.name.Name;
033    import org.jetbrains.jet.lang.resolve.scopes.JetScope;
034    import org.jetbrains.jet.lang.resolve.scopes.receivers.ExpressionReceiver;
035    import org.jetbrains.jet.lang.resolve.scopes.receivers.TransientReceiver;
036    import org.jetbrains.jet.lang.types.JetType;
037    import org.jetbrains.jet.lang.types.TypeUtils;
038    import org.jetbrains.jet.util.slicedmap.WritableSlice;
039    
040    import javax.inject.Inject;
041    import java.util.Collections;
042    
043    import static org.jetbrains.jet.lang.diagnostics.Errors.*;
044    import static org.jetbrains.jet.lang.psi.PsiPackage.JetPsiFactory;
045    import static org.jetbrains.jet.lang.resolve.BindingContext.*;
046    import static org.jetbrains.jet.lang.types.expressions.ExpressionTypingUtils.isBoolean;
047    
048    public class ForLoopConventionsChecker {
049    
050        private Project project;
051        private ExpressionTypingServices expressionTypingServices;
052        private ExpressionTypingUtils expressionTypingUtils;
053    
054        @Inject
055        public void setProject(@NotNull Project project) {
056            this.project = project;
057        }
058    
059        @Inject
060        public void setExpressionTypingUtils(@NotNull ExpressionTypingUtils expressionTypingUtils) {
061            this.expressionTypingUtils = expressionTypingUtils;
062        }
063    
064        @Inject
065        public void setExpressionTypingServices(@NotNull ExpressionTypingServices expressionTypingServices) {
066            this.expressionTypingServices = expressionTypingServices;
067        }
068    
069        public boolean isVariableIterable(@NotNull VariableDescriptor variableDescriptor, @NotNull JetScope scope) {
070            JetExpression expression = JetPsiFactory(project).createExpression("fake");
071            ExpressionReceiver expressionReceiver = new ExpressionReceiver(expression, variableDescriptor.getType());
072            ExpressionTypingContext context = ExpressionTypingContext.newContext(
073                    expressionTypingServices,
074                    new BindingTraceContext(),
075                    scope,
076                    DataFlowInfo.EMPTY,
077                    TypeUtils.NO_EXPECTED_TYPE
078            );
079            return checkIterableConvention(expressionReceiver, context) != null;
080        }
081    
082        @Nullable
083        /*package*/ JetType checkIterableConvention(@NotNull ExpressionReceiver loopRange, ExpressionTypingContext context) {
084            JetExpression loopRangeExpression = loopRange.getExpression();
085    
086            // Make a fake call loopRange.iterator(), and try to resolve it
087            Name iterator = Name.identifier("iterator");
088            Pair<Call, OverloadResolutionResults<FunctionDescriptor>> calls =
089                    expressionTypingUtils.makeAndResolveFakeCall(loopRange, context, Collections.<JetExpression>emptyList(), iterator);
090            Call iteratorCall = calls.getFirst();
091            OverloadResolutionResults<FunctionDescriptor> iteratorResolutionResults = calls.getSecond();
092    
093            if (iteratorResolutionResults.isSuccess()) {
094                ResolvedCall<FunctionDescriptor> iteratorResolvedCall = iteratorResolutionResults.getResultingCall();
095                context.trace.record(LOOP_RANGE_ITERATOR_RESOLVED_CALL, loopRangeExpression, iteratorResolvedCall);
096    
097                FunctionDescriptor iteratorFunction = iteratorResolvedCall.getResultingDescriptor();
098                JetType iteratorType = iteratorFunction.getReturnType();
099                JetType hasNextType = checkConventionForIterator(context, loopRangeExpression, iteratorType, "hasNext",
100                                                                 HAS_NEXT_FUNCTION_AMBIGUITY, HAS_NEXT_MISSING, HAS_NEXT_FUNCTION_NONE_APPLICABLE,
101                                                                 LOOP_RANGE_HAS_NEXT_RESOLVED_CALL);
102                if (hasNextType != null && !isBoolean(hasNextType)) {
103                    context.trace.report(HAS_NEXT_FUNCTION_TYPE_MISMATCH.on(loopRangeExpression, hasNextType));
104                }
105                return checkConventionForIterator(context, loopRangeExpression, iteratorType, "next",
106                                                  NEXT_AMBIGUITY, NEXT_MISSING, NEXT_NONE_APPLICABLE,
107                                                  LOOP_RANGE_NEXT_RESOLVED_CALL);
108            }
109            else {
110                if (iteratorResolutionResults.isAmbiguity()) {
111    //                    StringBuffer stringBuffer = new StringBuffer("Method 'iterator()' is ambiguous for this expression: ");
112    //                    for (FunctionDescriptor functionDescriptor : iteratorResolutionResults.getResultingCalls()) {
113    //                        stringBuffer.append(DescriptorRenderer.FQ_NAMES_IN_TYPES.render(functionDescriptor)).append(" ");
114    //                    }
115    //                    errorMessage = stringBuffer.toString();
116                    context.trace.report(ITERATOR_AMBIGUITY.on(loopRangeExpression, iteratorResolutionResults.getResultingCalls()));
117                }
118                else {
119                    context.trace.report(ITERATOR_MISSING.on(loopRangeExpression));
120                }
121            }
122            return null;
123        }
124    
125        @Nullable
126        private JetType checkConventionForIterator(
127                @NotNull ExpressionTypingContext context,
128                @NotNull JetExpression loopRangeExpression,
129                @NotNull JetType iteratorType,
130                @NotNull String name,
131                @NotNull DiagnosticFactory1<JetExpression, JetType> ambiguity,
132                @NotNull DiagnosticFactory1<JetExpression, JetType> missing,
133                @NotNull DiagnosticFactory1<JetExpression, JetType> noneApplicable,
134                @NotNull WritableSlice<JetExpression, ResolvedCall<FunctionDescriptor>> resolvedCallKey
135        ) {
136            OverloadResolutionResults<FunctionDescriptor> nextResolutionResults = expressionTypingUtils.resolveFakeCall(
137                    context, new TransientReceiver(iteratorType), Name.identifier(name));
138            if (nextResolutionResults.isAmbiguity()) {
139                context.trace.report(ambiguity.on(loopRangeExpression, iteratorType));
140            }
141            else if (nextResolutionResults.isNothing()) {
142                context.trace.report(missing.on(loopRangeExpression, iteratorType));
143            }
144            else if (!nextResolutionResults.isSuccess()) {
145                context.trace.report(noneApplicable.on(loopRangeExpression, iteratorType));
146            }
147            else {
148                assert nextResolutionResults.isSuccess();
149                ResolvedCall<FunctionDescriptor> resolvedCall = nextResolutionResults.getResultingCall();
150                context.trace.record(resolvedCallKey, loopRangeExpression, resolvedCall);
151                return resolvedCall.getResultingDescriptor().getReturnType();
152            }
153            return null;
154        }
155    }