|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.CSharp
{
internal partial class LocalRewriter
{
public override BoundNode VisitConvertedSwitchExpression(BoundConvertedSwitchExpression node)
{
// The switch expression is lowered to an expression that involves the use of side-effects
// such as jumps and labels, therefore it is represented by a BoundSpillSequence and
// the resulting nodes will need to be "spilled" to move such statements to the top
// level (i.e. into the enclosing statement list).
this._needsSpilling = true;
return SwitchExpressionLocalRewriter.Rewrite(this, node);
}
private sealed class SwitchExpressionLocalRewriter : BaseSwitchLocalRewriter
{
private SwitchExpressionLocalRewriter(BoundConvertedSwitchExpression node, LocalRewriter localRewriter)
: base(node.Syntax, localRewriter, node.SwitchArms.SelectAsArray(arm => arm.Syntax),
generateInstrumentation: !node.WasCompilerGenerated && localRewriter.Instrument)
{
}
public static BoundExpression Rewrite(LocalRewriter localRewriter, BoundConvertedSwitchExpression node)
{
var rewriter = new SwitchExpressionLocalRewriter(node, localRewriter);
BoundExpression result = rewriter.LowerSwitchExpression(node);
rewriter.Free();
return result;
}
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.GetDecisionDagForLowering(_factory.Compilation, out LabelSymbol? defaultLabel),
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 (_whenNodeIdentifierLocal is not null)
{
outerVariables.Add(_whenNodeIdentifierLocal);
}
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 (defaultLabel is not null)
{
result.Add(_factory.Label(defaultLabel));
if (produceDetailedSequencePoints)
result.Add(new BoundRestorePreviousSequencePoint(node.Syntax, restorePointForSwitchBody));
var objectType = _factory.SpecialType(SpecialType.System_Object);
var throwCall =
(implicitConversionExists(savedInputExpression, objectType) &&
_factory.WellKnownMember(WellKnownMember.System_Runtime_CompilerServices_SwitchExpressionException__ctorObject, isOptional: true) is MethodSymbol)
? ConstructThrowSwitchExpressionExceptionHelperCall(_factory, _factory.Convert(objectType, savedInputExpression)) :
(_factory.WellKnownMember(WellKnownMember.System_Runtime_CompilerServices_SwitchExpressionException__ctor, isOptional: true) is MethodSymbol)
? ConstructThrowSwitchExpressionExceptionParameterlessHelperCall(_factory) :
ConstructThrowInvalidOperationExceptionHelperCall(_factory);
result.Add(throwCall);
}
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)
{
var discardedUseSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded;
Conversion c = _localRewriter._compilation.Conversions.ClassifyConversionFromExpression(expression, type, isChecked: false, ref discardedUseSiteInfo);
return c.IsImplicit;
}
}
private static BoundStatement ConstructThrowSwitchExpressionExceptionHelperCall(SyntheticBoundNodeFactory factory, BoundExpression unmatchedValue)
{
Debug.Assert(factory.ModuleBuilderOpt is not null);
var module = factory.ModuleBuilderOpt;
var diagnosticSyntax = factory.CurrentFunction.GetNonNullSyntaxNode();
var diagnostics = factory.Diagnostics.DiagnosticBag;
Debug.Assert(diagnostics is not null);
var throwSwitchExpressionExceptionMethod = module.EnsureThrowSwitchExpressionExceptionExists(diagnosticSyntax, factory, diagnostics);
var call = factory.Call(
receiver: null,
throwSwitchExpressionExceptionMethod,
arg0: unmatchedValue);
return factory.HiddenSequencePoint(factory.ExpressionStatement(call));
}
private static BoundStatement ConstructThrowSwitchExpressionExceptionParameterlessHelperCall(SyntheticBoundNodeFactory factory)
{
Debug.Assert(factory.ModuleBuilderOpt is not null);
var module = factory.ModuleBuilderOpt!;
var diagnosticSyntax = factory.CurrentFunction.GetNonNullSyntaxNode();
var diagnostics = factory.Diagnostics.DiagnosticBag;
Debug.Assert(diagnostics is not null);
var throwSwitchExpressionExceptionMethod = module.EnsureThrowSwitchExpressionExceptionParameterlessExists(diagnosticSyntax, factory, diagnostics);
var call = factory.Call(
receiver: null,
throwSwitchExpressionExceptionMethod);
return factory.HiddenSequencePoint(factory.ExpressionStatement(call));
}
private static BoundStatement ConstructThrowInvalidOperationExceptionHelperCall(SyntheticBoundNodeFactory factory)
{
Debug.Assert(factory.ModuleBuilderOpt is not null);
var module = factory.ModuleBuilderOpt!;
var diagnosticSyntax = factory.CurrentFunction.GetNonNullSyntaxNode();
var diagnostics = factory.Diagnostics.DiagnosticBag;
Debug.Assert(diagnostics is not null);
var throwMethod = module.EnsureThrowInvalidOperationExceptionExists(diagnosticSyntax, factory, diagnostics);
var call = factory.Call(
receiver: null,
throwMethod);
return factory.HiddenSequencePoint(factory.ExpressionStatement(call));
}
}
}
}
|