Skip to content

Commit 8a68626

Browse files
authored
Refactor KeywordsHelper, TypeFinder and update comments on ParsingConfig (#866)
* Refactor KeywordsHelper, TypeFinder and update comments on ParsingConfig * . * operator aliases * . * .
1 parent cac9576 commit 8a68626

18 files changed

+440
-402
lines changed

src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs

+11-4
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using JetBrains.Annotations;
1010
using System.Linq.Dynamic.Core.Parser;
1111
using System.Linq.Dynamic.Core.Util;
12+
1213
#if !(SILVERLIGHT)
1314
using System.Diagnostics;
1415
#endif
@@ -314,7 +315,7 @@ public static IQueryable Cast(this IQueryable source, Type type)
314315
Check.NotNull(source);
315316
Check.NotNull(type);
316317

317-
var optimized = OptimizeExpression(Expression.Call(null, _cast.MakeGenericMethod(new[] { type }), new[] { source.Expression }));
318+
var optimized = OptimizeExpression(Expression.Call(null, _cast.MakeGenericMethod(type), source.Expression));
318319

319320
return source.Provider.CreateQuery(optimized);
320321
}
@@ -330,10 +331,13 @@ public static IQueryable Cast(this IQueryable source, ParsingConfig config, stri
330331
{
331332
Check.NotNull(source);
332333
Check.NotNull(config);
333-
Check.NotEmpty(typeName, nameof(typeName));
334+
Check.NotEmpty(typeName);
334335

335336
var finder = new TypeFinder(config, new KeywordsHelper(config));
336-
Type type = finder.FindTypeByName(typeName, null, true)!;
337+
if (!finder.TryFindTypeByName(typeName, null, true, out var type))
338+
{
339+
throw new ParseException(string.Format(CultureInfo.CurrentCulture, Res.TypeNotFound, typeName));
340+
}
337341

338342
return Cast(source, type);
339343
}
@@ -1445,7 +1449,10 @@ public static IQueryable OfType(this IQueryable source, ParsingConfig config, st
14451449
Check.NotEmpty(typeName);
14461450

14471451
var finder = new TypeFinder(config, new KeywordsHelper(config));
1448-
Type type = finder.FindTypeByName(typeName, null, true)!;
1452+
if (!finder.TryFindTypeByName(typeName, null, true, out var type))
1453+
{
1454+
throw new ParseException(string.Format(CultureInfo.CurrentCulture, Res.TypeNotFound, typeName));
1455+
}
14491456

14501457
return OfType(source, type);
14511458
}

src/System.Linq.Dynamic.Core/Exceptions/ParseException.cs

+67-56
Original file line numberDiff line numberDiff line change
@@ -4,74 +4,85 @@
44
using System.Runtime.Serialization;
55
#endif
66

7-
namespace System.Linq.Dynamic.Core.Exceptions
7+
namespace System.Linq.Dynamic.Core.Exceptions;
8+
9+
/// <summary>
10+
/// Represents errors that occur while parsing dynamic linq string expressions.
11+
/// </summary>
12+
#if !(SILVERLIGHT || UAP10_0 || NETSTANDARD || PORTABLE || WPSL || NETSTANDARD2_0)
13+
[Serializable]
14+
#endif
15+
public sealed class ParseException : Exception
816
{
17+
private const int UnknownPosition = -1;
18+
919
/// <summary>
10-
/// Represents errors that occur while parsing dynamic linq string expressions.
20+
/// The location in the parsed string that produced the <see cref="ParseException"/>.
21+
/// If the value is <c>-1</c>, the position is unknown.
1122
/// </summary>
12-
#if !(SILVERLIGHT || UAP10_0 || NETSTANDARD || PORTABLE || WPSL || NETSTANDARD2_0)
13-
[Serializable]
14-
#endif
15-
public sealed class ParseException : Exception
23+
public int Position { get; }
24+
25+
/// <summary>
26+
/// Initializes a new instance of the <see cref="ParseException"/> class with a specified error message and position.
27+
/// </summary>
28+
/// <param name="message">The message that describes the error.</param>
29+
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
30+
public ParseException(string message, Exception? innerException = null) : this(message, UnknownPosition, innerException)
1631
{
17-
/// <summary>
18-
/// The location in the parsed string that produced the <see cref="ParseException"/>.
19-
/// </summary>
20-
public int Position { get; }
32+
}
2133

22-
/// <summary>
23-
/// Initializes a new instance of the <see cref="ParseException"/> class with a specified error message and position.
24-
/// </summary>
25-
/// <param name="message">The message that describes the error.</param>
26-
/// <param name="position">The location in the parsed string that produced the <see cref="ParseException"/></param>
27-
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
28-
public ParseException(string message, int position, Exception? innerException = null) : base(message, innerException)
34+
/// <summary>
35+
/// Initializes a new instance of the <see cref="ParseException"/> class with a specified error message and position.
36+
/// </summary>
37+
/// <param name="message">The message that describes the error.</param>
38+
/// <param name="position">The location in the parsed string that produced the <see cref="ParseException"/></param>
39+
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
40+
public ParseException(string message, int position, Exception? innerException = null) : base(message, innerException)
41+
{
42+
Position = position;
43+
}
44+
45+
/// <summary>
46+
/// Creates and returns a string representation of the current exception.
47+
/// </summary>
48+
/// <returns>A string representation of the current exception.</returns>
49+
public override string ToString()
50+
{
51+
var text = string.Format(CultureInfo.CurrentCulture, Res.ParseExceptionFormat, Message, Position);
52+
53+
if (InnerException != null)
2954
{
30-
Position = position;
55+
text = $"{text} ---> {InnerException}{Environment.NewLine} --- End of inner exception stack trace ---";
3156
}
3257

33-
/// <summary>
34-
/// Creates and returns a string representation of the current exception.
35-
/// </summary>
36-
/// <returns>A string representation of the current exception.</returns>
37-
public override string ToString()
58+
if (StackTrace != null)
3859
{
39-
var text = string.Format(CultureInfo.CurrentCulture, Res.ParseExceptionFormat, Message, Position);
40-
41-
if (InnerException != null)
42-
{
43-
text = $"{text} ---> {InnerException}{Environment.NewLine} --- End of inner exception stack trace ---";
44-
}
45-
46-
if (StackTrace != null)
47-
{
48-
text = $"{text}{Environment.NewLine}{StackTrace}";
49-
}
50-
51-
return text;
60+
text = $"{text}{Environment.NewLine}{StackTrace}";
5261
}
5362

63+
return text;
64+
}
65+
5466
#if !(SILVERLIGHT || UAP10_0 || NETSTANDARD || PORTABLE || WPSL || NETSTANDARD2_0)
55-
private ParseException(SerializationInfo info, StreamingContext context) : base(info, context)
56-
{
57-
Position = (int)info.GetValue("position", typeof(int));
58-
}
67+
private ParseException(SerializationInfo info, StreamingContext context) : base(info, context)
68+
{
69+
Position = (int)info.GetValue("position", typeof(int))!;
70+
}
5971

60-
/// <summary>
61-
/// When overridden in a derived class, sets the <see cref="T:System.Runtime.Serialization.SerializationInfo" /> with information about the exception.
62-
/// </summary>
63-
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> that holds the serialized object data about the exception being thrown.</param>
64-
/// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext" /> that contains contextual information about the source or destination.</param>
65-
/// <PermissionSet>
66-
/// <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Read="*AllFiles*" PathDiscovery="*AllFiles*" />
67-
/// <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="SerializationFormatter" />
68-
/// </PermissionSet>
69-
public override void GetObjectData(SerializationInfo info, StreamingContext context)
70-
{
71-
base.GetObjectData(info, context);
72+
/// <summary>
73+
/// When overridden in a derived class, sets the <see cref="T:System.Runtime.Serialization.SerializationInfo" /> with information about the exception.
74+
/// </summary>
75+
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> that holds the serialized object data about the exception being thrown.</param>
76+
/// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext" /> that contains contextual information about the source or destination.</param>
77+
/// <PermissionSet>
78+
/// <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Read="*AllFiles*" PathDiscovery="*AllFiles*" />
79+
/// <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="SerializationFormatter" />
80+
/// </PermissionSet>
81+
public override void GetObjectData(SerializationInfo info, StreamingContext context)
82+
{
83+
base.GetObjectData(info, context);
7284

73-
info.AddValue("position", Position);
74-
}
75-
#endif
85+
info.AddValue("position", Position);
7686
}
77-
}
87+
#endif
88+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using System.Collections.Generic;
2+
3+
namespace System.Linq.Dynamic.Core.Extensions;
4+
5+
internal static class ListExtensions
6+
{
7+
internal static void AddIfNotNull<T>(this IList<T> list, T? value)
8+
{
9+
if (value != null)
10+
{
11+
list.Add(value);
12+
}
13+
}
14+
}

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

+27-24
Original file line numberDiff line numberDiff line change
@@ -906,8 +906,7 @@ private AnyOf<Expression, Type> ParseStringLiteral(bool forceParseAsString)
906906
if (_parsingConfig.SupportCastingToFullyQualifiedTypeAsString && !forceParseAsString && parsedStringValue.Length > 2 && parsedStringValue.Contains('.'))
907907
{
908908
// Try to resolve this string as a type
909-
var type = _typeFinder.FindTypeByName(parsedStringValue, null, false);
910-
if (type is { })
909+
if (_typeFinder.TryFindTypeByName(parsedStringValue, null, false, out var type))
911910
{
912911
return type;
913912
}
@@ -970,7 +969,7 @@ private Expression ParseIdentifier()
970969
{
971970
_textParser.ValidateToken(TokenId.Identifier);
972971

973-
var isValidKeyWord = _keywordsHelper.TryGetValue(_textParser.CurrentToken.Text, out var keywordOrType);
972+
var isValid = _keywordsHelper.TryGetValue(_textParser.CurrentToken.Text, out var keywordOrType);
974973
var shouldPrioritizeType = true;
975974

976975
if (_parsingConfig.PrioritizePropertyOrFieldOverTheType && keywordOrType.IsThird)
@@ -983,7 +982,7 @@ private Expression ParseIdentifier()
983982
}
984983
}
985984

986-
if (isValidKeyWord && shouldPrioritizeType)
985+
if (isValid && shouldPrioritizeType)
987986
{
988987
var keywordOrFunctionAllowed = !_usedForOrderBy || _usedForOrderBy && !_parsingConfig.RestrictOrderByToPropertyOrField;
989988
if (!keywordOrFunctionAllowed)
@@ -1397,8 +1396,7 @@ private Expression ParseNew()
13971396
_textParser.NextToken();
13981397
}
13991398

1400-
newType = _typeFinder.FindTypeByName(newTypeName, new[] { _it, _parent, _root }, false);
1401-
if (newType == null)
1399+
if (!_typeFinder.TryFindTypeByName(newTypeName, [_it, _parent, _root], false, out newType))
14021400
{
14031401
throw ParseError(_textParser.CurrentToken.Pos, Res.TypeNotFound, newTypeName);
14041402
}
@@ -1496,7 +1494,10 @@ private Expression CreateArrayInitializerExpression(List<Expression> expressions
14961494

14971495
if (newType != null)
14981496
{
1499-
return Expression.NewArrayInit(newType, expressions.Select(expression => _parsingConfig.ExpressionPromoter.Promote(expression, newType, true, true)));
1497+
var promotedExpressions = expressions
1498+
.Select(expression => _parsingConfig.ExpressionPromoter.Promote(expression, newType, true, true))
1499+
.OfType<Expression>();
1500+
return Expression.NewArrayInit(newType, promotedExpressions);
15001501
}
15011502

15021503
return Expression.NewArrayInit(expressions.All(expression => expression.Type == expressions[0].Type) ? expressions[0].Type : typeof(object), expressions);
@@ -1543,32 +1544,34 @@ private Expression CreateNewExpression(List<DynamicProperty> properties, List<Ex
15431544
{
15441545
propertyInfos = propertyInfos.Where(x => x.Name != "Item").ToArray();
15451546
}
1547+
15461548
var propertyTypes = propertyInfos.Select(p => p.PropertyType).ToArray();
15471549
var ctor = type.GetConstructor(propertyTypes);
15481550
if (ctor != null)
15491551
{
15501552
var constructorParameters = ctor.GetParameters();
15511553
if (constructorParameters.Length == expressions.Count)
15521554
{
1553-
bool bindParametersSequentially = !properties.All(p => constructorParameters
1555+
var bindParametersSequentially = !properties.All(p => constructorParameters
15541556
.Any(cp => cp.Name == p.Name && (cp.ParameterType == p.Type || p.Type == Nullable.GetUnderlyingType(cp.ParameterType))));
1555-
var expressionsPromoted = new List<Expression?>();
1557+
var expressionsPromoted = new List<Expression>();
15561558

15571559
// Loop all expressions and promote if needed
15581560
for (int i = 0; i < constructorParameters.Length; i++)
15591561
{
15601562
if (bindParametersSequentially)
15611563
{
1562-
expressionsPromoted.Add(_parsingConfig.ExpressionPromoter.Promote(expressions[i], propertyTypes[i], true, true));
1564+
expressionsPromoted.AddIfNotNull(_parsingConfig.ExpressionPromoter.Promote(expressions[i], propertyTypes[i], true, true));
15631565
}
15641566
else
15651567
{
1566-
Type propertyType = constructorParameters[i].ParameterType;
1567-
string cParameterName = constructorParameters[i].Name;
1568+
var propertyType = constructorParameters[i].ParameterType;
1569+
var cParameterName = constructorParameters[i].Name;
15681570
var propertyAndIndex = properties.Select((p, index) => new { p, index })
15691571
.First(p => p.p.Name == cParameterName && (p.p.Type == propertyType || p.p.Type == Nullable.GetUnderlyingType(propertyType)));
1572+
15701573
// Promote from Type to Nullable Type if needed
1571-
expressionsPromoted.Add(_parsingConfig.ExpressionPromoter.Promote(expressions[propertyAndIndex.index], propertyType, true, true));
1574+
expressionsPromoted.AddIfNotNull(_parsingConfig.ExpressionPromoter.Promote(expressions[propertyAndIndex.index], propertyType, true, true));
15721575
}
15731576
}
15741577

@@ -1584,6 +1587,7 @@ private Expression CreateNewExpression(List<DynamicProperty> properties, List<Ex
15841587
// Promote from Type to Nullable Type if needed
15851588
var expressionsPromoted = exactConstructor.GetParameters()
15861589
.Select((t, i) => _parsingConfig.ExpressionPromoter.Promote(expressions[i], t.ParameterType, true, true))
1590+
.OfType<Expression>()
15871591
.ToArray();
15881592

15891593
return Expression.New(exactConstructor, expressionsPromoted);
@@ -1661,14 +1665,14 @@ private Expression ParseTypeAccess(Type type, bool getNext)
16611665
}
16621666

16631667
// This is a shorthand for explicitly converting a string to something
1664-
bool shorthand = _textParser.CurrentToken.Id == TokenId.StringLiteral;
1665-
if (_textParser.CurrentToken.Id == TokenId.OpenParen || shorthand)
1668+
var isShorthand = _textParser.CurrentToken.Id == TokenId.StringLiteral;
1669+
if (_textParser.CurrentToken.Id == TokenId.OpenParen || isShorthand)
16661670
{
16671671
Expression[] args;
1668-
if (shorthand)
1672+
if (isShorthand)
16691673
{
16701674
var expressionOrType = ParseStringLiteral(true);
1671-
args = new[] { expressionOrType.First };
1675+
args = [expressionOrType.First];
16721676
}
16731677
else
16741678
{
@@ -1685,7 +1689,7 @@ private Expression ParseTypeAccess(Type type, bool getNext)
16851689
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
16861690
if (args.Length == 1 && (args[0] == null || args[0] is ConstantExpression) && TryGenerateConversion(args[0], type, out var generatedExpression))
16871691
{
1688-
return generatedExpression!;
1692+
return generatedExpression;
16891693
}
16901694

16911695
// If only 1 argument, and if the type is a ValueType and argType is also a ValueType, just Convert
@@ -2027,8 +2031,7 @@ private Expression ParseAsEnumOrNestedClass(string id)
20272031
}
20282032

20292033
var typeAsString = string.Concat(parts.Take(parts.Count - 2).ToArray());
2030-
var type = _typeFinder.FindTypeByName(typeAsString, null, true);
2031-
if (type == null)
2034+
if (!_typeFinder.TryFindTypeByName(typeAsString, null, true, out var type))
20322035
{
20332036
throw ParseError(_textParser.CurrentToken.Pos, Res.TypeNotFound, typeAsString);
20342037
}
@@ -2233,20 +2236,20 @@ private Type ResolveTypeFromExpressionValue(string functionName, ConstantExpress
22332236

22342237
private Type ResolveTypeStringFromArgument(string typeName)
22352238
{
2236-
bool typeIsNullable = false;
2239+
var typeIsNullable = false;
2240+
22372241
if (typeName.EndsWith("?"))
22382242
{
22392243
typeName = typeName.TrimEnd('?');
22402244
typeIsNullable = true;
22412245
}
22422246

2243-
var resultType = _typeFinder.FindTypeByName(typeName, new[] { _it, _parent, _root }, true);
2244-
if (resultType == null)
2247+
if (!_typeFinder.TryFindTypeByName(typeName, [_it, _parent, _root], true, out var type))
22452248
{
22462249
throw ParseError(_textParser.CurrentToken.Pos, Res.TypeNotFound, typeName);
22472250
}
22482251

2249-
return typeIsNullable ? TypeHelper.ToNullableType(resultType) : resultType;
2252+
return typeIsNullable ? TypeHelper.ToNullableType(type) : type;
22502253
}
22512254

22522255
private Expression[] ParseArgumentList()

0 commit comments

Comments
 (0)