diff --git a/src/FastExpressionCompiler/FastExpressionCompiler.cs b/src/FastExpressionCompiler/FastExpressionCompiler.cs
index 2ee422ec..f0783a0d 100644
--- a/src/FastExpressionCompiler/FastExpressionCompiler.cs
+++ b/src/FastExpressionCompiler/FastExpressionCompiler.cs
@@ -4368,7 +4368,8 @@ private static bool TryEmitArithmeticAndOrAssign(
// required for calling the method on the value type parameter
var objType = objExpr.Type;
objVarByAddress = !closure.LastEmitIsAddress && objType.IsValueType && // todo: @wip avoid ad-hocking with parameter here
- (objExpr.NodeType != ExpressionType.Parameter || !((ParameterExpression)objExpr).IsByRef);
+ (objExpr.NodeType != ExpressionType.Parameter || !((ParameterExpression)objExpr).IsByRef) &&
+ !objType.IsByRefLike(); // ref struct types cannot be stored in local variables
if (objVarByAddress)
objVar = EmitStoreAndLoadLocalVariableAddress(il, objType);
else
@@ -4938,7 +4939,7 @@ private static bool TryEmitMethodCall(Expression expr,
if (!TryEmit(objExpr, paramExprs, il, ref closure, setup, flags | ParentFlags.InstanceAccess))
return false;
objIsValueType = objExpr.Type.IsValueType;
- loadObjByAddress = objIsValueType && objExpr.NodeType != ExpressionType.Parameter && !closure.LastEmitIsAddress;
+ loadObjByAddress = objIsValueType && objExpr.NodeType != ExpressionType.Parameter && !closure.LastEmitIsAddress && !objExpr.Type.IsByRefLike();
}
var parCount = methodParams.Length;
@@ -5047,7 +5048,10 @@ public static bool TryEmitMemberGet(MemberExpression expr,
// Value type special treatment to load address of value instance in order to call a method.
// For the parameters, we will skip the address loading because the `LastEmitIsAddress == true` for `Ldarga`,
// so the condition here will be skipped
- if (!closure.LastEmitIsAddress && objExpr.Type.IsValueType)
+ // Ref struct types cannot be stored in local variables, so we skip them here
+ if (!closure.LastEmitIsAddress &&
+ objExpr.Type.IsValueType &&
+ !objExpr.Type.IsByRefLike())
EmitStoreAndLoadLocalVariableAddress(il, objExpr.Type);
}
@@ -5094,7 +5098,7 @@ public static bool TryEmitMemberGet(MemberExpression expr,
closure.LastEmitIsAddress = isByAddress;
if (!isByAddress)
{
- if (objExpr.Type.IsEnum)
+ if (objExpr.Type.IsEnum && !objExpr.Type.IsByRefLike())
EmitStoreAndLoadLocalVariableAddress(il, objExpr.Type);
il.Demit(OpCodes.Ldfld, field);
}
@@ -7816,6 +7820,33 @@ internal static bool IsFloatingPoint(this Type type) =>
type == typeof(float) ||
type == typeof(double);
+ /// Checks if the type is a ref struct (byref-like type).
+ /// Compatible with older frameworks that don't have Type.IsByRefLike property.
+ [MethodImpl((MethodImplOptions)256)]
+ [RequiresUnreferencedCode(Trimming.Message)]
+ internal static bool IsByRefLike(this Type type)
+ {
+#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER || NET6_0_OR_GREATER
+ return type.IsByRefLike;
+#else
+ // For older frameworks (net472, netstandard2.0), check for IsByRefLikeAttribute via reflection
+ // IsByRefLike property was introduced in .NET Standard 2.1 / .NET Core 2.1
+ if (!type.IsValueType)
+ return false;
+
+ // Check if the type has IsByRefLikeAttribute
+ var attributes = type.GetCustomAttributes(inherit: false);
+ foreach (var attr in attributes)
+ {
+ var attrType = attr.GetType();
+ if (attrType.Name == "IsByRefLikeAttribute" ||
+ attrType.FullName == "System.Runtime.CompilerServices.IsByRefLikeAttribute")
+ return true;
+ }
+ return false;
+#endif
+ }
+
internal static bool IsPrimitiveWithZeroDefaultExceptDecimal(this Type type)
{
switch (Type.GetTypeCode(type))
diff --git a/test/FastExpressionCompiler.IssueTests/Issue490_ref_struct_conditional.cs b/test/FastExpressionCompiler.IssueTests/Issue490_ref_struct_conditional.cs
new file mode 100644
index 00000000..5c2b7af3
--- /dev/null
+++ b/test/FastExpressionCompiler.IssueTests/Issue490_ref_struct_conditional.cs
@@ -0,0 +1,82 @@
+using System;
+
+#if LIGHT_EXPRESSION
+using static FastExpressionCompiler.LightExpression.Expression;
+namespace FastExpressionCompiler.LightExpression.IssueTests;
+#else
+using System.Linq.Expressions;
+using static System.Linq.Expressions.Expression;
+namespace FastExpressionCompiler.IssueTests;
+#endif
+
+public struct Issue490_ref_struct_conditional : ITest, ITestX
+{
+ public int Run()
+ {
+ Return_true_when_token_is_null();
+ Return_false_when_token_isnot_null();
+ return 1;
+ }
+
+ public void Run(TestRun t)
+ {
+ Return_true_when_token_is_null(t);
+ Return_false_when_token_isnot_null(t);
+ }
+
+ public delegate bool RefStructReaderDelegate(ref MyJsonReader reader);
+
+ public enum MyJsonTokenType : byte
+ {
+ None = 0,
+ StartObject = 1,
+ Null = 11
+ }
+
+ public ref struct MyJsonReader
+ {
+ public MyJsonTokenType TokenType { get; set; }
+ }
+
+ public void Return_true_when_token_is_null(TestContext t = default)
+ {
+ var readerParam = Parameter(typeof(MyJsonReader).MakeByRefType(), "reader");
+ var tokenType = Property(readerParam, nameof(MyJsonReader.TokenType));
+ var nullToken = Constant(MyJsonTokenType.Null);
+
+ var body = Condition(
+ Equal(tokenType, nullToken),
+ Constant(true),
+ Constant(false));
+
+ var lambda = Lambda(body, readerParam);
+ var func = lambda.CompileFast();
+
+ var reader = new MyJsonReader();
+ reader.TokenType = MyJsonTokenType.Null;
+
+ var result = func(ref reader);
+ t.AreEqual(true, result);
+ }
+
+ public void Return_false_when_token_isnot_null(TestContext t = default)
+ {
+ var readerParam = Parameter(typeof(MyJsonReader).MakeByRefType(), "reader");
+ var tokenType = Property(readerParam, nameof(MyJsonReader.TokenType));
+ var nullToken = Constant(MyJsonTokenType.Null);
+
+ var body = Condition(
+ Equal(tokenType, nullToken),
+ Constant(true),
+ Constant(false));
+
+ var lambda = Lambda(body, readerParam);
+ var func = lambda.CompileFast();
+
+ var reader = new MyJsonReader();
+ reader.TokenType = MyJsonTokenType.StartObject;
+
+ var result = func(ref reader);
+ t.AreEqual(false, result);
+ }
+}
\ No newline at end of file
diff --git a/test/FastExpressionCompiler.TestsRunner.Net472/Program.cs b/test/FastExpressionCompiler.TestsRunner.Net472/Program.cs
index 614d728b..acd49ea2 100644
--- a/test/FastExpressionCompiler.TestsRunner.Net472/Program.cs
+++ b/test/FastExpressionCompiler.TestsRunner.Net472/Program.cs
@@ -404,6 +404,9 @@ void Run(Func run, string name = null)
Run(new Issue461_InvalidProgramException_when_null_checking_type_by_ref().Run);
Run(new LightExpression.IssueTests.Issue461_InvalidProgramException_when_null_checking_type_by_ref().Run);
+ Run(new Issue490_ref_struct_conditional().Run);
+ Run(new LightExpression.IssueTests.Issue490_ref_struct_conditional().Run);
+
Console.WriteLine($"{Environment.NewLine}IssueTests are passing in {sw.ElapsedMilliseconds} ms.");
});
diff --git a/test/FastExpressionCompiler.TestsRunner/Program.cs b/test/FastExpressionCompiler.TestsRunner/Program.cs
index 45082583..4e027151 100644
--- a/test/FastExpressionCompiler.TestsRunner/Program.cs
+++ b/test/FastExpressionCompiler.TestsRunner/Program.cs
@@ -389,6 +389,9 @@ void Run(Func run, string name = null)
Run(new Issue461_InvalidProgramException_when_null_checking_type_by_ref().Run);
Run(new LightExpression.IssueTests.Issue461_InvalidProgramException_when_null_checking_type_by_ref().Run);
+ Run(new Issue490_ref_struct_conditional().Run);
+ Run(new LightExpression.IssueTests.Issue490_ref_struct_conditional().Run);
+
Console.WriteLine($"{Environment.NewLine}IssueTests are passing in {sw.ElapsedMilliseconds} ms.");
});