diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs index c2fb0102..b4a7245f 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs @@ -218,11 +218,11 @@ internal IList ParseOrdering(bool forceThenBy = false) { var expr = ParseConditionalOperator(); var ascending = true; - if (TokenIdentifierIs("asc") || TokenIdentifierIs("ascending")) + if (TokenIsIdentifier("asc") || TokenIsIdentifier("ascending")) { _textParser.NextToken(); } - else if (TokenIdentifierIs("desc") || TokenIdentifierIs("descending")) + else if (TokenIsIdentifier("desc") || TokenIsIdentifier("descending")) { _textParser.NextToken(); ascending = false; @@ -337,19 +337,34 @@ private Expression ParseAndOperator() return left; } - // in operator for literals - example: "x in (1,2,3,4)" - // in operator to mimic contains - example: "x in @0", compare to @0.Contains(x) - // Adapted from ticket submitted by github user mlewis9548 + // "in" / "not in" / "not_in" operator for literals - example: "x in (1,2,3,4)" + // "in" / "not in" / "not_in" operator to mimic contains - example: "x in @0", compare to @0.Contains(x) private Expression ParseIn() { Expression left = ParseLogicalAndOrOperator(); Expression accumulate = left; - while (TokenIdentifierIs("in")) + while (_textParser.TryGetToken(["in", "not_in", "not"], [TokenId.Exclamation], out var token)) { - var op = _textParser.CurrentToken; + var not = false; + if (token.Text == "not_in") + { + not = true; + } + else if (token.Text == "not" || token.Id == TokenId.Exclamation) + { + not = true; + + _textParser.NextToken(); + + if (!TokenIsIdentifier("in")) + { + throw ParseError(token.Pos, Res.TokenExpected, "in"); + } + } _textParser.NextToken(); + if (_textParser.CurrentToken.Id == TokenId.OpenParen) // literals (or other inline list) { while (_textParser.CurrentToken.Id != TokenId.CloseParen) @@ -364,18 +379,18 @@ private Expression ParseIn() { if (right is ConstantExpression constantExprRight) { - right = ParseEnumToConstantExpression(op.Pos, left.Type, constantExprRight); + right = ParseEnumToConstantExpression(token.Pos, left.Type, constantExprRight); } else if (_expressionHelper.TryUnwrapAsConstantExpression(right, out var unwrappedConstantExprRight)) { - right = ParseEnumToConstantExpression(op.Pos, left.Type, unwrappedConstantExprRight); + right = ParseEnumToConstantExpression(token.Pos, left.Type, unwrappedConstantExprRight); } } // else, check for direct type match else if (left.Type != right.Type) { - CheckAndPromoteOperands(typeof(IEqualitySignatures), TokenId.DoubleEqual, "==", ref left, ref right, op.Pos); + CheckAndPromoteOperands(typeof(IEqualitySignatures), TokenId.DoubleEqual, "==", ref left, ref right, token.Pos); } if (accumulate.Type != typeof(bool)) @@ -389,7 +404,7 @@ private Expression ParseIn() if (_textParser.CurrentToken.Id == TokenId.End) { - throw ParseError(op.Pos, Res.CloseParenOrCommaExpected); + throw ParseError(token.Pos, Res.CloseParenOrCommaExpected); } } @@ -413,7 +428,12 @@ private Expression ParseIn() } else { - throw ParseError(op.Pos, Res.OpenParenOrIdentifierExpected); + throw ParseError(token.Pos, Res.OpenParenOrIdentifierExpected); + } + + if (not) + { + accumulate = Expression.Not(accumulate); } } @@ -759,7 +779,7 @@ private Expression ParseAdditive() private Expression ParseArithmetic() { Expression left = ParseUnary(); - while (_textParser.CurrentToken.Id is TokenId.Asterisk or TokenId.Slash or TokenId.Percent || TokenIdentifierIs("mod")) + while (_textParser.CurrentToken.Id is TokenId.Asterisk or TokenId.Slash or TokenId.Percent || TokenIsIdentifier("mod")) { Token op = _textParser.CurrentToken; _textParser.NextToken(); @@ -787,11 +807,11 @@ private Expression ParseArithmetic() // -, !, not unary operators private Expression ParseUnary() { - if (_textParser.CurrentToken.Id == TokenId.Minus || _textParser.CurrentToken.Id == TokenId.Exclamation || TokenIdentifierIs("not")) + if (_textParser.CurrentToken.Id == TokenId.Minus || _textParser.CurrentToken.Id == TokenId.Exclamation || TokenIsIdentifier("not")) { Token op = _textParser.CurrentToken; _textParser.NextToken(); - if (op.Id == TokenId.Minus && (_textParser.CurrentToken.Id == TokenId.IntegerLiteral || _textParser.CurrentToken.Id == TokenId.RealLiteral)) + if (op.Id == TokenId.Minus && _textParser.CurrentToken.Id is TokenId.IntegerLiteral or TokenId.RealLiteral) { _textParser.CurrentToken.Text = "-" + _textParser.CurrentToken.Text; _textParser.CurrentToken.Pos = op.Pos; @@ -1445,7 +1465,7 @@ private Expression ParseNew() if (!arrayInitializer) { string? propName; - if (TokenIdentifierIs("as")) + if (TokenIsIdentifier("as")) { _textParser.NextToken(); propName = GetIdentifierAs(); @@ -2527,11 +2547,11 @@ private static Exception IncompatibleOperandsError(string opName, Expression lef #endif } - private bool TokenIdentifierIs(string id) + private bool TokenIsIdentifier(string id) { - return _textParser.CurrentToken.Id == TokenId.Identifier && string.Equals(id, _textParser.CurrentToken.Text, StringComparison.OrdinalIgnoreCase); + return _textParser.TokenIsIdentifier(id); } - + private string GetIdentifier() { _textParser.ValidateToken(TokenId.Identifier, Res.IdentifierExpected); diff --git a/src/System.Linq.Dynamic.Core/Tokenizer/TextParser.cs b/src/System.Linq.Dynamic.Core/Tokenizer/TextParser.cs index cea61324..4f5a3529 100644 --- a/src/System.Linq.Dynamic.Core/Tokenizer/TextParser.cs +++ b/src/System.Linq.Dynamic.Core/Tokenizer/TextParser.cs @@ -479,6 +479,40 @@ public void ValidateToken(TokenId tokenId, string? errorMessage = null) } } + /// + /// Check if the current token is an with the provided id . + /// + /// The id + public bool TokenIsIdentifier(string id) + { + return CurrentToken.Id == TokenId.Identifier && string.Equals(id, CurrentToken.Text, StringComparison.OrdinalIgnoreCase); + } + + /// + /// Try to get a token based on the id or . + /// + /// The ids. + /// The tokenIds. + /// The found token, or default when not found. + public bool TryGetToken(string[] ids, TokenId[] tokenIds, out Token token) + { + token = default; + + if (ids.Any(TokenIsIdentifier)) + { + token = CurrentToken; + return true; + } + + if (tokenIds.Any(tokenId => tokenId == CurrentToken.Id)) + { + token = CurrentToken; + return true; + } + + return false; + } + private void SetTextPos(int pos) { _textPos = pos; diff --git a/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs b/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs index 1c00d1d6..8121bbf3 100644 --- a/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs @@ -1307,28 +1307,44 @@ public void ExpressionTests_In_Short() public void ExpressionTests_In_String() { // Arrange - var testRange = Enumerable.Range(1, 100).ToArray(); var testModels = User.GenerateSampleModels(10); - var testModelByUsername = string.Format("Username in (\"{0}\",\"{1}\",\"{2}\")", testModels[0].UserName, testModels[1].UserName, testModels[2].UserName); + var testModelByUsername = $"Username in (\"{testModels[0].UserName}\",\"{testModels[1].UserName}\",\"{testModels[2].UserName}\")"; + + // Act + var result1 = testModels.AsQueryable().Where(testModelByUsername).ToArray(); + var result2 = testModels.AsQueryable().Where("Id in (@0, @1, @2)", testModels[0].Id, testModels[1].Id, testModels[2].Id).ToArray(); + + // Assert + Assert.Equal(testModels.Take(3).ToArray(), result1); + Assert.Equal(testModels.Take(3).ToArray(), result2); + } + + [Fact] + public void ExpressionTests_In_IntegerArray() + { + // Arrange + var testRange = Enumerable.Range(1, 10).ToArray(); var testInExpression = new[] { 2, 4, 6, 8 }; // Act var result1a = testRange.AsQueryable().Where("it in (2,4,6,8)").ToArray(); var result1b = testRange.AsQueryable().Where("it in (2, 4, 6, 8)").ToArray(); - // https://github.com/NArnott/System.Linq.Dynamic/issues/52 - var result2 = testModels.AsQueryable().Where(testModelByUsername).ToArray(); - var result3 = - testModels.AsQueryable() - .Where("Id in (@0, @1, @2)", testModels[0].Id, testModels[1].Id, testModels[2].Id) - .ToArray(); - var result4 = testRange.AsQueryable().Where("it in @0", testInExpression).ToArray(); + var result2 = testRange.AsQueryable().Where("it in @0", testInExpression).ToArray(); // Assert - Assert.Equal(new[] { 2, 4, 6, 8 }, result1a); - Assert.Equal(new[] { 2, 4, 6, 8 }, result1b); - Assert.Equal(testModels.Take(3).ToArray(), result2); - Assert.Equal(testModels.Take(3).ToArray(), result3); - Assert.Equal(new[] { 2, 4, 6, 8 }, result4); + Assert.Equal([2, 4, 6, 8], result1a); + Assert.Equal([2, 4, 6, 8], result1b); + Assert.Equal([2, 4, 6, 8], result2); + } + + [Fact] + public void ExpressionTests_InvalidNotIn_ThrowsException() + { + // Arrange + var testRange = Enumerable.Range(1, 10).ToArray(); + + // Act + Assert + Check.ThatCode(() => testRange.AsQueryable().Where("it not not in (2,4,6,8)").ToArray()).Throws(); } [Fact] @@ -1519,6 +1535,26 @@ public void ExpressionTests_Multiply_Number() Check.That(result).ContainsExactly(expected); } + [Fact] + public void ExpressionTests_NotIn_IntegerArray() + { + // Arrange + var testRange = Enumerable.Range(1, 9).ToArray(); + var testInExpression = new[] { 2, 4, 6, 8 }; + + // Act + var result1a = testRange.AsQueryable().Where("it not in (2,4,6,8)").ToArray(); + var result1b = testRange.AsQueryable().Where("it not_in (2, 4, 6, 8)").ToArray(); + var result2 = testRange.AsQueryable().Where("it not in @0", testInExpression).ToArray(); + var result3 = testRange.AsQueryable().Where("it not_in @0", testInExpression).ToArray(); + + // Assert + Assert.Equal([1, 3, 5, 7, 9], result1a); + Assert.Equal([1, 3, 5, 7, 9], result1b); + Assert.Equal([1, 3, 5, 7, 9], result2); + Assert.Equal([1, 3, 5, 7, 9], result3); + } + [Fact] public void ExpressionTests_NullCoalescing() { @@ -1699,7 +1735,7 @@ public void ExpressionTests_NullPropagating_Config_Has_UseDefault(string test, s queryAsString = queryAsString.Substring(queryAsString.IndexOf(".Select") + 1).TrimEnd(']'); Check.That(queryAsString).Equals(query); } - + [Fact] public void ExpressionTests_NullPropagation_Method() { @@ -2103,7 +2139,7 @@ public void ExpressionTests_StringEscaping() // Act var result = baseQuery.Where("it.Value == \"ab\\\"cd\"").ToList(); - + // Assert Assert.Single(result); Assert.Equal("ab\"cd", result[0].Value); diff --git a/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.cs b/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.cs index 9369d143..7c8d7e43 100644 --- a/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.cs @@ -35,7 +35,7 @@ public class MyView public ExpressionParserTests() { _dynamicTypeProviderMock = new Mock(); - _dynamicTypeProviderMock.Setup(dt => dt.GetCustomTypes()).Returns(new HashSet() { typeof(Company), typeof(MainCompany) }); + _dynamicTypeProviderMock.Setup(dt => dt.GetCustomTypes()).Returns([typeof(Company), typeof(MainCompany)]); _dynamicTypeProviderMock.Setup(dt => dt.ResolveType(typeof(Company).FullName!)).Returns(typeof(Company)); _dynamicTypeProviderMock.Setup(dt => dt.ResolveType(typeof(MainCompany).FullName!)).Returns(typeof(MainCompany)); _dynamicTypeProviderMock.Setup(dt => dt.ResolveTypeBySimpleName("Company")).Returns(typeof(Company)); @@ -57,8 +57,8 @@ public void Parse_BitwiseOperatorOr_On_2EnumFlags() #else var expected = "Convert((Convert(A, Int32) | Convert(B, Int32)), ExampleFlags)"; #endif - ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(typeof(int), "x") }; - var sut = new ExpressionParser(parameters, expression, new object[] { ExampleFlags.A, ExampleFlags.B }, null); + ParameterExpression[] parameters = [ParameterExpressionHelper.CreateParameterExpression(typeof(int), "x")]; + var sut = new ExpressionParser(parameters, expression, [ExampleFlags.A, ExampleFlags.B], null); // Act var parsedExpression = sut.Parse(null).ToString(); @@ -87,8 +87,8 @@ public void Parse_BitwiseOperatorAnd_On_2EnumFlags() #else var expected = "Convert((Convert(A, Int32) & Convert(B, Int32)), ExampleFlags)"; #endif - ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(typeof(int), "x") }; - var sut = new ExpressionParser(parameters, expression, new object[] { ExampleFlags.A, ExampleFlags.B }, null); + ParameterExpression[] parameters = [ParameterExpressionHelper.CreateParameterExpression(typeof(int), "x")]; + var sut = new ExpressionParser(parameters, expression, [ExampleFlags.A, ExampleFlags.B], null); // Act var parsedExpression = sut.Parse(null).ToString(); @@ -117,8 +117,8 @@ public void Parse_BitwiseOperatorOr_On_3EnumFlags() #else var expected = "Convert(((Convert(A, Int32) | Convert(B, Int32)) | Convert(C, Int32)), ExampleFlags)"; #endif - ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(typeof(int), "x") }; - var sut = new ExpressionParser(parameters, expression, new object[] { ExampleFlags.A, ExampleFlags.B, ExampleFlags.C }, null); + ParameterExpression[] parameters = [ParameterExpressionHelper.CreateParameterExpression(typeof(int), "x")]; + var sut = new ExpressionParser(parameters, expression, [ExampleFlags.A, ExampleFlags.B, ExampleFlags.C], null); // Act var parsedExpression = sut.Parse(null).ToString(); @@ -147,8 +147,8 @@ public void Parse_BitwiseOperatorAnd_On_3EnumFlags() #else var expected = "Convert(((Convert(A, Int32) & Convert(B, Int32)) & Convert(C, Int32)), ExampleFlags)"; #endif - ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(typeof(int), "x") }; - var sut = new ExpressionParser(parameters, expression, new object[] { ExampleFlags.A, ExampleFlags.B, ExampleFlags.C }, null); + ParameterExpression[] parameters = [ParameterExpressionHelper.CreateParameterExpression(typeof(int), "x")]; + var sut = new ExpressionParser(parameters, expression, [ExampleFlags.A, ExampleFlags.B, ExampleFlags.C], null); // Act var parsedExpression = sut.Parse(null).ToString(); @@ -172,7 +172,7 @@ public void Parse_ParseBinaryInteger() { // Arrange var expression = "0b1100000011101"; - ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(typeof(int), "x") }; + ParameterExpression[] parameters = [ParameterExpressionHelper.CreateParameterExpression(typeof(int), "x")]; var sut = new ExpressionParser(parameters, expression, null, null); // Act @@ -187,7 +187,7 @@ public void Parse_ParseHexadecimalInteger() { // Arrange var expression = "0xFF"; - ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(typeof(int), "x") }; + ParameterExpression[] parameters = [ParameterExpressionHelper.CreateParameterExpression(typeof(int), "x")]; var sut = new ExpressionParser(parameters, expression, null, null); // Act @@ -216,7 +216,7 @@ public void Parse_ParseHexadecimalInteger() public void Parse_ParseComparisonOperator(string expression, string result) { // Arrange - ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(typeof(int), "x") }; + ParameterExpression[] parameters = [ParameterExpressionHelper.CreateParameterExpression(typeof(int), "x")]; var sut = new ExpressionParser(parameters, expression, null, null); // Act @@ -233,7 +233,7 @@ public void Parse_ParseComparisonOperator(string expression, string result) public void Parse_ParseOrOperator(string expression, string result) { // Arrange - ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(typeof(bool), "x") }; + ParameterExpression[] parameters = [ParameterExpressionHelper.CreateParameterExpression(typeof(bool), "x")]; var sut = new ExpressionParser(parameters, expression, null, null); // Act @@ -250,7 +250,7 @@ public void Parse_ParseOrOperator(string expression, string result) public void Parse_ParseAndOperator(string expression, string result) { // Arrange - ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(typeof(bool), "x") }; + ParameterExpression[] parameters = [ParameterExpressionHelper.CreateParameterExpression(typeof(bool), "x")]; var sut = new ExpressionParser(parameters, expression, null, null); // Act @@ -264,22 +264,51 @@ public void Parse_ParseAndOperator(string expression, string result) public void Parse_ParseMultipleInOperators() { // Arrange - ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(typeof(Company), "x") }; - var sut = new ExpressionParser(parameters, "MainCompanyId in (1, 2) and Name in (\"A\", \"B\")", null, null); + ParameterExpression[] parameters = [ParameterExpressionHelper.CreateParameterExpression(typeof(Company), "x")]; + var sut = new ExpressionParser(parameters, "MainCompanyId in (1, 2) and Name in (\"A\", \"B\") && 'y' in Name && 'z' in Name", null, null); // Act var parsedExpression = sut.Parse(null).ToString(); // Assert - Check.That(parsedExpression).Equals("(((x.MainCompanyId == 1) OrElse (x.MainCompanyId == 2)) AndAlso ((x.Name == \"A\") OrElse (x.Name == \"B\")))"); + Check.That(parsedExpression).Equals("(((((x.MainCompanyId == 1) OrElse (x.MainCompanyId == 2)) AndAlso ((x.Name == \"A\") OrElse (x.Name == \"B\"))) AndAlso x.Name.Contains(y)) AndAlso x.Name.Contains(z))"); + } + + [Fact] + public void Parse_ParseMultipleInAndNotInOperators() + { + // Arrange + ParameterExpression[] parameters = [ParameterExpressionHelper.CreateParameterExpression(typeof(Company), "x")]; + var sut = new ExpressionParser(parameters, "MainCompanyId in (1, 2) and Name not in (\"A\", \"B\") && 'y' in Name && 'z' not in Name", null, null); + + // Act + var parsedExpression = sut.Parse(null).ToString(); + + // Assert + Check.That(parsedExpression).Equals("(((((x.MainCompanyId == 1) OrElse (x.MainCompanyId == 2)) AndAlso Not(((x.Name == \"A\") OrElse (x.Name == \"B\")))) AndAlso x.Name.Contains(y)) AndAlso Not(x.Name.Contains(z)))"); + } + + + [Fact] + public void Parse_ParseMultipleInAndNotInAndNot_InOperators() + { + // Arrange + ParameterExpression[] parameters = [ParameterExpressionHelper.CreateParameterExpression(typeof(Company), "x")]; + var sut = new ExpressionParser(parameters, "MainCompanyId in (1, 2) and MainCompanyId not in (3, 4) and Name not_in (\"A\", \"B\") && 'y' in Name && 'z' not in Name && 's' not_in Name", null, null); + + // Act + var parsedExpression = sut.Parse(null).ToString(); + + // Assert + Check.That(parsedExpression).Equals("(((((((x.MainCompanyId == 1) OrElse (x.MainCompanyId == 2)) AndAlso Not(((x.MainCompanyId == 3) OrElse (x.MainCompanyId == 4)))) AndAlso Not(((x.Name == \"A\") OrElse (x.Name == \"B\")))) AndAlso x.Name.Contains(y)) AndAlso Not(x.Name.Contains(z))) AndAlso Not(x.Name.Contains(s)))"); } [Fact] public void Parse_ParseInWrappedInParenthesis() { // Arrange - ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(typeof(Company), "x") }; - var sut = new ExpressionParser(parameters, "(MainCompanyId in @0)", new object[] { new long?[] { 1, 2 } }, null); + ParameterExpression[] parameters = [ParameterExpressionHelper.CreateParameterExpression(typeof(Company), "x")]; + var sut = new ExpressionParser(parameters, "(MainCompanyId in @0)", [new long?[] { 1, 2 }], null); // Act var parsedExpression = sut.Parse(null).ToString(); @@ -309,7 +338,7 @@ public void Parse_CastActingOnIt() public void Parse_CastStringIntShouldReturnConstantExpression(string expression, object result) { // Arrange - ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(typeof(bool), "x") }; + ParameterExpression[] parameters = [ParameterExpressionHelper.CreateParameterExpression(typeof(bool), "x")]; var sut = new ExpressionParser(parameters, expression, null, null); // Act @@ -332,7 +361,7 @@ public void Parse_CastStringIntShouldReturnConstantExpression(string expression, public void Parse_NullableShouldReturnNullable(string expression, object resultType, object result) { // Arrange - ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(typeof(bool), "x") }; + ParameterExpression[] parameters = [ParameterExpressionHelper.CreateParameterExpression(typeof(bool), "x")]; var sut = new ExpressionParser(parameters, expression, null, null); // Act @@ -361,7 +390,8 @@ public void Parse_When_PrioritizePropertyOrFieldOverTheType_IsTrue(string expres CustomTypeProvider = _dynamicTypeProviderMock.Object, AllowEqualsAndToStringMethodsOnObject = true }; - ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(typeof(Company), "company") }; + ParameterExpression[] parameters = [ParameterExpressionHelper.CreateParameterExpression(typeof(Company), "company") + ]; var sut = new ExpressionParser(parameters, expression, null, config); // Act @@ -394,7 +424,8 @@ public void Parse_When_PrioritizePropertyOrFieldOverTheType_IsFalse(string expre { PrioritizePropertyOrFieldOverTheType = false }; - ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(typeof(Company), "company") }; + ParameterExpression[] parameters = [ParameterExpressionHelper.CreateParameterExpression(typeof(Company), "company") + ]; // Act string parsedExpression; @@ -436,7 +467,7 @@ public void Parse_StringConcat(string expression, string result) public void Parse_InvalidExpressionShouldThrowArgumentException() { // Arrange & Act - Action act = () => DynamicExpressionParser.ParseLambda(ParsingConfig.Default, false, "Properties[\"foo\"] > 2", Array.Empty()); + Action act = () => DynamicExpressionParser.ParseLambda(ParsingConfig.Default, false, "Properties[\"foo\"] > 2", []); // Assert act.Should().Throw().WithMessage("Method 'Compare' not found on type 'System.String' or 'System.Int32'");