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.types.expressions.ExpressionTypingUtils.newWritableScopeImpl;
039    
040    public 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    }