未验证 提交 b0017931 编写于 作者: J Julien Couvreur 提交者: GitHub

Add BoundNode.DumpSource() method. Improve AssertEx.SetEqual() method (#31526)

上级 4e9a56d6
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis.CSharp.Symbols;
namespace Microsoft.CodeAnalysis.CSharp
{
internal abstract partial class BoundNode
{
#if DEBUG
/// <summary>
/// Gives an approximate printout of a bound node as C# code.
/// </summary>
internal string DumpSource()
{
int indentSize = 4;
var builder = new StringBuilder();
appendSourceCore(this, indent: 0, tempIdentifiers: new Dictionary<SynthesizedLocal, int>());
return builder.ToString();
void appendSourceCore(BoundNode node, int indent, Dictionary<SynthesizedLocal, int> tempIdentifiers)
{
switch (node)
{
case BoundTryStatement tryStatement:
{
appendLine("try");
appendSource(tryStatement.TryBlock);
var catchBlocks = tryStatement.CatchBlocks;
if (catchBlocks != null)
{
foreach (var catchBlock in catchBlocks)
{
append("catch (");
append(catchBlock.ExceptionTypeOpt.Name);
append(") ");
if (catchBlock.ExceptionFilterOpt != null)
{
append("... exception filter ommitted ...");
}
appendLine("");
appendSource(catchBlock.Body);
}
}
var finallyBlock = tryStatement.FinallyBlockOpt;
if (finallyBlock != null)
{
appendLine("finally");
appendSource(finallyBlock);
}
break;
}
case BoundThrowStatement throwStatement:
{
append("throw ");
if (throwStatement.ExpressionOpt != null)
{
appendSource(throwStatement.ExpressionOpt);
}
appendLine(";");
break;
}
case BoundBlock block:
{
var statements = block.Statements;
if (statements.Length == 1 && block.Locals.IsEmpty)
{
appendSource(statements[0]);
break;
}
appendLine("{");
foreach (var local in block.Locals)
{
if (local is SynthesizedLocal synthesized)
{
appendLine($"{local.Type.ToDisplayString()} {name(synthesized)};");
}
else
{
appendLine($"({local.GetDebuggerDisplay()});");
}
}
foreach (var statement in statements)
{
appendSource(statement);
}
appendLine("}");
break;
}
case BoundStateMachineScope stateMachineScope:
{
appendSource(stateMachineScope.Statement);
break;
}
case BoundSequencePoint seqPoint:
{
var statement = seqPoint.StatementOpt;
if (statement != null)
{
appendSource(statement);
}
break;
}
case BoundSequencePointExpression seqPoint:
{
var expression = seqPoint.Expression;
appendSource(expression);
break;
}
case BoundSequencePointWithSpan seqPoint:
{
var statement = seqPoint.StatementOpt;
if (statement != null)
{
appendSource(statement);
}
break;
}
case BoundYieldReturnStatement yieldStatement:
{
append("yield return ");
appendSource(yieldStatement.Expression);
appendLine(";");
break;
}
case BoundReturnStatement returnStatement:
{
append("return");
var value = returnStatement.ExpressionOpt;
if (value != null)
{
append(" ");
appendSource(value);
}
appendLine(";");
break;
}
case BoundGotoStatement gotoStatement:
{
append("goto ");
append(gotoStatement.Label.ToString());
appendLine(";");
break;
}
case BoundConditionalGoto gotoStatement:
{
append("if (");
append(gotoStatement.JumpIfTrue ? "" : "!");
appendSource(gotoStatement.Condition);
append(") ");
append("goto ");
append(gotoStatement.Label.ToString());
appendLine(";");
break;
}
case BoundLabelStatement label:
{
append(label.Label.ToString());
appendLine(": ;");
break;
}
case BoundTypeExpression type:
{
append(type.Type.Name);
break;
}
case BoundLocal local:
{
var symbol = local.LocalSymbol;
appendLocal(symbol);
break;
}
case BoundNoOpStatement noop:
{
break;
}
case BoundExpressionStatement expressionStatement:
{
appendSource(expressionStatement.Expression);
appendLine(";");
break;
}
case BoundAwaitExpression awaitExpression:
{
append("await ");
appendSource(awaitExpression.Expression);
break;
}
case BoundCall call:
{
var receiver = call.ReceiverOpt;
if (receiver != null)
{
appendSource(receiver);
append(".");
}
append(call.Method.Name);
append("(");
bool first = true;
foreach (var argument in call.Arguments)
{
if (!first)
{
append(", ");
}
first = false;
appendSource(argument);
}
append(")");
break;
}
case BoundLiteral literal:
{
var value = literal.ConstantValue.Value?.ToString();
if (value is null)
{
append("null");
break;
}
switch (literal.ConstantValue.Discriminator)
{
case ConstantValueTypeDiscriminator.String:
append($@"""{value}""");
break;
default:
append(value);
break;
}
break;
}
case BoundAssignmentOperator assignment:
{
appendSource(assignment.Left);
append(" = ");
appendSource(assignment.Right);
break;
}
case BoundThisReference thisReference:
{
append("this");
break;
}
case BoundFieldAccess fieldAccess:
{
var receiver = fieldAccess.ReceiverOpt;
if (receiver != null)
{
appendSource(receiver);
append(".");
}
append(fieldAccess.FieldSymbol.Name);
break;
}
case BoundSwitchStatement switchStatement:
{
append("switch (");
appendSource(switchStatement.Expression);
appendLine(")");
appendLine("{");
foreach (BoundSwitchSection section in switchStatement.SwitchSections)
{
foreach (var label in section.SwitchLabels)
{
append("case ");
appendSource(label);
appendLine(":");
}
incrementIndent();
foreach (var statement in section.Statements)
{
appendSource(statement);
}
appendLine("break;");
decrementIndent();
}
appendLine("}");
break;
}
case BoundSwitchLabel label:
{
appendSource(label.ExpressionOpt);
break;
}
case BoundUnaryOperator unary:
{
append($" {unary.OperatorKind.ToString()} ");
appendSource(unary.Operand);
break;
}
case BoundStatementList list:
{
foreach (var statement in list.Statements)
{
appendSource(statement);
}
break;
}
case BoundSequence sequence:
{
append("{ ");
foreach (var effect in sequence.SideEffects)
{
appendSource(effect);
append("; ");
}
appendSource(sequence.Value);
append(" }");
break;
}
case BoundDefaultExpression _:
{
append("default");
break;
}
case BoundBinaryOperator binary:
{
appendSource(binary.Left);
append(" ");
append(binary.OperatorKind.ToString());
append(" ");
appendSource(binary.Right);
break;
}
default:
appendLine(node.Kind.ToString());
break;
}
void appendSource(BoundNode n)
{
if (n is null)
{
append("NULL");
}
else
{
appendSourceCore(n, indent, tempIdentifiers);
}
}
void append(string s)
{
builder.Append(s);
}
void incrementIndent()
{
indent += indentSize;
builder.Append(' ', indentSize);
}
void decrementIndent()
{
indent -= indentSize;
builder.Remove(builder.Length - indentSize, indentSize);
}
void appendLine(string s)
{
if (s == "{")
{
indent += indentSize;
builder.AppendLine(s);
builder.Append(' ', indent);
}
else if (s == "}")
{
builder.Remove(builder.Length - indentSize, indentSize);
builder.AppendLine(s);
indent -= indentSize;
builder.Append(' ', indent);
}
else
{
builder.AppendLine(s);
builder.Append(' ', indent);
}
}
string name(SynthesizedLocal local)
{
if (!tempIdentifiers.TryGetValue(local, out int identifier))
{
identifier = tempIdentifiers.Count + 1;
tempIdentifiers.Add(local, identifier);
}
return "temp" + identifier.ToString();
}
void appendLocal(LocalSymbol symbol)
{
if (symbol is SynthesizedLocal synthesized)
{
append(name(synthesized));
}
else
{
append($"({symbol.GetDebuggerDisplay()})");
}
}
}
}
#endif
}
}
......@@ -265,6 +265,98 @@ private static bool SequenceEqual<T>(IEnumerable<T> expected, IEnumerable<T> act
return true;
}
public static void SetEqual(IEnumerable<string> expected, IEnumerable<string> actual, IEqualityComparer<string> comparer = null, string message = null, string itemSeparator = "\r\n", Func<string, string> itemInspector = null)
{
var indexes = new Dictionary<string, int>(comparer);
int counter = 0;
foreach (var expectedItem in expected)
{
if (!indexes.ContainsKey(expectedItem))
{
indexes.Add(expectedItem, counter++);
}
}
SetEqual<string>(expected, actual.OrderBy(e => getIndex(e)), comparer, message, itemSeparator, itemInspector);
int getIndex(string item)
{
// exact match to expected items
if (indexes.TryGetValue(item, out var index))
{
return index;
}
// closest match to expected items
int closestDistance = int.MaxValue;
string closestItem = null;
foreach (var expectedItem in indexes.Keys)
{
var distance = levenshtein(item, expectedItem);
if (distance < closestDistance)
{
closestDistance = distance;
closestItem = expectedItem;
}
}
if (closestItem != null)
{
_ = indexes.TryGetValue(closestItem, out index);
return index;
}
return -1;
}
// Adapted from Toub's https://blogs.msdn.microsoft.com/toub/2006/05/05/generic-levenshtein-edit-distance-with-c/
int levenshtein(string first, string second)
{
// Get the length of both. If either is 0, return
// the length of the other, since that number of insertions
// would be required.
int n = first.Length, m = second.Length;
if (n == 0) return m;
if (m == 0) return n;
// Rather than maintain an entire matrix (which would require O(n*m) space),
// just store the current row and the next row, each of which has a length m+1,
// so just O(m) space. Initialize the current row.
int curRow = 0, nextRow = 1;
int[][] rows = new int[][] { new int[m + 1], new int[m + 1] };
for (int j = 0; j <= m; ++j) rows[curRow][j] = j;
// For each virtual row (since we only have physical storage for two)
for (int i = 1; i <= n; ++i)
{
// Fill in the values in the row
rows[nextRow][0] = i;
for (int j = 1; j <= m; ++j)
{
int dist1 = rows[curRow][j] + 1;
int dist2 = rows[nextRow][j - 1] + 1;
int dist3 = rows[curRow][j - 1] + (first[i - 1].Equals(second[j - 1]) ? 0 : 1);
rows[nextRow][j] = Math.Min(dist1, Math.Min(dist2, dist3));
}
// Swap the current and next rows
if (curRow == 0)
{
curRow = 1;
nextRow = 0;
}
else
{
curRow = 0;
nextRow = 1;
}
}
// Return the computed edit distance
return rows[curRow][m];
}
}
public static void SetEqual<T>(IEnumerable<T> expected, IEnumerable<T> actual, IEqualityComparer<T> comparer = null, string message = null, string itemSeparator = "\r\n", Func<T, string> itemInspector = null)
{
var expectedSet = new HashSet<T>(expected, comparer);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册