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