internal SubsumptionDiagnosticBuilder(Symbol enclosingSymbol, Conversions conversions, BoundExpression expression) : base(enclosingSymbol, conversions) { _subsumptionTree = DecisionTree.Create(expression, expression.Type, enclosingSymbol); }
/// <summary> /// Lower the given decision tree into the given statement builder. /// </summary> public void LowerDecisionTree(BoundExpression expression, DecisionTree decisionTree, ArrayBuilder<BoundStatement> loweredDecisionTree) { var oldLoweredDecisionTree = this._loweredDecisionTree; this._loweredDecisionTree = loweredDecisionTree; LowerDecisionTree(expression, decisionTree); this._loweredDecisionTree = oldLoweredDecisionTree; }
protected DecisionTree AddToDecisionTree(DecisionTree decisionTree, SyntaxNode sectionSyntax, BoundPatternSwitchLabel label) { var pattern = label.Pattern; var guard = label.Guard; if (guard?.ConstantValue == ConstantValue.False) { return null; } switch (pattern.Kind) { case BoundKind.ConstantPattern: { var constantPattern = (BoundConstantPattern)pattern; DecisionMaker makeDecision = (e, t) => new DecisionTree.Guarded(e, t, default(ImmutableArray<KeyValuePair<BoundExpression, BoundExpression>>), sectionSyntax, guard, label); if (constantPattern.ConstantValue == ConstantValue.Null) { return AddByNull(decisionTree, makeDecision); } else { return AddByValue(decisionTree, constantPattern, makeDecision); } } case BoundKind.DeclarationPattern: { var declarationPattern = (BoundDeclarationPattern)pattern; DecisionMaker maker = (e, t) => new DecisionTree.Guarded(e, t, ImmutableArray.Create(new KeyValuePair<BoundExpression, BoundExpression>(e, declarationPattern.VariableAccess)), sectionSyntax, guard, label); if (declarationPattern.IsVar) { return Add(decisionTree, maker); } else { return AddByType(decisionTree, declarationPattern.DeclaredType.Type, maker); } } case BoundKind.WildcardPattern: // We do not yet support a wildcard pattern syntax. It is used exclusively // to model the "default:" case, which is handled specially in the caller. default: throw ExceptionUtilities.UnexpectedValue(pattern.Kind); } }
private DecisionTree LowerToDecisionTree( BoundExpression loweredExpression, BoundPatternSwitchStatement node) { var loweredDecisionTree = DecisionTree.Create(loweredExpression, loweredExpression.Type, _enclosingSymbol); BoundPatternSwitchLabel defaultLabel = null; SyntaxNode defaultSection = null; foreach (var section in node.SwitchSections) { var sectionSyntax = (SyntaxNode)section.Syntax; foreach (var label in section.SwitchLabels) { var loweredLabel = LowerSwitchLabel(label); if (loweredLabel.Syntax.Kind() == SyntaxKind.DefaultSwitchLabel) { if (defaultLabel != null) { // duplicate switch label will have been reported during initial binding. } else { defaultLabel = loweredLabel; defaultSection = sectionSyntax; } } else { Syntax = label.Syntax; AddToDecisionTree(loweredDecisionTree, sectionSyntax, loweredLabel); } } } if (defaultLabel != null && !loweredDecisionTree.MatchIsComplete) { Add(loweredDecisionTree, (e, t) => new DecisionTree.Guarded(loweredExpression, loweredExpression.Type, default(ImmutableArray <KeyValuePair <BoundExpression, BoundExpression> >), defaultSection, null, defaultLabel)); } // We discard use-site diagnostics, as they have been reported during initial binding. _useSiteDiagnostics.Clear(); return(loweredDecisionTree); }
private void AddTemps(DecisionTree decisionTree, ArrayBuilder<LocalSymbol> builder) { if (decisionTree == null) { return; } switch (decisionTree.Kind) { case DecisionTree.DecisionKind.ByType: { var byType = (DecisionTree.ByType)decisionTree; AddTemps(byType.WhenNull, builder); foreach (var td in byType.TypeAndDecision) { AddTemps(td.Value, builder); } AddTemps(byType.Default, builder); return; } case DecisionTree.DecisionKind.ByValue: { var byValue = (DecisionTree.ByValue)decisionTree; foreach (var vd in byValue.ValueAndDecision) { AddTemps(vd.Value, builder); } AddTemps(byValue.Default, builder); return; } case DecisionTree.DecisionKind.Guarded: { var guarded = (DecisionTree.Guarded)decisionTree; ComputeTemps(guarded.Default); return; } default: throw ExceptionUtilities.UnexpectedValue(decisionTree.Kind); } }
private DecisionTree AddByNull(DecisionTree decision, DecisionMaker makeDecision) { // the decision tree cannot be complete, as if that were so we would have considered this decision subsumed. Debug.Assert(!decision.MatchIsComplete); switch (decision.Kind) { case DecisionTree.DecisionKind.ByType: return(AddByNull((DecisionTree.ByType)decision, makeDecision)); case DecisionTree.DecisionKind.ByValue: throw ExceptionUtilities.Unreachable; case DecisionTree.DecisionKind.Guarded: return(AddByNull((DecisionTree.Guarded)decision, makeDecision)); default: throw ExceptionUtilities.UnexpectedValue(decision.Kind); } }
protected DecisionTree Add(DecisionTree decision, DecisionMaker makeDecision) { // the decision tree cannot be complete, otherwise we would have given a subsumption error for this case. Debug.Assert(!decision.MatchIsComplete); switch (decision.Kind) { case DecisionTree.DecisionKind.ByType: return(Add((DecisionTree.ByType)decision, makeDecision)); case DecisionTree.DecisionKind.ByValue: return(Add((DecisionTree.ByValue)decision, makeDecision)); case DecisionTree.DecisionKind.Guarded: return(Add((DecisionTree.Guarded)decision, makeDecision)); default: throw ExceptionUtilities.UnexpectedValue(decision.Kind); } }
protected DecisionTree AddToDecisionTree(DecisionTree decisionTree, SyntaxNode sectionSyntax, BoundPatternSwitchLabel label) { var pattern = label.Pattern; var guard = label.Guard; if (guard?.ConstantValue == ConstantValue.False) { return(null); } switch (pattern.Kind) { case BoundKind.ConstantPattern: { var constantPattern = (BoundConstantPattern)pattern; return(AddByValue(decisionTree, constantPattern, (e, t) => new DecisionTree.Guarded(e, t, default(ImmutableArray <KeyValuePair <BoundExpression, BoundExpression> >), sectionSyntax, guard, label))); } case BoundKind.DeclarationPattern: { var declarationPattern = (BoundDeclarationPattern)pattern; DecisionMaker maker = (e, t) => new DecisionTree.Guarded(e, t, ImmutableArray.Create(new KeyValuePair <BoundExpression, BoundExpression>(e, declarationPattern.VariableAccess)), sectionSyntax, guard, label); if (declarationPattern.IsVar) { return(Add(decisionTree, maker)); } else { return(AddByType(decisionTree, declarationPattern.DeclaredType.Type, maker)); } } case BoundKind.WildcardPattern: // We do not yet support a wildcard pattern syntax. It is used exclusively // to model the "default:" case, which is handled specially in the caller. default: throw ExceptionUtilities.UnexpectedValue(pattern.Kind); } }
private DecisionTree AddByValue(DecisionTree decision, BoundConstantPattern value, DecisionMaker makeDecision) { if (decision.MatchIsComplete) { return null; } // Even if value.ConstantValue == null, we proceed here for error recovery, so that the case label isn't // dropped on the floor. That is useful, for example to suppress unreachable code warnings on bad case labels. switch (decision.Kind) { case DecisionTree.DecisionKind.ByType: return AddByValue((DecisionTree.ByType)decision, value, makeDecision); case DecisionTree.DecisionKind.ByValue: return AddByValue((DecisionTree.ByValue)decision, value, makeDecision); case DecisionTree.DecisionKind.Guarded: return AddByValue((DecisionTree.Guarded)decision, value, makeDecision); default: throw ExceptionUtilities.UnexpectedValue(decision.Kind); } }
private DecisionTree AddByValue(DecisionTree decision, BoundConstantPattern value, DecisionMaker makeDecision) { Debug.Assert(!decision.MatchIsComplete); // otherwise we would have given a subsumption error // Even if value.ConstantValue == null, we proceed here for error recovery, so that the case label isn't // dropped on the floor. That is useful, for example to suppress unreachable code warnings on bad case labels. switch (decision.Kind) { case DecisionTree.DecisionKind.ByType: return(AddByValue((DecisionTree.ByType)decision, value, makeDecision)); case DecisionTree.DecisionKind.ByValue: return(AddByValue((DecisionTree.ByValue)decision, value, makeDecision)); case DecisionTree.DecisionKind.Guarded: return(AddByValue((DecisionTree.Guarded)decision, value, makeDecision)); default: throw ExceptionUtilities.UnexpectedValue(decision.Kind); } }
private DecisionTree AddByType(DecisionTree decision, TypeSymbol type, DecisionMaker makeDecision) { if (decision.MatchIsComplete || decision.Expression.ConstantValue?.IsNull == true) { return(null); } switch (decision.Kind) { case DecisionTree.DecisionKind.ByType: return(AddByType((DecisionTree.ByType)decision, type, makeDecision)); case DecisionTree.DecisionKind.ByValue: { var byValue = (DecisionTree.ByValue)decision; if (byValue.Default == null) { byValue.Default = makeDecision(byValue.Expression, byValue.Type); if (byValue.Default.MatchIsComplete) { byValue.MatchIsComplete = true; } return(byValue.Default); } else { Debug.Assert(byValue.Default.Type == type); return(Add(byValue.Default, makeDecision)); } } case DecisionTree.DecisionKind.Guarded: return(AddByType((DecisionTree.Guarded)decision, type, makeDecision)); default: throw ExceptionUtilities.UnexpectedValue(decision.Kind); } }
protected DecisionTree Add(DecisionTree decision, DecisionMaker makeDecision) { if (decision.MatchIsComplete) { return(null); } switch (decision.Kind) { case DecisionTree.DecisionKind.ByType: return(Add((DecisionTree.ByType)decision, makeDecision)); case DecisionTree.DecisionKind.ByValue: return(Add((DecisionTree.ByValue)decision, makeDecision)); case DecisionTree.DecisionKind.Guarded: return(Add((DecisionTree.Guarded)decision, makeDecision)); default: throw ExceptionUtilities.UnexpectedValue(decision.Kind); } }
private DecisionTree AddByValue(DecisionTree decision, BoundExpression value, DecisionMaker makeDecision, bool hasErrors) { if (decision.MatchIsComplete) { return(null); } // Even if value.ConstantValue == null, we proceed here for error recovery, so that the case label isn't // dropped on the floor. That is useful, for example to suppress unreachable code warnings on bad case labels. switch (decision.Kind) { case DecisionTree.DecisionKind.ByType: return(AddByValue((DecisionTree.ByType)decision, value, makeDecision, hasErrors)); case DecisionTree.DecisionKind.ByValue: return(AddByValue((DecisionTree.ByValue)decision, value, makeDecision, hasErrors)); case DecisionTree.DecisionKind.Guarded: return(AddByValue((DecisionTree.Guarded)decision, value, makeDecision, hasErrors)); default: throw ExceptionUtilities.UnexpectedValue(decision.Kind); } }
private void LowerDecisionTree(DecisionTree.ByValue byValue) { if (byValue.Expression.ConstantValue != null) { LowerConstantValueDecision(byValue); return; } if (byValue.ValueAndDecision.Count == 0) { LowerDecisionTree(byValue.Expression, byValue.Default); return; } if (byValue.Type.SpecialType == SpecialType.System_Boolean) { LowerBooleanSwitch(byValue); } else { LowerBasicSwitch(byValue); } }
/// <summary> /// Compute the set of temps needed for the whole decision tree. /// </summary> private ImmutableArray<LocalSymbol> ComputeTemps(DecisionTree decisionTree) { var builder = ArrayBuilder<LocalSymbol>.GetInstance(); AddTemps(decisionTree, builder); return builder.ToImmutableAndFree(); }
private void LowerDecisionTree(DecisionTree.Guarded guarded) { var sectionBuilder = this._switchSections[guarded.SectionSyntax]; var targetLabel = guarded.Label.Label; Debug.Assert(guarded.Guard?.ConstantValue != ConstantValue.False); if (guarded.Guard == null || guarded.Guard.ConstantValue == ConstantValue.True) { // unconditional Debug.Assert(guarded.Default == null); if (guarded.Bindings.IsDefaultOrEmpty) { _loweredDecisionTree.Add(_factory.Goto(targetLabel)); } else { // with bindings AddBindingsForCase(guarded.Bindings, sectionBuilder); sectionBuilder.Add(_factory.Goto(targetLabel)); } } else { AddBindingsForCase(guarded.Bindings, sectionBuilder); var guardTest = _factory.ConditionalGoto(guarded.Guard, targetLabel, true); // Only add instrumentation (such as a sequence point) if the node is not compiler-generated. if (!guarded.Guard.WasCompilerGenerated && _localRewriter.Instrument) { guardTest = _localRewriter._instrumenter.InstrumentPatternSwitchWhenClauseConditionalGotoBody(guarded.Guard, guardTest); } sectionBuilder.Add(guardTest); var guardFailed = _factory.GenerateLabel("guardFailed"); sectionBuilder.Add(_factory.Goto(guardFailed)); _loweredDecisionTree.Add(_factory.Label(guardFailed)); LowerDecisionTree(guarded.Expression, guarded.Default); } }
/// <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 || constantPattern.Value.ConstantValue.IsBad) { // 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 (ExpressionOfTypeMatchesPatternType(constantPattern.Value.Type, type, ref _useSiteDiagnostics) == 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 the pattern's type is already handled by the previous pattern // or the previous pattern handles all of the (non-null) input data... if (ExpressionOfTypeMatchesPatternType( declarationPattern.DeclaredType.Type.TupleUnderlyingTypeOrSelf(), type, ref _useSiteDiagnostics) == true || ExpressionOfTypeMatchesPatternType(byType.Type, type, ref _useSiteDiagnostics) == true) { // then we check if the pattern is subsumed by the previous decision 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 (ExpressionOfTypeMatchesPatternType(decisionTree.Type, type, ref _useSiteDiagnostics) == 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); } }
private DecisionTree AddByNull(DecisionTree.Guarded guarded, DecisionMaker makeDecision) { if (guarded.Default == null) { guarded.Default = new DecisionTree.ByType(guarded.Expression, guarded.Type, null); } var result = AddByNull(guarded.Default, makeDecision); if (guarded.Default.MatchIsComplete) { guarded.MatchIsComplete = true; } return result; }
private bool NonNullHandled(DecisionTree.ByType byType) { var inputType = byType.Type.StrippedType().TupleUnderlyingTypeOrSelf(); foreach (var td in byType.TypeAndDecision) { var type = td.Key; var decision = td.Value; if (ExpressionOfTypeMatchesPatternType(inputType, type, ref _useSiteDiagnostics) == true && decision.MatchIsComplete) { return true; } } return false; }
private DecisionTree AddByNull(DecisionTree.ByType byType, DecisionMaker makeDecision) { if (byType.WhenNull?.MatchIsComplete == true || byType.Default?.MatchIsComplete == true) { return null; } if (byType.Default != null) { try { return AddByNull(byType.Default, makeDecision); } finally { if (byType.Default.MatchIsComplete) { byType.MatchIsComplete = true; } } } DecisionTree result; if (byType.WhenNull == null) { result = byType.WhenNull = makeDecision(byType.Expression, byType.Type); } else { result = Add(byType.WhenNull, makeDecision); } if (byType.WhenNull.MatchIsComplete && NonNullHandled(byType)) { byType.MatchIsComplete = true; } return result; }
private DecisionTree AddByNull(DecisionTree decision, DecisionMaker makeDecision) { if (decision.MatchIsComplete) { return null; } switch (decision.Kind) { case DecisionTree.DecisionKind.ByType: return AddByNull((DecisionTree.ByType)decision, makeDecision); case DecisionTree.DecisionKind.ByValue: { var byValue = (DecisionTree.ByValue)decision; if (byValue.MatchIsComplete) { return null; } throw ExceptionUtilities.Unreachable; } case DecisionTree.DecisionKind.Guarded: return AddByNull((DecisionTree.Guarded)decision, makeDecision); default: throw ExceptionUtilities.UnexpectedValue(decision.Kind); } }
private DecisionTree AddByType(DecisionTree.ByType byType, TypeSymbol type, DecisionMaker makeDecision) { if (byType.Default != null) { try { return AddByType(byType.Default, type, makeDecision); } finally { if (byType.Default.MatchIsComplete) { byType.MatchIsComplete = true; } } } foreach (var kvp in byType.TypeAndDecision) { var MatchedType = kvp.Key; var Decision = kvp.Value; // See if matching Type matches this value switch (ExpressionOfTypeMatchesPatternType(type, MatchedType, ref _useSiteDiagnostics)) { case true: if (Decision.MatchIsComplete) { return null; } continue; case false: continue; case null: continue; } } var localSymbol = new SynthesizedLocal(_enclosingSymbol as MethodSymbol, type, SynthesizedLocalKind.PatternMatchingTemp, Syntax, false, RefKind.None); var expression = new BoundLocal(Syntax, localSymbol, null, type); var result = makeDecision(expression, type); Debug.Assert(result.Temp == null); result.Temp = localSymbol; byType.TypeAndDecision.Add(new KeyValuePair<TypeSymbol, DecisionTree>(type, result)); if (ExpressionOfTypeMatchesPatternType(byType.Type, type, ref _useSiteDiagnostics) == true && result.MatchIsComplete && byType.WhenNull?.MatchIsComplete == true) { byType.MatchIsComplete = true; } return result; }
private DecisionTree AddByType(DecisionTree decision, TypeSymbol type, DecisionMaker makeDecision) { if (decision.MatchIsComplete || decision.Expression.ConstantValue?.IsNull == true) { return null; } switch (decision.Kind) { case DecisionTree.DecisionKind.ByType: return AddByType((DecisionTree.ByType)decision, type, makeDecision); case DecisionTree.DecisionKind.ByValue: { var byValue = (DecisionTree.ByValue)decision; if (byValue.Default == null) { byValue.Default = makeDecision(byValue.Expression, byValue.Type); if (byValue.Default.MatchIsComplete) { byValue.MatchIsComplete = true; } return byValue.Default; } else { Debug.Assert(byValue.Default.Type == type); return Add(byValue.Default, makeDecision); } } case DecisionTree.DecisionKind.Guarded: return AddByType((DecisionTree.Guarded)decision, type, makeDecision); default: throw ExceptionUtilities.UnexpectedValue(decision.Kind); } }
private void LowerDecisionTree(DecisionTree.Guarded guarded) { var sectionBuilder = this.SwitchSections[guarded.Section]; var targetLabel = guarded.Label.Label; Debug.Assert(guarded.Guard?.ConstantValue != ConstantValue.False); if (guarded.Guard == null || guarded.Guard.ConstantValue == ConstantValue.True) { // unconditional Debug.Assert(guarded.Default == null); if (guarded.Bindings.IsDefaultOrEmpty) { _loweredDecisionTree.Add(_factory.Goto(targetLabel)); } else { // with bindings var matched = _factory.GenerateLabel("matched"); _loweredDecisionTree.Add(_factory.Goto(matched)); sectionBuilder.Add(_factory.Label(matched)); AddBindings(sectionBuilder, guarded.Bindings); sectionBuilder.Add(_factory.Goto(targetLabel)); } } else { var checkGuard = _factory.GenerateLabel("checkGuard"); _loweredDecisionTree.Add(_factory.Goto(checkGuard)); sectionBuilder.Add(_factory.Label(checkGuard)); AddBindings(sectionBuilder, guarded.Bindings); sectionBuilder.Add(_factory.ConditionalGoto(LocalRewriter.VisitExpression(guarded.Guard), targetLabel, true)); var guardFailed = _factory.GenerateLabel("guardFailed"); sectionBuilder.Add(_factory.Goto(guardFailed)); _loweredDecisionTree.Add(_factory.Label(guardFailed)); LowerDecisionTree(guarded.Expression, guarded.Default); } }
private DecisionTree AddByValue(DecisionTree.ByType byType, BoundConstantPattern value, DecisionMaker makeDecision) { if (byType.Default != null) { try { return AddByValue(byType.Default, value, makeDecision); } finally { if (byType.Default.MatchIsComplete) { byType.MatchIsComplete = true; } } } if (value.ConstantValue == ConstantValue.Null) { return byType.Expression.ConstantValue?.IsNull == false ? null : AddByNull((DecisionTree)byType, makeDecision); } foreach (var kvp in byType.TypeAndDecision) { var matchedType = kvp.Key; var decision = kvp.Value; // See if the test is already subsumed switch (ExpressionOfTypeMatchesPatternType(value.Value.Type, matchedType, ref _useSiteDiagnostics)) { case true: if (decision.MatchIsComplete) { return null; } continue; case false: case null: continue; } } DecisionTree forType = null; // Find an existing decision tree for the expression's type. Since this new test // should logically be last, we look for the last one we can piggy-back it onto. for (int i = byType.TypeAndDecision.Count - 1; i >= 0 && forType == null; i--) { var kvp = byType.TypeAndDecision[i]; var matchedType = kvp.Key; var decision = kvp.Value; if (matchedType.TupleUnderlyingTypeOrSelf() == value.Value.Type.TupleUnderlyingTypeOrSelf()) { forType = decision; break; } else if (ExpressionOfTypeMatchesPatternType(value.Value.Type, matchedType, ref _useSiteDiagnostics) != false) { break; } } if (forType == null) { var type = value.Value.Type; var localSymbol = new SynthesizedLocal(_enclosingSymbol as MethodSymbol, type, SynthesizedLocalKind.PatternMatchingTemp, Syntax, false, RefKind.None); var narrowedExpression = new BoundLocal(Syntax, localSymbol, null, type); forType = new DecisionTree.ByValue(narrowedExpression, value.Value.Type.TupleUnderlyingTypeOrSelf(), localSymbol); byType.TypeAndDecision.Add(new KeyValuePair<TypeSymbol, DecisionTree>(value.Value.Type, forType)); } return AddByValue(forType, value, makeDecision); }
protected DecisionTree Add(DecisionTree decision, DecisionMaker makeDecision) { if (decision.MatchIsComplete) { return null; } switch (decision.Kind) { case DecisionTree.DecisionKind.ByType: return Add((DecisionTree.ByType)decision, makeDecision); case DecisionTree.DecisionKind.ByValue: return Add((DecisionTree.ByValue)decision, makeDecision); case DecisionTree.DecisionKind.Guarded: return Add((DecisionTree.Guarded)decision, makeDecision); default: throw ExceptionUtilities.UnexpectedValue(decision.Kind); } }
private DecisionTree AddByValue(DecisionTree.ByType byType, BoundConstantPattern value, DecisionMaker makeDecision) { if (byType.Default != null) { try { return(AddByValue(byType.Default, value, makeDecision)); } finally { if (byType.Default.MatchIsComplete) { byType.MatchIsComplete = true; } } } if (value.ConstantValue == ConstantValue.Null) { return(byType.Expression.ConstantValue?.IsNull == false ? null : AddByNull((DecisionTree)byType, makeDecision)); } foreach (var kvp in byType.TypeAndDecision) { var matchedType = kvp.Key; var decision = kvp.Value; // See if the test is already subsumed switch (ExpressionOfTypeMatchesPatternType(value.Value.Type, matchedType, ref _useSiteDiagnostics)) { case true: if (decision.MatchIsComplete) { return(null); } continue; case false: case null: continue; } } DecisionTree forType = null; // Find an existing decision tree for the expression's type. Since this new test // should logically be last, we look for the last one we can piggy-back it onto. for (int i = byType.TypeAndDecision.Count - 1; i >= 0 && forType == null; i--) { var kvp = byType.TypeAndDecision[i]; var matchedType = kvp.Key; var decision = kvp.Value; if (matchedType.TupleUnderlyingTypeOrSelf() == value.Value.Type.TupleUnderlyingTypeOrSelf()) { forType = decision; break; } else if (ExpressionOfTypeMatchesPatternType(value.Value.Type, matchedType, ref _useSiteDiagnostics) != false) { break; } } if (forType == null) { var type = value.Value.Type; var localSymbol = new SynthesizedLocal(_enclosingSymbol as MethodSymbol, type, SynthesizedLocalKind.PatternMatchingTemp, Syntax, false, RefKind.None); var narrowedExpression = new BoundLocal(Syntax, localSymbol, null, type); forType = new DecisionTree.ByValue(narrowedExpression, value.Value.Type.TupleUnderlyingTypeOrSelf(), localSymbol); byType.TypeAndDecision.Add(new KeyValuePair <TypeSymbol, DecisionTree>(value.Value.Type, forType)); } return(AddByValue(forType, value, makeDecision)); }
private DecisionTree Add(DecisionTree.Guarded guarded, DecisionMaker makeDecision) { if (guarded.Default != null) { if (guarded.Default.MatchIsComplete) { return null; } var result = Add(guarded.Default, makeDecision); if (guarded.Default.MatchIsComplete) { guarded.MatchIsComplete = true; } return result; } else { var result = guarded.Default = makeDecision(guarded.Expression, guarded.Type); if (guarded.Default.MatchIsComplete) { guarded.MatchIsComplete = true; } return result; } }
private DecisionTree Add(DecisionTree.ByValue byValue, DecisionMaker makeDecision) { DecisionTree result; if (byValue.Default != null) { result = Add(byValue.Default, makeDecision); } else { result = byValue.Default = makeDecision(byValue.Expression, byValue.Type); } if (byValue.Default.MatchIsComplete) { byValue.MatchIsComplete = true; } return result; }
private DecisionTree Add(DecisionTree.ByType byType, DecisionMaker makeDecision) { try { if (byType.Default == null) { byType.Default = makeDecision(byType.Expression, byType.Type); return byType.Default; } else { return Add(byType.Default, makeDecision); } } finally { if (byType.Default.MatchIsComplete) { byType.MatchIsComplete = true; } } }
/// <summary> /// Lower the given decision tree into the given statement builder. /// </summary> private void LowerDecisionTree(BoundExpression expression, DecisionTree decisionTree, ArrayBuilder<BoundStatement> loweredDecisionTree) { // build a decision tree to dispatch the switch statement var oldLoweredDecisionTree = this._loweredDecisionTree; this._loweredDecisionTree = loweredDecisionTree; LowerDecisionTree(expression, decisionTree); this._loweredDecisionTree = oldLoweredDecisionTree; }
private void LowerBooleanSwitch(DecisionTree.ByValue byValue) { switch (byValue.ValueAndDecision.Count) { case 0: { // this should have been handled in the caller. throw ExceptionUtilities.Unreachable; } case 1: { DecisionTree decision; bool onBoolean = byValue.ValueAndDecision.TryGetValue(true, out decision); if (!onBoolean) { byValue.ValueAndDecision.TryGetValue(false, out decision); } Debug.Assert(decision != null); var onOther = _factory.GenerateLabel("on" + !onBoolean); _loweredDecisionTree.Add(_factory.ConditionalGoto(byValue.Expression, onOther, !onBoolean)); LowerDecisionTree(byValue.Expression, decision); // if we fall through here, that means the match was not complete and we invoke the default part _loweredDecisionTree.Add(_factory.Label(onOther)); LowerDecisionTree(byValue.Expression, byValue.Default); break; } case 2: { DecisionTree trueDecision, falseDecision; bool hasTrue = byValue.ValueAndDecision.TryGetValue(true, out trueDecision); bool hasFalse = byValue.ValueAndDecision.TryGetValue(false, out falseDecision); Debug.Assert(hasTrue && hasFalse); var tryAnother = _factory.GenerateLabel("tryAnother"); var onFalse = _factory.GenerateLabel("onFalse"); _loweredDecisionTree.Add(_factory.ConditionalGoto(byValue.Expression, onFalse, false)); LowerDecisionTree(byValue.Expression, trueDecision); _loweredDecisionTree.Add(_factory.Goto(tryAnother)); _loweredDecisionTree.Add(_factory.Label(onFalse)); LowerDecisionTree(byValue.Expression, falseDecision); _loweredDecisionTree.Add(_factory.Label(tryAnother)); // if both true and false (i.e. all values) are fully handled, there should be no default. Debug.Assert(!trueDecision.MatchIsComplete || !falseDecision.MatchIsComplete || byValue.Default == null); LowerDecisionTree(byValue.Expression, byValue.Default); break; } default: throw ExceptionUtilities.UnexpectedValue(byValue.ValueAndDecision.Count); } }
private DecisionTree AddByValue(DecisionTree.ByType byType, BoundConstantPattern value, DecisionMaker makeDecision) { if (byType.Default != null) { try { return(AddByValue(byType.Default, value, makeDecision)); } finally { if (byType.Default.MatchIsComplete) { // This code may be unreachable due to https://github.com/dotnet/roslyn/issues/16878 byType.MatchIsComplete = true; } } } if (value.ConstantValue == ConstantValue.Null) { // This should not occur, as the caller will have invoked AddByNull instead. throw ExceptionUtilities.Unreachable; } if ((object)value.Value.Type == null) { return(null); } foreach (var kvp in byType.TypeAndDecision) { var matchedType = kvp.Key; var decision = kvp.Value; // See if the test is already subsumed switch (ExpressionOfTypeMatchesPatternType(value.Value.Type, matchedType, ref _useSiteDiagnostics)) { case true: if (decision.MatchIsComplete) { return(null); } continue; case false: case null: continue; } } DecisionTree forType = null; // This new type test should logically be last. However it might be the same type as the one that is already // last. In that case we can produce better code by piggy-backing our new case on to the last decision. // Also, the last one might be a non-overlapping type, in which case we can piggy-back onto the second-last // type test. for (int i = byType.TypeAndDecision.Count - 1; i >= 0; i--) { var kvp = byType.TypeAndDecision[i]; var matchedType = kvp.Key; var decision = kvp.Value; if (matchedType.Equals(value.Value.Type, TypeCompareKind.IgnoreDynamicAndTupleNames)) { forType = decision; break; } else if (ExpressionOfTypeMatchesPatternType(value.Value.Type, matchedType, ref _useSiteDiagnostics) != false) { // because there is overlap, we cannot reuse some earlier entry break; } } // if we did not piggy-back, then create a new decision tree node for the type. if (forType == null) { var type = value.Value.Type; var localSymbol = new SynthesizedLocal(_enclosingSymbol as MethodSymbol, type, SynthesizedLocalKind.PatternMatching, Syntax, false, RefKind.None); var narrowedExpression = new BoundLocal(Syntax, localSymbol, null, type); forType = new DecisionTree.ByValue(narrowedExpression, value.Value.Type.TupleUnderlyingTypeOrSelf(), localSymbol); byType.TypeAndDecision.Add(new KeyValuePair <TypeSymbol, DecisionTree>(value.Value.Type, forType)); } return(AddByValue(forType, value, makeDecision)); }
// Visit all the branches in the decision tree private void VisitDecisionTree(DecisionTree decisionTree) { if (decisionTree == null) { return; } switch (decisionTree.Kind) { case DecisionTree.DecisionKind.ByType: { var byType = (DecisionTree.ByType)decisionTree; var inputConstant = byType.Expression.ConstantValue; if (inputConstant != null) { if (inputConstant.IsNull) { VisitDecisionTree(byType.WhenNull); } else { foreach (var kvp in byType.TypeAndDecision) { VisitDecisionTree(kvp.Value); if (kvp.Value.MatchIsComplete) { return; } } VisitDecisionTree(byType.Default); } } else { VisitDecisionTree(byType.WhenNull); foreach (var kvp in byType.TypeAndDecision) { VisitDecisionTree(kvp.Value); } VisitDecisionTree(byType.Default); } return; } case DecisionTree.DecisionKind.ByValue: { var byValue = (DecisionTree.ByValue)decisionTree; var inputConstant = byValue.Expression.ConstantValue; if (inputConstant != null) { DecisionTree onValue; if (byValue.ValueAndDecision.TryGetValue(inputConstant.Value, out onValue)) { VisitDecisionTree(onValue); if (!onValue.MatchIsComplete) { VisitDecisionTree(byValue.Default); } } else { VisitDecisionTree(byValue.Default); } } else { foreach (var kvp in byValue.ValueAndDecision) { VisitDecisionTree(kvp.Value); } VisitDecisionTree(byValue.Default); } return; } case DecisionTree.DecisionKind.Guarded: { VisitGuardedDecisionTree((DecisionTree.Guarded)decisionTree); return; } default: throw ExceptionUtilities.UnexpectedValue(decisionTree.Kind); } }
private DecisionTree AddByValue(DecisionTree.Guarded guarded, BoundExpression value, DecisionMaker makeDecision, bool hasErrors) { if (guarded.Default != null) { if (guarded.Default.MatchIsComplete) { return null; } } else { guarded.Default = new DecisionTree.ByValue(guarded.Expression, guarded.Type, null); } return AddByValue(guarded.Default, value, makeDecision, hasErrors); }
/// <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 (ExpressionOfTypeMatchesPatternType(constantPattern.Value.Type, type, ref _useSiteDiagnostics) == 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 (ExpressionOfTypeMatchesPatternType( declarationPattern.DeclaredType.Type.TupleUnderlyingTypeOrSelf(), type, ref _useSiteDiagnostics) == 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 (ExpressionOfTypeMatchesPatternType(decisionTree.Type, type, ref _useSiteDiagnostics) == 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); } }
private void LowerDecisionTree(DecisionTree.ByType byType) { var inputConstant = byType.Expression.ConstantValue; if (inputConstant != null) { if (inputConstant.IsNull) { // input is the constant null LowerDecisionTree(byType.Expression, byType.WhenNull); if (byType.WhenNull?.MatchIsComplete != true) { LowerDecisionTree(byType.Expression, byType.Default); } } else { // input is a non-null constant foreach (var kvp in byType.TypeAndDecision) { LowerDecisionTree(byType.Expression, kvp.Value); if (kvp.Value.MatchIsComplete) { return; } } LowerDecisionTree(byType.Expression, byType.Default); } } else { var defaultLabel = _factory.GenerateLabel("byTypeDefault"); // input is not a constant if (byType.Type.CanBeAssignedNull()) { // first test for null var notNullLabel = _factory.GenerateLabel("notNull"); var inputExpression = byType.Expression; var nullValue = _factory.Null(byType.Type); BoundExpression notNull = byType.Type.IsNullableType() ? LocalRewriter.RewriteNullableNullEquality(_factory.Syntax, BinaryOperatorKind.NullableNullNotEqual, byType.Expression, nullValue, _factory.SpecialType(SpecialType.System_Boolean)) : _factory.ObjectNotEqual(byType.Expression, nullValue); _loweredDecisionTree.Add(_factory.ConditionalGoto(notNull, notNullLabel, true)); LowerDecisionTree(byType.Expression, byType.WhenNull); if (byType.WhenNull?.MatchIsComplete != true) { _loweredDecisionTree.Add(_factory.Goto(defaultLabel)); } _loweredDecisionTree.Add(_factory.Label(notNullLabel)); } else { Debug.Assert(byType.WhenNull == null); } foreach (var td in byType.TypeAndDecision) { // then test for each type, sequentially var type = td.Key; var decision = td.Value; var failLabel = _factory.GenerateLabel("failedDecision"); var testAndCopy = TypeTestAndCopyToTemp(byType.Expression, decision.Expression); _loweredDecisionTree.Add(_factory.ConditionalGoto(testAndCopy, failLabel, false)); LowerDecisionTree(decision.Expression, decision); _loweredDecisionTree.Add(_factory.Label(failLabel)); } // finally, the default for when no type matches _loweredDecisionTree.Add(_factory.Label(defaultLabel)); LowerDecisionTree(byType.Expression, byType.Default); } }
private DecisionTree AddByValue(DecisionTree.ByType byType, BoundConstantPattern value, DecisionMaker makeDecision) { if (byType.Default != null) { try { return(AddByValue(byType.Default, value, makeDecision)); } finally { if (byType.Default.MatchIsComplete) { // This code may be unreachable due to https://github.com/dotnet/roslyn/issues/16878 byType.MatchIsComplete = true; } } } if (value.ConstantValue == ConstantValue.Null) { // This should not occur, as the caller will have invoked AddByNull instead. throw ExceptionUtilities.Unreachable; } if ((object)value.Value.Type == null || value.ConstantValue == null) { return(null); } foreach (var kvp in byType.TypeAndDecision) { var matchedType = kvp.Key; var decision = kvp.Value; // See if the test is already subsumed switch (ExpressionOfTypeMatchesPatternType(value.Value.Type, matchedType, ref _useSiteDiagnostics)) { case true: if (decision.MatchIsComplete) { // Subsumed case have been eliminated by semantic analysis. Debug.Assert(false); return(null); } continue; case false: case null: continue; } } DecisionTree forType = null; // This new type test should logically be last. However it might be the same type as the one that is already // last. In that case we can produce better code by piggy-backing our new case on to the last decision. // Also, the last one might be a non-overlapping type, in which case we can piggy-back onto the second-last // type test. for (int i = byType.TypeAndDecision.Count - 1; i >= 0; i--) { var kvp = byType.TypeAndDecision[i]; var matchedType = kvp.Key; var decision = kvp.Value; if (matchedType.Equals(value.Value.Type, TypeCompareKind.IgnoreDynamicAndTupleNames | TypeCompareKind.IgnoreNullableModifiersForReferenceTypes)) { forType = decision; break; } switch (ExpressionOfTypeMatchesPatternType(value.Value.Type, matchedType, ref _useSiteDiagnostics)) { case true: if (decision.MatchIsComplete) { // we should have reported this case as subsumed already. Debug.Assert(false); return(null); } else { goto case null; } case false: continue; case null: // because there is overlap, we cannot reuse some earlier entry goto noReuse; } } noReuse :; // if we did not piggy-back, then create a new decision tree node for the type. if (forType == null) { var type = value.Value.Type; if (byType.Type.Equals(type, TypeCompareKind.AllIgnoreOptions)) { // reuse the input expression when we have an equivalent type to reduce the number of generated temps forType = new DecisionTree.ByValue(byType.Expression, type.TupleUnderlyingTypeOrSelf(), null); } else { var narrowedExpression = GetBoundPatternMatchingLocal(type); forType = new DecisionTree.ByValue(narrowedExpression, type.TupleUnderlyingTypeOrSelf(), narrowedExpression.LocalSymbol); } byType.TypeAndDecision.Add(new KeyValuePair <TypeSymbol, DecisionTree>(type, forType)); } return(AddByValue(forType, value, makeDecision)); }
private void LowerConstantValueDecision(DecisionTree.ByValue byValue) { var value = byValue.Expression.ConstantValue.Value; Debug.Assert(value != null); DecisionTree onValue; if (byValue.ValueAndDecision.TryGetValue(value, out onValue)) { LowerDecisionTree(byValue.Expression, onValue); if (onValue.MatchIsComplete) { return; } } LowerDecisionTree(byValue.Expression, byValue.Default); }
/// <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 || constantPattern.Value.ConstantValue.IsBad) { // 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) { // Note: we do not have any test covering this. Is it reachable? // Possibly not given the simple patterns types we support today. return(ErrorCode.ERR_PatternIsSubsumed); } switch (decisionTree.Kind) { case DecisionTree.DecisionKind.ByValue: { var byValue = (DecisionTree.ByValue)decisionTree; if (isNull) { // This should not occur, as the decision tree should contain a handler for // null earlier, for example in a type test. throw ExceptionUtilities.Unreachable; } 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) { // Note: we do not have any test covering this. Is it reachable? // Possibly not given the simple patterns types we support today. 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 (ExpressionOfTypeMatchesPatternType(constantPattern.Value.Type, type, ref _useSiteDiagnostics) == 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: { // This is unreachable because the subsumption version of the decision tree // never contains guarded decision trees that are not complete, or that have // any guard other than `true`. throw ExceptionUtilities.Unreachable; } 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) { // Note: we do not have any test covering this. Is it reachable? // Possibly not given the simple patterns types we support today. 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 the pattern's type is already handled by the previous pattern // or the previous pattern handles all of the (non-null) input data... if (ExpressionOfTypeMatchesPatternType( declarationPattern.DeclaredType.Type.TupleUnderlyingTypeOrSelf(), type, ref _useSiteDiagnostics) == true || ExpressionOfTypeMatchesPatternType(byType.Type, type, ref _useSiteDiagnostics) == true) { // then we check if the pattern is subsumed by the previous decision var error = CheckSubsumed(pattern, decision, inputCouldBeNull); if (error != 0) { return(error); } } } if (byType.Default != null) { // Note: we do not have any test covering this. Is it reachable? // Possibly not given the simple patterns types we support today. return(CheckSubsumed(pattern, byType.Default, inputCouldBeNull)); } return(0); } case DecisionTree.DecisionKind.Guarded: { // Because all guarded decision trees in the subsumption tree are // complete, we should never get here. throw ExceptionUtilities.Unreachable; } default: throw ExceptionUtilities.UnexpectedValue(decisionTree.Kind); } } case BoundKind.WildcardPattern: // because we always handle `default:` last, and that is the only way to get a wildcard pattern, // we should never need to see if it subsumes something else. throw ExceptionUtilities.UnexpectedValue(decisionTree.Kind); default: throw ExceptionUtilities.UnexpectedValue(pattern.Kind); } }
// For switch statements, we have an option of completely rewriting the switch header // and switch sections into simpler constructs, i.e. we can rewrite the switch header // using bound conditional goto statements and the rewrite the switch sections into // bound labeled statements. // // However, all the logic for emitting the switch jump tables is language agnostic // and includes IL optimizations. Hence we delay the switch jump table generation // till the emit phase. This way we also get additional benefit of sharing this code // between both VB and C# compilers. // // For string switch statements, we need to determine if we are generating a hash // table based jump table or a non hash jump table, i.e. linear string comparisons // with each case label. We use the Dev10 Heuristic to determine this // (see SwitchStringJumpTableEmitter.ShouldGenerateHashTableSwitch() for details). // If we are generating a hash table based jump table, we use a simple // hash function to hash the string constants corresponding to the case labels. // See SwitchStringJumpTableEmitter.ComputeStringHash(). // We need to emit this same function to compute the hash value into the compiler generated // <PrivateImplementationDetails> class. // If we have at least one string switch statement in a module that needs a // hash table based jump table, we generate a single public string hash synthesized method // that is shared across the module. private void LowerBasicSwitch(DecisionTree.ByValue byValue) { var switchSections = ArrayBuilder<BoundSwitchSection>.GetInstance(); var noValueMatches = _factory.GenerateLabel("noValueMatches"); var underlyingSwitchType = byValue.Type.IsEnumType() ? byValue.Type.GetEnumUnderlyingType() : byValue.Type; foreach (var vd in byValue.ValueAndDecision) { var value = vd.Key; var decision = vd.Value; var constantValue = ConstantValue.Create(value, underlyingSwitchType.SpecialType); var constantExpression = new BoundLiteral(_factory.Syntax, constantValue, underlyingSwitchType); var label = _factory.GenerateLabel("case+" + value); var switchLabel = new BoundSwitchLabel(_factory.Syntax, label, constantExpression, constantValue); var forValue = ArrayBuilder<BoundStatement>.GetInstance(); LowerDecisionTree(byValue.Expression, decision, forValue); if (!decision.MatchIsComplete) { forValue.Add(_factory.Goto(noValueMatches)); } var section = new BoundSwitchSection(_factory.Syntax, ImmutableArray.Create(switchLabel), forValue.ToImmutableAndFree()); switchSections.Add(section); } var rewrittenSections = switchSections.ToImmutableAndFree(); MethodSymbol stringEquality = null; if (underlyingSwitchType.SpecialType == SpecialType.System_String) { LocalRewriter.EnsureStringHashFunction(rewrittenSections, _factory.Syntax); stringEquality = LocalRewriter.GetSpecialTypeMethod(_factory.Syntax, SpecialMember.System_String__op_Equality); } // The BoundSwitchStatement requires a constant target when there are no sections, so we accomodate that here. var constantTarget = rewrittenSections.IsEmpty ? noValueMatches : null; var switchStatement = new BoundSwitchStatement( _factory.Syntax, null, _factory.Convert(underlyingSwitchType, byValue.Expression), constantTarget, ImmutableArray<LocalSymbol>.Empty, ImmutableArray<LocalFunctionSymbol>.Empty, rewrittenSections, noValueMatches, stringEquality); // The bound switch statement implicitly defines the label noValueMatches at the end, so we do not add it explicitly. switch (underlyingSwitchType.SpecialType) { case SpecialType.System_Boolean: // boolean switch is handled in LowerBooleanSwitch, not here. throw ExceptionUtilities.Unreachable; case SpecialType.System_String: case SpecialType.System_Byte: case SpecialType.System_Char: case SpecialType.System_Int16: case SpecialType.System_Int32: case SpecialType.System_Int64: case SpecialType.System_SByte: case SpecialType.System_UInt16: case SpecialType.System_UInt32: case SpecialType.System_UInt64: { // emit knows how to efficiently generate code for these kinds of switches. _loweredDecisionTree.Add(switchStatement); break; } default: { // other types, such as float, double, and decimal, are not currently // handled by emit and must be lowered here. _loweredDecisionTree.Add(LowerNonprimitiveSwitch(switchStatement)); break; } } LowerDecisionTree(byValue.Expression, byValue.Default); }
private DecisionTree AddByValue(DecisionTree.ByValue byValue, BoundConstantPattern value, DecisionMaker makeDecision) { Debug.Assert(value.Value.Type == byValue.Type); if (byValue.Default != null) { return AddByValue(byValue.Default, value, makeDecision); } // For error recovery, to avoid "unreachable code" diagnostics when there is a bad case // label, we use the case label itself as the value key. object valueKey = value.ConstantValue?.Value ?? value; DecisionTree valueDecision; if (byValue.ValueAndDecision.TryGetValue(valueKey, out valueDecision)) { valueDecision = Add(valueDecision, makeDecision); } else { valueDecision = makeDecision(byValue.Expression, byValue.Type); byValue.ValueAndDecision.Add(valueKey, valueDecision); } if (byValue.Type.SpecialType == SpecialType.System_Boolean && byValue.ValueAndDecision.Count == 2 && byValue.ValueAndDecision.Values.All(d => d.MatchIsComplete)) { byValue.MatchIsComplete = true; } return valueDecision; }
private void LowerDecisionTree(BoundExpression expression, DecisionTree decisionTree) { if (decisionTree == null) { return; } // If the input expression was a constant or a simple read of a local, then that is the // decision tree's expression. Otherwise it is a newly created temp, to which we must // assign the switch expression. if (decisionTree.Temp != null) { // Store the input expression into a temp if (decisionTree.Expression != expression) { _loweredDecisionTree.Add(_factory.Assignment(decisionTree.Expression, expression)); } if (DeclaredTempSet.Add(decisionTree.Temp)) { DeclaredTemps.Add(decisionTree.Temp); } else { // we should only attempt to declare each temp once. throw ExceptionUtilities.Unreachable; } } switch (decisionTree.Kind) { case DecisionTree.DecisionKind.ByType: { LowerDecisionTree((DecisionTree.ByType)decisionTree); return; } case DecisionTree.DecisionKind.ByValue: { LowerDecisionTree((DecisionTree.ByValue)decisionTree); return; } case DecisionTree.DecisionKind.Guarded: { LowerDecisionTree((DecisionTree.Guarded)decisionTree); return; } default: throw ExceptionUtilities.UnexpectedValue(decisionTree.Kind); } }
private DecisionTree AddByValue(DecisionTree.Guarded guarded, BoundConstantPattern value, DecisionMaker makeDecision) { if (guarded.Default != null) { if (guarded.Default.MatchIsComplete) { return null; } } else { guarded.Default = new DecisionTree.ByValue(guarded.Expression, guarded.Type, null); } return AddByValue(guarded.Default, value, makeDecision); }
private bool FullyHandlesItsInput(DecisionTree decision) { if (decision == null) { return(false); } if (decision.MatchIsComplete) { return(true); } // We check for completeness based on value. Other cases were handled in the construction of the decision tree. if (decision.Expression.ConstantValue == null) { return(false); } var value = decision.Expression.ConstantValue; switch (decision.Kind) { case DecisionTree.DecisionKind.ByType: { var byType = (DecisionTree.ByType)decision; if (value.IsNull) { return(FullyHandlesItsInput(byType.WhenNull)); } foreach (var kv in byType.TypeAndDecision) { // the only types that should appear in the decision tree are those // that can accept the input constant. Other types should have been // removed when the decision tree was produced. This depends on the // fact that all constants are of sealed types. if (FullyHandlesItsInput(kv.Value)) { return(true); } } return(FullyHandlesItsInput(byType.Default)); } case DecisionTree.DecisionKind.ByValue: { var byValue = (DecisionTree.ByValue)decision; if (value.IsNull) { return(false); } DecisionTree onValue; return (byValue.ValueAndDecision.TryGetValue(value.Value, out onValue) && FullyHandlesItsInput(onValue) || byValue.Default != null && FullyHandlesItsInput(byValue.Default)); } case DecisionTree.DecisionKind.Guarded: { return(decision.MatchIsComplete); } default: throw ExceptionUtilities.UnexpectedValue(decision.Kind); } }
internal DecisionTree ComputeDecisionTree() { Debug.Assert(_section == null); var expression = _switchStatement.Expression; if (expression.ConstantValue == null && expression.Kind != BoundKind.Local) { // unless the expression is simple enough, copy it into a local var localSymbol = new SynthesizedLocal(_enclosingSymbol as MethodSymbol, expression.Type, SynthesizedLocalKind.PatternMatchingTemp, _switchStatement.Syntax, false, RefKind.None); expression = new BoundLocal(expression.Syntax, localSymbol, null, expression.Type); } var result = DecisionTree.Create(_switchStatement.Expression, _switchStatement.Expression.Type, _enclosingSymbol); var subsumptionTree = DecisionTree.Create(_switchStatement.Expression, _switchStatement.Expression.Type, _enclosingSymbol); BoundPatternSwitchLabel defaultLabel = null; BoundPatternSwitchSection defaultSection = null; foreach (var section in _switchStatement.SwitchSections) { this._section = section; foreach (var label in section.SwitchLabels) { if (label.Syntax.Kind() == SyntaxKind.DefaultSwitchLabel) { if (defaultLabel != null) { // duplicate switch label will have been reported during initial binding. } else { defaultLabel = label; defaultSection = section; } } else { this._syntax = label.Syntax; // For purposes of subsumption, we do not take into consideration the value // of the input expression. Therefore we consider null possible if the type permits. var subsumedErrorCode = CheckSubsumed(label.Pattern, subsumptionTree, inputCouldBeNull: true); if (subsumedErrorCode != 0 && subsumedErrorCode != ErrorCode.ERR_NoImplicitConvCast) { if (!label.HasErrors) { _diagnostics.Add(subsumedErrorCode, label.Pattern.Syntax.Location); } } else { AddToDecisionTree(result, label); if (label.Guard == null || label.Guard.ConstantValue == ConstantValue.True) { // Only unconditional switch labels contribute to subsumption AddToDecisionTree(subsumptionTree, label); } } } } } if (defaultLabel != null) { Add(result, (e, t) => new DecisionTree.Guarded(_switchStatement.Expression, _switchStatement.Expression.Type, default(ImmutableArray <KeyValuePair <BoundExpression, BoundExpression> >), defaultSection, null, defaultLabel)); } return(result); }