private void CheckSwitchErrors( SwitchStatementSyntax node, BoundExpression boundSwitchGoverningExpression, ref ImmutableArray <BoundSwitchSection> switchSections, BoundDecisionDag decisionDag, DiagnosticBag diagnostics) { var reachableLabels = decisionDag.ReachableLabels; bool isSubsumed(BoundSwitchLabel switchLabel) { return(!reachableLabels.Contains(switchLabel.Label)); } // If no switch sections are subsumed, just return if (!switchSections.Any(s => s.SwitchLabels.Any(l => isSubsumed(l)))) { return; } var sectionBuilder = ArrayBuilder <BoundSwitchSection> .GetInstance(switchSections.Length); foreach (var oldSection in switchSections) { var labelBuilder = ArrayBuilder <BoundSwitchLabel> .GetInstance(oldSection.SwitchLabels.Length); foreach (var label in oldSection.SwitchLabels) { var newLabel = label; if (!label.HasErrors && isSubsumed(label) && label.Syntax.Kind() != SyntaxKind.DefaultSwitchLabel) { var syntax = label.Syntax; switch (syntax) { case CasePatternSwitchLabelSyntax p: if (!p.Pattern.HasErrors) { diagnostics.Add(ErrorCode.ERR_SwitchCaseSubsumed, p.Pattern.Location); } break; case CaseSwitchLabelSyntax p: if (label.Pattern is BoundConstantPattern cp && !cp.ConstantValue.IsBad && FindMatchingSwitchCaseLabel(cp.ConstantValue, p) != label.Label) { // We use the traditional diagnostic when possible diagnostics.Add(ErrorCode.ERR_DuplicateCaseLabel, syntax.Location, cp.ConstantValue.GetValueToDisplay()); } else if (!label.Pattern.HasErrors) { diagnostics.Add(ErrorCode.ERR_SwitchCaseSubsumed, p.Value.Location); } break;
/// <summary> /// Produce assignment of the input expression. This method is also responsible for assigning /// variables for some pattern-matching temps that can be shared with user variables. /// </summary> protected BoundDecisionDag ShareTempsAndEvaluateInput( BoundExpression loweredInput, BoundDecisionDag decisionDag, Action <BoundExpression> addCode, out BoundExpression savedInputExpression) { var inputDagTemp = BoundDagTemp.ForOriginalInput(loweredInput); if ((loweredInput.Kind == BoundKind.Local || loweredInput.Kind == BoundKind.Parameter) && loweredInput.GetRefKind() == RefKind.None) { // If we're switching on a local variable and there is no when clause (checked by the caller), // we assume the value of the local variable does not change during the execution of the // decision automaton and we just reuse the local variable when we need the input expression. // It is possible for this assumption to be violated by a side-effecting Deconstruct that // modifies the local variable which has been captured in a lambda. Since the language assumes // that functions called by pattern-matching are idempotent and not side-effecting, we feel // justified in taking this assumption in the compiler too. bool tempAssigned = _tempAllocator.TrySetTemp(inputDagTemp, loweredInput); Debug.Assert(tempAssigned); } foreach (BoundDecisionDagNode node in decisionDag.TopologicallySortedNodes) { if (node is BoundWhenDecisionDagNode w) { // We share a slot for a user-declared pattern-matching variable with a pattern temp if there // is no user-written when-clause that could modify the variable before the matching // automaton is done with it (checked by the caller). foreach (BoundPatternBinding binding in w.Bindings) { if (binding.VariableAccess is BoundLocal l) { Debug.Assert(l.LocalSymbol.DeclarationKind == LocalDeclarationKind.PatternVariable); _ = _tempAllocator.TrySetTemp(binding.TempContainingValue, binding.VariableAccess); } } } } if (loweredInput.Type.IsTupleType && loweredInput.Syntax.Kind() == SyntaxKind.TupleExpression && loweredInput is BoundObjectCreationExpression expr && !decisionDag.TopologicallySortedNodes.Any(n => usesOriginalInput(n))) { // If the switch governing expression is a tuple literal whose whole value is not used anywhere, // (though perhaps its component parts are used), then we can save the component parts // and assign them into temps (or perhaps user variables) to avoid the creation of // the tuple altogether. decisionDag = RewriteTupleInput(decisionDag, expr, addCode, out savedInputExpression); }
internal BoundExpression LowerGeneralIsPattern(BoundIsPatternExpression node) { _factory.Syntax = node.Syntax; var resultBuilder = ArrayBuilder <BoundStatement> .GetInstance(); var inputExpression = _localRewriter.VisitExpression(node.Expression); BoundDecisionDag decisionDag = ShareTempsIfPossibleAndEvaluateInput( node.DecisionDag, inputExpression, resultBuilder, out _); // lower the decision dag. ImmutableArray <BoundStatement> loweredDag = LowerDecisionDagCore(decisionDag); resultBuilder.Add(_factory.Block(loweredDag)); Debug.Assert(node.Type is { SpecialType: SpecialType.System_Boolean });
private void ComputeLabelSet(BoundDecisionDag decisionDag) { // Nodes with more than one predecessor are assigned a label var hasPredecessor = PooledHashSet <BoundDecisionDagNode> .GetInstance(); foreach (BoundDecisionDagNode node in decisionDag.TopologicallySortedNodes) { switch (node) { case BoundWhenDecisionDagNode w: GetDagNodeLabel(node); if (w.WhenFalse != null) { GetDagNodeLabel(w.WhenFalse); } break; case BoundLeafDecisionDagNode d: // Leaf can branch directly to the target _dagNodeLabels[node] = d.Label; break; case BoundEvaluationDecisionDagNode e: notePredecessor(e.Next); break; case BoundTestDecisionDagNode p: notePredecessor(p.WhenTrue); notePredecessor(p.WhenFalse); break; default: throw ExceptionUtilities.UnexpectedValue(node.Kind); } } hasPredecessor.Free(); return; void notePredecessor(BoundDecisionDagNode successor) { if (successor != null && !hasPredecessor.Add(successor)) { GetDagNodeLabel(successor); } } }
public BoundDecisionDag GetDecisionDagForLowering(CSharpCompilation compilation) { BoundDecisionDag decisionDag = this.ReachabilityDecisionDag; if (decisionDag.ContainsAnySynthesizedNodes()) { decisionDag = DecisionDagBuilder.CreateDecisionDagForSwitchStatement( compilation, this.Syntax, this.Expression, this.SwitchSections, this.DefaultLabel?.Label ?? this.BreakLabel, BindingDiagnosticBag.Discarded, forLowering: true); Debug.Assert(!decisionDag.ContainsAnySynthesizedNodes()); } return(decisionDag); }
internal override BoundStatement BindSwitchStatementCore(SwitchStatementSyntax node, Binder originalBinder, BindingDiagnosticBag diagnostics) { Debug.Assert(SwitchSyntax.Equals(node)); if (node.Sections.Count == 0) { diagnostics.Add(ErrorCode.WRN_EmptySwitch, node.OpenBraceToken.GetLocation()); } // Bind switch expression and set the switch governing type. BoundExpression boundSwitchGoverningExpression = SwitchGoverningExpression; diagnostics.AddRange(SwitchGoverningDiagnostics, allowMismatchInDependencyAccumulation: true); ImmutableArray <BoundSwitchSection> switchSections = BindSwitchSections(originalBinder, diagnostics, out BoundSwitchLabel defaultLabel); ImmutableArray <LocalSymbol> locals = GetDeclaredLocalsForScope(node); ImmutableArray <LocalFunctionSymbol> functions = GetDeclaredLocalFunctionsForScope(node); BoundDecisionDag decisionDag = DecisionDagBuilder.CreateDecisionDagForSwitchStatement( compilation: this.Compilation, syntax: node, switchGoverningExpression: boundSwitchGoverningExpression, switchSections: switchSections, // If there is no explicit default label, the default action is to break out of the switch defaultLabel: defaultLabel?.Label ?? BreakLabel, diagnostics); // Report subsumption errors, but ignore the input's constant value for that. CheckSwitchErrors(node, boundSwitchGoverningExpression, ref switchSections, decisionDag, diagnostics); // When the input is constant, we use that to reshape the decision dag that is returned // so that flow analysis will see that some of the cases may be unreachable. decisionDag = decisionDag.SimplifyDecisionDagIfConstantInput(boundSwitchGoverningExpression); return(new BoundSwitchStatement( syntax: node, expression: boundSwitchGoverningExpression, innerLocals: locals, innerLocalFunctions: functions, switchSections: switchSections, defaultLabel: defaultLabel, breakLabel: this.BreakLabel, reachabilityDecisionDag: decisionDag)); }
public BoundDecisionDag GetDecisionDagForLowering(CSharpCompilation compilation) { BoundDecisionDag decisionDag = this.ReachabilityDecisionDag; if (decisionDag.ContainsAnySynthesizedNodes()) { decisionDag = DecisionDagBuilder.CreateDecisionDagForIsPattern( compilation, this.Syntax, this.Expression, this.Pattern, this.WhenTrueLabel, this.WhenFalseLabel, BindingDiagnosticBag.Discarded, forLowering: true); Debug.Assert(!decisionDag.ContainsAnySynthesizedNodes()); } return(decisionDag); }
protected BoundDecisionDag ShareTempsIfPossibleAndEvaluateInput( BoundDecisionDag decisionDag, BoundExpression loweredSwitchGoverningExpression, ArrayBuilder <BoundStatement> result, out BoundExpression savedInputExpression) { // Note that a when-clause can contain an assignment to a // pattern variable declared in a different when-clause (e.g. in the same section, or // in a different section via the use of a local function), so we need to analyze all // of the when clauses to see if they are all simple enough to conclude that they do // not mutate pattern variables. var mightAssignWalker = new WhenClauseMightAssignWalker(); bool canShareTemps = !decisionDag.TopologicallySortedNodes .Any(node => node is BoundWhenDecisionDagNode w && mightAssignWalker.MightAssignSomething(w.WhenExpression)); if (canShareTemps) { decisionDag = ShareTempsAndEvaluateInput(loweredSwitchGoverningExpression, decisionDag, expr => result.Add(_factory.ExpressionStatement(expr)), out savedInputExpression); } else { // assign the input expression to its temp. BoundExpression inputTemp = _tempAllocator.GetTemp(BoundDagTemp.ForOriginalInput(loweredSwitchGoverningExpression)); Debug.Assert(inputTemp != loweredSwitchGoverningExpression); result.Add(_factory.Assignment(inputTemp, loweredSwitchGoverningExpression)); savedInputExpression = inputTemp; } // In a switch statement, there is a hidden sequence point after evaluating the input at the start of // the code to handle the decision dag. This is necessary so that jumps back from a `when` clause into // the decision dag do not appear to jump back up to the enclosing construct. if (IsSwitchStatement) { result.Add(_factory.HiddenSequencePoint()); } return(decisionDag); }
public BoundDecisionDag GetDecisionDagForLowering(CSharpCompilation compilation, out LabelSymbol?defaultLabel) { defaultLabel = this.DefaultLabel; BoundDecisionDag decisionDag = this.ReachabilityDecisionDag; if (decisionDag.ContainsAnySynthesizedNodes()) { decisionDag = DecisionDagBuilder.CreateDecisionDagForSwitchExpression( compilation, this.Syntax, this.Expression, this.SwitchArms, // there's no default label if the original switch is exhaustive. // we generate a new label here because the new dag might not be. defaultLabel ??= new GeneratedLabelSymbol("default"), BindingDiagnosticBag.Discarded, forLowering: true); Debug.Assert(!decisionDag.ContainsAnySynthesizedNodes()); } return(decisionDag); }
public BoundExpression LowerIsPattern( BoundIsPatternExpression isPatternExpression, BoundPattern pattern, CSharpCompilation compilation, DiagnosticBag diagnostics) { BoundDecisionDag decisionDag = isPatternExpression.DecisionDag; LabelSymbol whenTrueLabel = isPatternExpression.WhenTrueLabel; LabelSymbol whenFalseLabel = isPatternExpression.WhenFalseLabel; BoundExpression loweredInput = _localRewriter.VisitExpression(isPatternExpression.Expression); // The optimization of sharing pattern-matching temps with user variables can always apply to // an is-pattern expression because there is no when clause that could possibly intervene during // the execution of the pattern-matching automaton and change one of those variables. decisionDag = ShareTempsAndEvaluateInput(loweredInput, decisionDag, expr => _sideEffectBuilder.Add(expr), out _); var node = decisionDag.RootNode; // We follow the "good" path in the decision dag. We depend on it being nicely linear in structure. // If we add "or" patterns that assumption breaks down. while (node.Kind != BoundKind.LeafDecisionDagNode && node.Kind != BoundKind.WhenDecisionDagNode) { switch (node) { case BoundEvaluationDecisionDagNode evalNode: { LowerOneTest(evalNode.Evaluation); node = evalNode.Next; } break; case BoundTestDecisionDagNode testNode: { Debug.Assert(testNode.WhenFalse is BoundLeafDecisionDagNode x && x.Label == whenFalseLabel); if (testNode.WhenTrue is BoundEvaluationDecisionDagNode e && TryLowerTypeTestAndCast(testNode.Test, e.Evaluation, out BoundExpression sideEffect, out BoundExpression testExpression)) { _sideEffectBuilder.Add(sideEffect); AddConjunct(testExpression); node = e.Next; }
private void LowerDecisionDagCore(BoundDecisionDag decisionDag) { ImmutableArray <BoundDecisionDagNode> sortedNodes = decisionDag.TopologicallySortedNodes; var firstNode = sortedNodes[0]; switch (firstNode) { case BoundWhenDecisionDagNode _: case BoundLeafDecisionDagNode _: // If the first node is a leaf or when clause rather than the code for the // lowered decision dag, jump there to start. _loweredDecisionDag.Add(_factory.Goto(GetDagNodeLabel(firstNode))); break; } // Code for each when clause goes in the separate code section for its switch section. foreach (BoundDecisionDagNode node in sortedNodes) { if (node is BoundWhenDecisionDagNode w) { LowerWhenClause(w); } } ImmutableArray <BoundDecisionDagNode> nodesToLower = sortedNodes.WhereAsArray(n => n.Kind != BoundKind.WhenDecisionDagNode && n.Kind != BoundKind.LeafDecisionDagNode); var loweredNodes = PooledHashSet <BoundDecisionDagNode> .GetInstance(); for (int i = 0, length = nodesToLower.Length; i < length; i++) { BoundDecisionDagNode node = nodesToLower[i]; if (loweredNodes.Contains(node)) { Debug.Assert(!_dagNodeLabels.TryGetValue(node, out _)); continue; } if (_dagNodeLabels.TryGetValue(node, out LabelSymbol? label)) { _loweredDecisionDag.Add(_factory.Label(label)); } // If we can generate an IL switch instruction, do so if (GenerateSwitchDispatch(node, loweredNodes)) { continue; } // If we can generate a type test and cast more efficiently as an `is` followed by a null check, do so if (GenerateTypeTestAndCast(node, loweredNodes, nodesToLower, i)) { continue; } // We pass the node that will follow so we can permit a test to fall through if appropriate BoundDecisionDagNode?nextNode = ((i + 1) < length) ? nodesToLower[i + 1] : null; if (nextNode != null && loweredNodes.Contains(nextNode)) { nextNode = null; } LowerDecisionDagNode(node, nextNode); } loweredNodes.Free(); }
protected (ImmutableArray <BoundStatement> loweredDag, ImmutableDictionary <SyntaxNode, ImmutableArray <BoundStatement> > switchSections) LowerDecisionDag(BoundDecisionDag decisionDag) { Debug.Assert(this._loweredDecisionDag.IsEmpty()); ComputeLabelSet(decisionDag); LowerDecisionDagCore(decisionDag); ImmutableArray <BoundStatement> loweredDag = _loweredDecisionDag.ToImmutableAndFree(); var switchSections = _switchArms.ToImmutableDictionary(kv => kv.Key, kv => kv.Value.ToImmutableAndFree()); _switchArms.Clear(); return(loweredDag, switchSections); }
/// <summary> /// Build the decision dag, giving an error if some cases are subsumed and a warning if the switch expression is not exhaustive. /// </summary> /// <param name="node"></param> /// <param name="boundInputExpression"></param> /// <param name="switchArms"></param> /// <param name="decisionDag"></param> /// <param name="diagnostics"></param> /// <returns>true if there was a non-exhaustive warning reported</returns> private bool CheckSwitchExpressionExhaustive( SwitchExpressionSyntax node, BoundExpression boundInputExpression, ImmutableArray <BoundSwitchExpressionArm> switchArms, out BoundDecisionDag decisionDag, out LabelSymbol defaultLabel, DiagnosticBag diagnostics) { defaultLabel = new GeneratedLabelSymbol("default"); decisionDag = DecisionDagBuilder.CreateDecisionDagForSwitchExpression(this.Compilation, node, boundInputExpression, switchArms, defaultLabel, diagnostics); var reachableLabels = decisionDag.ReachableLabels; foreach (BoundSwitchExpressionArm arm in switchArms) { if (!reachableLabels.Contains(arm.Label)) { diagnostics.Add(ErrorCode.ERR_SwitchArmSubsumed, arm.Pattern.Syntax.Location); } } if (!reachableLabels.Contains(defaultLabel)) { // switch expression is exhaustive; no default label needed. defaultLabel = null; return(false); } // We only report exhaustive warnings when the default label is reachable through some series of // tests that do not include a test in which the value is known to be null. Handling paths with // nulls is the job of the nullable walker. foreach (var n in TopologicalSort.IterativeSort <BoundDecisionDagNode>(new[] { decisionDag.RootNode }, nonNullSuccessors)) { if (n is BoundLeafDecisionDagNode leaf && leaf.Label == defaultLabel) { diagnostics.Add(ErrorCode.WRN_SwitchExpressionNotExhaustive, node.SwitchKeyword.GetLocation()); return(true); } } return(false); ImmutableArray <BoundDecisionDagNode> nonNullSuccessors(BoundDecisionDagNode n) { switch (n) { case BoundTestDecisionDagNode p: switch (p.Test) { case BoundDagNonNullTest t: // checks that the input is not null return(ImmutableArray.Create(p.WhenTrue)); case BoundDagExplicitNullTest t: // checks that the input is null return(ImmutableArray.Create(p.WhenFalse)); default: return(BoundDecisionDag.Successors(n)); } default: return(BoundDecisionDag.Successors(n)); } } }
private BoundExpression LowerSwitchExpression(BoundConvertedSwitchExpression node) { _factory.Syntax = node.Syntax; var result = ArrayBuilder <BoundStatement> .GetInstance(); var outerVariables = ArrayBuilder <LocalSymbol> .GetInstance(); var loweredSwitchGoverningExpression = _localRewriter.VisitExpression(node.Expression); BoundDecisionDag decisionDag = ShareTempsIfPossibleAndEvaluateInput( node.DecisionDag, loweredSwitchGoverningExpression, result, out BoundExpression savedInputExpression); Debug.Assert(savedInputExpression != null); // lower the decision dag. (ImmutableArray <BoundStatement> loweredDag, ImmutableDictionary <SyntaxNode, ImmutableArray <BoundStatement> > switchSections) = LowerDecisionDag(decisionDag); // then add the rest of the lowered dag that references that input result.Add(_factory.Block(loweredDag)); // A branch to the default label when no switch case matches is included in the // decision tree, so the code in result is unreachable at this point. // Lower each switch expression arm LocalSymbol resultTemp = _factory.SynthesizedLocal(node.Type, node.Syntax, kind: SynthesizedLocalKind.LoweringTemp); LabelSymbol afterSwitchExpression = _factory.GenerateLabel("afterSwitchExpression"); foreach (BoundSwitchExpressionArm arm in node.SwitchArms) { _factory.Syntax = arm.Syntax; var sectionBuilder = ArrayBuilder <BoundStatement> .GetInstance(); sectionBuilder.AddRange(switchSections[arm.Syntax]); sectionBuilder.Add(_factory.Label(arm.Label)); sectionBuilder.Add(_factory.Assignment(_factory.Local(resultTemp), _localRewriter.VisitExpression(arm.Value))); sectionBuilder.Add(_factory.Goto(afterSwitchExpression)); var statements = sectionBuilder.ToImmutableAndFree(); if (arm.Locals.IsEmpty) { result.Add(_factory.StatementList(statements)); } else { // Lifetime of these locals is expanded to the entire switch body, as it is possible to // share them as temps in the decision dag. outerVariables.AddRange(arm.Locals); // Note the language scope of the locals, even though they are included for the purposes of // lifetime analysis in the enclosing scope. result.Add(new BoundScope(arm.Syntax, arm.Locals, statements)); } } _factory.Syntax = node.Syntax; if (node.DefaultLabel != null) { result.Add(_factory.Label(node.DefaultLabel)); var objectType = _factory.SpecialType(SpecialType.System_Object); var thrownExpression = (implicitConversionExists(savedInputExpression, objectType) && _factory.WellKnownMember(WellKnownMember.System_Runtime_CompilerServices_SwitchExpressionException__ctorObject, isOptional: true) is MethodSymbol exception1) ? _factory.New(exception1, _factory.Convert(objectType, savedInputExpression)) : (_factory.WellKnownMember(WellKnownMember.System_Runtime_CompilerServices_SwitchExpressionException__ctor, isOptional: true) is MethodSymbol exception0) ? _factory.New(exception0) : _factory.New(_factory.WellKnownMethod(WellKnownMember.System_InvalidOperationException__ctor)); result.Add(_factory.Throw(thrownExpression)); } result.Add(_factory.Label(afterSwitchExpression)); outerVariables.Add(resultTemp); outerVariables.AddRange(_tempAllocator.AllTemps()); return(_factory.SpillSequence(outerVariables.ToImmutableAndFree(), result.ToImmutableAndFree(), _factory.Local(resultTemp))); bool implicitConversionExists(BoundExpression expression, TypeSymbol type) { HashSet <DiagnosticInfo>?discarded = null; Conversion c = _localRewriter._compilation.Conversions.ClassifyConversionFromExpression(expression, type, ref discarded); return(c.IsImplicit); } }
private bool CheckSwitchExpressionExhaustive( SwitchExpressionSyntax node, BoundExpression boundInputExpression, ImmutableArray <BoundSwitchExpressionArm> switchArms, out BoundDecisionDag decisionDag, out LabelSymbol defaultLabel, BindingDiagnosticBag diagnostics) { defaultLabel = new GeneratedLabelSymbol("default"); decisionDag = DecisionDagBuilder.CreateDecisionDagForSwitchExpression(this.Compilation, node, boundInputExpression, switchArms, defaultLabel, diagnostics); var reachableLabels = decisionDag.ReachableLabels; bool hasErrors = false; foreach (BoundSwitchExpressionArm arm in switchArms) { hasErrors |= arm.HasErrors; if (!hasErrors && !reachableLabels.Contains(arm.Label)) { diagnostics.Add(ErrorCode.ERR_SwitchArmSubsumed, arm.Pattern.Syntax.Location); } } if (!reachableLabels.Contains(defaultLabel)) { // switch expression is exhaustive; no default label needed. defaultLabel = null; return(false); } if (hasErrors) { return(true); } // We only report exhaustive warnings when the default label is reachable through some series of // tests that do not include a test in which the value is known to be null. Handling paths with // nulls is the job of the nullable walker. bool wasAcyclic = TopologicalSort.TryIterativeSort <BoundDecisionDagNode>(new[] { decisionDag.RootNode }, nonNullSuccessors, out var nodes); // Since decisionDag.RootNode is acyclic by construction, its subset of nodes sorted here cannot be cyclic Debug.Assert(wasAcyclic); foreach (var n in nodes) { if (n is BoundLeafDecisionDagNode leaf && leaf.Label == defaultLabel) { var samplePattern = PatternExplainer.SamplePatternForPathToDagNode( BoundDagTemp.ForOriginalInput(boundInputExpression), nodes, n, nullPaths: false, out bool requiresFalseWhenClause, out bool unnamedEnumValue); ErrorCode warningCode = requiresFalseWhenClause ? ErrorCode.WRN_SwitchExpressionNotExhaustiveWithWhen : unnamedEnumValue ? ErrorCode.WRN_SwitchExpressionNotExhaustiveWithUnnamedEnumValue : ErrorCode.WRN_SwitchExpressionNotExhaustive; diagnostics.Add( warningCode, node.SwitchKeyword.GetLocation(), samplePattern); return(true); } } return(false); ImmutableArray <BoundDecisionDagNode> nonNullSuccessors(BoundDecisionDagNode n) { switch (n) { case BoundTestDecisionDagNode p: switch (p.Test) { case BoundDagNonNullTest t: // checks that the input is not null return(ImmutableArray.Create(p.WhenTrue)); case BoundDagExplicitNullTest t: // checks that the input is null return(ImmutableArray.Create(p.WhenFalse)); default: return(BoundDecisionDag.Successors(n)); } default: return(BoundDecisionDag.Successors(n)); } } }
private BoundExpression LowerSwitchExpression(BoundConvertedSwitchExpression node) { // When compiling for Debug (not Release), we produce the most detailed sequence points. var produceDetailedSequencePoints = GenerateInstrumentation && _localRewriter._compilation.Options.OptimizationLevel != OptimizationLevel.Release; _factory.Syntax = node.Syntax; var result = ArrayBuilder <BoundStatement> .GetInstance(); var outerVariables = ArrayBuilder <LocalSymbol> .GetInstance(); var loweredSwitchGoverningExpression = _localRewriter.VisitExpression(node.Expression); BoundDecisionDag decisionDag = ShareTempsIfPossibleAndEvaluateInput( node.DecisionDag, loweredSwitchGoverningExpression, result, out BoundExpression savedInputExpression); Debug.Assert(savedInputExpression != null); object restorePointForEnclosingStatement = new object(); object restorePointForSwitchBody = new object(); // lower the decision dag. (ImmutableArray <BoundStatement> loweredDag, ImmutableDictionary <SyntaxNode, ImmutableArray <BoundStatement> > switchSections) = LowerDecisionDag(decisionDag); if (produceDetailedSequencePoints) { var syntax = (SwitchExpressionSyntax)node.Syntax; result.Add(new BoundSavePreviousSequencePoint(syntax, restorePointForEnclosingStatement)); // While evaluating the state machine, we highlight the `switch {...}` part. var spanStart = syntax.SwitchKeyword.Span.Start; var spanEnd = syntax.Span.End; var spanForSwitchBody = new TextSpan(spanStart, spanEnd - spanStart); result.Add(new BoundStepThroughSequencePoint(node.Syntax, span: spanForSwitchBody)); result.Add(new BoundSavePreviousSequencePoint(syntax, restorePointForSwitchBody)); } // add the rest of the lowered dag that references that input result.Add(_factory.Block(loweredDag)); // A branch to the default label when no switch case matches is included in the // decision tree, so the code in result is unreachable at this point. // Lower each switch expression arm LocalSymbol resultTemp = _factory.SynthesizedLocal(node.Type, node.Syntax, kind: SynthesizedLocalKind.LoweringTemp); LabelSymbol afterSwitchExpression = _factory.GenerateLabel("afterSwitchExpression"); foreach (BoundSwitchExpressionArm arm in node.SwitchArms) { _factory.Syntax = arm.Syntax; var sectionBuilder = ArrayBuilder <BoundStatement> .GetInstance(); sectionBuilder.AddRange(switchSections[arm.Syntax]); sectionBuilder.Add(_factory.Label(arm.Label)); var loweredValue = _localRewriter.VisitExpression(arm.Value); if (GenerateInstrumentation) { loweredValue = this._localRewriter._instrumenter.InstrumentSwitchExpressionArmExpression(arm.Value, loweredValue, _factory); } sectionBuilder.Add(_factory.Assignment(_factory.Local(resultTemp), loweredValue)); sectionBuilder.Add(_factory.Goto(afterSwitchExpression)); var statements = sectionBuilder.ToImmutableAndFree(); if (arm.Locals.IsEmpty) { result.Add(_factory.StatementList(statements)); } else { // Lifetime of these locals is expanded to the entire switch body, as it is possible to // share them as temps in the decision dag. outerVariables.AddRange(arm.Locals); // Note the language scope of the locals, even though they are included for the purposes of // lifetime analysis in the enclosing scope. result.Add(new BoundScope(arm.Syntax, arm.Locals, statements)); } } _factory.Syntax = node.Syntax; if (node.DefaultLabel != null) { result.Add(_factory.Label(node.DefaultLabel)); if (produceDetailedSequencePoints) { result.Add(new BoundRestorePreviousSequencePoint(node.Syntax, restorePointForSwitchBody)); } var objectType = _factory.SpecialType(SpecialType.System_Object); var thrownExpression = (implicitConversionExists(savedInputExpression, objectType) && _factory.WellKnownMember(WellKnownMember.System_Runtime_CompilerServices_SwitchExpressionException__ctorObject, isOptional: true) is MethodSymbol exception1) ? _factory.New(exception1, _factory.Convert(objectType, savedInputExpression)) : (_factory.WellKnownMember(WellKnownMember.System_Runtime_CompilerServices_SwitchExpressionException__ctor, isOptional: true) is MethodSymbol exception0) ? _factory.New(exception0) : _factory.New(_factory.WellKnownMethod(WellKnownMember.System_InvalidOperationException__ctor)); result.Add(_factory.Throw(thrownExpression)); } if (GenerateInstrumentation) { result.Add(_factory.HiddenSequencePoint()); } result.Add(_factory.Label(afterSwitchExpression)); if (produceDetailedSequencePoints) { result.Add(new BoundRestorePreviousSequencePoint(node.Syntax, restorePointForEnclosingStatement)); } outerVariables.Add(resultTemp); outerVariables.AddRange(_tempAllocator.AllTemps()); return(_factory.SpillSequence(outerVariables.ToImmutableAndFree(), result.ToImmutableAndFree(), _factory.Local(resultTemp))); bool implicitConversionExists(BoundExpression expression, TypeSymbol type) { HashSet <DiagnosticInfo>?discarded = null; Conversion c = _localRewriter._compilation.Conversions.ClassifyConversionFromExpression(expression, type, ref discarded); return(c.IsImplicit); } }
private BoundStatement LowerSwitchStatement(BoundSwitchStatement node) { _factory.Syntax = node.Syntax; var result = ArrayBuilder <BoundStatement> .GetInstance(); var outerVariables = ArrayBuilder <LocalSymbol> .GetInstance(); var loweredSwitchGoverningExpression = _localRewriter.VisitExpression(node.Expression); if (!node.WasCompilerGenerated && _localRewriter.Instrument) { // EnC: We need to insert a hidden sequence point to handle function remapping in case // the containing method is edited while methods invoked in the expression are being executed. var instrumentedExpression = _localRewriter._instrumenter.InstrumentSwitchStatementExpression(node, loweredSwitchGoverningExpression, _factory); if (loweredSwitchGoverningExpression.ConstantValue == null) { loweredSwitchGoverningExpression = instrumentedExpression; } else { // If the expression is a constant, we leave it alone (the decision dag lowering code needs // to see that constant). But we add an additional leading statement with the instrumented expression. result.Add(_factory.ExpressionStatement(instrumentedExpression)); } } // The set of variables attached to the outer block outerVariables.AddRange(node.InnerLocals); // Evaluate the input and set up sharing for dag temps with user variables BoundDecisionDag decisionDag = ShareTempsIfPossibleAndEvaluateInput(node.DecisionDag, loweredSwitchGoverningExpression, result, out _); // lower the decision dag. (ImmutableArray <BoundStatement> loweredDag, ImmutableDictionary <SyntaxNode, ImmutableArray <BoundStatement> > switchSections) = LowerDecisionDag(decisionDag); // then add the rest of the lowered dag that references that input result.Add(_factory.Block(loweredDag)); // A branch to the default label when no switch case matches is included in the // decision dag, so the code in `result` is unreachable at this point. // Lower each switch section. foreach (BoundSwitchSection section in node.SwitchSections) { _factory.Syntax = section.Syntax; var sectionBuilder = ArrayBuilder <BoundStatement> .GetInstance(); sectionBuilder.AddRange(switchSections[section.Syntax]); foreach (BoundSwitchLabel switchLabel in section.SwitchLabels) { sectionBuilder.Add(_factory.Label(switchLabel.Label)); } // Add the translated body of the switch section sectionBuilder.AddRange(_localRewriter.VisitList(section.Statements)); // By the semantics of the switch statement, the end of each section is required to be unreachable. // So we can just seal the block and there is no need to follow it by anything. ImmutableArray <BoundStatement> statements = sectionBuilder.ToImmutableAndFree(); if (section.Locals.IsEmpty) { result.Add(_factory.StatementList(statements)); } else { // Lifetime of these locals is expanded to the entire switch body, as it is possible to capture // them in a different section by using a local function as an intermediary. outerVariables.AddRange(section.Locals); // Note the language scope of the locals, even though they are included for the purposes of // lifetime analysis in the enclosing scope. result.Add(new BoundScope(section.Syntax, section.Locals, statements)); } } // Dispatch temps are in scope throughout the switch statement, as they are used // both in the dispatch section to hold temporary values from the translation of // the decision dag, and in the branches where the temp values are assigned to the // pattern variables of matched patterns. outerVariables.AddRange(_tempAllocator.AllTemps()); _factory.Syntax = node.Syntax; result.Add(_factory.Label(node.BreakLabel)); BoundStatement translatedSwitch = _factory.Block(outerVariables.ToImmutableAndFree(), node.InnerLocalFunctions, result.ToImmutableAndFree()); // Only add instrumentation (such as a sequence point) if the node is not compiler-generated. if (!node.WasCompilerGenerated && _localRewriter.Instrument) { translatedSwitch = _localRewriter._instrumenter.InstrumentSwitchStatement(node, translatedSwitch); } return(translatedSwitch); }
LearnFromDecisionDag( SyntaxNode node, BoundDecisionDag decisionDag, BoundExpression expression, TypeWithState expressionType, ref LocalState initialState) { // We reuse the slot at the beginning of a switch (or is-pattern expression), pretending that we are // not copying the input to evaluate the patterns. In this way we infer non-nullability of the original // variable's parts based on matched pattern parts. Mutations in `when` clauses can show the inaccuracy // of analysis based on this choice. var rootTemp = BoundDagTemp.ForOriginalInput(expression); int originalInputSlot = MakeSlot(expression); if (originalInputSlot <= 0) { originalInputSlot = makeDagTempSlot(expressionType.ToTypeWithAnnotations(), rootTemp); initialState[originalInputSlot] = expressionType.State; } var tempMap = PooledDictionary <BoundDagTemp, (int slot, TypeSymbol type)> .GetInstance(); Debug.Assert(originalInputSlot > 0); tempMap.Add(rootTemp, (originalInputSlot, expressionType.Type)); var nodeStateMap = PooledDictionary <BoundDecisionDagNode, (LocalState state, bool believedReachable)> .GetInstance(); nodeStateMap.Add(decisionDag.RootNode, (state: initialState.Clone(), believedReachable: true)); var labelStateMap = PooledDictionary <LabelSymbol, (LocalState state, bool believedReachable)> .GetInstance(); foreach (var dagNode in decisionDag.TopologicallySortedNodes) { bool found = nodeStateMap.TryGetValue(dagNode, out var nodeStateAndBelievedReachable); Debug.Assert(found); // the topologically sorted nodes should contain only reachable nodes (LocalState nodeState, bool nodeBelievedReachable) = nodeStateAndBelievedReachable; SetState(nodeState); switch (dagNode) { case BoundEvaluationDecisionDagNode p: { var evaluation = p.Evaluation; (int inputSlot, TypeSymbol inputType) = tempMap.TryGetValue(evaluation.Input, out var slotAndType) ? slotAndType : throw ExceptionUtilities.Unreachable; Debug.Assert(inputSlot > 0); var inputState = this.State[inputSlot]; switch (evaluation) { case BoundDagDeconstructEvaluation e: { // https://github.com/dotnet/roslyn/issues/34232 // We may need to recompute the Deconstruct method for a deconstruction if // the receiver type has changed (e.g. its nested nullability). var method = e.DeconstructMethod; int extensionExtra = method.IsStatic ? 1 : 0; for (int i = 0; i < method.ParameterCount - extensionExtra; i++) { var parameterType = method.Parameters[i + extensionExtra].TypeWithAnnotations; var output = new BoundDagTemp(e.Syntax, parameterType.Type, e, i); int outputSlot = makeDagTempSlot(parameterType, output); Debug.Assert(outputSlot > 0); addToTempMap(output, outputSlot, parameterType.Type); } break; } case BoundDagTypeEvaluation e: { var output = new BoundDagTemp(e.Syntax, e.Type, e); HashSet <DiagnosticInfo> discardedDiagnostics = null; int outputSlot; switch (_conversions.WithNullability(false).ClassifyConversionFromType(inputType, e.Type, ref discardedDiagnostics).Kind) { case ConversionKind.Identity: case ConversionKind.ImplicitReference: case ConversionKind.NoConversion: case ConversionKind.ExplicitReference: outputSlot = inputSlot; break; case ConversionKind.ExplicitNullable when AreNullableAndUnderlyingTypes(inputType, e.Type, out _): outputSlot = GetNullableOfTValueSlot(inputType, inputSlot, out _, forceSlotEvenIfEmpty: true); if (outputSlot < 0) { goto default; } break; default: outputSlot = makeDagTempSlot(TypeWithAnnotations.Create(e.Type, NullableAnnotation.NotAnnotated), output); break; } State[outputSlot] = NullableFlowState.NotNull; var outputType = TypeWithState.Create(e.Type, inputState); addToTempMap(output, outputSlot, outputType.Type); break; } case BoundDagFieldEvaluation e: { Debug.Assert(inputSlot > 0); var field = (FieldSymbol)AsMemberOfType(inputType, e.Field); int outputSlot = GetOrCreateSlot(field, inputSlot, forceSlotEvenIfEmpty: true); Debug.Assert(outputSlot > 0); var type = field.Type; var output = new BoundDagTemp(e.Syntax, type, e); addToTempMap(output, outputSlot, type); break; } case BoundDagPropertyEvaluation e: { Debug.Assert(inputSlot > 0); var property = (PropertySymbol)AsMemberOfType(inputType, e.Property); var type = property.TypeWithAnnotations; var output = new BoundDagTemp(e.Syntax, type.Type, e); int outputSlot = GetOrCreateSlot(property, inputSlot, forceSlotEvenIfEmpty: true); if (outputSlot <= 0) { // This is needed due to https://github.com/dotnet/roslyn/issues/29619 outputSlot = makeDagTempSlot(type, output); } Debug.Assert(outputSlot > 0); addToTempMap(output, outputSlot, type.Type); break; } case BoundDagIndexEvaluation e: { var type = TypeWithAnnotations.Create(e.Property.Type, NullableAnnotation.Annotated); var output = new BoundDagTemp(e.Syntax, type.Type, e); int outputSlot = makeDagTempSlot(type, output); Debug.Assert(outputSlot > 0); addToTempMap(output, outputSlot, type.Type); break; } default: throw ExceptionUtilities.UnexpectedValue(p.Evaluation.Kind); } gotoNode(p.Next, this.State, nodeBelievedReachable); break; } case BoundTestDecisionDagNode p: { var test = p.Test; bool foundTemp = tempMap.TryGetValue(test.Input, out var slotAndType); Debug.Assert(foundTemp); (int inputSlot, TypeSymbol inputType) = slotAndType; var inputState = this.State[inputSlot]; Split(); switch (test) { case BoundDagTypeTest t: if (inputSlot > 0) { learnFromNonNullTest(inputSlot, ref this.StateWhenTrue); } gotoNode(p.WhenTrue, this.StateWhenTrue, nodeBelievedReachable); gotoNode(p.WhenFalse, this.StateWhenFalse, nodeBelievedReachable); break; case BoundDagNonNullTest t: if (inputSlot > 0) { learnFromNonNullTest(inputSlot, ref this.StateWhenTrue); } gotoNode(p.WhenTrue, this.StateWhenTrue, nodeBelievedReachable); gotoNode(p.WhenFalse, this.StateWhenFalse, nodeBelievedReachable & inputState.MayBeNull()); break; case BoundDagExplicitNullTest t: if (inputSlot > 0) { LearnFromNullTest(inputSlot, inputType, ref this.StateWhenTrue); learnFromNonNullTest(inputSlot, ref this.StateWhenFalse); } gotoNode(p.WhenTrue, this.StateWhenTrue, nodeBelievedReachable); gotoNode(p.WhenFalse, this.StateWhenFalse, nodeBelievedReachable); break; case BoundDagValueTest t: Debug.Assert(t.Value != ConstantValue.Null); if (inputSlot > 0) { learnFromNonNullTest(inputSlot, ref this.StateWhenTrue); } gotoNode(p.WhenTrue, this.StateWhenTrue, nodeBelievedReachable); gotoNode(p.WhenFalse, this.StateWhenFalse, nodeBelievedReachable); break; default: throw ExceptionUtilities.UnexpectedValue(test.Kind); } break; } case BoundLeafDecisionDagNode d: // We have one leaf decision dag node per reachable label labelStateMap.Add(d.Label, (this.State, nodeBelievedReachable)); break; case BoundWhenDecisionDagNode w: // bind the pattern variables, inferring their types as well foreach (var binding in w.Bindings) { var variableAccess = binding.VariableAccess; var tempSource = binding.TempContainingValue; var foundTemp = tempMap.TryGetValue(tempSource, out var tempSlotAndType); Debug.Assert(foundTemp); var(tempSlot, tempType) = tempSlotAndType; var tempState = this.State[tempSlot]; if (variableAccess is BoundLocal { LocalSymbol: SourceLocalSymbol { IsVar: true } local }) { var inferredType = TypeWithState.Create(tempType, tempState).ToTypeWithAnnotations(); if (_variableTypes.TryGetValue(local, out var existingType)) { // merge inferred nullable annotation from different branches of the decision tree _variableTypes[local] = TypeWithAnnotations.Create(existingType.Type, existingType.NullableAnnotation.Join(inferredType.NullableAnnotation)); } else { _variableTypes[local] = inferredType; } int localSlot = GetOrCreateSlot(local, forceSlotEvenIfEmpty: true); this.State[localSlot] = tempState; }
protected ImmutableArray <BoundStatement> LowerDecisionDagCore(BoundDecisionDag decisionDag) { _loweredDecisionDag = ArrayBuilder <BoundStatement> .GetInstance(); ComputeLabelSet(decisionDag); ImmutableArray <BoundDecisionDagNode> sortedNodes = decisionDag.TopologicallySortedNodes; var firstNode = sortedNodes[0]; switch (firstNode) { case BoundWhenDecisionDagNode _: case BoundLeafDecisionDagNode _: // If the first node is a leaf or when clause rather than the code for the // lowered decision dag, jump there to start. _loweredDecisionDag.Add(_factory.Goto(GetDagNodeLabel(firstNode))); break; } // Code for each when clause goes in the separate code section for its switch section. foreach (BoundDecisionDagNode node in sortedNodes) { if (node is BoundWhenDecisionDagNode w) { LowerWhenClause(w); } } ImmutableArray <BoundDecisionDagNode> nodesToLower = sortedNodes.WhereAsArray(n => n.Kind != BoundKind.WhenDecisionDagNode && n.Kind != BoundKind.LeafDecisionDagNode); var loweredNodes = PooledHashSet <BoundDecisionDagNode> .GetInstance(); for (int i = 0, length = nodesToLower.Length; i < length; i++) { BoundDecisionDagNode node = nodesToLower[i]; // A node may have been lowered as part of a switch dispatch, but if it had a label, we'll need to lower it individually as well bool alreadyLowered = loweredNodes.Contains(node); if (alreadyLowered && !_dagNodeLabels.TryGetValue(node, out _)) { continue; } if (_dagNodeLabels.TryGetValue(node, out LabelSymbol label)) { _loweredDecisionDag.Add(_factory.Label(label)); } // If we can generate an IL switch instruction, do so if (!alreadyLowered && GenerateSwitchDispatch(node, loweredNodes)) { continue; } // If we can generate a type test and cast more efficiently as an `is` followed by a null check, do so if (GenerateTypeTestAndCast(node, loweredNodes, nodesToLower, i)) { continue; } // We pass the node that will follow so we can permit a test to fall through if appropriate BoundDecisionDagNode nextNode = ((i + 1) < length) ? nodesToLower[i + 1] : null; if (nextNode != null && loweredNodes.Contains(nextNode)) { nextNode = null; } LowerDecisionDagNode(node, nextNode); } loweredNodes.Free(); var result = _loweredDecisionDag.ToImmutableAndFree(); _loweredDecisionDag = null; return(result); }