/// <summary> /// Check if the pattern is subsumed by the decisions in the decision tree, given that the input could /// (or could not) be null based on the parameter <paramref name="inputCouldBeNull"/>. If it is subsumed, /// returns an error code suitable for reporting the issue. If it is not subsumed, returns 0. /// </summary> private ErrorCode CheckSubsumed(BoundPattern pattern, DecisionTree decisionTree, bool inputCouldBeNull) { if (decisionTree.MatchIsComplete) { return(ErrorCode.ERR_PatternIsSubsumed); } switch (pattern.Kind) { case BoundKind.ConstantPattern: { var constantPattern = (BoundConstantPattern)pattern; if (constantPattern.Value.HasErrors || constantPattern.Value.ConstantValue == null) { // since this will have been reported earlier, we use ErrorCode.ERR_NoImplicitConvCast // as a flag to suppress errors in subsumption analysis. return(ErrorCode.ERR_NoImplicitConvCast); } bool isNull = constantPattern.Value.ConstantValue.IsNull; // If null inputs have been handled by previous patterns, then // the input can no longer be null. In that case a null pattern is subsumed. if (isNull && !inputCouldBeNull) { return(ErrorCode.ERR_PatternIsSubsumed); } switch (decisionTree.Kind) { case DecisionTree.DecisionKind.ByValue: { var byValue = (DecisionTree.ByValue)decisionTree; if (isNull) { return(0); // null must be handled at a type test } DecisionTree decision; if (byValue.ValueAndDecision.TryGetValue(constantPattern.Value.ConstantValue.Value, out decision)) { var error = CheckSubsumed(pattern, decision, inputCouldBeNull); if (error != 0) { return(error); } } if (byValue.Default != null) { return(CheckSubsumed(pattern, byValue.Default, inputCouldBeNull)); } return(0); } case DecisionTree.DecisionKind.ByType: { var byType = (DecisionTree.ByType)decisionTree; if (isNull) { if (byType.WhenNull != null) { var result = CheckSubsumed(pattern, byType.WhenNull, inputCouldBeNull); if (result != 0) { return(result); } } } else { foreach (var td in byType.TypeAndDecision) { var type = td.Key; var decision = td.Value; if (_conversions.ExpressionOfTypeMatchesPatternType(constantPattern.Value.Type, type) == true) { var error = CheckSubsumed(pattern, decision, false); if (error != 0) { return(error); } } } } return((byType.Default != null) ? CheckSubsumed(pattern, byType.Default, inputCouldBeNull) : 0); } case DecisionTree.DecisionKind.Guarded: { var guarded = (DecisionTree.Guarded)decisionTree; return ((guarded.Guard == null || guarded.Guard.ConstantValue == ConstantValue.True) ? ErrorCode.ERR_PatternIsSubsumed : guarded.Default == null ? 0 : CheckSubsumed(pattern, guarded.Default, inputCouldBeNull)); } default: throw ExceptionUtilities.UnexpectedValue(decisionTree.Kind); } } case BoundKind.DeclarationPattern: { var declarationPattern = (BoundDeclarationPattern)pattern; switch (decisionTree.Kind) { case DecisionTree.DecisionKind.ByValue: { // A declaration pattern is only subsumed by a value pattern if all of the values are accounted for. // For example, when switching on a bool, do we handle both true and false? // For now, we do not handle this case. Also, this provides compatibility with previous compilers. if (inputCouldBeNull) { return(0); // null could never be handled by a value decision } var byValue = (DecisionTree.ByValue)decisionTree; if (byValue.Default != null) { return(CheckSubsumed(pattern, byValue.Default, false)); } return(0); } case DecisionTree.DecisionKind.ByType: { var byType = (DecisionTree.ByType)decisionTree; if (declarationPattern.IsVar && inputCouldBeNull && (byType.WhenNull == null || CheckSubsumed(pattern, byType.WhenNull, inputCouldBeNull) == 0) && (byType.Default == null || CheckSubsumed(pattern, byType.Default, inputCouldBeNull) == 0)) { return(0); // new pattern catches null if not caught by existing WhenNull or Default } inputCouldBeNull = false; foreach (var td in byType.TypeAndDecision) { var type = td.Key; var decision = td.Value; if (_conversions.ExpressionOfTypeMatchesPatternType(declarationPattern.DeclaredType.Type.TupleUnderlyingTypeOrSelf(), type) == true) { var error = CheckSubsumed(pattern, decision, inputCouldBeNull); if (error != 0) { return(error); } } } if (byType.Default != null) { return(CheckSubsumed(pattern, byType.Default, inputCouldBeNull)); } return(0); } case DecisionTree.DecisionKind.Guarded: { var guarded = (DecisionTree.Guarded)decisionTree; return((guarded.Guard == null || guarded.Guard.ConstantValue == ConstantValue.True) ? ErrorCode.ERR_PatternIsSubsumed : guarded.Default != null?CheckSubsumed(pattern, guarded.Default, inputCouldBeNull) : 0); } default: throw ExceptionUtilities.UnexpectedValue(decisionTree.Kind); } } case BoundKind.WildcardPattern: { switch (decisionTree.Kind) { case DecisionTree.DecisionKind.ByValue: return(0); // a value pattern is always considered incomplete (even bool true and false) case DecisionTree.DecisionKind.ByType: { var byType = (DecisionTree.ByType)decisionTree; if (inputCouldBeNull && (byType.WhenNull == null || CheckSubsumed(pattern, byType.WhenNull, inputCouldBeNull) == 0) && (byType.Default == null || CheckSubsumed(pattern, byType.Default, inputCouldBeNull) == 0)) { return(0); // new pattern catches null if not caught by existing WhenNull or Default } inputCouldBeNull = false; foreach (var td in byType.TypeAndDecision) { var type = td.Key; var decision = td.Value; if (_conversions.ExpressionOfTypeMatchesPatternType(decisionTree.Type, type) == true) { var error = CheckSubsumed(pattern, decision, inputCouldBeNull); if (error != 0) { return(error); } } } if (byType.Default != null) { return(CheckSubsumed(pattern, byType.Default, inputCouldBeNull)); } return(0); } case DecisionTree.DecisionKind.Guarded: { var guarded = (DecisionTree.Guarded)decisionTree; return((guarded.Guard == null || guarded.Guard.ConstantValue == ConstantValue.True) ? ErrorCode.ERR_PatternIsSubsumed : guarded.Default != null?CheckSubsumed(pattern, guarded.Default, inputCouldBeNull) : 0); } default: throw ExceptionUtilities.UnexpectedValue(decisionTree.Kind); } } default: throw ExceptionUtilities.UnexpectedValue(pattern.Kind); } }