Skip to content

Commit

Permalink
Merge pull request #353 from kindermannhubert/syntaxInterceptionForRe…
Browse files Browse the repository at this point in the history
…fStructHandling

Syntax interception for ref struct handling + formatting of (ReadOnly)Span<T>/(ReadOnly)Memory<T>
  • Loading branch information
kindermannhubert authored Apr 1, 2024
2 parents af51164 + 4d80331 commit 5f5f2d4
Show file tree
Hide file tree
Showing 18 changed files with 407 additions and 113 deletions.
5 changes: 5 additions & 0 deletions CSharpRepl.Services/CSharpRepl.Services.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@
</AssemblyAttribute>
</ItemGroup>


<ItemGroup>
<EmbeddedResource Include="RuntimeHelper.cs" />
</ItemGroup>

<ItemGroup>
<None Update="runtime.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
Expand Down
8 changes: 5 additions & 3 deletions CSharpRepl.Services/Disassembly/Disassembler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@ internal class Disassembler
{
private readonly AssemblyReferenceService referenceService;
private readonly (string name, CompileDelegate compile)[] compilers;
private readonly CSharpParseOptions parseOptions;
private readonly CSharpCompilationOptions compilationOptions;

public Disassembler(CSharpCompilationOptions compilationOptions, AssemblyReferenceService referenceService, ScriptRunner scriptRunner)
public Disassembler(CSharpParseOptions parseOptions, CSharpCompilationOptions compilationOptions, AssemblyReferenceService referenceService, ScriptRunner scriptRunner)
{
this.parseOptions = parseOptions.WithKind(SourceCodeKind.Regular);
this.compilationOptions = compilationOptions;
this.referenceService = referenceService;

Expand All @@ -45,7 +47,7 @@ public Disassembler(CSharpCompilationOptions compilationOptions, AssemblyReferen
compile: (code, optimizationLevel) => Compile(code, optimizationLevel, OutputKind.DynamicallyLinkedLibrary)),
// Compiling as a script will work for most other cases, but it's quite verbose so we use it as a last resort.
(name: "Scripting session (will be overly verbose)",
compile: (code, optimizationLevel) => scriptRunner.CompileTransient(code, optimizationLevel))
compile: scriptRunner.CompileTransient)
];
}

Expand Down Expand Up @@ -151,7 +153,7 @@ static PlainTextOutput DisassembleAll(PEFile file, PlainTextOutput ilCodeOutput)

private Compilation Compile(string code, OptimizationLevel optimizationLevel, OutputKind outputKind)
{
var ast = CSharpSyntaxTree.ParseText(code, new CSharpParseOptions(LanguageVersion.Latest));
var ast = CSharpSyntaxTree.ParseText(code, parseOptions);
var compilation = CSharpCompilation.Create("CompilationForDisassembly",
[ast],
referenceService.LoadedReferenceAssemblies,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,45 +22,46 @@ public override StyledString FormatToText(IEnumerable value, Level level, Format
var sb = new StyledStringBuilder();

//header
AppendHeader(sb, value, formatter);

//items
sb.Append(" { ");
var enumerator = value.GetEnumerator();
try
if (AppendHeader(sb, value, formatter))
{
var maxParagraphLength = LengthLimiting.GetMaxParagraphLength(level, formatter.ConsoleProfile);
bool any = false;
while (enumerator.MoveNext())
//items
sb.Append(" { ");
var enumerator = value.GetEnumerator();
try
{
if (any)
var maxParagraphLength = LengthLimiting.GetMaxParagraphLength(level, formatter.ConsoleProfile);
bool any = false;
while (enumerator.MoveNext())
{
sb.Append(", ");

if (maxParagraphLength > sb.Length &&
sb.Length > formatter.ConsoleProfile.Width / 2) //just heuristic
if (any)
{
sb.Append(", ...");
break;
sb.Append(", ");

if (maxParagraphLength > sb.Length &&
sb.Length > formatter.ConsoleProfile.Width / 2) //just heuristic
{
sb.Append(", ...");
break;
}
}
var formattedItem = formatter.FormatObjectToText(enumerator.Current, level.Increment());
sb.Append(formattedItem);
any = true;
}
var formattedItem = formatter.FormatObjectToText(enumerator.Current, level.Increment());
sb.Append(formattedItem);
any = true;
}
}
catch (Exception ex)
{
sb.Append(formatter.GetValueRetrievalExceptionText(ex, level.Increment()));
}
finally
{
if (enumerator is IDisposable disposable)
catch (Exception ex)
{
sb.Append(formatter.GetValueRetrievalExceptionText(ex, level.Increment()));
}
finally
{
disposable.Dispose();
if (enumerator is IDisposable disposable)
{
disposable.Dispose();
}
}
sb.Append(" }");
}
sb.Append(" }");

return sb.ToStyledString();
}
Expand All @@ -73,79 +74,110 @@ public override FormattedObjectRenderable FormatToRenderable(IEnumerable value,
}

var sb = new StyledStringBuilder();

AppendHeader(sb, value, formatter);
var header = sb.ToStyledString().ToParagraph();

var table = new Table().AddColumns("Name", "Value", "Type");

var enumerator = value.GetEnumerator();
try
if (AppendHeader(sb, value, formatter))
{
var maxItems = LengthLimiting.GetTableMaxItems(level, formatter.ConsoleProfile);
int counter = 0;
while (enumerator.MoveNext())
var header = sb.ToStyledString().ToParagraph();
var table = new Table().AddColumns("Name", "Value", "Type");

var enumerator = value.GetEnumerator();
try
{
if (counter > maxItems)
var maxItems = LengthLimiting.GetTableMaxItems(level, formatter.ConsoleProfile);
int counter = 0;
while (enumerator.MoveNext())
{
table.AddRow("...", "...", "...");
break;
}
if (counter > maxItems)
{
table.AddRow("...", "...", "...");
break;
}

sb.Clear();
sb.Append('[').Append(formatter.FormatObjectToText(counter, Level.FirstSimple)).Append(']');
sb.Clear();
sb.Append('[').Append(formatter.FormatObjectToText(counter, Level.FirstSimple)).Append(']');

var name = sb.ToStyledString();
var name = sb.ToStyledString();

var itemValue = formatter.FormatObjectToRenderable(enumerator.Current, level.Increment());
var itemValue = formatter.FormatObjectToRenderable(enumerator.Current, level.Increment());

var itemType =
enumerator.Current is null ?
new Paragraph("") :
formatter.FormatObjectToText(enumerator.Current.GetType(), level.Increment()).ToParagraph();
var itemType =
enumerator.Current is null ?
new Paragraph("") :
formatter.FormatObjectToText(enumerator.Current.GetType(), level.Increment()).ToParagraph();

table.AddRow(name.ToParagraph(), itemValue, itemType);
table.AddRow(name.ToParagraph(), itemValue, itemType);

counter++;
}
counter++;
}

if (counter == 0)
if (counter == 0)
{
return new FormattedObjectRenderable(header, renderOnNewLine: false);
}
}
catch (Exception ex)
{
table.AddRow(new Paragraph(""), formatter.GetValueRetrievalExceptionText(ex, level.Increment()).ToParagraph(), new Paragraph(""));
}
finally
{
return new FormattedObjectRenderable(header, renderOnNewLine: false);
if (enumerator is IDisposable disposable)
{
disposable.Dispose();
}
}

return new FormattedObjectRenderable(
new RenderableSequence(header, table, separateByLineBreak: true),
renderOnNewLine: false);
}
catch (Exception ex)
else
{
table.AddRow(new Paragraph(""), formatter.GetValueRetrievalExceptionText(ex, level.Increment()).ToParagraph(), new Paragraph(""));
return new FormattedObjectRenderable(sb.ToStyledString().ToParagraph(), renderOnNewLine: false);
}
finally
}

private static bool AppendHeader(StyledStringBuilder sb, IEnumerable value, Formatter formatter)
{
var type = value.GetType();

if (type.FullName?.EndsWith(typeof(__CSharpRepl_RuntimeHelper.CharSpanOutput).FullName!) == true)
{
if (enumerator is IDisposable disposable)
if (type.GetField(nameof(__CSharpRepl_RuntimeHelper.CharSpanOutput.Text))?.GetValue(value) is string text &&
type.GetField(nameof(__CSharpRepl_RuntimeHelper.CharSpanOutput.OriginalType))?.GetValue(value) is Type originalType)
{
disposable.Dispose();
AppendTypeName(originalType, isArray: false);
sb.Append(Environment.NewLine);
sb.Append(formatter.FormatObjectToText(text, Level.FirstDetailed));
return false;
}
}
else if (type.FullName?.EndsWith(typeof(__CSharpRepl_RuntimeHelper.SpanOutput).FullName!) == true)
{
if (type.GetField(nameof(__CSharpRepl_RuntimeHelper.SpanOutput.OriginalType))?.GetValue(value) is Type originalType)
{
type = originalType;
}
}

return new FormattedObjectRenderable(
new RenderableSequence(header, table, separateByLineBreak: true),
renderOnNewLine: false);
}

private static void AppendHeader(StyledStringBuilder sb, IEnumerable value, Formatter formatter)
{
var isArray = value is Array;
sb.Append(
formatter.FormatTypeName(
isArray ? (value.GetType().GetElementType() ?? value.GetType()) : value.GetType(),
showNamespaces: false,
useLanguageKeywords: true,
hideSystemNamespace: true));

if (TryGetCount(value, formatter, out var count))
AppendTypeName(type, isArray);
return true;

void AppendTypeName(Type type, bool isArray)
{
sb.Append(isArray ? '[' : '(');
sb.Append(count);
sb.Append(isArray ? ']' : ')');
sb.Append(
formatter.FormatTypeName(
isArray ? (type.GetElementType() ?? type) : type,
showNamespaces: false,
useLanguageKeywords: true,
hideSystemNamespace: true));

if (TryGetCount(value, formatter, out var count))
{
sb.Append(isArray ? '[' : '(');
sb.Append(count);
sb.Append(isArray ? ']' : ')');
}
}
}

Expand Down
3 changes: 1 addition & 2 deletions CSharpRepl.Services/Roslyn/Formatting/FormattedObject.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using Spectre.Console.Rendering;

namespace CSharpRepl.Services.Roslyn.Formatting;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,16 @@ internal sealed class AssemblyReferenceService
private readonly HashSet<string> implementationAssemblyPaths;
private readonly HashSet<string> sharedFrameworkImplementationAssemblyPaths;
private readonly HashSet<UsingDirectiveSyntax> usings;
private readonly CSharpParseOptions parseOptions;

public IReadOnlySet<string> ImplementationAssemblyPaths => implementationAssemblyPaths;
public IReadOnlySet<MetadataReference> LoadedImplementationAssemblies => loadedImplementationAssemblies;
public IReadOnlySet<MetadataReference> LoadedReferenceAssemblies => loadedReferenceAssemblies;
public IReadOnlyCollection<UsingDirectiveSyntax> Usings => usings;

public AssemblyReferenceService(Configuration config, ITraceLogger logger)
public AssemblyReferenceService(Configuration config, CSharpParseOptions parseOptions, ITraceLogger logger)
{
this.parseOptions = parseOptions;
this.dotnetInstallationLocator = new DotNetInstallationLocator(logger);
this.referenceAssemblyPaths = [];
this.implementationAssemblyPaths = [];
Expand Down Expand Up @@ -201,7 +203,7 @@ public void LoadSharedFrameworkConfiguration(SharedFramework[] sharedFrameworks)
}

internal IReadOnlyCollection<UsingDirectiveSyntax> GetUsings(string code) =>
CSharpSyntaxTree.ParseText(code)
CSharpSyntaxTree.ParseText(code, parseOptions)
.GetRoot()
.DescendantNodes()
.OfType<UsingDirectiveSyntax>()
Expand All @@ -210,7 +212,7 @@ internal IReadOnlyCollection<UsingDirectiveSyntax> GetUsings(string code) =>
internal void TrackUsings(IReadOnlyCollection<UsingDirectiveSyntax> usingsToAdd) =>
usings.UnionWith(usingsToAdd);

private IReadOnlyCollection<MetadataReference> CreateDefaultReferences(string assemblyPath, IReadOnlyCollection<string> assemblies)
private List<PortableExecutableReference> CreateDefaultReferences(string assemblyPath, IReadOnlyCollection<string> assemblies)
{
return assemblies
.AsParallel()
Expand Down
Loading

0 comments on commit 5f5f2d4

Please sign in to comment.