Skip to content

Commit

Permalink
Support for ref like struct implicit parameter conversions.
Browse files Browse the repository at this point in the history
  • Loading branch information
drunkcod committed Feb 6, 2023
1 parent 7790842 commit 0a9be5d
Show file tree
Hide file tree
Showing 10 changed files with 132 additions and 24 deletions.
22 changes: 20 additions & 2 deletions Cone.sln
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30225.117
# Visual Studio Version 17
VisualStudioVersion = 17.4.33213.308
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{9F49969B-C9E7-4176-9538-40ADF6A36D82}"
ProjectSection(SolutionItems) = preProject
Expand All @@ -25,6 +25,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cone.Worker", "Source\Cone.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Check.That", "Source\Check.That\Check.That.csproj", "{9F67C549-4D0B-4185-9C84-F4DE2BCCD68D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CheckThat.Specs", "Specs\CheckThat.Specs\CheckThat.Specs.csproj", "{8475E60F-D664-4185-871F-2A5B9D43F646}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -159,6 +161,22 @@ Global
{9F67C549-4D0B-4185-9C84-F4DE2BCCD68D}.Release|x64.Build.0 = Release|Any CPU
{9F67C549-4D0B-4185-9C84-F4DE2BCCD68D}.Release|x86.ActiveCfg = Release|Any CPU
{9F67C549-4D0B-4185-9C84-F4DE2BCCD68D}.Release|x86.Build.0 = Release|Any CPU
{8475E60F-D664-4185-871F-2A5B9D43F646}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8475E60F-D664-4185-871F-2A5B9D43F646}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8475E60F-D664-4185-871F-2A5B9D43F646}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{8475E60F-D664-4185-871F-2A5B9D43F646}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{8475E60F-D664-4185-871F-2A5B9D43F646}.Debug|x64.ActiveCfg = Debug|Any CPU
{8475E60F-D664-4185-871F-2A5B9D43F646}.Debug|x64.Build.0 = Debug|Any CPU
{8475E60F-D664-4185-871F-2A5B9D43F646}.Debug|x86.ActiveCfg = Debug|Any CPU
{8475E60F-D664-4185-871F-2A5B9D43F646}.Debug|x86.Build.0 = Debug|Any CPU
{8475E60F-D664-4185-871F-2A5B9D43F646}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8475E60F-D664-4185-871F-2A5B9D43F646}.Release|Any CPU.Build.0 = Release|Any CPU
{8475E60F-D664-4185-871F-2A5B9D43F646}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{8475E60F-D664-4185-871F-2A5B9D43F646}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{8475E60F-D664-4185-871F-2A5B9D43F646}.Release|x64.ActiveCfg = Release|Any CPU
{8475E60F-D664-4185-871F-2A5B9D43F646}.Release|x64.Build.0 = Release|Any CPU
{8475E60F-D664-4185-871F-2A5B9D43F646}.Release|x86.ActiveCfg = Release|Any CPU
{8475E60F-D664-4185-871F-2A5B9D43F646}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
70 changes: 53 additions & 17 deletions Source/Check.That/Expressions/ExpressionEvaluatorContext.cs
Expand Up @@ -49,11 +49,15 @@ class ExpressionEvaluatorContext
}

public EvaluationResult EvaluateAll(ICollection<Expression> expressions) {
var evald = Array.ConvertAll(expressions.ToArray(), Evaluate);
var fail = Array.FindIndex(evald, x => x.IsError);
if(fail != -1)
return evald[fail];
return Success(typeof(object[]), Array.ConvertAll(evald, x => x.Result));
var r = new object[expressions.Count];
var i = 0;
foreach(var item in expressions) {
var e = Evaluate(item);
if(e.IsError)
return e;
r[i++] = e.Result;
}
return Success(typeof(object[]), r);
}

EvaluationResult Lambda(LambdaExpression expression) {
Expand Down Expand Up @@ -105,20 +109,52 @@ class ExpressionEvaluatorContext
}

EvaluationResult Call(MethodCallExpression expression) =>
EvaluateAsTarget(expression.Object).Then<object>(target =>
EvaluateAll(expression.Arguments).Then<object[]>(input => {
var method = expression.Method;
EvaluateAsTarget(expression.Object).Then<object>(target => {
var method = expression.Method;
var ps = method.GetParameters();
if (HasByRefLikeParameter(ps))
return InvokeWithByRefArgs(target, expression);
return EvaluateAll(expression.Arguments).Then<object[]>(input => {
return GuardedInvocation(expression,
() => Success(method.ReturnType, method.Invoke(target, input)),
() => AssignOutParameters(expression.Arguments, input, method.GetParameters()));
}));
() => AssignOutParameters(expression.Arguments, input, ps));
});
});

static readonly Func<ParameterInfo[], bool> HasByRefLikeParameter = Lambdas.TryGetProperty<Type, bool>("IsByRefLike", out var isByRefLike)
? xs => xs.Any(x => isByRefLike(x.ParameterType))
: _ => false;

EvaluationResult InvokeWithByRefArgs(object target, MethodCallExpression expression) {
var args = new object[parameters.Count + 1];
var ps = new List<ParameterExpression>(parameters.Count + 1) {
Expression.Parameter(expression.Method.DeclaringType)
};

args[0] = target;
foreach(var item in parameters) {
args[ps.Count] = item.Value;
ps.Add(item.Key);
}

var call =
Expression.Lambda(
Expression.Call(expression.Method.IsStatic ? null : ps[0], expression.Method, expression.Arguments), ps)
.Compile();
return GuardedInvocation(expression,
() => Success(expression.Method.ReturnType, call.DynamicInvoke(args)),
() => { });

}

void AssignOutParameters(IReadOnlyList<Expression> arguments, object[] results, ParameterInfo[] parameters) {
if(results.Length == 0)
return;
for(int i = 0; i != parameters.Length; ++i)
if(parameters[i].IsOut) {
var member = (arguments[i] as MemberExpression);
var member = arguments[i] as MemberExpression;
var field = member.Member as FieldInfo;
field.SetValue(EvaluateMember(member), results[i]);
}
Expand Down Expand Up @@ -187,24 +223,24 @@ class ExpressionEvaluatorContext
EvaluationResult Parameter(ParameterExpression expression) => Success(expression.Type, parameters[expression]);

ExpressionEvaluatorContext Rebind(Expression newContext) =>
new ExpressionEvaluatorContext(newContext, ExpressionEvaluatorParameters.Empty, Unsupported) {
new(newContext, ExpressionEvaluatorParameters.Empty, Unsupported) {
NullSubexpression = NullSubexpression
};

object ChangeType(object value, Type to) => ObjectConverter.ChangeType(value, to);

EvaluationResult Success(Type type, object value) => EvaluationResult.Success(type, value);
EvaluationResult Failure(Expression expression, Exception e) => EvaluationResult.Failure(expression, e);
static EvaluationResult Success(Type type, object value) => EvaluationResult.Success(type, value);
static EvaluationResult Failure(Expression expression, Exception e) => EvaluationResult.Failure(expression, e);

EvaluationResult GuardedInvocation(Expression expression, Func<EvaluationResult> action, Action @finally) {
static EvaluationResult GuardedInvocation(Expression expression, Func<EvaluationResult> action, Action @finally) {
try {
return action();
} catch (TargetInvocationException e) {
return Failure(expression, e.InnerException);
} finally { @finally(); }
}

EvaluationResult GuardedInvocation<T>(T expression, Func<T, EvaluationResult> action) where T : Expression {
static EvaluationResult GuardedInvocation<T>(T expression, Func<T, EvaluationResult> action) where T : Expression {
try {
return action(expression);
} catch (TargetInvocationException e) {
Expand Down
13 changes: 13 additions & 0 deletions Source/Check.That/Internals/Lambdas.cs
@@ -1,4 +1,5 @@
using System;
using System.Drawing;
using System.Reflection;

namespace CheckThat.Internals
Expand All @@ -8,6 +9,18 @@ public static class Lambdas
public static T Unbound<T>(MethodInfo method) where T : Delegate =>
(T)Delegate.CreateDelegate(typeof(T), null, method);


public static bool TryGetProperty<T, TProp>(string propertyName, out Func<T, TProp> found) {
var foundProperty = typeof(Type).GetProperty(propertyName);
if (foundProperty == null || !foundProperty.CanRead) {
found = null;
return false;
}

found = Unbound<Func<T, TProp>>(foundProperty.GetGetMethod());
return true;
}

public static new readonly Func<object, string> ToString = Unbound<Func<object, string>>(typeof(object).GetMethod(nameof(object.ToString)));
}
}
2 changes: 1 addition & 1 deletion Source/Cone.Build/Cone.Build.csproj
Expand Up @@ -10,7 +10,7 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Cone.Build</RootNamespace>
<AssemblyName>Cone.Build</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<BuildPath>$(SolutionDir)</BuildPath>
<OutputPath>$(BuildPath)Bin\$(MSBuildProjectName)</OutputPath>
Expand Down
2 changes: 1 addition & 1 deletion Source/Cone.TestAdapter/Cone.TestAdapter.csproj
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
<Import Project="$(SolutionDir)\Cone.props" />
<PropertyGroup>
<TargetFrameworks>net452;net462;net472</TargetFrameworks>
<TargetFrameworks>net452;net472</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Version.cs" Link="Version.cs" />
Expand Down
2 changes: 1 addition & 1 deletion Source/Cone.Worker/Cone.Worker.csproj
Expand Up @@ -2,7 +2,7 @@
<Import Project="$(SolutionDir)\Cone.props" />
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net452;net462;net472;netcoreapp2.1;netcoreapp3.1</TargetFrameworks>
<TargetFrameworks>net452;net472;netcoreapp3.1</TargetFrameworks>
<TargetFrameworkFamily Condition="$(TargetFramework.StartsWith('net4'))">NETFX</TargetFrameworkFamily>
<DefineConstants>$(DefineConstants);$(TargetFrameworkFamily)</DefineConstants>
<ApplicationIcon />
Expand Down
2 changes: 1 addition & 1 deletion Source/Cone/Cone.csproj
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
<Import Project="$(SolutionDir)\Cone.props" />
<PropertyGroup>
<TargetFrameworks>net452;net462;net472;netstandard2.0</TargetFrameworks>
<TargetFrameworks>net452;net472;netstandard2.0</TargetFrameworks>
<TargetFrameworkFamily Condition="$(TargetFramework.StartsWith('netstandard'))">NETSTANDARD</TargetFrameworkFamily>
<TargetFrameworkFamily Condition="$(TargetFramework.StartsWith('net4'))">NETFX</TargetFrameworkFamily>
<DefineConstants>$(DefineConstants);$(TargetFrameworkFamily)</DefineConstants>
Expand Down
22 changes: 22 additions & 0 deletions Specs/CheckThat.Specs/CheckThat.Specs.csproj
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\Source\Check.That\Check.That.csproj" />
</ItemGroup>

</Project>
19 changes: 19 additions & 0 deletions Specs/CheckThat.Specs/CheckThat_.cs
@@ -0,0 +1,19 @@
using Xunit;

namespace CheckThat.Specs
{

public class CheckThat_
{
[Fact]
public void call_with_ref_struct_parameter() =>
Check.That(() => StringFromSpan(new char[] { 'H', 'e', 'l', 'l', 'o' }) == "Hello");

[Fact]
public void call_with_ref_struct_parameter2() => Check
.With(() => new char[] { 'H', 'e', 'l', 'l', 'o' })
.That(x => StringFromSpan(x) == "Hello");

static string StringFromSpan(Span<char> cs) => new(cs);
}
}
2 changes: 1 addition & 1 deletion Specs/Cone.Specs/Cone.Specs.csproj
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net452;net472;netcoreapp2.1</TargetFrameworks>
<TargetFrameworks>net452;net472</TargetFrameworks>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
Expand Down

0 comments on commit 0a9be5d

Please sign in to comment.