Skip to content

Commit

Permalink
Refactor KeywordsHelper, TypeFinder and update comments on ParsingConfig
Browse files Browse the repository at this point in the history
  • Loading branch information
StefH committed Dec 25, 2024
1 parent cac9576 commit 8bc7080
Show file tree
Hide file tree
Showing 14 changed files with 264 additions and 239 deletions.
15 changes: 11 additions & 4 deletions src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using JetBrains.Annotations;
using System.Linq.Dynamic.Core.Parser;
using System.Linq.Dynamic.Core.Util;

#if !(SILVERLIGHT)
using System.Diagnostics;
#endif
Expand Down Expand Up @@ -314,7 +315,7 @@ public static IQueryable Cast(this IQueryable source, Type type)
Check.NotNull(source);
Check.NotNull(type);

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

return source.Provider.CreateQuery(optimized);
}
Expand All @@ -330,10 +331,13 @@ public static IQueryable Cast(this IQueryable source, ParsingConfig config, stri
{
Check.NotNull(source);
Check.NotNull(config);
Check.NotEmpty(typeName, nameof(typeName));
Check.NotEmpty(typeName);

var finder = new TypeFinder(config, new KeywordsHelper(config));
Type type = finder.FindTypeByName(typeName, null, true)!;
if (!finder.TryFindTypeByName(typeName, null, true, out var type))
{
throw new ParseException(string.Format(CultureInfo.CurrentCulture, Res.TypeNotFound, typeName));
}

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

var finder = new TypeFinder(config, new KeywordsHelper(config));
Type type = finder.FindTypeByName(typeName, null, true)!;
if (!finder.TryFindTypeByName(typeName, null, true, out var type))
{
throw new ParseException(string.Format(CultureInfo.CurrentCulture, Res.TypeNotFound, typeName));
}

return OfType(source, type);
}
Expand Down
123 changes: 67 additions & 56 deletions src/System.Linq.Dynamic.Core/Exceptions/ParseException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,74 +4,85 @@
using System.Runtime.Serialization;
#endif

namespace System.Linq.Dynamic.Core.Exceptions
namespace System.Linq.Dynamic.Core.Exceptions;

/// <summary>
/// Represents errors that occur while parsing dynamic linq string expressions.
/// </summary>
#if !(SILVERLIGHT || UAP10_0 || NETSTANDARD || PORTABLE || WPSL || NETSTANDARD2_0)
[Serializable]
#endif
public sealed class ParseException : Exception
{
private const int UnknownPosition = -1;

/// <summary>
/// Represents errors that occur while parsing dynamic linq string expressions.
/// The location in the parsed string that produced the <see cref="ParseException"/>.
/// If the value is <c>-1</c>, the position is unknown.
/// </summary>
#if !(SILVERLIGHT || UAP10_0 || NETSTANDARD || PORTABLE || WPSL || NETSTANDARD2_0)
[Serializable]
#endif
public sealed class ParseException : Exception
public int Position { get; }

/// <summary>
/// Initializes a new instance of the <see cref="ParseException"/> class with a specified error message and position.
/// </summary>
/// <param name="message">The message that describes the error.</param>
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
public ParseException(string message, Exception? innerException = null) : this(message, UnknownPosition, innerException)
{
/// <summary>
/// The location in the parsed string that produced the <see cref="ParseException"/>.
/// </summary>
public int Position { get; }
}

/// <summary>
/// Initializes a new instance of the <see cref="ParseException"/> class with a specified error message and position.
/// </summary>
/// <param name="message">The message that describes the error.</param>
/// <param name="position">The location in the parsed string that produced the <see cref="ParseException"/></param>
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
public ParseException(string message, int position, Exception? innerException = null) : base(message, innerException)
/// <summary>
/// Initializes a new instance of the <see cref="ParseException"/> class with a specified error message and position.
/// </summary>
/// <param name="message">The message that describes the error.</param>
/// <param name="position">The location in the parsed string that produced the <see cref="ParseException"/></param>
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
public ParseException(string message, int position, Exception? innerException = null) : base(message, innerException)
{
Position = position;
}

/// <summary>
/// Creates and returns a string representation of the current exception.
/// </summary>
/// <returns>A string representation of the current exception.</returns>
public override string ToString()
{
var text = string.Format(CultureInfo.CurrentCulture, Res.ParseExceptionFormat, Message, Position);

if (InnerException != null)
{
Position = position;
text = $"{text} ---> {InnerException}{Environment.NewLine} --- End of inner exception stack trace ---";
}

/// <summary>
/// Creates and returns a string representation of the current exception.
/// </summary>
/// <returns>A string representation of the current exception.</returns>
public override string ToString()
if (StackTrace != null)
{
var text = string.Format(CultureInfo.CurrentCulture, Res.ParseExceptionFormat, Message, Position);

if (InnerException != null)
{
text = $"{text} ---> {InnerException}{Environment.NewLine} --- End of inner exception stack trace ---";
}

if (StackTrace != null)
{
text = $"{text}{Environment.NewLine}{StackTrace}";
}

return text;
text = $"{text}{Environment.NewLine}{StackTrace}";
}

return text;
}

#if !(SILVERLIGHT || UAP10_0 || NETSTANDARD || PORTABLE || WPSL || NETSTANDARD2_0)
private ParseException(SerializationInfo info, StreamingContext context) : base(info, context)
{
Position = (int)info.GetValue("position", typeof(int));
}
private ParseException(SerializationInfo info, StreamingContext context) : base(info, context)

Check warning on line 67 in src/System.Linq.Dynamic.Core/Exceptions/ParseException.cs

View workflow job for this annotation

GitHub Actions / Linux: Build and Tests

'Exception.Exception(SerializationInfo, StreamingContext)' is obsolete: 'This API supports obsolete formatter-based serialization. It should not be called or extended by application code.' (https://aka.ms/dotnet-warnings/SYSLIB0051)

Check warning on line 67 in src/System.Linq.Dynamic.Core/Exceptions/ParseException.cs

View workflow job for this annotation

GitHub Actions / Linux: Build and Tests

'Exception.Exception(SerializationInfo, StreamingContext)' is obsolete: 'This API supports obsolete formatter-based serialization. It should not be called or extended by application code.' (https://aka.ms/dotnet-warnings/SYSLIB0051)

Check warning on line 67 in src/System.Linq.Dynamic.Core/Exceptions/ParseException.cs

View workflow job for this annotation

GitHub Actions / Linux: Build and Tests

'Exception.Exception(SerializationInfo, StreamingContext)' is obsolete: 'This API supports obsolete formatter-based serialization. It should not be called or extended by application code.' (https://aka.ms/dotnet-warnings/SYSLIB0051)
{
Position = (int)info.GetValue("position", typeof(int))!;
}

/// <summary>
/// When overridden in a derived class, sets the <see cref="T:System.Runtime.Serialization.SerializationInfo" /> with information about the exception.
/// </summary>
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> that holds the serialized object data about the exception being thrown.</param>
/// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext" /> that contains contextual information about the source or destination.</param>
/// <PermissionSet>
/// <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Read="*AllFiles*" PathDiscovery="*AllFiles*" />
/// <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="SerializationFormatter" />
/// </PermissionSet>
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
/// <summary>
/// When overridden in a derived class, sets the <see cref="T:System.Runtime.Serialization.SerializationInfo" /> with information about the exception.
/// </summary>
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> that holds the serialized object data about the exception being thrown.</param>
/// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext" /> that contains contextual information about the source or destination.</param>
/// <PermissionSet>
/// <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Read="*AllFiles*" PathDiscovery="*AllFiles*" />
/// <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="SerializationFormatter" />
/// </PermissionSet>
public override void GetObjectData(SerializationInfo info, StreamingContext context)

Check warning on line 81 in src/System.Linq.Dynamic.Core/Exceptions/ParseException.cs

View workflow job for this annotation

GitHub Actions / Linux: Build and Tests

Member 'ParseException.GetObjectData(SerializationInfo, StreamingContext)' overrides obsolete member 'Exception.GetObjectData(SerializationInfo, StreamingContext)'. Add the Obsolete attribute to 'ParseException.GetObjectData(SerializationInfo, StreamingContext)'.

Check warning on line 81 in src/System.Linq.Dynamic.Core/Exceptions/ParseException.cs

View workflow job for this annotation

GitHub Actions / Linux: Build and Tests

Member 'ParseException.GetObjectData(SerializationInfo, StreamingContext)' overrides obsolete member 'Exception.GetObjectData(SerializationInfo, StreamingContext)'. Add the Obsolete attribute to 'ParseException.GetObjectData(SerializationInfo, StreamingContext)'.

Check warning on line 81 in src/System.Linq.Dynamic.Core/Exceptions/ParseException.cs

View workflow job for this annotation

GitHub Actions / Linux: Build and Tests

Member 'ParseException.GetObjectData(SerializationInfo, StreamingContext)' overrides obsolete member 'Exception.GetObjectData(SerializationInfo, StreamingContext)'. Add the Obsolete attribute to 'ParseException.GetObjectData(SerializationInfo, StreamingContext)'.

Check warning on line 81 in src/System.Linq.Dynamic.Core/Exceptions/ParseException.cs

View workflow job for this annotation

GitHub Actions / Linux: Build and Tests

Member 'ParseException.GetObjectData(SerializationInfo, StreamingContext)' overrides obsolete member 'Exception.GetObjectData(SerializationInfo, StreamingContext)'. Add the Obsolete attribute to 'ParseException.GetObjectData(SerializationInfo, StreamingContext)'.

Check warning on line 81 in src/System.Linq.Dynamic.Core/Exceptions/ParseException.cs

View workflow job for this annotation

GitHub Actions / Linux: Build and Tests

Member 'ParseException.GetObjectData(SerializationInfo, StreamingContext)' overrides obsolete member 'Exception.GetObjectData(SerializationInfo, StreamingContext)'. Add the Obsolete attribute to 'ParseException.GetObjectData(SerializationInfo, StreamingContext)'.

Check warning on line 81 in src/System.Linq.Dynamic.Core/Exceptions/ParseException.cs

View workflow job for this annotation

GitHub Actions / Linux: Build and Tests

Member 'ParseException.GetObjectData(SerializationInfo, StreamingContext)' overrides obsolete member 'Exception.GetObjectData(SerializationInfo, StreamingContext)'. Add the Obsolete attribute to 'ParseException.GetObjectData(SerializationInfo, StreamingContext)'.
{
base.GetObjectData(info, context);

Check warning on line 83 in src/System.Linq.Dynamic.Core/Exceptions/ParseException.cs

View workflow job for this annotation

GitHub Actions / Linux: Build and Tests

'Exception.GetObjectData(SerializationInfo, StreamingContext)' is obsolete: 'This API supports obsolete formatter-based serialization. It should not be called or extended by application code.' (https://aka.ms/dotnet-warnings/SYSLIB0051)

Check warning on line 83 in src/System.Linq.Dynamic.Core/Exceptions/ParseException.cs

View workflow job for this annotation

GitHub Actions / Linux: Build and Tests

'Exception.GetObjectData(SerializationInfo, StreamingContext)' is obsolete: 'This API supports obsolete formatter-based serialization. It should not be called or extended by application code.' (https://aka.ms/dotnet-warnings/SYSLIB0051)

Check warning on line 83 in src/System.Linq.Dynamic.Core/Exceptions/ParseException.cs

View workflow job for this annotation

GitHub Actions / Linux: Build and Tests

'Exception.GetObjectData(SerializationInfo, StreamingContext)' is obsolete: 'This API supports obsolete formatter-based serialization. It should not be called or extended by application code.' (https://aka.ms/dotnet-warnings/SYSLIB0051)

info.AddValue("position", Position);
}
#endif
info.AddValue("position", Position);
}
}
#endif
}
27 changes: 13 additions & 14 deletions src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -906,8 +906,7 @@ private AnyOf<Expression, Type> ParseStringLiteral(bool forceParseAsString)
if (_parsingConfig.SupportCastingToFullyQualifiedTypeAsString && !forceParseAsString && parsedStringValue.Length > 2 && parsedStringValue.Contains('.'))
{
// Try to resolve this string as a type
var type = _typeFinder.FindTypeByName(parsedStringValue, null, false);
if (type is { })
if (_typeFinder.TryFindTypeByName(parsedStringValue, null, false, out var type))
{
return type;
}
Expand Down Expand Up @@ -970,7 +969,7 @@ private Expression ParseIdentifier()
{
_textParser.ValidateToken(TokenId.Identifier);

var isValidKeyWord = _keywordsHelper.TryGetValue(_textParser.CurrentToken.Text, out var keywordOrType);
var isValid = _keywordsHelper.TryGetValue(_textParser.CurrentToken.Text, out var keywordOrType);
var shouldPrioritizeType = true;

if (_parsingConfig.PrioritizePropertyOrFieldOverTheType && keywordOrType.IsThird)
Expand All @@ -983,7 +982,7 @@ private Expression ParseIdentifier()
}
}

if (isValidKeyWord && shouldPrioritizeType)
if (isValid && shouldPrioritizeType)
{
var keywordOrFunctionAllowed = !_usedForOrderBy || _usedForOrderBy && !_parsingConfig.RestrictOrderByToPropertyOrField;
if (!keywordOrFunctionAllowed)
Expand Down Expand Up @@ -1397,8 +1396,7 @@ private Expression ParseNew()
_textParser.NextToken();
}

newType = _typeFinder.FindTypeByName(newTypeName, new[] { _it, _parent, _root }, false);
if (newType == null)
if (!_typeFinder.TryFindTypeByName(newTypeName, [_it, _parent, _root], false, out newType))
{
throw ParseError(_textParser.CurrentToken.Pos, Res.TypeNotFound, newTypeName);
}
Expand Down Expand Up @@ -1543,14 +1541,15 @@ private Expression CreateNewExpression(List<DynamicProperty> properties, List<Ex
{
propertyInfos = propertyInfos.Where(x => x.Name != "Item").ToArray();
}

var propertyTypes = propertyInfos.Select(p => p.PropertyType).ToArray();
var ctor = type.GetConstructor(propertyTypes);
if (ctor != null)
{
var constructorParameters = ctor.GetParameters();
if (constructorParameters.Length == expressions.Count)
{
bool bindParametersSequentially = !properties.All(p => constructorParameters
var bindParametersSequentially = !properties.All(p => constructorParameters
.Any(cp => cp.Name == p.Name && (cp.ParameterType == p.Type || p.Type == Nullable.GetUnderlyingType(cp.ParameterType))));
var expressionsPromoted = new List<Expression?>();

Expand All @@ -1564,9 +1563,10 @@ private Expression CreateNewExpression(List<DynamicProperty> properties, List<Ex
else
{
Type propertyType = constructorParameters[i].ParameterType;
string cParameterName = constructorParameters[i].Name;
var cParameterName = constructorParameters[i].Name;
var propertyAndIndex = properties.Select((p, index) => new { p, index })
.First(p => p.p.Name == cParameterName && (p.p.Type == propertyType || p.p.Type == Nullable.GetUnderlyingType(propertyType)));

// Promote from Type to Nullable Type if needed
expressionsPromoted.Add(_parsingConfig.ExpressionPromoter.Promote(expressions[propertyAndIndex.index], propertyType, true, true));
}
Expand Down Expand Up @@ -2027,8 +2027,7 @@ private Expression ParseAsEnumOrNestedClass(string id)
}

var typeAsString = string.Concat(parts.Take(parts.Count - 2).ToArray());
var type = _typeFinder.FindTypeByName(typeAsString, null, true);
if (type == null)
if (!_typeFinder.TryFindTypeByName(typeAsString, null, true, out var type))
{
throw ParseError(_textParser.CurrentToken.Pos, Res.TypeNotFound, typeAsString);
}
Expand Down Expand Up @@ -2233,20 +2232,20 @@ private Type ResolveTypeFromExpressionValue(string functionName, ConstantExpress

private Type ResolveTypeStringFromArgument(string typeName)
{
bool typeIsNullable = false;
var typeIsNullable = false;

if (typeName.EndsWith("?"))
{
typeName = typeName.TrimEnd('?');
typeIsNullable = true;
}

var resultType = _typeFinder.FindTypeByName(typeName, new[] { _it, _parent, _root }, true);
if (resultType == null)
if (!_typeFinder.TryFindTypeByName(typeName, [_it, _parent, _root], true, out var type))
{
throw ParseError(_textParser.CurrentToken.Pos, Res.TypeNotFound, typeName);
}

return typeIsNullable ? TypeHelper.ToNullableType(resultType) : resultType;
return typeIsNullable ? TypeHelper.ToNullableType(type) : type;
}

private Expression[] ParseArgumentList()
Expand Down
4 changes: 2 additions & 2 deletions src/System.Linq.Dynamic.Core/Parser/IKeywordsHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace System.Linq.Dynamic.Core.Parser;

interface IKeywordsHelper
internal interface IKeywordsHelper
{
bool TryGetValue(string name, out AnyOf<string, Expression, Type> keywordOrType);
bool TryGetValue(string text, out AnyOf<string, Expression, Type> value);
}
12 changes: 6 additions & 6 deletions src/System.Linq.Dynamic.Core/Parser/ITypeFinder.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
using System.Linq.Expressions;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;

namespace System.Linq.Dynamic.Core.Parser
namespace System.Linq.Dynamic.Core.Parser;

internal interface ITypeFinder
{
interface ITypeFinder
{
Type? FindTypeByName(string name, ParameterExpression?[]? expressions, bool forceUseCustomTypeProvider);
}
bool TryFindTypeByName(string name, ParameterExpression?[]? expressions, bool forceUseCustomTypeProvider, [NotNullWhen(true)] out Type? type);
}
Loading

0 comments on commit 8bc7080

Please sign in to comment.