Skip to content

Commit 4d80331

Browse files
Support for Memory<T>/ReadOnlyMemory<T> outputs + formatting. Resolves #317.
1 parent e2af472 commit 4d80331

File tree

13 files changed

+79
-53
lines changed

13 files changed

+79
-53
lines changed

CSharpRepl.Services/Roslyn/Formatting/CustomObjectFormatters/IEnumerableFormatter.cs

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -143,30 +143,27 @@ private static bool AppendHeader(StyledStringBuilder sb, IEnumerable value, Form
143143
if (type.FullName?.EndsWith(typeof(__CSharpRepl_RuntimeHelper.CharSpanOutput).FullName!) == true)
144144
{
145145
if (type.GetField(nameof(__CSharpRepl_RuntimeHelper.CharSpanOutput.Text))?.GetValue(value) is string text &&
146-
type.GetField(nameof(__CSharpRepl_RuntimeHelper.CharSpanOutput.SpanWasReadOnly))?.GetValue(value) is bool readOnly)
146+
type.GetField(nameof(__CSharpRepl_RuntimeHelper.CharSpanOutput.OriginalType))?.GetValue(value) is Type originalType)
147147
{
148-
type = readOnly ? typeof(ReadOnlySpan<char>) : typeof(Span<char>);
149-
150-
AppendTypeName(isArray: false);
148+
AppendTypeName(originalType, isArray: false);
151149
sb.Append(Environment.NewLine);
152150
sb.Append(formatter.FormatObjectToText(text, Level.FirstDetailed));
153-
return false;
151+
return false;
154152
}
155153
}
156154
else if (type.FullName?.EndsWith(typeof(__CSharpRepl_RuntimeHelper.SpanOutput).FullName!) == true)
157155
{
158-
if (type.GetField(nameof(__CSharpRepl_RuntimeHelper.SpanOutput.Array))?.GetValue(value) is Array array &&
159-
type.GetField(nameof(__CSharpRepl_RuntimeHelper.SpanOutput.SpanWasReadOnly))?.GetValue(value) is bool readOnly)
156+
if (type.GetField(nameof(__CSharpRepl_RuntimeHelper.SpanOutput.OriginalType))?.GetValue(value) is Type originalType)
160157
{
161-
type = (readOnly ? typeof(ReadOnlySpan<>) : typeof(Span<>)).MakeGenericType(array.GetType().GetElementType() ?? array.GetType());
158+
type = originalType;
162159
}
163160
}
164161

165162
var isArray = value is Array;
166-
AppendTypeName(isArray);
163+
AppendTypeName(type, isArray);
167164
return true;
168165

169-
void AppendTypeName(bool isArray)
166+
void AppendTypeName(Type type, bool isArray)
170167
{
171168
sb.Append(
172169
formatter.FormatTypeName(

CSharpRepl.Services/Roslyn/Formatting/FormattedObject.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
using System;
2-
using System.Collections.Generic;
1+
using System.Collections.Generic;
32
using Spectre.Console.Rendering;
43

54
namespace CSharpRepl.Services.Roslyn.Formatting;

CSharpRepl.Services/Roslyn/RoslynServices.cs

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -462,17 +462,20 @@ private async Task<string> InterceptInputWhenRefStructReturnNeedsToBeHandled(str
462462
{
463463
await Initialization.ConfigureAwait(false);
464464

465-
if ((await scriptRunner.HasValueReturningStatement(input, cancellationToken).ConfigureAwait(false)).TryGet(out var result) &&
466-
result.Type.IsRefLikeType)
465+
if ((await scriptRunner.HasValueReturningStatement(input, cancellationToken).ConfigureAwait(false)).TryGet(out var result))
467466
{
468467
var root = result.Expression.SyntaxTree.GetRoot(cancellationToken);
469468
var expressionToBeWrapped = result.Expression;
470-
ExpressionSyntax wrappedExpression;
469+
var wrappedExpression = expressionToBeWrapped;
471470
if (result.Type is { Name: "Span" or "ReadOnlySpan", ContainingNamespace.Name: "System" })
472471
{
473472
wrappedExpression = Wrap(nameof(__CSharpRepl_RuntimeHelper.HandleSpanOutput), result.Expression);
474473
}
475-
else
474+
else if (result.Type is { Name: "Memory" or "ReadOnlyMemory", ContainingNamespace.Name: "System" })
475+
{
476+
wrappedExpression = Wrap(nameof(__CSharpRepl_RuntimeHelper.HandleMemoryOutput), result.Expression);
477+
}
478+
else if (result.Type.IsRefLikeType)
476479
{
477480
var toStringMethod = result.Type
478481
.GetMembers(nameof(ToString))
@@ -484,22 +487,26 @@ private async Task<string> InterceptInputWhenRefStructReturnNeedsToBeHandled(str
484487
{
485488
expressionToBeWrapped = SyntaxFactory.InvocationExpression(
486489
SyntaxFactory.MemberAccessExpression(
487-
SyntaxKind.SimpleMemberAccessExpression,
488-
expressionToBeWrapped,
490+
SyntaxKind.SimpleMemberAccessExpression,
491+
expressionToBeWrapped,
489492
SyntaxFactory.IdentifierName(nameof(ToString))));
490493
}
491494
else
492495
{
493496
expressionToBeWrapped = SyntaxFactory.LiteralExpression(
494-
SyntaxKind.StringLiteralExpression,
497+
SyntaxKind.StringLiteralExpression,
495498
SyntaxFactory.Literal($"Cannot output a value of '{result.Type.Name}' because it's a ref-struct. It has to override ToString() to see its value."));
496499
}
497500
wrappedExpression = Wrap(
498501
nameof(__CSharpRepl_RuntimeHelper.HandleRefStructOutput),
499502
expressionToBeWrapped);
500503
}
501-
root = root.ReplaceNode(result.Expression, wrappedExpression);
502-
return root.GetText().ToString();
504+
505+
if (result.Expression != wrappedExpression)
506+
{
507+
root = root.ReplaceNode(result.Expression, wrappedExpression);
508+
return root.GetText().ToString();
509+
}
503510
}
504511
return input;
505512

@@ -515,4 +522,4 @@ static ExpressionSyntax Wrap(string runtimeHelperMethod, ExpressionSyntax expres
515522
SyntaxFactory.Argument(
516523
expressionToBeWrapped))));
517524
}
518-
}
525+
}

CSharpRepl.Services/RuntimeHelper.cs

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,51 +6,57 @@
66

77
public static class __CSharpRepl_RuntimeHelper
88
{
9-
public static SpanOutput HandleSpanOutput<T>(System.Span<T> span) => SpanOutput.Create<T>(span, false);
10-
public static SpanOutput HandleSpanOutput<T>(System.ReadOnlySpan<T> span) => SpanOutput.Create(span, true);
9+
public static SpanOutput HandleSpanOutput<T>(System.Span<T> span) => SpanOutput.Create<T>(span, typeof(System.Span<T>));
10+
public static SpanOutput HandleSpanOutput<T>(System.ReadOnlySpan<T> span) => SpanOutput.Create(span, typeof(System.ReadOnlySpan<T>));
1111

12-
public static CharSpanOutput HandleSpanOutput(System.Span<char> span) => CharSpanOutput.Create(span, false);
13-
public static CharSpanOutput HandleSpanOutput(System.ReadOnlySpan<char> span) => CharSpanOutput.Create(span, true);
12+
public static CharSpanOutput HandleSpanOutput(System.Span<char> span) => CharSpanOutput.Create(span, typeof(System.Span<char>));
13+
public static CharSpanOutput HandleSpanOutput(System.ReadOnlySpan<char> span) => CharSpanOutput.Create(span, typeof(System.ReadOnlySpan<char>));
14+
15+
public static SpanOutput HandleMemoryOutput<T>(System.Memory<T> memory) => SpanOutput.Create<T>(memory.Span, typeof(System.Memory<T>));
16+
public static SpanOutput HandleMemoryOutput<T>(System.ReadOnlyMemory<T> memory) => SpanOutput.Create(memory.Span, typeof(System.ReadOnlyMemory<T>));
17+
18+
public static CharSpanOutput HandleMemoryOutput(System.Memory<char> memory) => CharSpanOutput.Create(memory.Span, typeof(System.Memory<char>));
19+
public static CharSpanOutput HandleMemoryOutput(System.ReadOnlyMemory<char> memory) => CharSpanOutput.Create(memory.Span, typeof(System.ReadOnlyMemory<char>));
1420

1521
public static RefStructOutput HandleRefStructOutput(string text) => new(text);
1622

17-
public abstract class SpanOutputBase(int originalLength, bool spanWasReadOnly)
23+
public abstract class SpanOutputBase(int originalLength, System.Type originalType)
1824
: System.Collections.IEnumerable
1925
{
2026
public readonly int OriginalLength = originalLength;
21-
public readonly bool SpanWasReadOnly = spanWasReadOnly;
27+
public readonly System.Type OriginalType = originalType;
2228

2329
//Necessary for correct output formatting.
2430
public int Count => OriginalLength;
2531

2632
public abstract System.Collections.IEnumerator GetEnumerator();
2733
}
2834

29-
public sealed class SpanOutput(System.Array array, int originalLength, bool spanWasReadOnly)
30-
: SpanOutputBase(originalLength, spanWasReadOnly)
35+
public sealed class SpanOutput(System.Array array, int originalLength, System.Type originalType)
36+
: SpanOutputBase(originalLength, originalType)
3137
{
3238
private const int MaxLength = 1024;
3339

34-
public readonly System.Array Array = array;
40+
private readonly System.Array array = array;
3541

36-
public override System.Collections.IEnumerator GetEnumerator() => Array.GetEnumerator();
42+
public override System.Collections.IEnumerator GetEnumerator() => array.GetEnumerator();
3743

38-
public static SpanOutput Create<T>(System.ReadOnlySpan<T> span, bool spanWasReadOnly) => new(
44+
public static SpanOutput Create<T>(System.ReadOnlySpan<T> span, System.Type originalType) => new(
3945
span[..System.Math.Min(MaxLength, span.Length)].ToArray(),
4046
span.Length,
41-
spanWasReadOnly);
47+
originalType);
4248
}
4349

44-
public sealed class CharSpanOutput(string text, int originalLength, bool spanWasReadOnly)
45-
: SpanOutputBase(originalLength, spanWasReadOnly)
50+
public sealed class CharSpanOutput(string text, int originalLength, System.Type originalType)
51+
: SpanOutputBase(originalLength, originalType)
4652
{
4753
private const int MaxLength = 10_000;
4854

4955
public readonly string Text = text;
5056

5157
public override System.Collections.IEnumerator GetEnumerator() => Text.GetEnumerator();
5258

53-
public static CharSpanOutput Create(System.ReadOnlySpan<char> span, bool spanWasReadOnly)
59+
public static CharSpanOutput Create(System.ReadOnlySpan<char> span, System.Type originalType)
5460
{
5561
var len = System.Math.Min(MaxLength, span.Length);
5662
System.Span<char> buffer = stackalloc char[len];
@@ -61,7 +67,7 @@ public static CharSpanOutput Create(System.ReadOnlySpan<char> span, bool spanWas
6167
buffer[^2] = '.';
6268
buffer[^3] = '.';
6369
}
64-
return new(buffer.ToString(), span.Length, spanWasReadOnly);
70+
return new(buffer.ToString(), span.Length, originalType);
6571
}
6672
}
6773

CSharpRepl.Tests/CompletionTests.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// License, v. 2.0. If a copy of the MPL was not distributed with this
33
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
44

5-
using System;
65
using System.Linq;
76
using System.Threading.Tasks;
87
using CSharpRepl.PrettyPromptConfig;

CSharpRepl.Tests/DisassemblerTests.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
using System;
2-
using System.IO;
1+
using System.IO;
32
using System.Threading.Tasks;
43
using CSharpRepl.Services;
54
using CSharpRepl.Services.Roslyn;

CSharpRepl.Tests/EvaluationTests.cs

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
using CSharpRepl.Services.Roslyn;
1313
using CSharpRepl.Services.Roslyn.Scripting;
1414
using Xunit;
15-
using static PrettyPrompt.Highlighting.FormattedString.TextElementsEnumerator;
1615

1716
namespace CSharpRepl.Tests;
1817

@@ -210,19 +209,45 @@ public async Task Evaluate_SpanResult()
210209
dynamic r1 = Assert.IsType<EvaluationResult.Success>(eval1).ReturnValue.Value;
211210
Assert.EndsWith($"{nameof(__CSharpRepl_RuntimeHelper)}+{nameof(__CSharpRepl_RuntimeHelper.SpanOutput)}", r1.GetType().FullName);
212211
Assert.Equal(3, r1.Count);
213-
Assert.Equal(false, r1.SpanWasReadOnly);
212+
Assert.Equal(typeof(Span<int>), r1.OriginalType);
214213

215214
var eval2 = await services.EvaluateAsync(@"(ReadOnlySpan<int>)[1,2,3]");
216215
dynamic r2 = Assert.IsType<EvaluationResult.Success>(eval2).ReturnValue.Value;
217216
Assert.EndsWith($"{nameof(__CSharpRepl_RuntimeHelper)}+{nameof(__CSharpRepl_RuntimeHelper.SpanOutput)}", r2.GetType().FullName);
218217
Assert.Equal(3, r2.Count);
219-
Assert.Equal(true, r2.SpanWasReadOnly);
218+
Assert.Equal(typeof(ReadOnlySpan<int>), r2.OriginalType);
220219

221220
var eval3 = await services.EvaluateAsync(@"""Hello World"".AsSpan()");
222221
dynamic r3 = Assert.IsType<EvaluationResult.Success>(eval3).ReturnValue.Value;
223222
Assert.EndsWith($"{nameof(__CSharpRepl_RuntimeHelper)}+{nameof(__CSharpRepl_RuntimeHelper.CharSpanOutput)}", r3.GetType().FullName);
224223
Assert.Equal(11, r3.Count);
225-
Assert.Equal(true, r3.SpanWasReadOnly);
224+
Assert.Equal(typeof(ReadOnlySpan<char>), r3.OriginalType);
225+
}
226+
227+
/// <summary>
228+
/// https://github.com/waf/CSharpRepl/issues/317
229+
/// </summary>
230+
[Fact]
231+
public async Task Evaluate_MemoryResult()
232+
{
233+
234+
var eval1 = await services.EvaluateAsync(@"new[]{1,2,3}.AsMemory()");
235+
dynamic r1 = Assert.IsType<EvaluationResult.Success>(eval1).ReturnValue.Value;
236+
Assert.EndsWith($"{nameof(__CSharpRepl_RuntimeHelper)}+{nameof(__CSharpRepl_RuntimeHelper.SpanOutput)}", r1.GetType().FullName);
237+
Assert.Equal(3, r1.Count);
238+
Assert.Equal(typeof(Memory<int>), r1.OriginalType);
239+
240+
var eval2 = await services.EvaluateAsync(@"(ReadOnlyMemory<int>)new[] { 1, 2, 3 }.AsMemory()");
241+
dynamic r2 = Assert.IsType<EvaluationResult.Success>(eval2).ReturnValue.Value;
242+
Assert.EndsWith($"{nameof(__CSharpRepl_RuntimeHelper)}+{nameof(__CSharpRepl_RuntimeHelper.SpanOutput)}", r2.GetType().FullName);
243+
Assert.Equal(3, r2.Count);
244+
Assert.Equal(typeof(ReadOnlyMemory<int>), r2.OriginalType);
245+
246+
var eval3 = await services.EvaluateAsync(@"""Hello World"".AsMemory()");
247+
dynamic r3 = Assert.IsType<EvaluationResult.Success>(eval3).ReturnValue.Value;
248+
Assert.EndsWith($"{nameof(__CSharpRepl_RuntimeHelper)}+{nameof(__CSharpRepl_RuntimeHelper.CharSpanOutput)}", r3.GetType().FullName);
249+
Assert.Equal(11, r3.Count);
250+
Assert.Equal(typeof(ReadOnlyMemory<char>), r3.OriginalType);
226251
}
227252

228253
/// <summary>

CSharpRepl.Tests/ObjectFormatting/TestFormatter.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
#nullable enable
22

3-
using System;
43
using CSharpRepl.Services;
54
using CSharpRepl.Services.Roslyn.Formatting;
65
using CSharpRepl.Services.Roslyn.Formatting.CustomObjectFormatters;

CSharpRepl.Tests/RoslynServicesFixture.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
using System;
2-
using System.Text;
1+
using System.Text;
32
using System.Threading.Tasks;
43
using CSharpRepl.Services;
54
using CSharpRepl.Services.Roslyn;

CSharpRepl.Tests/RoslynServicesTests.Overloads.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// License, v. 2.0. If a copy of the MPL was not distributed with this
33
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
44

5-
using System;
65
using System.Collections.Generic;
76
using System.Linq;
87
using System.Threading.Tasks;

0 commit comments

Comments
 (0)