Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
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 @@
{
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 @@
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 Expand Up @@ -2104,7 +2111,7 @@
/// <inheritdoc cref="SelectMany(IQueryable, ParsingConfig, string, string, string, string, object[], object[])"/>
public static IQueryable SelectMany(this IQueryable source, string collectionSelector, string resultSelector, string collectionParameterName, string resultParameterName, object?[]? collectionSelectorArgs = null, params object?[]? resultSelectorArgs)
{
return SelectMany(source, ParsingConfig.Default, collectionSelector, resultSelector, collectionParameterName, resultParameterName, collectionSelectorArgs, resultSelectorArgs);

Check warning on line 2114 in src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs

View workflow job for this annotation

GitHub Actions / Linux: Build and Tests

Argument of type 'object?[]' cannot be used for parameter 'resultSelectorArgs' of type 'object[]' in 'IQueryable DynamicQueryableExtensions.SelectMany(IQueryable source, ParsingConfig config, string collectionSelector, string resultSelector, string collectionParameterName, string resultParameterName, object?[]? collectionSelectorArgs = null, params object[]? resultSelectorArgs)' due to differences in the nullability of reference types.

Check warning on line 2114 in src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs

View workflow job for this annotation

GitHub Actions / Linux: Build and Tests

Argument of type 'object?[]' cannot be used for parameter 'resultSelectorArgs' of type 'object[]' in 'IQueryable DynamicQueryableExtensions.SelectMany(IQueryable source, ParsingConfig config, string collectionSelector, string resultSelector, string collectionParameterName, string resultParameterName, object?[]? collectionSelectorArgs = null, params object[]? resultSelectorArgs)' due to differences in the nullability of reference types.

Check warning on line 2114 in src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs

View workflow job for this annotation

GitHub Actions / Linux: Build and Tests

Argument of type 'object?[]' cannot be used for parameter 'resultSelectorArgs' of type 'object[]' in 'IQueryable DynamicQueryableExtensions.SelectMany(IQueryable source, ParsingConfig config, string collectionSelector, string resultSelector, string collectionParameterName, string resultParameterName, object?[]? collectionSelectorArgs = null, params object[]? resultSelectorArgs)' due to differences in the nullability of reference types.

Check warning on line 2114 in src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs

View workflow job for this annotation

GitHub Actions / Linux: Build and Tests

Argument of type 'object?[]' cannot be used for parameter 'resultSelectorArgs' of type 'object[]' in 'IQueryable DynamicQueryableExtensions.SelectMany(IQueryable source, ParsingConfig config, string collectionSelector, string resultSelector, string collectionParameterName, string resultParameterName, object?[]? collectionSelectorArgs = null, params object[]? resultSelectorArgs)' due to differences in the nullability of reference types.

Check warning on line 2114 in src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs

View workflow job for this annotation

GitHub Actions / Windows: Build and Tests

Argument of type 'object?[]' cannot be used for parameter 'resultSelectorArgs' of type 'object[]' in 'IQueryable DynamicQueryableExtensions.SelectMany(IQueryable source, ParsingConfig config, string collectionSelector, string resultSelector, string collectionParameterName, string resultParameterName, object?[]? collectionSelectorArgs = null, params object[]? resultSelectorArgs)' due to differences in the nullability of reference types.
}

#endregion SelectMany
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)'.
{
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
}
14 changes: 14 additions & 0 deletions src/System.Linq.Dynamic.Core/Extensions/ListExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.Collections.Generic;

namespace System.Linq.Dynamic.Core.Extensions;

internal static class ListExtensions
{
internal static void AddIfNotNull<T>(this IList<T> list, T? value)
{
if (value != null)
{
list.Add(value);
}
}
}
51 changes: 27 additions & 24 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 @@ -1496,7 +1494,10 @@ private Expression CreateArrayInitializerExpression(List<Expression> expressions

if (newType != null)
{
return Expression.NewArrayInit(newType, expressions.Select(expression => _parsingConfig.ExpressionPromoter.Promote(expression, newType, true, true)));
var promotedExpressions = expressions
.Select(expression => _parsingConfig.ExpressionPromoter.Promote(expression, newType, true, true))
.OfType<Expression>();
return Expression.NewArrayInit(newType, promotedExpressions);
}

return Expression.NewArrayInit(expressions.All(expression => expression.Type == expressions[0].Type) ? expressions[0].Type : typeof(object), expressions);
Expand Down Expand Up @@ -1543,32 +1544,34 @@ 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?>();
var expressionsPromoted = new List<Expression>();

// Loop all expressions and promote if needed
for (int i = 0; i < constructorParameters.Length; i++)
{
if (bindParametersSequentially)
{
expressionsPromoted.Add(_parsingConfig.ExpressionPromoter.Promote(expressions[i], propertyTypes[i], true, true));
expressionsPromoted.AddIfNotNull(_parsingConfig.ExpressionPromoter.Promote(expressions[i], propertyTypes[i], true, true));
}
else
{
Type propertyType = constructorParameters[i].ParameterType;
string cParameterName = constructorParameters[i].Name;
var propertyType = constructorParameters[i].ParameterType;
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));
expressionsPromoted.AddIfNotNull(_parsingConfig.ExpressionPromoter.Promote(expressions[propertyAndIndex.index], propertyType, true, true));
}
}

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

return Expression.New(exactConstructor, expressionsPromoted);
Expand Down Expand Up @@ -1661,14 +1665,14 @@ private Expression ParseTypeAccess(Type type, bool getNext)
}

// This is a shorthand for explicitly converting a string to something
bool shorthand = _textParser.CurrentToken.Id == TokenId.StringLiteral;
if (_textParser.CurrentToken.Id == TokenId.OpenParen || shorthand)
var isShorthand = _textParser.CurrentToken.Id == TokenId.StringLiteral;
if (_textParser.CurrentToken.Id == TokenId.OpenParen || isShorthand)
{
Expression[] args;
if (shorthand)
if (isShorthand)
{
var expressionOrType = ParseStringLiteral(true);
args = new[] { expressionOrType.First };
args = [expressionOrType.First];
}
else
{
Expand All @@ -1685,7 +1689,7 @@ private Expression ParseTypeAccess(Type type, bool getNext)
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
if (args.Length == 1 && (args[0] == null || args[0] is ConstantExpression) && TryGenerateConversion(args[0], type, out var generatedExpression))
{
return generatedExpression!;
return generatedExpression;
}

// If only 1 argument, and if the type is a ValueType and argType is also a ValueType, just Convert
Expand Down Expand Up @@ -2027,8 +2031,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 +2236,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
Loading
Loading