From 5f13ffef81b0cccc19bf24c4fb6ff6de6992af5d Mon Sep 17 00:00:00 2001 From: Alireza Habibi Date: Mon, 26 Jun 2023 15:44:53 +0330 Subject: [PATCH] Avoid creating result temp for is-expressions --- .../CSharp/Portable/BoundTree/BoundNode.cs | 8 + .../CSharp/Portable/BoundTree/BoundNodes.xml | 9 + .../CSharp/Portable/CodeGen/EmitExpression.cs | 51 ++++- .../CSharp/Portable/CodeGen/Optimizer.cs | 10 + .../Portable/FlowAnalysis/AbstractFlowPass.cs | 5 + .../Generated/BoundNodes.xml.Generated.cs | 92 ++++++++- .../LocalRewriter_IsPatternOperator.cs | 21 +- .../Portable/Lowering/SpillSequenceSpiller.cs | 148 ++++++++------ .../CSharp/Test/Emit/CodeGen/PatternTests.cs | 184 ++++++++---------- .../CSharp/Test/Emit/CodeGen/SwitchTests.cs | 2 +- .../Emit2/Semantics/PatternMatchingTests5.cs | 88 +++++---- 11 files changed, 398 insertions(+), 220 deletions(-) diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundNode.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundNode.cs index a406f548b27d5..93a2c44efb109 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundNode.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundNode.cs @@ -544,6 +544,14 @@ private void CheckDeclared(LocalSymbol local) return null; } + public override BoundNode? VisitLoweredIsPatternExpression(BoundLoweredIsPatternExpression node) + { + AddAll(node.Locals); + base.VisitLoweredIsPatternExpression(node); + RemoveAll(node.Locals); + return null; + } + public override BoundNode? VisitSwitchStatement(BoundSwitchStatement node) { AddAll(node.InnerLocals); diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml index db0a64e70a2d5..e12773c35de5d 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml @@ -2265,6 +2265,7 @@ 'is not Type t') will need to compensate for negated patterns. IsNegated is set if Pattern is the negated form of the inner pattern represented by DecisionDag. --> + @@ -2273,6 +2274,14 @@ + + + + + + + + diff --git a/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs b/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs index f6e06e022773e..60d6e55fb6390 100644 --- a/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs +++ b/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs @@ -315,6 +315,10 @@ private void EmitExpressionCore(BoundExpression expression, bool used) EmitRefValueOperator((BoundRefValueOperator)expression, used); break; + case BoundKind.LoweredIsPatternExpression: + EmitLoweredIsPatternExpression((BoundLoweredIsPatternExpression)expression, used); + break; + case BoundKind.LoweredConditionalAccess: EmitLoweredConditionalAccessExpression((BoundLoweredConditionalAccess)expression, used); break; @@ -352,6 +356,33 @@ private void EmitExpressionCore(BoundExpression expression, bool used) } } + private void EmitLoweredIsPatternExpression(BoundLoweredIsPatternExpression node, bool used) + { + _builder.AssertStackEmpty(); + + DefineLocals(node.Syntax, node.Locals); + EmitStatements(node.Statements); + + if (!used) + { + _builder.MarkLabel(node.WhenTrueLabel); + _builder.MarkLabel(node.WhenFalseLabel); + } + else + { + var doneLabel = new object(); + _builder.MarkLabel(node.WhenTrueLabel); + _builder.EmitBoolConstant(true); + _builder.EmitBranch(ILOpCode.Br, doneLabel); + _builder.AdjustStack(-1); + _builder.MarkLabel(node.WhenFalseLabel); + _builder.EmitBoolConstant(false); + _builder.MarkLabel(doneLabel); + } + + FreeLocals(node.Locals); + } + private void EmitThrowExpression(BoundThrowExpression node, bool used) { this.EmitThrow(node.Expression); @@ -852,29 +883,39 @@ private void EmitSequenceExpression(BoundSequence sequence, bool used) private void DefineLocals(BoundSequence sequence) { - if (sequence.Locals.IsEmpty) + DefineLocals(sequence.Syntax, sequence.Locals); + } + + private void DefineLocals(SyntaxNode syntax, ImmutableArray locals) + { + if (locals.IsEmpty) { return; } _builder.OpenLocalScope(); - foreach (var local in sequence.Locals) + foreach (var local in locals) { - DefineLocal(local, sequence.Syntax); + DefineLocal(local, syntax); } } private void FreeLocals(BoundSequence sequence) { - if (sequence.Locals.IsEmpty) + FreeLocals(sequence.Locals); + } + + private void FreeLocals(ImmutableArray locals) + { + if (locals.IsEmpty) { return; } _builder.CloseLocalScope(); - foreach (var local in sequence.Locals) + foreach (var local in locals) { FreeLocal(local); } diff --git a/src/Compilers/CSharp/Portable/CodeGen/Optimizer.cs b/src/Compilers/CSharp/Portable/CodeGen/Optimizer.cs index 36003e3bcf275..016249f8fae22 100644 --- a/src/Compilers/CSharp/Portable/CodeGen/Optimizer.cs +++ b/src/Compilers/CSharp/Portable/CodeGen/Optimizer.cs @@ -1447,6 +1447,16 @@ public override BoundNode VisitSwitchDispatch(BoundSwitchDispatch node) return node.Update(boundExpression, node.Cases, node.DefaultLabel, node.LengthBasedStringSwitchDataOpt); } + public override BoundNode VisitLoweredIsPatternExpression(BoundLoweredIsPatternExpression node) + { + EnsureOnlyEvalStack(); + DeclareLocals(node.Locals, stack: 0); + var result = base.VisitLoweredIsPatternExpression(node); + RecordBranch(node.WhenTrueLabel); + RecordBranch(node.WhenFalseLabel); + return result; + } + public override BoundNode VisitConditionalOperator(BoundConditionalOperator node) { var origStack = StackDepth(); diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs index 707c5bfd71a2b..62ea5b9cb9dca 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs @@ -2973,6 +2973,11 @@ public override BoundNode VisitConditionalReceiver(BoundConditionalReceiver node return null; } + public override BoundNode VisitLoweredIsPatternExpression(BoundLoweredIsPatternExpression node) + { + return null; + } + public override BoundNode VisitComplexConditionalReceiver(BoundComplexConditionalReceiver node) { var savedState = this.State.Clone(); diff --git a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs index efb758b5f0f8d..9f4bd55773cc9 100644 --- a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs @@ -221,6 +221,7 @@ internal enum BoundKind : byte InterpolatedStringArgumentPlaceholder, StringInsert, IsPatternExpression, + LoweredIsPatternExpression, ConstantPattern, DiscardPattern, DeclarationPattern, @@ -7626,7 +7627,7 @@ public BoundStringInsert Update(BoundExpression value, BoundExpression? alignmen internal sealed partial class BoundIsPatternExpression : BoundExpression { - public BoundIsPatternExpression(SyntaxNode syntax, BoundExpression expression, BoundPattern pattern, bool isNegated, BoundDecisionDag reachabilityDecisionDag, LabelSymbol whenTrueLabel, LabelSymbol whenFalseLabel, TypeSymbol? type, bool hasErrors = false) + public BoundIsPatternExpression(SyntaxNode syntax, BoundExpression expression, BoundPattern pattern, bool isNegated, BoundDecisionDag reachabilityDecisionDag, LabelSymbol whenTrueLabel, LabelSymbol whenFalseLabel, TypeSymbol type, bool hasErrors = false) : base(BoundKind.IsPatternExpression, syntax, type, hasErrors || expression.HasErrors() || pattern.HasErrors() || reachabilityDecisionDag.HasErrors()) { @@ -7635,6 +7636,7 @@ public BoundIsPatternExpression(SyntaxNode syntax, BoundExpression expression, B RoslynDebug.Assert(reachabilityDecisionDag is object, "Field 'reachabilityDecisionDag' cannot be null (make the type nullable in BoundNodes.xml to remove this check)"); RoslynDebug.Assert(whenTrueLabel is object, "Field 'whenTrueLabel' cannot be null (make the type nullable in BoundNodes.xml to remove this check)"); RoslynDebug.Assert(whenFalseLabel is object, "Field 'whenFalseLabel' cannot be null (make the type nullable in BoundNodes.xml to remove this check)"); + RoslynDebug.Assert(type is object, "Field 'type' cannot be null (make the type nullable in BoundNodes.xml to remove this check)"); this.Expression = expression; this.Pattern = pattern; @@ -7644,6 +7646,7 @@ public BoundIsPatternExpression(SyntaxNode syntax, BoundExpression expression, B this.WhenFalseLabel = whenFalseLabel; } + public new TypeSymbol Type => base.Type!; public BoundExpression Expression { get; } public BoundPattern Pattern { get; } public bool IsNegated { get; } @@ -7654,7 +7657,7 @@ public BoundIsPatternExpression(SyntaxNode syntax, BoundExpression expression, B [DebuggerStepThrough] public override BoundNode? Accept(BoundTreeVisitor visitor) => visitor.VisitIsPatternExpression(this); - public BoundIsPatternExpression Update(BoundExpression expression, BoundPattern pattern, bool isNegated, BoundDecisionDag reachabilityDecisionDag, LabelSymbol whenTrueLabel, LabelSymbol whenFalseLabel, TypeSymbol? type) + public BoundIsPatternExpression Update(BoundExpression expression, BoundPattern pattern, bool isNegated, BoundDecisionDag reachabilityDecisionDag, LabelSymbol whenTrueLabel, LabelSymbol whenFalseLabel, TypeSymbol type) { if (expression != this.Expression || pattern != this.Pattern || isNegated != this.IsNegated || reachabilityDecisionDag != this.ReachabilityDecisionDag || !Symbols.SymbolEqualityComparer.ConsiderEverything.Equals(whenTrueLabel, this.WhenTrueLabel) || !Symbols.SymbolEqualityComparer.ConsiderEverything.Equals(whenFalseLabel, this.WhenFalseLabel) || !TypeSymbol.Equals(type, this.Type, TypeCompareKind.ConsiderEverything)) { @@ -7666,6 +7669,45 @@ public BoundIsPatternExpression Update(BoundExpression expression, BoundPattern } } + internal sealed partial class BoundLoweredIsPatternExpression : BoundExpression + { + public BoundLoweredIsPatternExpression(SyntaxNode syntax, ImmutableArray locals, ImmutableArray statements, LabelSymbol whenTrueLabel, LabelSymbol whenFalseLabel, TypeSymbol type, bool hasErrors = false) + : base(BoundKind.LoweredIsPatternExpression, syntax, type, hasErrors || statements.HasErrors()) + { + + RoslynDebug.Assert(!locals.IsDefault, "Field 'locals' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)"); + RoslynDebug.Assert(!statements.IsDefault, "Field 'statements' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)"); + RoslynDebug.Assert(whenTrueLabel is object, "Field 'whenTrueLabel' cannot be null (make the type nullable in BoundNodes.xml to remove this check)"); + RoslynDebug.Assert(whenFalseLabel is object, "Field 'whenFalseLabel' cannot be null (make the type nullable in BoundNodes.xml to remove this check)"); + RoslynDebug.Assert(type is object, "Field 'type' cannot be null (make the type nullable in BoundNodes.xml to remove this check)"); + + this.Locals = locals; + this.Statements = statements; + this.WhenTrueLabel = whenTrueLabel; + this.WhenFalseLabel = whenFalseLabel; + } + + public new TypeSymbol Type => base.Type!; + public ImmutableArray Locals { get; } + public ImmutableArray Statements { get; } + public LabelSymbol WhenTrueLabel { get; } + public LabelSymbol WhenFalseLabel { get; } + + [DebuggerStepThrough] + public override BoundNode? Accept(BoundTreeVisitor visitor) => visitor.VisitLoweredIsPatternExpression(this); + + public BoundLoweredIsPatternExpression Update(ImmutableArray locals, ImmutableArray statements, LabelSymbol whenTrueLabel, LabelSymbol whenFalseLabel, TypeSymbol type) + { + if (locals != this.Locals || statements != this.Statements || !Symbols.SymbolEqualityComparer.ConsiderEverything.Equals(whenTrueLabel, this.WhenTrueLabel) || !Symbols.SymbolEqualityComparer.ConsiderEverything.Equals(whenFalseLabel, this.WhenFalseLabel) || !TypeSymbol.Equals(type, this.Type, TypeCompareKind.ConsiderEverything)) + { + var result = new BoundLoweredIsPatternExpression(this.Syntax, locals, statements, whenTrueLabel, whenFalseLabel, type, this.HasErrors); + result.CopyAttributes(this); + return result; + } + return this; + } + } + internal abstract partial class BoundPattern : BoundNode { protected BoundPattern(BoundKind kind, SyntaxNode syntax, TypeSymbol inputType, TypeSymbol narrowedType, bool hasErrors) @@ -8913,6 +8955,8 @@ internal R VisitInternal(BoundNode node, A arg) return VisitStringInsert((BoundStringInsert)node, arg); case BoundKind.IsPatternExpression: return VisitIsPatternExpression((BoundIsPatternExpression)node, arg); + case BoundKind.LoweredIsPatternExpression: + return VisitLoweredIsPatternExpression((BoundLoweredIsPatternExpression)node, arg); case BoundKind.ConstantPattern: return VisitConstantPattern((BoundConstantPattern)node, arg); case BoundKind.DiscardPattern: @@ -9168,6 +9212,7 @@ internal abstract partial class BoundTreeVisitor public virtual R VisitInterpolatedStringArgumentPlaceholder(BoundInterpolatedStringArgumentPlaceholder node, A arg) => this.DefaultVisit(node, arg); public virtual R VisitStringInsert(BoundStringInsert node, A arg) => this.DefaultVisit(node, arg); public virtual R VisitIsPatternExpression(BoundIsPatternExpression node, A arg) => this.DefaultVisit(node, arg); + public virtual R VisitLoweredIsPatternExpression(BoundLoweredIsPatternExpression node, A arg) => this.DefaultVisit(node, arg); public virtual R VisitConstantPattern(BoundConstantPattern node, A arg) => this.DefaultVisit(node, arg); public virtual R VisitDiscardPattern(BoundDiscardPattern node, A arg) => this.DefaultVisit(node, arg); public virtual R VisitDeclarationPattern(BoundDeclarationPattern node, A arg) => this.DefaultVisit(node, arg); @@ -9396,6 +9441,7 @@ internal abstract partial class BoundTreeVisitor public virtual BoundNode? VisitInterpolatedStringArgumentPlaceholder(BoundInterpolatedStringArgumentPlaceholder node) => this.DefaultVisit(node); public virtual BoundNode? VisitStringInsert(BoundStringInsert node) => this.DefaultVisit(node); public virtual BoundNode? VisitIsPatternExpression(BoundIsPatternExpression node) => this.DefaultVisit(node); + public virtual BoundNode? VisitLoweredIsPatternExpression(BoundLoweredIsPatternExpression node) => this.DefaultVisit(node); public virtual BoundNode? VisitConstantPattern(BoundConstantPattern node) => this.DefaultVisit(node); public virtual BoundNode? VisitDiscardPattern(BoundDiscardPattern node) => this.DefaultVisit(node); public virtual BoundNode? VisitDeclarationPattern(BoundDeclarationPattern node) => this.DefaultVisit(node); @@ -10299,6 +10345,11 @@ internal abstract partial class BoundTreeWalker : BoundTreeVisitor this.Visit(node.Pattern); return null; } + public override BoundNode? VisitLoweredIsPatternExpression(BoundLoweredIsPatternExpression node) + { + this.VisitList(node.Statements); + return null; + } public override BoundNode? VisitConstantPattern(BoundConstantPattern node) { this.Visit(node.Value); @@ -11616,6 +11667,12 @@ internal abstract partial class BoundTreeRewriter : BoundTreeVisitor TypeSymbol? type = this.VisitType(node.Type); return node.Update(expression, pattern, node.IsNegated, reachabilityDecisionDag, node.WhenTrueLabel, node.WhenFalseLabel, type); } + public override BoundNode? VisitLoweredIsPatternExpression(BoundLoweredIsPatternExpression node) + { + ImmutableArray statements = this.VisitList(node.Statements); + TypeSymbol? type = this.VisitType(node.Type); + return node.Update(node.Locals, statements, node.WhenTrueLabel, node.WhenFalseLabel, type); + } public override BoundNode? VisitConstantPattern(BoundConstantPattern node) { BoundExpression value = (BoundExpression)this.Visit(node.Value); @@ -14160,7 +14217,7 @@ public NullabilityRewriter(ImmutableDictionary locals = GetUpdatedArray(node, node.Locals); + ImmutableArray statements = this.VisitList(node.Statements); + BoundLoweredIsPatternExpression updatedNode; + + if (_updatedNullabilities.TryGetValue(node, out (NullabilityInfo Info, TypeSymbol? Type) infoAndType)) + { + updatedNode = node.Update(locals, statements, node.WhenTrueLabel, node.WhenFalseLabel, infoAndType.Type!); + updatedNode.TopLevelNullability = infoAndType.Info; + } + else + { + updatedNode = node.Update(locals, statements, node.WhenTrueLabel, node.WhenFalseLabel, node.Type); + } + return updatedNode; + } + public override BoundNode? VisitConstantPattern(BoundConstantPattern node) { TypeSymbol inputType = GetUpdatedSymbol(node, node.InputType); @@ -16284,6 +16359,17 @@ private BoundTreeDumperNodeProducer() new TreeDumperNode("hasErrors", node.HasErrors, null) } ); + public override TreeDumperNode VisitLoweredIsPatternExpression(BoundLoweredIsPatternExpression node, object? arg) => new TreeDumperNode("loweredIsPatternExpression", null, new TreeDumperNode[] + { + new TreeDumperNode("locals", node.Locals, null), + new TreeDumperNode("statements", null, from x in node.Statements select Visit(x, null)), + new TreeDumperNode("whenTrueLabel", node.WhenTrueLabel, null), + new TreeDumperNode("whenFalseLabel", node.WhenFalseLabel, null), + new TreeDumperNode("type", node.Type, null), + new TreeDumperNode("isSuppressed", node.IsSuppressed, null), + new TreeDumperNode("hasErrors", node.HasErrors, null) + } + ); public override TreeDumperNode VisitConstantPattern(BoundConstantPattern node, object? arg) => new TreeDumperNode("constantPattern", null, new TreeDumperNode[] { new TreeDumperNode("value", null, new TreeDumperNode[] { Visit(node.Value, null) }), diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IsPatternOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IsPatternOperator.cs index 753a242ab44fc..b20701b2e9f5d 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IsPatternOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IsPatternOperator.cs @@ -108,6 +108,8 @@ public IsPatternExpressionGeneralLocalRewriter( internal BoundExpression LowerGeneralIsPattern(BoundIsPatternExpression node, BoundDecisionDag decisionDag) { + Debug.Assert(node.Type is { SpecialType: SpecialType.System_Boolean }); + _factory.Syntax = node.Syntax; var resultBuilder = ArrayBuilder.GetInstance(); var inputExpression = _localRewriter.VisitExpression(node.Expression); @@ -115,22 +117,11 @@ internal BoundExpression LowerGeneralIsPattern(BoundIsPatternExpression node, Bo // lower the decision dag. ImmutableArray loweredDag = LowerDecisionDagCore(decisionDag); - resultBuilder.Add(_factory.Block(loweredDag)); - Debug.Assert(node.Type is { SpecialType: SpecialType.System_Boolean }); - LocalSymbol resultTemp = _factory.SynthesizedLocal(node.Type, node.Syntax, kind: SynthesizedLocalKind.LoweringTemp); - LabelSymbol afterIsPatternExpression = _factory.GenerateLabel("afterIsPatternExpression"); - LabelSymbol trueLabel = node.WhenTrueLabel; - LabelSymbol falseLabel = node.WhenFalseLabel; - if (_statements.Count != 0) - resultBuilder.Add(_factory.Block(_statements.ToArray())); - resultBuilder.Add(_factory.Label(trueLabel)); - resultBuilder.Add(_factory.Assignment(_factory.Local(resultTemp), _factory.Literal(true))); - resultBuilder.Add(_factory.Goto(afterIsPatternExpression)); - resultBuilder.Add(_factory.Label(falseLabel)); - resultBuilder.Add(_factory.Assignment(_factory.Local(resultTemp), _factory.Literal(false))); - resultBuilder.Add(_factory.Label(afterIsPatternExpression)); + resultBuilder.AddRange(loweredDag); + resultBuilder.AddRange(_statements); _localRewriter._needsSpilling = true; - return _factory.SpillSequence(_tempAllocator.AllTemps().Add(resultTemp), resultBuilder.ToImmutableAndFree(), _factory.Local(resultTemp)); + return new BoundLoweredIsPatternExpression(node.Syntax, _tempAllocator.AllTemps(), + resultBuilder.ToImmutableAndFree(), node.WhenTrueLabel, node.WhenFalseLabel, node.Type); } } diff --git a/src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs b/src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs index 631b689c990a8..f106c64fc4beb 100644 --- a/src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs +++ b/src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs @@ -45,7 +45,7 @@ private sealed class BoundSpillSequenceBuilder : BoundExpression public BoundSpillSequenceBuilder(SyntaxNode syntax, BoundExpression value = null) : base(SpillSequenceBuilderKind, syntax, value?.Type) { - Debug.Assert(value?.Kind != SpillSequenceBuilderKind); + Debug.Assert(value is null || !IsSpillSequenceProducer(value)); this.Value = value; } @@ -122,12 +122,7 @@ private static void IncludeAndFree(ref ArrayBuilder left, ref ArrayBuilder public void AddLocal(LocalSymbol local) { - if (_locals == null) - { - _locals = ArrayBuilder.GetInstance(); - } - - _locals.Add(local); + (_locals ??= ArrayBuilder.GetInstance()).Add(local); } public void AddLocals(ImmutableArray locals) @@ -140,12 +135,7 @@ public void AddLocals(ImmutableArray locals) public void AddStatement(BoundStatement statement) { - if (_statements == null) - { - _statements = ArrayBuilder.GetInstance(); - } - - _statements.Add(statement); + (_statements ??= ArrayBuilder.GetInstance()).Add(statement); } public void AddStatements(ImmutableArray statements) @@ -242,25 +232,43 @@ internal static BoundStatement Rewrite(BoundStatement body, MethodSymbol method, return (BoundStatement)result; } - private BoundExpression VisitExpression(ref BoundSpillSequenceBuilder builder, BoundExpression expression) + private BoundExpression VisitTopLevelExpression(ref BoundSpillSequenceBuilder builder, BoundExpression expression) + { + Debug.Assert(builder is null); + return VisitExpression(ref builder, expression, topLevel: true); + } + + private BoundExpression VisitExpression(ref BoundSpillSequenceBuilder builder, BoundExpression expression, bool topLevel = false) { var e = (BoundExpression)this.Visit(expression); - if (e == null || e.Kind != SpillSequenceBuilderKind) + switch (e) { - return e; - } + case BoundSpillSequenceBuilder newBuilder: + if (builder == null) + { + builder = newBuilder.Update(null); + } + else + { + builder.Include(newBuilder); + } - var newBuilder = (BoundSpillSequenceBuilder)e; - if (builder == null) - { - builder = newBuilder.Update(null); - } - else - { - builder.Include(newBuilder); - } + return newBuilder.Value; + + case BoundLoweredIsPatternExpression loweredIs: + if (!topLevel) + { + builder ??= new BoundSpillSequenceBuilder(expression.Syntax); - return newBuilder.Value; + return Spill(builder, loweredIs); + } + + return loweredIs; + + default: + Debug.Assert(e is null || !IsSpillSequenceProducer(e)); + return e; + } } private static BoundExpression UpdateExpression(BoundSpillSequenceBuilder builder, BoundExpression expression) @@ -429,6 +437,24 @@ private BoundExpression Spill( // later, if needed return expression; + case BoundKind.LoweredIsPatternExpression: + var loweredIs = (BoundLoweredIsPatternExpression)expression; + + var doneLabel = _F.GenerateLabel("afterIsExpression"); + var resultTemp = _F.SynthesizedLocal(loweredIs.Type, syntax: _F.Syntax); + + builder.AddLocals(loweredIs.Locals); + builder.AddLocal(resultTemp); + builder.AddStatements(loweredIs.Statements); + builder.AddStatement(_F.Label(loweredIs.WhenTrueLabel)); + builder.AddStatement(_F.Assignment(_F.Local(resultTemp), _F.Literal(true))); + builder.AddStatement(_F.Goto(doneLabel)); + builder.AddStatement(_F.Label(loweredIs.WhenFalseLabel)); + builder.AddStatement(_F.Assignment(_F.Local(resultTemp), _F.Literal(false))); + builder.AddStatement(_F.Label(doneLabel)); + + return _F.Local(resultTemp); + default: if (expression.Type.IsVoidType() || sideEffectsOnly) { @@ -537,7 +563,7 @@ private ImmutableArray VisitExpressionList( lastSpill = -1; for (int i = newList.Length - 1; i >= 0; i--) { - if (newList[i].Kind == SpillSequenceBuilderKind) + if (IsSpillSequenceProducer(newList[i])) { lastSpill = i; break; @@ -550,10 +576,8 @@ private ImmutableArray VisitExpressionList( return newList; } - if (builder == null) - { - builder = new BoundSpillSequenceBuilder(lastSpill < newList.Length ? (newList[lastSpill] as BoundSpillSequenceBuilder)?.Syntax : null); - } + var lastSpillNode = lastSpill < newList.Length ? newList[lastSpill] : null; + builder ??= new BoundSpillSequenceBuilder(lastSpillNode?.Syntax); var result = ArrayBuilder.GetInstance(newList.Length); @@ -574,9 +598,18 @@ private ImmutableArray VisitExpressionList( // the value of the last spill and everything that follows is not spilled if (lastSpill < newList.Length) { - var lastSpillNode = (BoundSpillSequenceBuilder)newList[lastSpill]; - builder.Include(lastSpillNode); - result.Add(lastSpillNode.Value); + switch (lastSpillNode) + { + case BoundSpillSequenceBuilder newBuilder: + builder.Include(newBuilder); + result.Add(newBuilder.Value); + break; + case BoundLoweredIsPatternExpression loweredIs: + result.Add(Spill(builder, loweredIs)); + break; + default: + throw ExceptionUtilities.Unreachable(); + } for (int i = lastSpill + 1; i < newList.Length; i++) { @@ -587,26 +620,31 @@ private ImmutableArray VisitExpressionList( return result.ToImmutableAndFree(); } + private static bool IsSpillSequenceProducer(BoundExpression expression) + { + return expression.Kind is SpillSequenceBuilderKind or BoundKind.LoweredIsPatternExpression; + } + #region Statement Visitors public override BoundNode VisitSwitchDispatch(BoundSwitchDispatch node) { BoundSpillSequenceBuilder builder = null; - var expression = VisitExpression(ref builder, node.Expression); + var expression = VisitTopLevelExpression(ref builder, node.Expression); return UpdateStatement(builder, node.Update(expression, node.Cases, node.DefaultLabel, node.LengthBasedStringSwitchDataOpt)); } public override BoundNode VisitThrowStatement(BoundThrowStatement node) { BoundSpillSequenceBuilder builder = null; - BoundExpression expression = VisitExpression(ref builder, node.ExpressionOpt); + BoundExpression expression = VisitTopLevelExpression(ref builder, node.ExpressionOpt); return UpdateStatement(builder, node.Update(expression)); } public override BoundNode VisitExpressionStatement(BoundExpressionStatement node) { BoundSpillSequenceBuilder builder = null; - BoundExpression expr = VisitExpression(ref builder, node.Expression); + BoundExpression expr = VisitTopLevelExpression(ref builder, node.Expression); Debug.Assert(expr != null); Debug.Assert(builder == null || builder.Value == null); return UpdateStatement(builder, node.Update(expr)); @@ -615,21 +653,21 @@ public override BoundNode VisitExpressionStatement(BoundExpressionStatement node public override BoundNode VisitConditionalGoto(BoundConditionalGoto node) { BoundSpillSequenceBuilder builder = null; - var condition = VisitExpression(ref builder, node.Condition); + var condition = VisitTopLevelExpression(ref builder, node.Condition); return UpdateStatement(builder, node.Update(condition, node.JumpIfTrue, node.Label)); } public override BoundNode VisitReturnStatement(BoundReturnStatement node) { BoundSpillSequenceBuilder builder = null; - var expression = VisitExpression(ref builder, node.ExpressionOpt); + var expression = VisitTopLevelExpression(ref builder, node.ExpressionOpt); return UpdateStatement(builder, node.Update(node.RefKind, expression, @checked: node.Checked)); } public override BoundNode VisitYieldReturnStatement(BoundYieldReturnStatement node) { BoundSpillSequenceBuilder builder = null; - var expression = VisitExpression(ref builder, node.Expression); + var expression = VisitTopLevelExpression(ref builder, node.Expression); return UpdateStatement(builder, node.Update(expression)); } @@ -666,6 +704,12 @@ public override BoundNode DefaultVisit(BoundNode node) #region Expression Visitors + public override BoundNode VisitLoweredIsPatternExpression(BoundLoweredIsPatternExpression node) + { + // the spilling will occur in the enclosing node. + return node; + } + public override BoundNode VisitAwaitExpression(BoundAwaitExpression node) { // An await expression has already been wrapped in a BoundSpillSequence if not at the top level, so @@ -713,10 +757,7 @@ public override BoundNode VisitArrayAccess(BoundArrayAccess node) if (indicesBuilder != null) { // spill the array if there were await expressions in the indices - if (builder == null) - { - builder = new BoundSpillSequenceBuilder(indicesBuilder.Syntax); - } + builder ??= new BoundSpillSequenceBuilder(indicesBuilder.Syntax); expression = Spill(builder, expression); } @@ -1046,9 +1087,9 @@ public override BoundNode VisitConditionalOperator(BoundConditionalOperator node return UpdateExpression(conditionBuilder, node.Update(node.IsRef, condition, consequence, alternative, node.ConstantValueOpt, node.NaturalTypeOpt, node.WasTargetTyped, node.Type)); } - if (conditionBuilder == null) conditionBuilder = new BoundSpillSequenceBuilder((consequenceBuilder ?? alternativeBuilder).Syntax); - if (consequenceBuilder == null) consequenceBuilder = new BoundSpillSequenceBuilder(alternativeBuilder.Syntax); - if (alternativeBuilder == null) alternativeBuilder = new BoundSpillSequenceBuilder(consequenceBuilder.Syntax); + conditionBuilder ??= new BoundSpillSequenceBuilder((consequenceBuilder ?? alternativeBuilder).Syntax); + consequenceBuilder ??= new BoundSpillSequenceBuilder(alternativeBuilder.Syntax); + alternativeBuilder ??= new BoundSpillSequenceBuilder(consequenceBuilder.Syntax); if (node.Type.IsVoidType()) { @@ -1180,9 +1221,9 @@ public override BoundNode VisitLoweredConditionalAccess(BoundLoweredConditionalA return UpdateExpression(receiverBuilder, node.Update(receiver, node.HasValueMethodOpt, whenNotNull, whenNullOpt, node.Id, node.ForceCopyOfNullableValueType, node.Type)); } - if (receiverBuilder == null) receiverBuilder = new BoundSpillSequenceBuilder((whenNotNullBuilder ?? whenNullBuilder).Syntax); - if (whenNotNullBuilder == null) whenNotNullBuilder = new BoundSpillSequenceBuilder(whenNullBuilder.Syntax); - if (whenNullBuilder == null) whenNullBuilder = new BoundSpillSequenceBuilder(whenNotNullBuilder.Syntax); + receiverBuilder ??= new BoundSpillSequenceBuilder((whenNotNullBuilder ?? whenNullBuilder).Syntax); + whenNotNullBuilder ??= new BoundSpillSequenceBuilder(whenNullBuilder.Syntax); + whenNullBuilder ??= new BoundSpillSequenceBuilder(whenNotNullBuilder.Syntax); BoundExpression condition; if (receiver.Type.IsReferenceType || receiver.Type.IsValueType || receiverRefKind == RefKind.None) @@ -1367,10 +1408,7 @@ public override BoundNode VisitSequence(BoundSequence node) return node.Update(node.Locals, sideEffects, value, node.Type); } - if (builder == null) - { - builder = new BoundSpillSequenceBuilder(valueBuilder.Syntax); - } + builder ??= new BoundSpillSequenceBuilder(valueBuilder.Syntax); PromoteAndAddLocals(builder, node.Locals); builder.AddExpressions(sideEffects); diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs index caad6cb2b227c..2167d0711ef17 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs @@ -5519,23 +5519,19 @@ public static void Main() var compVerifier = CompileAndVerify(compilation, expectedOutput: expectedOutput); compVerifier.VerifyIL("C.M1", """ { - // Code size 26 (0x1a) + // Code size 23 (0x17) .maxstack 1 - .locals init (bool V_0) IL_0000: ldarg.0 IL_0001: isinst "int" IL_0006: brtrue.s IL_0012 IL_0008: ldarg.0 IL_0009: isinst "long" IL_000e: brtrue.s IL_0012 - IL_0010: br.s IL_0016 + IL_0010: br.s IL_0015 IL_0012: ldc.i4.1 - IL_0013: stloc.0 - IL_0014: br.s IL_0018 - IL_0016: ldc.i4.0 - IL_0017: stloc.0 - IL_0018: ldloc.0 - IL_0019: ret + IL_0013: br.s IL_0016 + IL_0015: ldc.i4.0 + IL_0016: ret } """); compVerifier.VerifyIL("C.M2", """ @@ -5560,22 +5556,18 @@ .maxstack 2 compVerifier = CompileAndVerify(compilation, expectedOutput: expectedOutput); compVerifier.VerifyIL("C.M1", """ { - // Code size 24 (0x18) + // Code size 20 (0x14) .maxstack 1 - .locals init (bool V_0) IL_0000: ldarg.0 IL_0001: isinst "int" IL_0006: brtrue.s IL_0010 IL_0008: ldarg.0 IL_0009: isinst "long" - IL_000e: brfalse.s IL_0014 + IL_000e: brfalse.s IL_0012 IL_0010: ldc.i4.1 - IL_0011: stloc.0 - IL_0012: br.s IL_0016 - IL_0014: ldc.i4.0 - IL_0015: stloc.0 - IL_0016: ldloc.0 - IL_0017: ret + IL_0011: ret + IL_0012: ldc.i4.0 + IL_0013: ret } """); compVerifier.VerifyIL("C.M2", @" @@ -5725,41 +5717,37 @@ public static void Main() compilation.VerifyDiagnostics(); var expectedOutput = @"TrueFalseTrueFalse"; var compVerifier = CompileAndVerify(compilation, expectedOutput: expectedOutput); - compVerifier.VerifyIL("C.M1", @" - { - // Code size 47 (0x2f) - .maxstack 2 - .locals init (char V_0, - bool V_1) - IL_0000: ldarg.0 - IL_0001: isinst ""char"" - IL_0006: brfalse.s IL_002b - IL_0008: ldarg.0 - IL_0009: unbox.any ""char"" - IL_000e: stloc.0 - IL_000f: ldloc.0 - IL_0010: ldc.i4.s 97 - IL_0012: blt.s IL_001b - IL_0014: ldloc.0 - IL_0015: ldc.i4.s 122 - IL_0017: ble.s IL_0027 - IL_0019: br.s IL_002b - IL_001b: ldloc.0 - IL_001c: ldc.i4.s 65 - IL_001e: blt.s IL_002b - IL_0020: ldloc.0 - IL_0021: ldc.i4.s 90 - IL_0023: ble.s IL_0027 - IL_0025: br.s IL_002b - IL_0027: ldc.i4.1 - IL_0028: stloc.1 - IL_0029: br.s IL_002d - IL_002b: ldc.i4.0 - IL_002c: stloc.1 - IL_002d: ldloc.1 - IL_002e: ret - } -"); + compVerifier.VerifyIL("C.M1", """ +{ + // Code size 44 (0x2c) + .maxstack 2 + .locals init (char V_0) + IL_0000: ldarg.0 + IL_0001: isinst "char" + IL_0006: brfalse.s IL_002a + IL_0008: ldarg.0 + IL_0009: unbox.any "char" + IL_000e: stloc.0 + IL_000f: ldloc.0 + IL_0010: ldc.i4.s 97 + IL_0012: blt.s IL_001b + IL_0014: ldloc.0 + IL_0015: ldc.i4.s 122 + IL_0017: ble.s IL_0027 + IL_0019: br.s IL_002a + IL_001b: ldloc.0 + IL_001c: ldc.i4.s 65 + IL_001e: blt.s IL_002a + IL_0020: ldloc.0 + IL_0021: ldc.i4.s 90 + IL_0023: ble.s IL_0027 + IL_0025: br.s IL_002a + IL_0027: ldc.i4.1 + IL_0028: br.s IL_002b + IL_002a: ldc.i4.0 + IL_002b: ret +} +"""); compVerifier.VerifyIL("C.M2", @" { // Code size 48 (0x30) @@ -5798,40 +5786,36 @@ .locals init (char V_0) //c compilation = CreateCompilation(source, options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularWithPatternCombinators); compilation.VerifyDiagnostics(); compVerifier = CompileAndVerify(compilation, expectedOutput: expectedOutput); - compVerifier.VerifyIL("C.M1", @" - { - // Code size 45 (0x2d) - .maxstack 2 - .locals init (char V_0, - bool V_1) - IL_0000: ldarg.0 - IL_0001: isinst ""char"" - IL_0006: brfalse.s IL_0029 - IL_0008: ldarg.0 - IL_0009: unbox.any ""char"" - IL_000e: stloc.0 - IL_000f: ldloc.0 - IL_0010: ldc.i4.s 97 - IL_0012: blt.s IL_001b - IL_0014: ldloc.0 - IL_0015: ldc.i4.s 122 - IL_0017: ble.s IL_0025 - IL_0019: br.s IL_0029 - IL_001b: ldloc.0 - IL_001c: ldc.i4.s 65 - IL_001e: blt.s IL_0029 - IL_0020: ldloc.0 - IL_0021: ldc.i4.s 90 - IL_0023: bgt.s IL_0029 - IL_0025: ldc.i4.1 - IL_0026: stloc.1 - IL_0027: br.s IL_002b - IL_0029: ldc.i4.0 - IL_002a: stloc.1 - IL_002b: ldloc.1 - IL_002c: ret - } -"); + compVerifier.VerifyIL("C.M1", """ +{ + // Code size 41 (0x29) + .maxstack 2 + .locals init (char V_0) + IL_0000: ldarg.0 + IL_0001: isinst "char" + IL_0006: brfalse.s IL_0027 + IL_0008: ldarg.0 + IL_0009: unbox.any "char" + IL_000e: stloc.0 + IL_000f: ldloc.0 + IL_0010: ldc.i4.s 97 + IL_0012: blt.s IL_001b + IL_0014: ldloc.0 + IL_0015: ldc.i4.s 122 + IL_0017: ble.s IL_0025 + IL_0019: br.s IL_0027 + IL_001b: ldloc.0 + IL_001c: ldc.i4.s 65 + IL_001e: blt.s IL_0027 + IL_0020: ldloc.0 + IL_0021: ldc.i4.s 90 + IL_0023: bgt.s IL_0027 + IL_0025: ldc.i4.1 + IL_0026: ret + IL_0027: ldc.i4.0 + IL_0028: ret +} +"""); compVerifier.VerifyIL("C.M2", @" { // Code size 45 (0x2d) @@ -6118,33 +6102,29 @@ .locals init (bool V_0, compVerifier = CompileAndVerify(compilation, expectedOutput: expectedOutput); compVerifier.VerifyIL("C.M1", @" { - // Code size 35 (0x23) + // Code size 32 (0x20) .maxstack 2 - .locals init (bool V_0) IL_0000: ldarg.0 IL_0001: ldc.i4.s 97 IL_0003: blt.s IL_000c IL_0005: ldarg.0 IL_0006: ldc.i4.s 122 IL_0008: ble.s IL_0016 - IL_000a: br.s IL_001a + IL_000a: br.s IL_0019 IL_000c: ldarg.0 IL_000d: ldc.i4.s 65 - IL_000f: blt.s IL_001a + IL_000f: blt.s IL_0019 IL_0011: ldarg.0 IL_0012: ldc.i4.s 90 - IL_0014: bgt.s IL_001a + IL_0014: bgt.s IL_0019 IL_0016: ldc.i4.1 - IL_0017: stloc.0 - IL_0018: br.s IL_001c - IL_001a: ldc.i4.0 - IL_001b: stloc.0 - IL_001c: ldloc.0 - IL_001d: brfalse.s IL_0021 - IL_001f: ldc.i4.1 - IL_0020: ret - IL_0021: ldc.i4.0 - IL_0022: ret + IL_0017: br.s IL_001a + IL_0019: ldc.i4.0 + IL_001a: brfalse.s IL_001e + IL_001c: ldc.i4.1 + IL_001d: ret + IL_001e: ldc.i4.0 + IL_001f: ret } "); compVerifier.VerifyIL("C.M2", @" diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/SwitchTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/SwitchTests.cs index 9e114ce1aec4a..7d66a3a0286e5 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/SwitchTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/SwitchTests.cs @@ -7125,7 +7125,7 @@ .maxstack 2 IL_0760: ret }"; compVerifier.VerifyIL("ConsoleApplication24.Program.IsWarning", codeForSwitchStatement); - compVerifier.VerifyIL("ConsoleApplication24.Program.IsWarning_IsExpression", codeForExpression); + compVerifier.VerifyIL("ConsoleApplication24.Program.IsWarning_IsExpression", codeForSwitchStatement); compVerifier.VerifyIL("ConsoleApplication24.Program.IsWarning_SwitchExpression", codeForExpression); } diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/PatternMatchingTests5.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/PatternMatchingTests5.cs index 0120dc6e33d29..14bfe93d9dc4a 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/PatternMatchingTests5.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/PatternMatchingTests5.cs @@ -3178,21 +3178,17 @@ public static bool Test(int a) var compilation = CompileAndVerify(source, expectedOutput: "True"); compilation.VerifyIL("C.Test", """ { - // Code size 14 (0xe) + // Code size 10 (0xa) .maxstack 2 - .locals init (bool V_0) IL_0000: ldarg.0 IL_0001: ldc.i4.1 IL_0002: sub IL_0003: ldc.i4.7 - IL_0004: bgt.un.s IL_000a + IL_0004: bgt.un.s IL_0008 IL_0006: ldc.i4.1 - IL_0007: stloc.0 - IL_0008: br.s IL_000c - IL_000a: ldc.i4.0 - IL_000b: stloc.0 - IL_000c: ldloc.0 - IL_000d: ret + IL_0007: ret + IL_0008: ldc.i4.0 + IL_0009: ret } """); } @@ -3227,9 +3223,8 @@ public static bool Test(int a, object b) var compilation = CompileAndVerify(source, expectedOutput: "True"); compilation.VerifyIL("C.Test", """ { - // Code size 72 (0x48) + // Code size 68 (0x44) .maxstack 2 - .locals init (bool V_0) IL_0000: ldarg.0 IL_0001: ldc.i4.1 IL_0002: sub @@ -3238,29 +3233,26 @@ .locals init (bool V_0) IL_0024, IL_002e, IL_0038) - IL_0018: br.s IL_0044 + IL_0018: br.s IL_0042 IL_001a: ldarg.1 IL_001b: isinst "int" IL_0020: brtrue.s IL_0040 - IL_0022: br.s IL_0044 + IL_0022: br.s IL_0042 IL_0024: ldarg.1 IL_0025: isinst "bool" IL_002a: brtrue.s IL_0040 - IL_002c: br.s IL_0044 + IL_002c: br.s IL_0042 IL_002e: ldarg.1 IL_002f: isinst "double" IL_0034: brtrue.s IL_0040 - IL_0036: br.s IL_0044 + IL_0036: br.s IL_0042 IL_0038: ldarg.1 IL_0039: isinst "long" - IL_003e: brfalse.s IL_0044 + IL_003e: brfalse.s IL_0042 IL_0040: ldc.i4.1 - IL_0041: stloc.0 - IL_0042: br.s IL_0046 - IL_0044: ldc.i4.0 - IL_0045: stloc.0 - IL_0046: ldloc.0 - IL_0047: ret + IL_0041: ret + IL_0042: ldc.i4.0 + IL_0043: ret } """); } @@ -3294,24 +3286,23 @@ public static bool Test(string a) var compilation = CompileAndVerify(source, expectedOutput: "True"); compilation.VerifyIL("C.Test", """ { - // Code size 73 (0x49) + // Code size 69 (0x45) .maxstack 2 - .locals init (bool V_0, - int V_1, - char V_2) + .locals init (int V_0, + char V_1) IL_0000: ldarg.0 - IL_0001: brfalse.s IL_0045 + IL_0001: brfalse.s IL_0043 IL_0003: ldarg.0 IL_0004: call "int string.Length.get" - IL_0009: stloc.1 - IL_000a: ldloc.1 + IL_0009: stloc.0 + IL_000a: ldloc.0 IL_000b: ldc.i4.1 - IL_000c: bne.un.s IL_0045 + IL_000c: bne.un.s IL_0043 IL_000e: ldarg.0 IL_000f: ldc.i4.0 IL_0010: call "char string.this[int].get" - IL_0015: stloc.2 - IL_0016: ldloc.2 + IL_0015: stloc.1 + IL_0016: ldloc.1 IL_0017: ldc.i4.s 49 IL_0019: sub IL_001a: switch ( @@ -3323,14 +3314,11 @@ .locals init (bool V_0, IL_0041, IL_0041, IL_0041) - IL_003f: br.s IL_0045 + IL_003f: br.s IL_0043 IL_0041: ldc.i4.1 - IL_0042: stloc.0 - IL_0043: br.s IL_0047 - IL_0045: ldc.i4.0 - IL_0046: stloc.0 - IL_0047: ldloc.0 - IL_0048: ret + IL_0042: ret + IL_0043: ldc.i4.0 + IL_0044: ret } """); } @@ -3356,5 +3344,27 @@ bool M0({type} x0) Diagnostic(ErrorCode.ERR_ConstantExpected, expression).WithLocation(6, 22) ); } + + [Fact] + public void BootstrapFail() + { + var source = """ +using System; +using System.Diagnostics; + +class C +{ + char PeekNextChar() => ' '; + void Assert(bool b) {} + object ParseParameterList() + { + Assert(PeekNextChar() is '(' or '['); + return null; + } +} +"""; + var verifier = CompileAndVerify(source, options: TestOptions.ReleaseDll); + + } } }