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