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.Sets;
020import com.intellij.openapi.util.Ref;
021import org.jetbrains.annotations.NotNull;
022import org.jetbrains.annotations.Nullable;
023import org.jetbrains.jet.lang.diagnostics.Errors;
024import org.jetbrains.jet.lang.psi.*;
025import org.jetbrains.jet.lang.resolve.BindingContext;
026import org.jetbrains.jet.lang.resolve.calls.autocasts.DataFlowInfo;
027import org.jetbrains.jet.lang.resolve.calls.autocasts.DataFlowValue;
028import org.jetbrains.jet.lang.resolve.calls.autocasts.DataFlowValueFactory;
029import org.jetbrains.jet.lang.resolve.scopes.WritableScope;
030import org.jetbrains.jet.lang.types.*;
031import org.jetbrains.jet.lang.types.checker.JetTypeChecker;
032import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
033
034import java.util.Collections;
035import java.util.Set;
036
037import static org.jetbrains.jet.lang.diagnostics.Errors.*;
038import static org.jetbrains.jet.lang.types.expressions.ExpressionTypingUtils.newWritableScopeImpl;
039
040public class PatternMatchingTypingVisitor extends ExpressionTypingVisitor {
041    protected PatternMatchingTypingVisitor(@NotNull ExpressionTypingInternals facade) {
042        super(facade);
043    }
044
045    @Override
046    public JetTypeInfo visitIsExpression(JetIsExpression expression, ExpressionTypingContext contextWithExpectedType) {
047        ExpressionTypingContext context = contextWithExpectedType.replaceExpectedType(TypeUtils.NO_EXPECTED_TYPE);
048        JetExpression leftHandSide = expression.getLeftHandSide();
049        JetTypeInfo typeInfo = facade.safeGetTypeInfo(leftHandSide, context.replaceScope(context.scope));
050        JetType knownType = typeInfo.getType();
051        DataFlowInfo dataFlowInfo = typeInfo.getDataFlowInfo();
052        if (expression.getTypeRef() != null) {
053            DataFlowValue dataFlowValue = DataFlowValueFactory.INSTANCE.createDataFlowValue(leftHandSide, knownType, context.trace.getBindingContext());
054            DataFlowInfo conditionInfo = checkTypeForIs(context, knownType, expression.getTypeRef(), dataFlowValue).thenInfo;
055            DataFlowInfo newDataFlowInfo = conditionInfo.and(dataFlowInfo);
056            context.trace.record(BindingContext.DATAFLOW_INFO_AFTER_CONDITION, expression, newDataFlowInfo);
057        }
058        return DataFlowUtils.checkType(KotlinBuiltIns.getInstance().getBooleanType(), expression, contextWithExpectedType, dataFlowInfo);
059    }
060
061    @Override
062    public JetTypeInfo visitWhenExpression(JetWhenExpression expression, ExpressionTypingContext context) {
063        return visitWhenExpression(expression, context, false);
064    }
065
066    public JetTypeInfo visitWhenExpression(JetWhenExpression expression, ExpressionTypingContext contextWithExpectedType, boolean isStatement) {
067        ExpressionTypingContext context = contextWithExpectedType.replaceExpectedType(TypeUtils.NO_EXPECTED_TYPE);
068        // TODO :change scope according to the bound value in the when header
069        JetExpression subjectExpression = expression.getSubjectExpression();
070
071        JetType subjectType;
072        if (subjectExpression == null) {
073            subjectType = ErrorUtils.createErrorType("Unknown type");
074        }
075        else {
076            JetTypeInfo typeInfo = facade.safeGetTypeInfo(subjectExpression, context);
077            subjectType = typeInfo.getType();
078            context = context.replaceDataFlowInfo(typeInfo.getDataFlowInfo());
079        }
080        DataFlowValue subjectDataFlowValue = subjectExpression != null
081                ? DataFlowValueFactory.INSTANCE.createDataFlowValue(subjectExpression, subjectType, context.trace.getBindingContext())
082                : DataFlowValue.NULL;
083
084        // TODO : exhaustive patterns
085
086        Set<JetType> expressionTypes = Sets.newHashSet();
087        DataFlowInfo commonDataFlowInfo = null;
088        DataFlowInfo elseDataFlowInfo = context.dataFlowInfo;
089        for (JetWhenEntry whenEntry : expression.getEntries()) {
090            JetWhenCondition[] conditions = whenEntry.getConditions();
091            DataFlowInfo newDataFlowInfo;
092            WritableScope scopeToExtend;
093            if (whenEntry.isElse()) {
094                scopeToExtend = newWritableScopeImpl(context, "Scope extended in when-else entry");
095                newDataFlowInfo = elseDataFlowInfo;
096            }
097            else if (conditions.length == 1) {
098                scopeToExtend = newWritableScopeImpl(context, "Scope extended in when entry");
099                newDataFlowInfo = context.dataFlowInfo;
100                JetWhenCondition condition = conditions[0];
101                if (condition != null) {
102                    DataFlowInfos infos = checkWhenCondition(
103                            subjectExpression, subjectExpression == null,
104                            subjectType, condition,
105                            context, subjectDataFlowValue);
106                    newDataFlowInfo = infos.thenInfo;
107                    elseDataFlowInfo = elseDataFlowInfo.and(infos.elseInfo);
108                }
109            }
110            else {
111                scopeToExtend = newWritableScopeImpl(context, "pattern matching"); // We don't write to this scope
112                newDataFlowInfo = null;
113                for (JetWhenCondition condition : conditions) {
114                    DataFlowInfos infos = checkWhenCondition(subjectExpression, subjectExpression == null, subjectType, condition,
115                                                             context, subjectDataFlowValue);
116                    if (newDataFlowInfo == null) {
117                        newDataFlowInfo = infos.thenInfo;
118                    }
119                    else {
120                        newDataFlowInfo = newDataFlowInfo.or(infos.thenInfo);
121                    }
122                    elseDataFlowInfo = elseDataFlowInfo.and(infos.elseInfo);
123                }
124                if (newDataFlowInfo == null) {
125                    newDataFlowInfo = context.dataFlowInfo;
126                }
127            }
128            JetExpression bodyExpression = whenEntry.getExpression();
129            if (bodyExpression != null) {
130                ExpressionTypingContext newContext = contextWithExpectedType.replaceScope(scopeToExtend).replaceDataFlowInfo(newDataFlowInfo);
131                CoercionStrategy coercionStrategy = isStatement ? CoercionStrategy.COERCION_TO_UNIT : CoercionStrategy.NO_COERCION;
132                JetTypeInfo typeInfo = context.expressionTypingServices.getBlockReturnedTypeWithWritableScope(scopeToExtend, Collections.singletonList(bodyExpression), coercionStrategy, newContext, context.trace);
133                JetType type = typeInfo.getType();
134                if (type != null) {
135                    expressionTypes.add(type);
136                }
137                if (commonDataFlowInfo == null) {
138                    commonDataFlowInfo = typeInfo.getDataFlowInfo();
139                }
140                else {
141                    commonDataFlowInfo = commonDataFlowInfo.or(typeInfo.getDataFlowInfo());
142                }
143            }
144        }
145
146        if (commonDataFlowInfo == null) {
147            commonDataFlowInfo = context.dataFlowInfo;
148        }
149
150        if (!expressionTypes.isEmpty()) {
151            return DataFlowUtils.checkImplicitCast(CommonSupertypes.commonSupertype(expressionTypes), expression, contextWithExpectedType, isStatement, commonDataFlowInfo);
152        }
153        return JetTypeInfo.create(null, commonDataFlowInfo);
154    }
155
156    private DataFlowInfos checkWhenCondition(
157            @Nullable final JetExpression subjectExpression,
158            final boolean expectedCondition,
159            final JetType subjectType,
160            JetWhenCondition condition,
161            final ExpressionTypingContext context,
162            final DataFlowValue subjectDataFlowValue
163    ) {
164        final Ref<DataFlowInfos> newDataFlowInfo = new Ref<DataFlowInfos>(noChange(context));
165        condition.accept(new JetVisitorVoid() {
166            @Override
167            public void visitWhenConditionInRange(JetWhenConditionInRange condition) {
168                JetExpression rangeExpression = condition.getRangeExpression();
169                if (rangeExpression == null) return;
170                if (expectedCondition) {
171                    context.trace.report(EXPECTED_CONDITION.on(condition));
172                    DataFlowInfo dataFlowInfo = facade.getTypeInfo(rangeExpression, context).getDataFlowInfo();
173                    newDataFlowInfo.set(new DataFlowInfos(dataFlowInfo, dataFlowInfo));
174                    return;
175                }
176                JetTypeInfo typeInfo = facade.checkInExpression(condition, condition.getOperationReference(),
177                                                                subjectExpression, rangeExpression, context);
178                DataFlowInfo dataFlowInfo = typeInfo.getDataFlowInfo();
179                newDataFlowInfo.set(new DataFlowInfos(dataFlowInfo, dataFlowInfo));
180                if (!KotlinBuiltIns.getInstance().getBooleanType().equals(typeInfo.getType())) {
181                    context.trace.report(TYPE_MISMATCH_IN_RANGE.on(condition));
182                }
183            }
184
185            @Override
186            public void visitWhenConditionIsPattern(JetWhenConditionIsPattern condition) {
187                if (expectedCondition) {
188                    context.trace.report(EXPECTED_CONDITION.on(condition));
189                }
190                if (condition.getTypeRef() != null) {
191                    DataFlowInfos result = checkTypeForIs(context, subjectType, condition.getTypeRef(), subjectDataFlowValue);
192                    if (condition.isNegated()) {
193                        newDataFlowInfo.set(new DataFlowInfos(result.elseInfo, result.thenInfo));
194                    }
195                    else {
196                        newDataFlowInfo.set(result);
197                    }
198                }
199            }
200
201            @Override
202            public void visitWhenConditionWithExpression(JetWhenConditionWithExpression condition) {
203                JetExpression expression = condition.getExpression();
204                if (expression != null) {
205                    newDataFlowInfo.set(checkTypeForExpressionCondition(context, expression, subjectType, subjectExpression == null,
206                                                                        subjectDataFlowValue));
207                }
208            }
209
210            @Override
211            public void visitJetElement(JetElement element) {
212                context.trace.report(UNSUPPORTED.on(element, getClass().getCanonicalName()));
213            }
214        });
215        return newDataFlowInfo.get();
216    }
217
218    private static class DataFlowInfos {
219        private final DataFlowInfo thenInfo;
220        private final DataFlowInfo elseInfo;
221
222        private DataFlowInfos(DataFlowInfo thenInfo, DataFlowInfo elseInfo) {
223            this.thenInfo = thenInfo;
224            this.elseInfo = elseInfo;
225        }
226    }
227
228    private DataFlowInfos checkTypeForExpressionCondition(
229            ExpressionTypingContext context,
230            JetExpression expression,
231            JetType subjectType,
232            boolean conditionExpected,
233            DataFlowValue subjectDataFlowValue
234    ) {
235        if (expression == null) {
236            return noChange(context);
237        }
238        JetTypeInfo typeInfo = facade.getTypeInfo(expression, context);
239        JetType type = typeInfo.getType();
240        if (type == null) {
241            return noChange(context);
242        }
243        context = context.replaceDataFlowInfo(typeInfo.getDataFlowInfo());
244        if (conditionExpected) {
245            JetType booleanType = KotlinBuiltIns.getInstance().getBooleanType();
246            if (!JetTypeChecker.INSTANCE.equalTypes(booleanType, type)) {
247                context.trace.report(TYPE_MISMATCH_IN_CONDITION.on(expression, type));
248            }
249            else {
250                DataFlowInfo ifInfo = DataFlowUtils.extractDataFlowInfoFromCondition(expression, true, context);
251                DataFlowInfo elseInfo = DataFlowUtils.extractDataFlowInfoFromCondition(expression, false, context);
252                return new DataFlowInfos(ifInfo, elseInfo);
253            }
254            return noChange(context);
255        }
256        checkTypeCompatibility(context, type, subjectType, expression);
257        DataFlowValue expressionDataFlowValue =
258                DataFlowValueFactory.INSTANCE.createDataFlowValue(expression, type, context.trace.getBindingContext());
259        DataFlowInfos result = noChange(context);
260        result = new DataFlowInfos(
261                result.thenInfo.equate(subjectDataFlowValue, expressionDataFlowValue),
262                result.elseInfo.disequate(subjectDataFlowValue, expressionDataFlowValue)
263        );
264        return result;
265    }
266
267    private static DataFlowInfos checkTypeForIs(
268            ExpressionTypingContext context,
269            JetType subjectType,
270            JetTypeReference typeReferenceAfterIs,
271            DataFlowValue subjectDataFlowValue
272    ) {
273        if (typeReferenceAfterIs == null) {
274            return noChange(context);
275        }
276        JetType type = context.expressionTypingServices.getTypeResolver().resolveType(context.scope, typeReferenceAfterIs, context.trace, true);
277        if (!subjectType.isNullable() && type.isNullable()) {
278            JetTypeElement element = typeReferenceAfterIs.getTypeElement();
279            assert element instanceof JetNullableType : "element must be instance of " + JetNullableType.class.getName();
280            JetNullableType nullableType = (JetNullableType) element;
281            context.trace.report(Errors.USELESS_NULLABLE_CHECK.on(nullableType));
282        }
283        checkTypeCompatibility(context, type, subjectType, typeReferenceAfterIs);
284        if (BasicExpressionTypingVisitor.isCastErased(subjectType, type, JetTypeChecker.INSTANCE)) {
285            context.trace.report(Errors.CANNOT_CHECK_FOR_ERASED.on(typeReferenceAfterIs, type));
286        }
287        return new DataFlowInfos(context.dataFlowInfo.establishSubtyping(subjectDataFlowValue, type), context.dataFlowInfo);
288    }
289
290    private static DataFlowInfos noChange(ExpressionTypingContext context) {
291        return new DataFlowInfos(context.dataFlowInfo, context.dataFlowInfo);
292    }
293
294    /*
295     * (a: SubjectType) is Type
296     */
297    private static void checkTypeCompatibility(
298            @NotNull ExpressionTypingContext context,
299            @Nullable JetType type,
300            @NotNull JetType subjectType,
301            @NotNull JetElement reportErrorOn
302    ) {
303        // TODO : Take auto casts into account?
304        if (type == null) {
305            return;
306        }
307        if (TypeUtils.isIntersectionEmpty(type, subjectType)) {
308            context.trace.report(INCOMPATIBLE_TYPES.on(reportErrorOn, type, subjectType));
309            return;
310        }
311
312        // check if the pattern is essentially a 'null' expression
313        if (KotlinBuiltIns.getInstance().isNullableNothing(type) && !subjectType.isNullable()) {
314            context.trace.report(SENSELESS_NULL_IN_WHEN.on(reportErrorOn));
315        }
316    }
317}