Skip to content

Commit

Permalink
Support for ref struct outputs.
Browse files Browse the repository at this point in the history
  • Loading branch information
kindermannhubert committed Apr 1, 2024
1 parent c2e7979 commit e2af472
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 13 deletions.
56 changes: 43 additions & 13 deletions CSharpRepl.Services/Roslyn/RoslynServices.cs
Original file line number Diff line number Diff line change
Expand Up @@ -465,24 +465,54 @@ private async Task<string> InterceptInputWhenRefStructReturnNeedsToBeHandled(str
if ((await scriptRunner.HasValueReturningStatement(input, cancellationToken).ConfigureAwait(false)).TryGet(out var result) &&
result.Type.IsRefLikeType)
{
var root = result.Expression.SyntaxTree.GetRoot(cancellationToken);
var expressionToBeWrapped = result.Expression;
ExpressionSyntax wrappedExpression;
if (result.Type is { Name: "Span" or "ReadOnlySpan", ContainingNamespace.Name: "System" })
{
var root = result.Expression.SyntaxTree.GetRoot(cancellationToken);
var wrappedExpression =
SyntaxFactory.InvocationExpression(
wrappedExpression = Wrap(nameof(__CSharpRepl_RuntimeHelper.HandleSpanOutput), result.Expression);
}
else
{
var toStringMethod = result.Type
.GetMembers(nameof(ToString))
.OfType<IMethodSymbol>()
.Where(m => m.ReturnType.SpecialType == SpecialType.System_String && m.Parameters.Length == 0 && m.IsOverride)
.FirstOrDefault();

if (toStringMethod != null)
{
expressionToBeWrapped = SyntaxFactory.InvocationExpression(
SyntaxFactory.MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
SyntaxFactory.IdentifierName(nameof(__CSharpRepl_RuntimeHelper)),
SyntaxFactory.IdentifierName(nameof(__CSharpRepl_RuntimeHelper.HandleSpanOutput))))
.WithArgumentList(
SyntaxFactory.ArgumentList(
SyntaxFactory.SingletonSeparatedList(
SyntaxFactory.Argument(
result.Expression))));
root = root.ReplaceNode(result.Expression, wrappedExpression);
return root.GetText().ToString();
SyntaxKind.SimpleMemberAccessExpression,
expressionToBeWrapped,
SyntaxFactory.IdentifierName(nameof(ToString))));
}
else
{
expressionToBeWrapped = SyntaxFactory.LiteralExpression(
SyntaxKind.StringLiteralExpression,
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."));
}
wrappedExpression = Wrap(
nameof(__CSharpRepl_RuntimeHelper.HandleRefStructOutput),
expressionToBeWrapped);
}
root = root.ReplaceNode(result.Expression, wrappedExpression);
return root.GetText().ToString();
}
return input;

static ExpressionSyntax Wrap(string runtimeHelperMethod, ExpressionSyntax expressionToBeWrapped) =>
SyntaxFactory.InvocationExpression(
SyntaxFactory.MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
SyntaxFactory.IdentifierName(nameof(__CSharpRepl_RuntimeHelper)),
SyntaxFactory.IdentifierName(runtimeHelperMethod)))
.WithArgumentList(
SyntaxFactory.ArgumentList(
SyntaxFactory.SingletonSeparatedList(
SyntaxFactory.Argument(
expressionToBeWrapped))));
}
}
8 changes: 8 additions & 0 deletions CSharpRepl.Services/RuntimeHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ public static class __CSharpRepl_RuntimeHelper
public static CharSpanOutput HandleSpanOutput(System.Span<char> span) => CharSpanOutput.Create(span, false);
public static CharSpanOutput HandleSpanOutput(System.ReadOnlySpan<char> span) => CharSpanOutput.Create(span, true);

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

public abstract class SpanOutputBase(int originalLength, bool spanWasReadOnly)
: System.Collections.IEnumerable
{
Expand Down Expand Up @@ -62,4 +64,10 @@ public static CharSpanOutput Create(System.ReadOnlySpan<char> span, bool spanWas
return new(buffer.ToString(), span.Length, spanWasReadOnly);
}
}

public class RefStructOutput(string text)
{
private readonly string text = text;
public override string ToString() => text;
}
}
18 changes: 18 additions & 0 deletions CSharpRepl.Tests/EvaluationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using CSharpRepl.Services.Roslyn;
using CSharpRepl.Services.Roslyn.Scripting;
using Xunit;
using static PrettyPrompt.Highlighting.FormattedString.TextElementsEnumerator;

namespace CSharpRepl.Tests;

Expand Down Expand Up @@ -223,4 +224,21 @@ public async Task Evaluate_SpanResult()
Assert.Equal(11, r3.Count);
Assert.Equal(true, r3.SpanWasReadOnly);
}

/// <summary>
/// https://github.com/waf/CSharpRepl/issues/318
/// </summary>
[Fact]
public async Task Evaluate_RefStructResult()
{
var e1 = await services.EvaluateAsync(@"ref struct S; default(S)");
var r1 = Assert.IsType<EvaluationResult.Success>(e1).ReturnValue.Value;
Assert.EndsWith($"{nameof(__CSharpRepl_RuntimeHelper)}+{nameof(__CSharpRepl_RuntimeHelper.RefStructOutput)}", r1.GetType().FullName);
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());

var e2 = await services.EvaluateAsync(@"ref struct S{public override string ToString()=>""custom result"";} default(S)");
var r2 = Assert.IsType<EvaluationResult.Success>(e2).ReturnValue.Value;
Assert.EndsWith($"{nameof(__CSharpRepl_RuntimeHelper)}+{nameof(__CSharpRepl_RuntimeHelper.RefStructOutput)}", r2.GetType().FullName);
Assert.Equal("custom result", r2.ToString());
}
}

0 comments on commit e2af472

Please sign in to comment.