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