Skip to content

Commit e2af472

Browse files
Support for ref struct outputs.
1 parent c2e7979 commit e2af472

File tree

3 files changed

+69
-13
lines changed

3 files changed

+69
-13
lines changed

CSharpRepl.Services/Roslyn/RoslynServices.cs

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -465,24 +465,54 @@ private async Task<string> InterceptInputWhenRefStructReturnNeedsToBeHandled(str
465465
if ((await scriptRunner.HasValueReturningStatement(input, cancellationToken).ConfigureAwait(false)).TryGet(out var result) &&
466466
result.Type.IsRefLikeType)
467467
{
468+
var root = result.Expression.SyntaxTree.GetRoot(cancellationToken);
469+
var expressionToBeWrapped = result.Expression;
470+
ExpressionSyntax wrappedExpression;
468471
if (result.Type is { Name: "Span" or "ReadOnlySpan", ContainingNamespace.Name: "System" })
469472
{
470-
var root = result.Expression.SyntaxTree.GetRoot(cancellationToken);
471-
var wrappedExpression =
472-
SyntaxFactory.InvocationExpression(
473+
wrappedExpression = Wrap(nameof(__CSharpRepl_RuntimeHelper.HandleSpanOutput), result.Expression);
474+
}
475+
else
476+
{
477+
var toStringMethod = result.Type
478+
.GetMembers(nameof(ToString))
479+
.OfType<IMethodSymbol>()
480+
.Where(m => m.ReturnType.SpecialType == SpecialType.System_String && m.Parameters.Length == 0 && m.IsOverride)
481+
.FirstOrDefault();
482+
483+
if (toStringMethod != null)
484+
{
485+
expressionToBeWrapped = SyntaxFactory.InvocationExpression(
473486
SyntaxFactory.MemberAccessExpression(
474-
SyntaxKind.SimpleMemberAccessExpression,
475-
SyntaxFactory.IdentifierName(nameof(__CSharpRepl_RuntimeHelper)),
476-
SyntaxFactory.IdentifierName(nameof(__CSharpRepl_RuntimeHelper.HandleSpanOutput))))
477-
.WithArgumentList(
478-
SyntaxFactory.ArgumentList(
479-
SyntaxFactory.SingletonSeparatedList(
480-
SyntaxFactory.Argument(
481-
result.Expression))));
482-
root = root.ReplaceNode(result.Expression, wrappedExpression);
483-
return root.GetText().ToString();
487+
SyntaxKind.SimpleMemberAccessExpression,
488+
expressionToBeWrapped,
489+
SyntaxFactory.IdentifierName(nameof(ToString))));
490+
}
491+
else
492+
{
493+
expressionToBeWrapped = SyntaxFactory.LiteralExpression(
494+
SyntaxKind.StringLiteralExpression,
495+
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."));
496+
}
497+
wrappedExpression = Wrap(
498+
nameof(__CSharpRepl_RuntimeHelper.HandleRefStructOutput),
499+
expressionToBeWrapped);
484500
}
501+
root = root.ReplaceNode(result.Expression, wrappedExpression);
502+
return root.GetText().ToString();
485503
}
486504
return input;
505+
506+
static ExpressionSyntax Wrap(string runtimeHelperMethod, ExpressionSyntax expressionToBeWrapped) =>
507+
SyntaxFactory.InvocationExpression(
508+
SyntaxFactory.MemberAccessExpression(
509+
SyntaxKind.SimpleMemberAccessExpression,
510+
SyntaxFactory.IdentifierName(nameof(__CSharpRepl_RuntimeHelper)),
511+
SyntaxFactory.IdentifierName(runtimeHelperMethod)))
512+
.WithArgumentList(
513+
SyntaxFactory.ArgumentList(
514+
SyntaxFactory.SingletonSeparatedList(
515+
SyntaxFactory.Argument(
516+
expressionToBeWrapped))));
487517
}
488518
}

CSharpRepl.Services/RuntimeHelper.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ public static class __CSharpRepl_RuntimeHelper
1212
public static CharSpanOutput HandleSpanOutput(System.Span<char> span) => CharSpanOutput.Create(span, false);
1313
public static CharSpanOutput HandleSpanOutput(System.ReadOnlySpan<char> span) => CharSpanOutput.Create(span, true);
1414

15+
public static RefStructOutput HandleRefStructOutput(string text) => new(text);
16+
1517
public abstract class SpanOutputBase(int originalLength, bool spanWasReadOnly)
1618
: System.Collections.IEnumerable
1719
{
@@ -62,4 +64,10 @@ public static CharSpanOutput Create(System.ReadOnlySpan<char> span, bool spanWas
6264
return new(buffer.ToString(), span.Length, spanWasReadOnly);
6365
}
6466
}
67+
68+
public class RefStructOutput(string text)
69+
{
70+
private readonly string text = text;
71+
public override string ToString() => text;
72+
}
6573
}

CSharpRepl.Tests/EvaluationTests.cs

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

1617
namespace CSharpRepl.Tests;
1718

@@ -223,4 +224,21 @@ public async Task Evaluate_SpanResult()
223224
Assert.Equal(11, r3.Count);
224225
Assert.Equal(true, r3.SpanWasReadOnly);
225226
}
227+
228+
/// <summary>
229+
/// https://github.com/waf/CSharpRepl/issues/318
230+
/// </summary>
231+
[Fact]
232+
public async Task Evaluate_RefStructResult()
233+
{
234+
var e1 = await services.EvaluateAsync(@"ref struct S; default(S)");
235+
var r1 = Assert.IsType<EvaluationResult.Success>(e1).ReturnValue.Value;
236+
Assert.EndsWith($"{nameof(__CSharpRepl_RuntimeHelper)}+{nameof(__CSharpRepl_RuntimeHelper.RefStructOutput)}", r1.GetType().FullName);
237+
Assert.Equal($"Cannot output a value of 'S' because it's a ref-struct. It has to override ToString() to see its value.", r1.ToString());
238+
239+
var e2 = await services.EvaluateAsync(@"ref struct S{public override string ToString()=>""custom result"";} default(S)");
240+
var r2 = Assert.IsType<EvaluationResult.Success>(e2).ReturnValue.Value;
241+
Assert.EndsWith($"{nameof(__CSharpRepl_RuntimeHelper)}+{nameof(__CSharpRepl_RuntimeHelper.RefStructOutput)}", r2.GetType().FullName);
242+
Assert.Equal("custom result", r2.ToString());
243+
}
226244
}

0 commit comments

Comments
 (0)