Skip to content

Commit 2050e63

Browse files
CopilotStefH
andcommitted
Fix nullable IComparable types not working with relational operators (>, >=, <, <=)
When comparing two values of the same nullable type (e.g., Instant?) using relational operators, the check for IComparable<> interface was done on the nullable type itself (Nullable<T>), which doesn't directly implement IComparable<>. The fix uses TypeHelper.GetNonNullableType() to get the underlying type first, so that if T implements IComparable<T>, nullable T? also works correctly with relational operators. Fixes: Operator '>' incompatible with operand types 'Instant?' and 'Instant?' Agent-Logs-Url: https://github.com/zzzprojects/System.Linq.Dynamic.Core/sessions/b497145d-ba3d-430a-b608-eda596efffdd Co-authored-by: StefH <249938+StefH@users.noreply.github.com>
1 parent 9cf37b3 commit 2050e63

File tree

2 files changed

+55
-1
lines changed

2 files changed

+55
-1
lines changed

src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -607,7 +607,8 @@ private Expression ParseComparisonOperator()
607607
bool typesAreSameAndImplementCorrectInterface = false;
608608
if (left.Type == right.Type)
609609
{
610-
var interfaces = left.Type.GetInterfaces().Where(x => x.GetTypeInfo().IsGenericType);
610+
var typeToCheck = TypeHelper.GetNonNullableType(left.Type);
611+
var interfaces = typeToCheck.GetInterfaces().Where(x => x.GetTypeInfo().IsGenericType);
611612
if (isEquality)
612613
{
613614
typesAreSameAndImplementCorrectInterface = interfaces.Any(x => x.GetGenericTypeDefinition() == typeof(IEquatable<>));

test/System.Linq.Dynamic.Core.Tests/TypeConvertors/NodaTimeConverterTests.cs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,59 @@ public void FilterByNullableLocalDate_WithDynamicExpressionParser_CompareWithNul
158158
result.Should().HaveCount(numberOfEntities);
159159
}
160160

161+
private class EntityWithInstant
162+
{
163+
public Instant Timestamp { get; set; }
164+
public Instant? TimestampNullable { get; set; }
165+
}
166+
167+
[Theory]
168+
[InlineData(">")]
169+
[InlineData(">=")]
170+
[InlineData("<")]
171+
[InlineData("<=")]
172+
public void FilterByInstant_WithRelationalOperator(string op)
173+
{
174+
// Arrange
175+
var now = SystemClock.Instance.GetCurrentInstant();
176+
var data = new List<EntityWithInstant>
177+
{
178+
new EntityWithInstant { Timestamp = now - Duration.FromHours(1) },
179+
new EntityWithInstant { Timestamp = now },
180+
new EntityWithInstant { Timestamp = now + Duration.FromHours(1) }
181+
}.AsQueryable();
182+
183+
// Act
184+
var result = data.Where($"Timestamp {op} @0", now).ToList();
185+
186+
// Assert
187+
result.Should().NotBeNull();
188+
}
189+
190+
[Theory]
191+
[InlineData(">")]
192+
[InlineData(">=")]
193+
[InlineData("<")]
194+
[InlineData("<=")]
195+
public void FilterByNullableInstant_WithRelationalOperator(string op)
196+
{
197+
// Arrange
198+
var now = SystemClock.Instance.GetCurrentInstant();
199+
var data = new List<EntityWithInstant>
200+
{
201+
new EntityWithInstant { TimestampNullable = now - Duration.FromHours(1) },
202+
new EntityWithInstant { TimestampNullable = now },
203+
new EntityWithInstant { TimestampNullable = now + Duration.FromHours(1) },
204+
new EntityWithInstant { TimestampNullable = null }
205+
}.AsQueryable();
206+
207+
// Act
208+
var result = data.Where($"TimestampNullable {op} @0", now).ToList();
209+
210+
// Assert
211+
result.Should().NotBeNull();
212+
}
213+
161214
public class LocalDateConverter : TypeConverter
162215
{
163216
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) => sourceType == typeof(string);

0 commit comments

Comments
 (0)