diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 34599c4..4491657 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,19 +8,15 @@ on: jobs: test-linux: - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: '0' # all - - name: Setup .NET Core 3.1 - uses: actions/setup-dotnet@v3 + - name: Setup .NET 8.0 + uses: actions/setup-dotnet@v4 with: - dotnet-version: '3.1.x' - - name: Setup .NET 7.0 - uses: actions/setup-dotnet@v3 - with: - dotnet-version: '7.0.x' + dotnet-version: '8.0.x' - name: Setup gitversion run: dotnet tool install --global GitVersion.Tool - name: Calculate version @@ -32,18 +28,20 @@ jobs: run: dotnet restore DynamicExpresso.sln - name: Build run: dotnet build DynamicExpresso.sln --no-restore -c Release /p:Version=${{steps.calc_version.outputs.PROJECT_VERSION}} - - name: Test .net core 3.1 - run: dotnet test DynamicExpresso.sln --no-build --no-restore -c Release --verbosity normal -f netcoreapp3.1 - - name: Test .net core 7.0 - run: dotnet test DynamicExpresso.sln --no-build --no-restore -c Release --verbosity normal -f net7.0 + - name: Test .net core 8.0 + run: dotnet test DynamicExpresso.sln --no-build --no-restore -c Release --verbosity normal -f net8.0 test-win: runs-on: windows-2019 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup .NET 7.0 - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: '7.0.x' + - name: Setup .NET 8.0 + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.0.x' - name: Restore packages run: dotnet restore DynamicExpresso.sln - name: Build @@ -52,3 +50,5 @@ jobs: run: dotnet test DynamicExpresso.sln --no-build --no-restore -c Release --verbosity normal -f net461 - name: Test .net 4.5 run: dotnet test DynamicExpresso.sln --no-build --no-restore -c Release --verbosity normal -f net45 + - name: Test .net core 8.0 + run: dotnet test DynamicExpresso.sln --no-build --no-restore -c Release --verbosity normal -f net8.0 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 09e9c54..46bf8e7 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -14,16 +14,16 @@ on: default: 'true' jobs: publish-nuget: - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: ref: "${{ github.event.inputs.ref }}" fetch-depth: '0' # all - - name: Setup .NET Core 7.0 - uses: actions/setup-dotnet@v3 + - name: Setup .NET Core 8.0 + uses: actions/setup-dotnet@v4 with: - dotnet-version: '7.0.x' + dotnet-version: '8.0.x' - name: Setup gitversion run: dotnet tool install --global GitVersion.Tool - name: Calculate version @@ -35,10 +35,8 @@ jobs: run: dotnet restore DynamicExpresso.sln - name: Build run: dotnet build DynamicExpresso.sln --no-restore -c Release /p:Version=${{steps.calc_version.outputs.PROJECT_VERSION}} - - name: Test .net core 3.1 - run: dotnet test DynamicExpresso.sln --no-build --no-restore -c Release /p:Version=${{steps.calc_version.outputs.PROJECT_VERSION}} --verbosity normal -f netcoreapp3.1 - - name: Test .net core 7.0 - run: dotnet test DynamicExpresso.sln --no-build --no-restore -c Release /p:Version=${{steps.calc_version.outputs.PROJECT_VERSION}} --verbosity normal -f net7.0 + - name: Test .net core 8.0 + run: dotnet test DynamicExpresso.sln --no-build --no-restore -c Release /p:Version=${{steps.calc_version.outputs.PROJECT_VERSION}} --verbosity normal -f net8.0 - name: Setup nuget sources run: dotnet nuget add source --name github "https://nuget.pkg.github.com/dynamicexpresso/index.json" - name: Pack diff --git a/src/DynamicExpresso.Core/Detector.cs b/src/DynamicExpresso.Core/Detector.cs index a315125..77a8191 100644 --- a/src/DynamicExpresso.Core/Detector.cs +++ b/src/DynamicExpresso.Core/Detector.cs @@ -76,9 +76,18 @@ public IdentifiersInfo DetectIdentifiers(string expression) if (IsReservedKeyword(identifier)) continue; - // don't consider member accesses as identifiers (e.g. "x.Length" will only return x but not Length) - if (idGroup.Index > 0 && expression[idGroup.Index - 1] == '.') - continue; + if (idGroup.Index > 0) + { + var previousChar = expression[idGroup.Index - 1]; + + // don't consider member accesses as identifiers (e.g. "x.Length" will only return x but not Length) + if (previousChar == '.') + continue; + + // don't consider number literals as identifiers + if (char.IsDigit(previousChar)) + continue; + } if (_settings.Identifiers.TryGetValue(identifier, out Identifier knownIdentifier)) knownIdentifiers.Add(knownIdentifier); diff --git a/src/DynamicExpresso.Core/Exceptions/ParseException.cs b/src/DynamicExpresso.Core/Exceptions/ParseException.cs index 2a032eb..d740f60 100644 --- a/src/DynamicExpresso.Core/Exceptions/ParseException.cs +++ b/src/DynamicExpresso.Core/Exceptions/ParseException.cs @@ -21,6 +21,11 @@ public ParseException(string message, int position, Exception innerException) public int Position { get; private set; } + public static ParseException Create(int pos, string format, params object[] args) + { + return new ParseException(string.Format(format, args), pos); + } + protected ParseException( SerializationInfo info, StreamingContext context) diff --git a/src/DynamicExpresso.Core/Parameter.cs b/src/DynamicExpresso.Core/Parameter.cs index cf49185..011d57b 100644 --- a/src/DynamicExpresso.Core/Parameter.cs +++ b/src/DynamicExpresso.Core/Parameter.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq.Expressions; namespace DynamicExpresso @@ -49,4 +49,18 @@ public static Parameter Create(string name, T value) public ParameterExpression Expression { get; private set; } } + + /// + /// Parameter with its position in the expression. + /// + internal class ParameterWithPosition : Parameter + { + public ParameterWithPosition(int pos, string name, Type type) + : base(name, type) + { + Position = pos; + } + + public int Position { get; } + } } diff --git a/src/DynamicExpresso.Core/Parsing/InterpreterExpression.cs b/src/DynamicExpresso.Core/Parsing/InterpreterExpression.cs new file mode 100644 index 0000000..75d5dfb --- /dev/null +++ b/src/DynamicExpresso.Core/Parsing/InterpreterExpression.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using DynamicExpresso.Exceptions; +using DynamicExpresso.Reflection; + +namespace DynamicExpresso.Parsing +{ + internal class InterpreterExpression : Expression + { + private readonly Interpreter _interpreter; + private readonly string _expressionText; + private readonly IList _parameters; + private Type _type; + + public InterpreterExpression(ParserArguments parserArguments, string expressionText, params ParameterWithPosition[] parameters) + { + var settings = parserArguments.Settings.Clone(); + _interpreter = new Interpreter(settings); + _expressionText = expressionText; + _parameters = parameters; + + // Take the parent expression's parameters and set them as an identifier that + // can be accessed by any lower call + // note: this doesn't impact the initial settings, because they're cloned + foreach (var dp in parserArguments.DeclaredParameters) + { + // Have to mark the parameter as "Used" otherwise we can get a compilation error. + parserArguments.TryGetParameters(dp.Name, out var pe); + _interpreter.SetIdentifier(new Identifier(dp.Name, pe)); + } + + foreach (var myParameter in parameters) + { + if (settings.Identifiers.ContainsKey(myParameter.Name)) + { + throw new ParseException($"A local or parameter named '{myParameter.Name}' cannot be declared in this scope because that name is used in an enclosing local scope to define a local or parameter", myParameter.Position); + } + } + + // prior to evaluation, we don't know the generic arguments types + _type = ReflectionExtensions.GetFuncType(parameters.Length); + } + + public IList Parameters + { + get { return _parameters; } + } + + public override Type Type + { + get { return _type; } + } + + internal LambdaExpression EvalAs(Type delegateType) + { + if (!IsCompatibleWithDelegate(delegateType)) + return null; + + var lambdaExpr = _interpreter.ParseAsExpression(delegateType, _expressionText, _parameters.Select(p => p.Name).ToArray()); + _type = lambdaExpr.Type; + return lambdaExpr; + } + + internal bool IsCompatibleWithDelegate(Type target) + { + if (!target.IsGenericType || target.BaseType != typeof(MulticastDelegate)) + return false; + + var genericTypeDefinition = target.GetGenericTypeDefinition(); + return genericTypeDefinition == ReflectionExtensions.GetFuncType(_parameters.Count) + || genericTypeDefinition == ReflectionExtensions.GetActionType(_parameters.Count); + } + } +} diff --git a/src/DynamicExpresso.Core/Parsing/Parser.cs b/src/DynamicExpresso.Core/Parsing/Parser.cs index b606b96..282526e 100644 --- a/src/DynamicExpresso.Core/Parsing/Parser.cs +++ b/src/DynamicExpresso.Core/Parsing/Parser.cs @@ -1,17 +1,14 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Dynamic; using System.Globalization; using System.Linq; using System.Linq.Expressions; using System.Reflection; -using System.Runtime.CompilerServices; -using System.Security; using System.Text; using DynamicExpresso.Exceptions; using DynamicExpresso.Reflection; +using DynamicExpresso.Resolution; using DynamicExpresso.Resources; using Microsoft.CSharp.RuntimeBinder; @@ -39,31 +36,6 @@ public static Expression Parse(ParserArguments arguments) private readonly ParserArguments _arguments; - private static readonly MethodInfo _concatMethod = GetConcatMethod(); - private static readonly MethodInfo _toStringMethod = GetToStringMethod(); - - private static MethodInfo GetConcatMethod() - { - var methodInfo = typeof(string).GetMethod("Concat", new[] {typeof(string), typeof(string)}); - if (methodInfo == null) - { - throw new Exception("String concat method not found"); - } - - return methodInfo; - } - - private static MethodInfo GetToStringMethod() - { - var toStringMethod = typeof(object).GetMethod("ToString", Type.EmptyTypes); - if (toStringMethod == null) - { - throw new Exception("ToString method not found"); - } - - return toStringMethod; - } - // Working context implementation //ParameterExpression it; @@ -73,8 +45,7 @@ private static MethodInfo GetToStringMethod() private char _parseChar; private Token _token; - private readonly BindingFlags _bindingCase; - private readonly MemberFilter _memberFilterCase; + private readonly MemberFinder _memberFinder; private readonly DefaultNumberType _defaultNumberType; @@ -82,8 +53,7 @@ private Parser(ParserArguments arguments) { _arguments = arguments; - _bindingCase = arguments.Settings.CaseInsensitive ? BindingFlags.IgnoreCase : BindingFlags.Default; - _memberFilterCase = arguments.Settings.CaseInsensitive ? Type.FilterNameIgnoreCase : Type.FilterName; + _memberFinder = new MemberFinder(arguments); _defaultNumberType = arguments.Settings.DefaultNumberType; @@ -191,17 +161,6 @@ private Expression ParseLambdaExpression() } } - private class ParameterWithPosition : Parameter - { - public ParameterWithPosition(int pos, string name, Type type) - : base(name, type) - { - Position = pos; - } - - public int Position { get; } - } - private ParameterWithPosition[] ParseLambdaParameterList() { var hasOpenParen = _token.id == TokenId.OpenParen; @@ -264,15 +223,15 @@ private Expression ParseAssignment() if (!IsWritable(left)) - throw CreateParseException(_token.pos, ErrorMessages.ExpressionMustBeWritable); + throw ParseException.Create(_token.pos, ErrorMessages.ExpressionMustBeWritable); NextToken(); var right = ParseAssignment(); - var promoted = PromoteExpression(right, left.Type, true); + var promoted = ExpressionUtils.PromoteExpression(right, left.Type, true); if (promoted == null) - throw CreateParseException(_token.pos, ErrorMessages.CannotConvertValue, - GetTypeName(right.Type), GetTypeName(left.Type)); + throw ParseException.Create(_token.pos, ErrorMessages.CannotConvertValue, + TypeUtils.GetTypeName(right.Type), TypeUtils.GetTypeName(left.Type)); left = Expression.Assign(left, promoted); } @@ -399,8 +358,8 @@ private Expression ParseComparison() // } // else // { - // throw CreateParseException(op.pos, ErrorMessages.IncompatibleOperands, - // op.text, GetTypeName(left.Type), GetTypeName(right.Type)); + // throw ParseException.Create(op.pos, ErrorMessages.IncompatibleOperands, + // op.text, TypeUtils.GetTypeName(left.Type), TypeUtils.GetTypeName(right.Type)); // } // } //} @@ -419,8 +378,8 @@ private Expression ParseComparison() // } // else // { - // throw CreateParseException(op.pos, ErrorMessages.IncompatibleOperands, - // op.text, GetTypeName(left.Type), GetTypeName(right.Type)); + // throw ParseException.Create(op.pos, ErrorMessages.IncompatibleOperands, + // op.text, TypeUtils.GetTypeName(left.Type), TypeUtils.GetTypeName(right.Type)); // } // } //} @@ -474,14 +433,14 @@ private Expression ParseTypeTesting() Type knownType; if (!TryParseKnownType(_token.text, out knownType)) - throw CreateParseException(op.pos, ErrorMessages.TypeIdentifierExpected); + throw ParseException.Create(op.pos, ErrorMessages.TypeIdentifierExpected); if (typeOperator == ParserConstants.KeywordIs) left = Expression.TypeIs(left, knownType); else if (typeOperator == ParserConstants.KeywordAs) left = Expression.TypeAs(left, knownType); else - throw CreateParseException(_token.pos, ErrorMessages.SyntaxError); + throw ParseException.Create(_token.pos, ErrorMessages.SyntaxError); } return left; @@ -702,9 +661,9 @@ private MethodData FindUnaryOperator(string operatorName, Expression expr) var args = new[] { expr }; // try to find the user defined operator on both operands - var applicableMethods = FindMethods(type, operatorName, true, args); + var applicableMethods = _memberFinder.FindMethods(type, operatorName, true, args); if (applicableMethods.Length > 1) - throw CreateParseException(errorPos, ErrorMessages.AmbiguousUnaryOperatorInvocation, operatorName, GetTypeName(type)); + throw ParseException.Create(errorPos, ErrorMessages.AmbiguousUnaryOperatorInvocation, operatorName, TypeUtils.GetTypeName(type)); MethodData userDefinedOperator = null; if (applicableMethods.Length == 1) @@ -764,7 +723,7 @@ private Expression ParsePrimary() else if (typeof(Delegate).IsAssignableFrom(expr.Type)) expr = ParseDelegateInvocation(expr, tokenPos); else - throw CreateParseException(tokenPos, ErrorMessages.InvalidMethodCall, GetTypeName(expr.Type)); + throw ParseException.Create(tokenPos, ErrorMessages.InvalidMethodCall, TypeUtils.GetTypeName(expr.Type)); } else { @@ -779,7 +738,7 @@ private Expression ParsePrimary() /// private Expression GenerateGetNullableValue(Expression expr) { - if (!IsNullableType(expr.Type)) + if (!TypeUtils.IsNullableType(expr.Type)) return expr; return GeneratePropertyOrFieldExpression(expr.Type, expr, _token.pos, "Value"); } @@ -803,7 +762,7 @@ private Expression ParsePrimaryStart() case TokenId.End: return Expression.Empty(); default: - throw CreateParseException(_token.pos, ErrorMessages.ExpressionExpected); + throw ParseException.Create(_token.pos, ErrorMessages.ExpressionExpected); } } @@ -815,7 +774,7 @@ private Expression ParseCharLiteral() s = EvalEscapeStringLiteral(s); if (s.Length != 1) - throw CreateParseException(_token.pos, ErrorMessages.InvalidCharacterLiteral); + throw ParseException.Create(_token.pos, ErrorMessages.InvalidCharacterLiteral); NextToken(); return CreateLiteral(s[0]); @@ -852,7 +811,7 @@ private string EvalEscapeStringLiteral(string source) if (c == '\\') { if ((i + 1) == source.Length) - throw CreateParseException(_token.pos, ErrorMessages.InvalidEscapeSequence); + throw ParseException.Create(_token.pos, ErrorMessages.InvalidEscapeSequence); builder.Append(EvalEscapeChar(source[++i])); } @@ -890,7 +849,7 @@ private char EvalEscapeChar(char source) case 'v': return '\v'; default: - throw CreateParseException(_token.pos, ErrorMessages.InvalidEscapeSequence); + throw ParseException.Create(_token.pos, ErrorMessages.InvalidEscapeSequence); } } @@ -925,13 +884,13 @@ private Expression ParseIntegerLiteral() { var hex = text.Substring(2); if (!ulong.TryParse(hex, ParseLiteralHexNumberStyle, ParseCulture, out value)) - throw CreateParseException(_token.pos, ErrorMessages.InvalidIntegerLiteral, text); + throw ParseException.Create(_token.pos, ErrorMessages.InvalidIntegerLiteral, text); } else if (text.StartsWith("0b") || text.StartsWith("0B")) { var binary = text.Substring(2); if (string.IsNullOrEmpty(binary)) - throw CreateParseException(_token.pos, ErrorMessages.InvalidIntegerLiteral, text); + throw ParseException.Create(_token.pos, ErrorMessages.InvalidIntegerLiteral, text); try { @@ -943,7 +902,7 @@ private Expression ParseIntegerLiteral() } } else if (!ulong.TryParse(text, ParseLiteralUnsignedNumberStyle, ParseCulture, out value)) - throw CreateParseException(_token.pos, ErrorMessages.InvalidIntegerLiteral, text); + throw ParseException.Create(_token.pos, ErrorMessages.InvalidIntegerLiteral, text); NextToken(); @@ -959,7 +918,7 @@ private Expression ParseIntegerLiteral() else { if (!long.TryParse(text, ParseLiteralNumberStyle, ParseCulture, out long value)) - throw CreateParseException(_token.pos, ErrorMessages.InvalidIntegerLiteral, text); + throw ParseException.Create(_token.pos, ErrorMessages.InvalidIntegerLiteral, text); NextToken(); @@ -1013,7 +972,7 @@ private Expression ParseRealLiteral() } if (value == null) - throw CreateParseException(_token.pos, ErrorMessages.InvalidRealLiteral, text); + throw ParseException.Create(_token.pos, ErrorMessages.InvalidRealLiteral, text); NextToken(); @@ -1131,11 +1090,11 @@ private Expression ParseTypeof() NextToken(); var args = ParseArgumentList(); if (args.Length != 1) - throw CreateParseException(errorPos, ErrorMessages.TypeofRequiresOneArg); + throw ParseException.Create(errorPos, ErrorMessages.TypeofRequiresOneArg); var constExp = args[0] as ConstantExpression; if (constExp == null || !(constExp.Value is Type)) - throw CreateParseException(errorPos, ErrorMessages.TypeofRequiresAType); + throw ParseException.Create(errorPos, ErrorMessages.TypeofRequiresAType); return constExp; } @@ -1160,11 +1119,11 @@ private Expression GenerateConditional(Expression test, Expression expr1, Expres return GenerateConditionalDynamic(test, expr1, expr2,errorPos); if (test.Type != typeof(bool)) - throw CreateParseException(errorPos, ErrorMessages.FirstExprMustBeBool); + throw ParseException.Create(errorPos, ErrorMessages.FirstExprMustBeBool); if (expr1.Type != expr2.Type) { - var expr1As2 = expr2 != ParserConstants.NullLiteralExpression ? PromoteExpression(expr1, expr2.Type, true) : null; - var expr2As1 = expr1 != ParserConstants.NullLiteralExpression ? PromoteExpression(expr2, expr1.Type, true) : null; + var expr1As2 = expr2 != ParserConstants.NullLiteralExpression ? ExpressionUtils.PromoteExpression(expr1, expr2.Type, true) : null; + var expr2As1 = expr1 != ParserConstants.NullLiteralExpression ? ExpressionUtils.PromoteExpression(expr2, expr1.Type, true) : null; if (expr1As2 != null && expr2As1 == null) { expr1 = expr1As2; @@ -1178,9 +1137,9 @@ private Expression GenerateConditional(Expression test, Expression expr1, Expres var type1 = expr1 != ParserConstants.NullLiteralExpression ? expr1.Type.Name : "null"; var type2 = expr2 != ParserConstants.NullLiteralExpression ? expr2.Type.Name : "null"; if (expr1As2 != null) - throw CreateParseException(errorPos, ErrorMessages.BothTypesConvertToOther, type1, type2); + throw ParseException.Create(errorPos, ErrorMessages.BothTypesConvertToOther, type1, type2); - throw CreateParseException(errorPos, ErrorMessages.NeitherTypeConvertsToOther, type1, type2); + throw ParseException.Create(errorPos, ErrorMessages.NeitherTypeConvertsToOther, type1, type2); } } return Expression.Condition(test, expr1, expr2); @@ -1206,7 +1165,7 @@ private Expression ParseNew() if (newType.IsArray) { if (newType.GetArrayRank() != 1) - throw CreateParseException(_token.pos, ErrorMessages.UnsupportedMultidimensionalArrays, newType); + throw ParseException.Create(_token.pos, ErrorMessages.UnsupportedMultidimensionalArrays, newType); args = ParseArrayInitializerList(); return Expression.NewArrayInit(newType.GetElementType(), args); @@ -1220,12 +1179,12 @@ private Expression ParseNew() ValidateToken(TokenId.OpenCurlyBracket, ErrorMessages.OpenCurlyBracketExpected); } - var applicableConstructors = FindBestMethod(newType.GetConstructors(), args); + var applicableConstructors = MethodResolution.FindBestMethod(newType.GetConstructors(), args); if (applicableConstructors.Length == 0) - throw CreateParseException(_token.pos, ErrorMessages.NoApplicableConstructor, newType); + throw ParseException.Create(_token.pos, ErrorMessages.NoApplicableConstructor, newType); if (applicableConstructors.Length > 1) - throw CreateParseException(_token.pos, ErrorMessages.AmbiguousConstructorInvocation, newType); + throw ParseException.Create(_token.pos, ErrorMessages.AmbiguousConstructorInvocation, newType); var constructor = applicableConstructors[0]; var newExpr = Expression.New((ConstructorInfo)constructor.MethodBase, constructor.PromotedParameters); @@ -1288,7 +1247,7 @@ private void ParsePossibleMemberBinding(Type newType, int originalPos, List 0) { - throw CreateParseException(pos, ErrorMessages.InvalidInitializerMemberDeclarator); + throw ParseException.Create(pos, ErrorMessages.InvalidInitializerMemberDeclarator); } } else if (_token.id != TokenId.Equal || _arguments.TryGetIdentifier(propertyOrFieldName, out _) || _arguments.TryGetParameters(propertyOrFieldName, out _)) @@ -1314,7 +1273,7 @@ private void ParsePossibleMemberBinding(Type newType, int originalPos, List 0) { - throw CreateParseException(originalPos, ErrorMessages.InvalidInitializerMemberDeclarator); + throw ParseException.Create(originalPos, ErrorMessages.InvalidInitializerMemberDeclarator); } if (_token.id == TokenId.OpenCurlyBracket) { @@ -1346,7 +1305,7 @@ private void ParseCollectionInitalizer(Type newType, int originalPos, List _.Method), args); + var applicableMethods = MethodResolution.FindBestMethod(candidates.Select(_ => _.Method), args); // no method found: retry with the delegate's method // (the parameters might be different, e.g. params array, default value, etc) if (applicableMethods.Length == 0) - applicableMethods = FindBestMethod(candidates.Select(_ => _.InvokeMethod), args); + applicableMethods = MethodResolution.FindBestMethod(candidates.Select(_ => _.InvokeMethod), args); if (applicableMethods.Length == 0) - throw CreateParseException(errorPos, ErrorMessages.ArgsIncompatibleWithDelegate); + throw ParseException.Create(errorPos, ErrorMessages.ArgsIncompatibleWithDelegate); if (applicableMethods.Length > 1) - throw CreateParseException(errorPos, ErrorMessages.AmbiguousDelegateInvocation); + throw ParseException.Create(errorPos, ErrorMessages.AmbiguousDelegateInvocation); var applicableMethod = applicableMethods[0]; var usedDeledate = candidates.Single(_ => new[] { _.Method, _.InvokeMethod?.MethodBase }.Any(m => m == applicableMethod.MethodBase)).Delegate; @@ -1491,8 +1450,8 @@ private Type ParseTypeModifiers(Type type) var errorPos = _token.pos; if (_token.id == TokenId.Question) { - if (!type.IsValueType || IsNullableType(type)) - throw CreateParseException(errorPos, ErrorMessages.TypeHasNoNullableForm, GetTypeName(type)); + if (!type.IsValueType || TypeUtils.IsNullableType(type)) + throw ParseException.Create(errorPos, ErrorMessages.TypeHasNoNullableForm, TypeUtils.GetTypeName(type)); type = typeof(Nullable<>).MakeGenericType(type); NextToken(); @@ -1607,11 +1566,11 @@ private Expression ParseTypeKeyword(Type type) // case 0: // if (args.Length == 1) // return GenerateConversion(args[0], type, errorPos); - // throw ParseError(errorPos, ErrorMessages.NoMatchingConstructor, GetTypeName(type)); + // throw ParseError(errorPos, ErrorMessages.NoMatchingConstructor, TypeUtils.GetTypeName(type)); // case 1: // return Expression.New((ConstructorInfo)method, args); // default: - // throw ParseError(errorPos, ErrorMessages.AmbiguousConstructorInvocation, GetTypeName(type)); + // throw ParseError(errorPos, ErrorMessages.AmbiguousConstructorInvocation, TypeUtils.GetTypeName(type)); // } //} @@ -1668,8 +1627,8 @@ private Expression GenerateConversion(Expression expr, Type type, int errorPos) } catch (InvalidOperationException) { - throw CreateParseException(errorPos, ErrorMessages.CannotConvertValue, - GetTypeName(exprType), GetTypeName(type)); + throw ParseException.Create(errorPos, ErrorMessages.CannotConvertValue, + TypeUtils.GetTypeName(exprType), TypeUtils.GetTypeName(type)); } } @@ -1692,7 +1651,7 @@ private Expression ParseMemberAccess(Type type, Expression instance) private Expression GeneratePropertyOrFieldExpression(Type type, Expression instance, int errorPos, string propertyOrFieldName) { - var member = FindPropertyOrField(type, propertyOrFieldName, instance == null); + var member = _memberFinder.FindPropertyOrField(type, propertyOrFieldName, instance == null); if (member != null) { return member is PropertyInfo ? @@ -1700,10 +1659,10 @@ private Expression GeneratePropertyOrFieldExpression(Type type, Expression insta Expression.Field(instance, (FieldInfo)member); } - if (IsDynamicType(type) || IsDynamicExpression(instance)) + if (TypeUtils.IsDynamicType(type) || IsDynamicExpression(instance)) return ParseDynamicProperty(type, instance, propertyOrFieldName); - throw CreateParseException(errorPos, ErrorMessages.UnknownPropertyOrField, propertyOrFieldName, GetTypeName(type)); + throw ParseException.Create(errorPos, ErrorMessages.UnknownPropertyOrField, propertyOrFieldName, TypeUtils.GetTypeName(type)); } private Expression ParseMethodInvocation(Type type, Expression instance, int errorPos, string methodName) @@ -1711,6 +1670,7 @@ private Expression ParseMethodInvocation(Type type, Expression instance, int err return ParseMethodInvocation(type, instance, errorPos, methodName, TokenId.OpenParen, ErrorMessages.OpenParenExpected, TokenId.CloseParen, ErrorMessages.CloseParenOrCommaExpected); } + private Expression ParseMethodInvocation(Type type, Expression instance, int errorPos, string methodName, TokenId open, string openExpected, TokenId close, string closeExpected) { var args = ParseArgumentList(open, openExpected, close, closeExpected); @@ -1724,10 +1684,10 @@ private Expression ParseMethodInvocation(Type type, Expression instance, int err if (methodInvocationExpression != null) return methodInvocationExpression; - if (IsDynamicType(type) || IsDynamicExpression(instance)) + if (TypeUtils.IsDynamicType(type) || IsDynamicExpression(instance)) return ParseDynamicMethodInvocation(type, instance, methodName, args); - throw new NoApplicableMethodException(methodName, GetTypeName(type), errorPos); + throw new NoApplicableMethodException(methodName, TypeUtils.GetTypeName(type), errorPos); } private Expression ParseExtensionMethodInvocation(Type type, Expression instance, int errorPos, string id, Expression[] args) @@ -1736,9 +1696,9 @@ private Expression ParseExtensionMethodInvocation(Type type, Expression instance extensionMethodsArguments[0] = instance; args.CopyTo(extensionMethodsArguments, 1); - var extensionMethods = FindExtensionMethods(id, extensionMethodsArguments); + var extensionMethods = _memberFinder.FindExtensionMethods(id, extensionMethodsArguments); if (extensionMethods.Length > 1) - throw CreateParseException(errorPos, ErrorMessages.AmbiguousMethodInvocation, id, GetTypeName(type)); + throw ParseException.Create(errorPos, ErrorMessages.AmbiguousMethodInvocation, id, TypeUtils.GetTypeName(type)); if (extensionMethods.Length == 1) { @@ -1754,9 +1714,9 @@ private Expression ParseExtensionMethodInvocation(Type type, Expression instance private Expression ParseNormalMethodInvocation(Type type, Expression instance, int errorPos, string id, Expression[] args) { - var applicableMethods = FindMethods(type, id, instance == null, args); + var applicableMethods = _memberFinder.FindMethods(type, id, instance == null, args); if (applicableMethods.Length > 1) - throw CreateParseException(errorPos, ErrorMessages.AmbiguousMethodInvocation, id, GetTypeName(type)); + throw ParseException.Create(errorPos, ErrorMessages.AmbiguousMethodInvocation, id, TypeUtils.GetTypeName(type)); if (applicableMethods.Length == 1) { @@ -1816,21 +1776,6 @@ private Expression ParseNormalMethodInvocation(Type type, Expression instance, i // return Expression.Call(typeof(Enumerable), signature.Name, typeArgs, args); //} - /// - /// Returns null if is an Array type. Needed because the lookup methods fail with a if the array type is used. - /// Everything still miraculously works on the array if null is given for the type. - /// - /// - /// - private static Type RemoveArrayType(Type t) - { - if (t == null || t.IsArray) - { - return null; - } - return t; - } - private static Expression ParseDynamicProperty(Type type, Expression instance, string propertyOrFieldName) { return Expression.Dynamic(new LateGetMemberCallSiteBinder(propertyOrFieldName), typeof(object), instance); @@ -1863,7 +1808,7 @@ private Expression[] ParseArgumentList(TokenId openToken, string missingOpenToke if (_token.id != TokenId.Comma) break; NextToken(); if (!allowTrailingComma && _token.id == closeToken) - throw CreateParseException(_token.pos, missingCloseTokenMsg); + throw ParseException.Create(_token.pos, missingCloseTokenMsg); } ValidateToken(closeToken, missingCloseTokenMsg); NextToken(); @@ -1884,48 +1829,38 @@ private Expression ParseElementAccess(Expression expr) if (expr.Type.IsArray) { if (expr.Type.GetArrayRank() != args.Length) - throw CreateParseException(errorPos, ErrorMessages.IncorrectNumberOfIndexes); + throw ParseException.Create(errorPos, ErrorMessages.IncorrectNumberOfIndexes); for (int i = 0; i < args.Length; i++) { - args[i] = PromoteExpression(args[i], typeof(int), true); + args[i] = ExpressionUtils.PromoteExpression(args[i], typeof(int), true); if (args[i] == null) - throw CreateParseException(errorPos, ErrorMessages.InvalidIndex); + throw ParseException.Create(errorPos, ErrorMessages.InvalidIndex); } return Expression.ArrayAccess(expr, args); } - if (IsDynamicType(expr.Type) || IsDynamicExpression(expr)) + if (TypeUtils.IsDynamicType(expr.Type) || IsDynamicExpression(expr)) return ParseDynamicIndex(expr.Type, expr, args); - var applicableMethods = FindIndexer(expr.Type, args); + var applicableMethods = _memberFinder.FindIndexer(expr.Type, args); if (applicableMethods.Length == 0) { - throw CreateParseException(errorPos, ErrorMessages.NoApplicableIndexer, - GetTypeName(expr.Type)); + throw ParseException.Create(errorPos, ErrorMessages.NoApplicableIndexer, + TypeUtils.GetTypeName(expr.Type)); } if (applicableMethods.Length > 1) { - throw CreateParseException(errorPos, ErrorMessages.AmbiguousIndexerInvocation, - GetTypeName(expr.Type)); + throw ParseException.Create(errorPos, ErrorMessages.AmbiguousIndexerInvocation, + TypeUtils.GetTypeName(expr.Type)); } var indexer = (IndexerData)applicableMethods[0]; return Expression.Property(expr, indexer.Indexer, indexer.PromotedParameters); } - private static bool IsNullableType(Type type) - { - return Nullable.GetUnderlyingType(type) != null; - } - - private static bool IsDynamicType(Type type) - { - return typeof(IDynamicMetaObjectProvider).IsAssignableFrom(type); - } - private bool IsDynamicExpression(Expression instance) { return instance != null && @@ -1933,60 +1868,6 @@ private bool IsDynamicExpression(Expression instance) (_arguments.Settings.LateBindObject && instance.Type == typeof(object))); } - private static Type GetNonNullableType(Type type) - { - return IsNullableType(type) ? type.GetGenericArguments()[0] : type; - } - - private static string GetTypeName(Type type) - { - var baseType = GetNonNullableType(type); - var s = baseType.Name; - if (type != baseType) s += '?'; - return s; - } - - static bool IsNumericType(Type type) - { - return GetNumericTypeKind(type) != 0; - } - - private static bool IsSignedIntegralType(Type type) - { - return GetNumericTypeKind(type) == 2; - } - - private static bool IsUnsignedIntegralType(Type type) - { - return GetNumericTypeKind(type) == 3; - } - - private static int GetNumericTypeKind(Type type) - { - type = GetNonNullableType(type); - if (type.IsEnum) return 0; - switch (Type.GetTypeCode(type)) - { - case TypeCode.Char: - case TypeCode.Single: - case TypeCode.Double: - case TypeCode.Decimal: - return 1; - case TypeCode.SByte: - case TypeCode.Int16: - case TypeCode.Int32: - case TypeCode.Int64: - return 2; - case TypeCode.Byte: - case TypeCode.UInt16: - case TypeCode.UInt32: - case TypeCode.UInt64: - return 3; - default: - return 0; - } - } - //static bool IsEnumType(Type type) //{ // return GetNonNullableType(type).IsEnum; @@ -2003,7 +1884,8 @@ private void CheckAndPromoteOperand(Type signatures, ref Expression expr) private void CheckAndPromoteOperands(Type signatures, ref Expression left, ref Expression right) { - if ((IsNullableType(left.Type) || IsNullableType(right.Type)) && (GetNonNullableType(left.Type) == right.Type || GetNonNullableType(right.Type) == left.Type)) + if ((TypeUtils.IsNullableType(left.Type) || TypeUtils.IsNullableType(right.Type)) && + (TypeUtils.GetNonNullableType(left.Type) == right.Type || TypeUtils.GetNonNullableType(right.Type) == left.Type)) { left = GenerateNullableTypeConversion(left); right = GenerateNullableTypeConversion(right); @@ -2019,594 +1901,13 @@ private void CheckAndPromoteOperands(Type signatures, ref Expression left, ref E private Expression[] PrepareOperandArguments(Type signatures, Expression[] args) { - var applicableMethods = FindMethods(signatures, "F", false, args); + var applicableMethods = _memberFinder.FindMethods(signatures, "F", false, args); if (applicableMethods.Length == 1) return applicableMethods[0].PromotedParameters; return args; } - private MemberInfo FindPropertyOrField(Type type, string memberName, bool staticAccess) - { - var flags = BindingFlags.Public | BindingFlags.DeclaredOnly | - (staticAccess ? BindingFlags.Static : BindingFlags.Instance) | _bindingCase; - - foreach (var t in SelfAndBaseTypes(type)) - { - var members = t.FindMembers(MemberTypes.Property | MemberTypes.Field, flags, _memberFilterCase, memberName); - if (members.Length != 0) - return members[0]; - } - return null; - } - - private MethodData[] FindMethods(Type type, string methodName, bool staticAccess, Expression[] args) - { - var flags = BindingFlags.Public | BindingFlags.DeclaredOnly | - (staticAccess ? BindingFlags.Static : BindingFlags.Instance) | _bindingCase; - foreach (var t in SelfAndBaseTypes(type)) - { - var members = t.FindMembers(MemberTypes.Method, flags, _memberFilterCase, methodName); - var applicableMethods = FindBestMethod(members.Cast(), args); - - if (applicableMethods.Length > 0) - return applicableMethods; - } - - return new MethodData[0]; - } - - private MethodData FindInvokeMethod(Type type) - { - var flags = BindingFlags.Public | BindingFlags.DeclaredOnly | - BindingFlags.Instance | _bindingCase; - foreach (var t in SelfAndBaseTypes(type)) - { - var method = t.FindMembers(MemberTypes.Method, flags, _memberFilterCase, "Invoke") - .Cast() - .SingleOrDefault(); - - if (method != null) - return MethodData.Gen(method); - } - - return null; - } - - private MethodData[] FindExtensionMethods(string methodName, Expression[] args) - { - var matchMethods = _arguments.GetExtensionMethods(methodName); - - return FindBestMethod(matchMethods, args); - } - - private MethodData[] FindIndexer(Type type, Expression[] args) - { - foreach (var t in SelfAndBaseTypes(type)) - { - MemberInfo[] members = t.GetDefaultMembers(); - if (members.Length != 0) - { - IEnumerable methods = members. - OfType(). - Select(p => (MethodData)new IndexerData(p)); - - var applicableMethods = FindBestMethod(methods, args); - if (applicableMethods.Length > 0) - return applicableMethods; - } - } - - return new MethodData[0]; - } - - private static IEnumerable SelfAndBaseTypes(Type type) - { - if (type.IsInterface) - { - var types = new List(); - AddInterface(types, type); - - types.Add(typeof(object)); - - return types; - } - return SelfAndBaseClasses(type); - } - - private static IEnumerable SelfAndBaseClasses(Type type) - { - while (type != null) - { - yield return type; - type = type.BaseType; - } - } - - private static void AddInterface(List types, Type type) - { - if (!types.Contains(type)) - { - types.Add(type); - foreach (Type t in type.GetInterfaces()) - { - AddInterface(types, t); - } - } - } - - private static MethodData[] FindBestMethod(IEnumerable methods, Expression[] args) - { - return FindBestMethod(methods.Select(MethodData.Gen), args); - } - - private static MethodData[] FindBestMethod(IEnumerable methods, Expression[] args) - { - var applicable = methods. - Where(m => CheckIfMethodIsApplicableAndPrepareIt(m, args)). - ToArray(); - if (applicable.Length > 1) - { - var bestCandidates = applicable - .Where(m => applicable.All(n => m == n || MethodHasPriority(args, m, n))) - .ToArray(); - - // bestCandidates.Length == 0 means that no applicable method has priority - // we don't return bestCandidates to prevent callers from thinking no method was found - if (bestCandidates.Length > 0) - return bestCandidates; - } - - return applicable; - } - - private static Type GetConcreteTypeForGenericMethod(Type type, List promotedArgs, MethodData method) - { - if (type.IsGenericType) - { - //Generic type - var genericArguments = type.GetGenericArguments(); - var concreteTypeParameters = new Type[genericArguments.Length]; - - for (var i = 0; i < genericArguments.Length; i++) - { - concreteTypeParameters[i] = GetConcreteTypeForGenericMethod(genericArguments[i], promotedArgs,method); - } - - return type.GetGenericTypeDefinition().MakeGenericType(concreteTypeParameters); - } - else if (type.ContainsGenericParameters) - { - //T case - //try finding an actual parameter for the generic - for (var i = 0; i < promotedArgs.Count; i++) - { - if (method.Parameters[i].ParameterType == type) - { - return promotedArgs[i].Type; - } - } - } - - return type;//already a concrete type - } - - private static bool CheckIfMethodIsApplicableAndPrepareIt(MethodData method, Expression[] args) - { - if (method.Parameters.Count(y => !y.HasDefaultValue && !HasParamsArrayType(y)) > args.Length) - return false; - - var promotedArgs = new List(); - var declaredWorkingParameters = 0; - - Type paramsArrayTypeFound = null; - List paramsArrayPromotedArgument = null; - - foreach (var currentArgument in args) - { - Type parameterType; - - if (paramsArrayTypeFound != null) - { - parameterType = paramsArrayTypeFound; - } - else - { - if (declaredWorkingParameters >= method.Parameters.Length) - { - return false; - } - - var parameterDeclaration = method.Parameters[declaredWorkingParameters]; - if (parameterDeclaration.IsOut) - { - return false; - } - - parameterType = parameterDeclaration.ParameterType; - - if (HasParamsArrayType(parameterDeclaration)) - { - paramsArrayTypeFound = parameterType; - } - - declaredWorkingParameters++; - } - - if (paramsArrayPromotedArgument == null && (paramsArrayTypeFound == null || args.Length == method.Parameters.Length)) - { - if (parameterType.IsGenericParameter) - { - // an interpreter expression can only be matched to a parameter of type Func - if (currentArgument is InterpreterExpression) - return false; - - promotedArgs.Add(currentArgument); - continue; - } - - var promoted = PromoteExpression(currentArgument, parameterType, true); - if (promoted != null) - { - promotedArgs.Add(promoted); - continue; - } - } - - if (paramsArrayTypeFound != null) - { - var paramsArrayElementType = paramsArrayTypeFound.GetElementType(); - if (paramsArrayElementType.IsGenericParameter) - { - paramsArrayPromotedArgument = paramsArrayPromotedArgument ?? new List(); - paramsArrayPromotedArgument.Add(currentArgument); - continue; - } - - var promoted = PromoteExpression(currentArgument, paramsArrayElementType, true); - if (promoted != null) - { - paramsArrayPromotedArgument = paramsArrayPromotedArgument ?? new List(); - paramsArrayPromotedArgument.Add(promoted); - continue; - } - } - - return false; - } - - if (paramsArrayPromotedArgument != null) - { - method.HasParamsArray = true; - var paramsArrayElementType = paramsArrayTypeFound.GetElementType(); - if (paramsArrayElementType == null) - throw CreateParseException(-1, ErrorMessages.ParamsArrayTypeNotAnArray); - - if (paramsArrayElementType.IsGenericParameter) - { - var actualTypes = paramsArrayPromotedArgument.Select(_ => _.Type).Distinct().ToArray(); - if (actualTypes.Length != 1) - throw CreateParseException(-1, ErrorMessages.MethodTypeParametersCantBeInferred, method.MethodBase); - - paramsArrayElementType = actualTypes[0]; - } - - promotedArgs.Add(Expression.NewArrayInit(paramsArrayElementType, paramsArrayPromotedArgument)); - } - - // Add default params, if needed. - promotedArgs.AddRange(method.Parameters.Skip(promotedArgs.Count).Select(x => - { - if (x.HasDefaultValue) - { - var parameterType = GetConcreteTypeForGenericMethod(x.ParameterType, promotedArgs, method); - - return Expression.Constant(x.DefaultValue, parameterType); - } - - - if (HasParamsArrayType(x)) - { - method.HasParamsArray = true; - return Expression.NewArrayInit(x.ParameterType.GetElementType()); - } - - throw new Exception("No default value found!"); - })); - - method.PromotedParameters = promotedArgs.ToArray(); - - if (method.MethodBase != null && method.MethodBase.IsGenericMethodDefinition && - method.MethodBase is MethodInfo) - { - var genericMethod = MakeGenericMethod(method); - if (genericMethod == null) - return false; - - // we have all the type information we can get, update interpreter expressions and evaluate them - var actualMethodParameters = genericMethod.GetParameters(); - for (var i = 0; i < method.PromotedParameters.Length; i++) - { - if (method.PromotedParameters[i] is InterpreterExpression ie) - { - var actualParamInfo = actualMethodParameters[i]; - var lambdaExpr = GenerateLambdaFromInterpreterExpression(ie, actualParamInfo.ParameterType); - if (lambdaExpr == null) - return false; - - method.PromotedParameters[i] = lambdaExpr; - - // we have inferred all types, update the method definition - genericMethod = MakeGenericMethod(method); - } - } - - method.MethodBase = genericMethod; - } - - return true; - } - - private static LambdaExpression GenerateLambdaFromInterpreterExpression(InterpreterExpression ie, Type delegateType) - { - return ie.EvalAs(delegateType); - } - - private static MethodInfo MakeGenericMethod(MethodData method) - { - var methodInfo = (MethodInfo)method.MethodBase; - var actualGenericArgs = ExtractActualGenericArguments( - method.Parameters.Select(p => p.ParameterType).ToArray(), - method.PromotedParameters.Select(p => p.Type).ToArray()); - - var genericArgs = methodInfo.GetGenericArguments() - .Select(p => actualGenericArgs.TryGetValue(p.Name, out var typ) ? typ : typeof(object)) - .ToArray(); - - MethodInfo genericMethod = null; - try - { - genericMethod = methodInfo.MakeGenericMethod(genericArgs); - } - catch (ArgumentException e) when (e.InnerException is VerificationException) - { - // this exception is thrown when a generic argument violates the generic constraints - return null; - } - - return genericMethod; - } - - private static Dictionary ExtractActualGenericArguments( - Type[] methodGenericParameters, - Type[] methodActualParameters) - { - var extractedGenericTypes = new Dictionary(); - - for (var i = 0; i < methodGenericParameters.Length; i++) - { - var requestedType = methodGenericParameters[i]; - var actualType = methodActualParameters[i]; - - if (requestedType.IsGenericParameter) - { - if (!actualType.IsGenericParameter) - extractedGenericTypes[requestedType.Name] = actualType; - } - else if (requestedType.IsArray && actualType.IsArray) - { - var innerGenericTypes = ExtractActualGenericArguments( - new[] { requestedType.GetElementType() }, - new[] { actualType.GetElementType() - }); - - foreach (var innerGenericType in innerGenericTypes) - extractedGenericTypes[innerGenericType.Key] = innerGenericType.Value; - } - else if (requestedType.ContainsGenericParameters) - { - if (actualType.IsGenericParameter) - { - extractedGenericTypes[actualType.Name] = requestedType; - } - else - { - var requestedInnerGenericArgs = requestedType.GetGenericArguments(); - var actualInnerGenericArgs = actualType.GetGenericArguments(); - if (requestedInnerGenericArgs.Length != actualInnerGenericArgs.Length) - continue; - - var innerGenericTypes = ExtractActualGenericArguments(requestedInnerGenericArgs, actualInnerGenericArgs); - foreach (var innerGenericType in innerGenericTypes) - extractedGenericTypes[innerGenericType.Key] = innerGenericType.Value; - } - } - } - - return extractedGenericTypes; - } - - private static Expression PromoteExpression(Expression expr, Type type, bool exact) - { - if (expr.Type == type) return expr; - if (expr is ConstantExpression) - { - var ce = (ConstantExpression)expr; - if (ce == ParserConstants.NullLiteralExpression) - { - if (type.ContainsGenericParameters) - return null; - if (!type.IsValueType || IsNullableType(type)) - return Expression.Constant(null, type); - } - } - - if (expr is InterpreterExpression ie) - { - if (!ie.IsCompatibleWithDelegate(type)) - return null; - - if (!type.ContainsGenericParameters) - return GenerateLambdaFromInterpreterExpression(ie, type); - - return expr; - } - - if (type.IsGenericType && !IsNumericType(type)) - { - var genericType = FindAssignableGenericType(expr.Type, type); - if (genericType != null) - return Expression.Convert(expr, genericType); - } - - if (IsCompatibleWith(expr.Type, type) || expr is DynamicExpression) - { - if (type.IsValueType || exact) - { - return Expression.Convert(expr, type); - } - return expr; - } - - return null; - } - - private static bool IsCompatibleWith(Type source, Type target) - { - if (source == target) - { - return true; - } - - if (target.IsGenericParameter) - { - return true; - } - - if (!target.IsValueType) - { - return target.IsAssignableFrom(source); - } - var st = GetNonNullableType(source); - var tt = GetNonNullableType(target); - if (st != source && tt == target) return false; - var sc = st.IsEnum ? TypeCode.Object : Type.GetTypeCode(st); - var tc = tt.IsEnum ? TypeCode.Object : Type.GetTypeCode(tt); - switch (sc) - { - case TypeCode.SByte: - switch (tc) - { - case TypeCode.SByte: - case TypeCode.Int16: - case TypeCode.Int32: - case TypeCode.Int64: - case TypeCode.Single: - case TypeCode.Double: - case TypeCode.Decimal: - return true; - } - break; - case TypeCode.Byte: - switch (tc) - { - case TypeCode.Byte: - case TypeCode.Int16: - case TypeCode.UInt16: - case TypeCode.Int32: - case TypeCode.UInt32: - case TypeCode.Int64: - case TypeCode.UInt64: - case TypeCode.Single: - case TypeCode.Double: - case TypeCode.Decimal: - return true; - } - break; - case TypeCode.Int16: - switch (tc) - { - case TypeCode.Int16: - case TypeCode.Int32: - case TypeCode.Int64: - case TypeCode.Single: - case TypeCode.Double: - case TypeCode.Decimal: - return true; - } - break; - case TypeCode.UInt16: - switch (tc) - { - case TypeCode.UInt16: - case TypeCode.Int32: - case TypeCode.UInt32: - case TypeCode.Int64: - case TypeCode.UInt64: - case TypeCode.Single: - case TypeCode.Double: - case TypeCode.Decimal: - return true; - } - break; - case TypeCode.Int32: - switch (tc) - { - case TypeCode.Int32: - case TypeCode.Int64: - case TypeCode.Single: - case TypeCode.Double: - case TypeCode.Decimal: - return true; - } - break; - case TypeCode.UInt32: - switch (tc) - { - case TypeCode.UInt32: - case TypeCode.Int64: - case TypeCode.UInt64: - case TypeCode.Single: - case TypeCode.Double: - case TypeCode.Decimal: - return true; - } - break; - case TypeCode.Int64: - switch (tc) - { - case TypeCode.Int64: - case TypeCode.Single: - case TypeCode.Double: - case TypeCode.Decimal: - return true; - } - break; - case TypeCode.UInt64: - switch (tc) - { - case TypeCode.UInt64: - case TypeCode.Single: - case TypeCode.Double: - case TypeCode.Decimal: - return true; - } - break; - case TypeCode.Single: - switch (tc) - { - case TypeCode.Single: - case TypeCode.Double: - return true; - } - break; - default: - if (st == tt) return true; - break; - } - return false; - } - private static bool IsWritable(Expression expression) { switch (expression.NodeType) @@ -2626,145 +1927,11 @@ private static bool IsWritable(Expression expression) } case ExpressionType.Parameter: return true; - } return false; } - // from http://stackoverflow.com/a/1075059/209727 - private static Type FindAssignableGenericType(Type givenType, Type constructedGenericType) - { - var genericTypeDefinition = constructedGenericType.GetGenericTypeDefinition(); - var interfaceTypes = givenType.GetInterfaces(); - - foreach (var it in interfaceTypes) - { - if (it.IsGenericType && it.GetGenericTypeDefinition() == genericTypeDefinition) - { - return it; - } - } - - if (givenType.IsGenericType && givenType.GetGenericTypeDefinition() == genericTypeDefinition) - { - // the given type has the same generic type of the fully constructed generic type - // => check if the generic arguments are compatible (e.g. Nullable and Nullable: int is not compatible with DateTime) - var givenTypeGenericsArgs = givenType.GenericTypeArguments; - var constructedGenericsArgs = constructedGenericType.GenericTypeArguments; - if (givenTypeGenericsArgs.Zip(constructedGenericsArgs, (g, c) => IsCompatibleWith(g, c)).Any(compatible => !compatible)) - return null; - - return givenType; - } - - var baseType = givenType.BaseType; - if (baseType == null) - return null; - - return FindAssignableGenericType(baseType, genericTypeDefinition); - } - - private static bool HasParamsArrayType(ParameterInfo parameterInfo) - { - return parameterInfo.IsDefined(typeof(ParamArrayAttribute), false); - } - - private static Type GetParameterType(ParameterInfo parameterInfo) - { - var isParamsArray = HasParamsArrayType(parameterInfo); - var type = isParamsArray - ? parameterInfo.ParameterType.GetElementType() - : parameterInfo.ParameterType; - return type; - } - - private static bool MethodHasPriority(Expression[] args, MethodData method, MethodData otherMethod) - { - var better = false; - - // check conversion from argument list to parameter list - for (int i = 0, m = 0, o = 0; i < args.Length; i++) - { - var arg = args[i]; - var methodParam = method.Parameters[m]; - var otherMethodParam = otherMethod.Parameters[o]; - var methodParamType = GetParameterType(methodParam); - var otherMethodParamType = GetParameterType(otherMethodParam); - - if (methodParamType.ContainsGenericParameters) - methodParamType = method.PromotedParameters[i].Type; - - if (otherMethodParamType.ContainsGenericParameters) - otherMethodParamType = otherMethod.PromotedParameters[i].Type; - - var c = CompareConversions(arg.Type, methodParamType, otherMethodParamType); - if (c < 0) - return false; - if (c > 0) - better = true; - - if (!HasParamsArrayType(methodParam)) m++; - if (!HasParamsArrayType(otherMethodParam)) o++; - } - - if (better) - return true; - - if (method.MethodBase != null && - otherMethod.MethodBase != null && - !method.MethodBase.IsGenericMethod && - otherMethod.MethodBase.IsGenericMethod) - return true; - - if (!method.HasParamsArray && otherMethod.HasParamsArray) - return true; - - // if a method has a params parameter, it can have less parameters than the number of arguments - if (method.HasParamsArray && otherMethod.HasParamsArray && method.Parameters.Length > otherMethod.Parameters.Length) - return true; - - if (method is IndexerData indexer && otherMethod is IndexerData otherIndexer) - { - var declaringType = indexer.Indexer.DeclaringType; - var otherDeclaringType = otherIndexer.Indexer.DeclaringType; - - var isOtherIndexerIsInParentType = otherDeclaringType.IsAssignableFrom(declaringType); - if (isOtherIndexerIsInParentType) - { - var isIndexerIsInDescendantType = !declaringType.IsAssignableFrom(otherDeclaringType); - return isIndexerIsInDescendantType; - } - } - - return better; - } - - // Return 1 if s -> t1 is a better conversion than s -> t2 - // Return -1 if s -> t2 is a better conversion than s -> t1 - // Return 0 if neither conversion is better - private static int CompareConversions(Type s, Type t1, Type t2) - { - if (t1 == t2) return 0; - if (s == t1) return 1; - if (s == t2) return -1; - - var assignableT1 = t1.IsAssignableFrom(s); - var assignableT2 = t2.IsAssignableFrom(s); - if (assignableT1 && !assignableT2) return 1; - if (assignableT2 && !assignableT1) return -1; - - var compatibleT1T2 = IsCompatibleWith(t1, t2); - var compatibleT2T1 = IsCompatibleWith(t2, t1); - if (compatibleT1T2 && !compatibleT2T1) return 1; - if (compatibleT2T1 && !compatibleT1T2) return -1; - - if (IsSignedIntegralType(t1) && IsUnsignedIntegralType(t2)) return 1; - if (IsSignedIntegralType(t2) && IsUnsignedIntegralType(t1)) return -1; - - return 0; - } - private Expression GenerateEqual(Expression left, Expression right) { return GenerateBinary(ExpressionType.Equal, left, right); @@ -2921,14 +2088,14 @@ private MethodData FindBinaryOperator(string operatorName, Expression left, Expr var errorPos = _token.pos; var leftType = left.Type; var rightType = right.Type; - var error = CreateParseException(errorPos, ErrorMessages.AmbiguousBinaryOperatorInvocation, operatorName, GetTypeName(leftType), GetTypeName(rightType)); + var error = ParseException.Create(errorPos, ErrorMessages.AmbiguousBinaryOperatorInvocation, operatorName, TypeUtils.GetTypeName(leftType), TypeUtils.GetTypeName(rightType)); var args = new[] { left, right }; MethodData userDefinedOperator = null; // try to find the user defined operator on both operands - var opOnLeftType = FindMethods(leftType, operatorName, true, args); + var opOnLeftType = _memberFinder.FindMethods(leftType, operatorName, true, args); if (opOnLeftType.Length > 1) throw error; @@ -2937,7 +2104,7 @@ private MethodData FindBinaryOperator(string operatorName, Expression left, Expr if (leftType != rightType) { - var opOnRightType = FindMethods(rightType, operatorName, true, args); + var opOnRightType = _memberFinder.FindMethods(rightType, operatorName, true, args); if (opOnRightType.Length > 1) throw error; @@ -2964,13 +2131,13 @@ private static Expression GenerateStringConcat(Expression left, Expression right return Expression.Call( null, - _concatMethod, + ReflectionExtensions.StringConcatMethod, new[] { leftObj, rightObj }); } private static Expression ToStringOrNull(Expression expression) { - var nullableExpression = IsNullableType(expression.Type) ? + var nullableExpression = TypeUtils.IsNullableType(expression.Type) ? expression : GenerateNullableTypeConversion(expression); @@ -2984,7 +2151,7 @@ private static Expression ToStringOrNull(Expression expression) return Expression.Condition( condition, stringNullConstant, - Expression.Call(expression, _toStringMethod)); + Expression.Call(expression, ReflectionExtensions.ObjectToStringMethod)); } private static Expression GenerateStringConcatOperand(Expression expression) @@ -2994,14 +2161,10 @@ private static Expression GenerateStringConcatOperand(Expression expression) : expression; } - private static MethodInfo GetStaticMethod(string methodName, Expression left, Expression right) - { - return left.Type.GetMethod(methodName, new[] { left.Type, right.Type }); - } - private static Expression GenerateStaticMethodCall(string methodName, Expression left, Expression right) { - return Expression.Call(null, GetStaticMethod(methodName, left, right), new[] { left, right }); + var staticMethod = left.Type.GetMethod(methodName, new[] { left.Type, right.Type }); + return Expression.Call(null, staticMethod, new[] { left, right }); } private void SetTextPos(int pos) @@ -3218,7 +2381,7 @@ private void NextToken() } if (_parsePosition == _expressionTextLength) - throw CreateParseException(_parsePosition, ErrorMessages.UnterminatedStringLiteral); + throw ParseException.Create(_parsePosition, ErrorMessages.UnterminatedStringLiteral); NextChar(); @@ -3236,7 +2399,7 @@ private void NextToken() } if (_parsePosition == _expressionTextLength) - throw CreateParseException(_parsePosition, ErrorMessages.UnterminatedStringLiteral); + throw ParseException.Create(_parsePosition, ErrorMessages.UnterminatedStringLiteral); NextChar(); @@ -3370,7 +2533,7 @@ private void NextToken() t = TokenId.End; break; } - throw CreateParseException(_parsePosition, ErrorMessages.InvalidCharacter, _parseChar); + throw ParseException.Create(_parsePosition, ErrorMessages.InvalidCharacter, _parseChar); } _token.id = t; _token.text = _expressionText.Substring(tokenPos, _parsePosition - tokenPos); @@ -3389,21 +2552,21 @@ private string GetIdentifier() private void ValidateDigit() { if (!char.IsDigit(_parseChar)) - throw CreateParseException(_parsePosition, ErrorMessages.DigitExpected); + throw ParseException.Create(_parsePosition, ErrorMessages.DigitExpected); } // ReSharper disable once UnusedParameter.Local private void ValidateToken(TokenId t, string errorMessage) { if (_token.id != t) - throw CreateParseException(_token.pos, errorMessage); + throw ParseException.Create(_token.pos, errorMessage); } // ReSharper disable once UnusedParameter.Local private void ValidateToken(TokenId t) { if (_token.id != t) - throw CreateParseException(_token.pos, ErrorMessages.SyntaxError); + throw ParseException.Create(_token.pos, ErrorMessages.SyntaxError); } private static Exception WrapWithParseException(int pos, string format, Exception ex, params object[] args) @@ -3411,17 +2574,11 @@ private static Exception WrapWithParseException(int pos, string format, Exceptio return new ParseException(string.Format(format, args), pos, ex); } - private static Exception CreateParseException(int pos, string format, params object[] args) - { - return new ParseException(string.Format(format, args), pos); - } - private static Expression GenerateNullableTypeConversion(Expression expr) { var exprType = expr.Type; - if (IsNullableType(exprType) || - !exprType.IsValueType) + if (TypeUtils.IsNullableType(exprType) || !exprType.IsValueType) { return expr; } @@ -3429,192 +2586,5 @@ private static Expression GenerateNullableTypeConversion(Expression expr) var conversionType = typeof(Nullable<>).MakeGenericType(exprType); return Expression.ConvertChecked(expr, conversionType); } - - /// - /// Expression that wraps over an interpreter. This is used when parsing a lambda expression - /// definition, because we don't know the parameters type before resolution. - /// - private class InterpreterExpression : Expression - { - private readonly Interpreter _interpreter; - private readonly string _expressionText; - private readonly IList _parameters; - private Type _type; - - public InterpreterExpression(ParserArguments parserArguments, string expressionText, params ParameterWithPosition[] parameters) - { - var settings = parserArguments.Settings.Clone(); - _interpreter = new Interpreter(settings); - _expressionText = expressionText; - _parameters = parameters; - - // Take the parent expression's parameters and set them as an identifier that - // can be accessed by any lower call - // note: this doesn't impact the initial settings, because they're cloned - foreach (var dp in parserArguments.DeclaredParameters) - { - // Have to mark the parameter as "Used" otherwise we can get a compilation error. - parserArguments.TryGetParameters(dp.Name, out var pe); - _interpreter.SetIdentifier(new Identifier(dp.Name, pe)); - } - - foreach (var myParameter in parameters) - { - if (settings.Identifiers.ContainsKey(myParameter.Name)) - { - throw new ParseException($"A local or parameter named '{myParameter.Name}' cannot be declared in this scope because that name is used in an enclosing local scope to define a local or parameter", myParameter.Position); - } - } - - // prior to evaluation, we don't know the generic arguments types - _type = ReflectionExtensions.GetFuncType(parameters.Length); - } - - public IList Parameters - { - get { return _parameters; } - } - - public override Type Type - { - get { return _type; } - } - - internal LambdaExpression EvalAs(Type delegateType) - { - if (!IsCompatibleWithDelegate(delegateType)) - return null; - - var lambdaExpr = _interpreter.ParseAsExpression(delegateType, _expressionText, _parameters.Select(p => p.Name).ToArray()); - _type = lambdaExpr.Type; - return lambdaExpr; - } - - internal bool IsCompatibleWithDelegate(Type target) - { - if (!target.IsGenericType || target.BaseType != typeof(MulticastDelegate)) - return false; - - var genericTypeDefinition = target.GetGenericTypeDefinition(); - return genericTypeDefinition == ReflectionExtensions.GetFuncType(_parameters.Count) - || genericTypeDefinition == ReflectionExtensions.GetActionType(_parameters.Count); - } - } - - private class MethodData - { - public MethodBase MethodBase; - public ParameterInfo[] Parameters; - public Expression[] PromotedParameters; - public bool HasParamsArray; - - public static MethodData Gen(MethodBase method) - { - return new MethodData - { - MethodBase = method, - Parameters = method.GetParameters() - }; - } - - public override string ToString() - { - return MethodBase.ToString(); - } - } - - private class IndexerData : MethodData - { - public readonly PropertyInfo Indexer; - - public IndexerData(PropertyInfo indexer) - { - Indexer = indexer; - - var method = indexer.GetGetMethod(); - if (method != null) - { - Parameters = method.GetParameters(); - } - else - { - method = indexer.GetSetMethod(); - Parameters = RemoveLast(method.GetParameters()); - } - } - - private static T[] RemoveLast(T[] array) - { - T[] result = new T[array.Length - 1]; - Array.Copy(array, 0, result, 0, result.Length); - return result; - } - } - - /// - /// Binds to a member access of an instance as late as possible. This allows the use of anonymous types on dynamic values. - /// - private class LateGetMemberCallSiteBinder : CallSiteBinder - { - private readonly string _propertyOrFieldName; - - public LateGetMemberCallSiteBinder(string propertyOrFieldName) - { - _propertyOrFieldName = propertyOrFieldName; - } - - public override Expression Bind(object[] args, ReadOnlyCollection parameters, LabelTarget returnLabel) - { - var binder = Microsoft.CSharp.RuntimeBinder.Binder.GetMember( - Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags.None, - _propertyOrFieldName, - RemoveArrayType(args[0]?.GetType()), - new[] { Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags.None, null) } - ); - return binder.Bind(args, parameters, returnLabel); - } - } - - /// - /// Binds to a method invocation of an instance as late as possible. This allows the use of anonymous types on dynamic values. - /// - private class LateInvokeMethodCallSiteBinder : CallSiteBinder - { - private readonly string _methodName; - - public LateInvokeMethodCallSiteBinder(string methodName) - { - _methodName = methodName; - } - - public override Expression Bind(object[] args, ReadOnlyCollection parameters, LabelTarget returnLabel) - { - var binderM = Microsoft.CSharp.RuntimeBinder.Binder.InvokeMember( - Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags.None, - _methodName, - null, - RemoveArrayType(args[0]?.GetType()), - parameters.Select(x => Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags.None, null)) - ); - return binderM.Bind(args, parameters, returnLabel); - } - } - - /// - /// Binds to an items invocation of an instance as late as possible. This allows the use of anonymous types on dynamic values. - /// - private class LateInvokeIndexCallSiteBinder : CallSiteBinder - { - public override Expression Bind(object[] args, ReadOnlyCollection parameters, LabelTarget returnLabel) - { - var binder = Microsoft.CSharp.RuntimeBinder.Binder.GetIndex( - Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags.None, - RemoveArrayType(args[0]?.GetType()), - parameters.Select(x => Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags.None, null)) - ); - return binder.Bind(args, parameters, returnLabel); - } - } - } } diff --git a/src/DynamicExpresso.Core/Reflection/IndexerData.cs b/src/DynamicExpresso.Core/Reflection/IndexerData.cs new file mode 100644 index 0000000..bb1f037 --- /dev/null +++ b/src/DynamicExpresso.Core/Reflection/IndexerData.cs @@ -0,0 +1,33 @@ +using System; +using System.Reflection; + +namespace DynamicExpresso.Reflection +{ + internal class IndexerData : MethodData + { + public readonly PropertyInfo Indexer; + + public IndexerData(PropertyInfo indexer) + { + Indexer = indexer; + + var method = indexer.GetGetMethod(); + if (method != null) + { + Parameters = method.GetParameters(); + } + else + { + method = indexer.GetSetMethod(); + Parameters = RemoveLast(method.GetParameters()); + } + } + + private static T[] RemoveLast(T[] array) + { + var result = new T[array.Length - 1]; + Array.Copy(array, 0, result, 0, result.Length); + return result; + } + } +} diff --git a/src/DynamicExpresso.Core/Reflection/MemberFinder.cs b/src/DynamicExpresso.Core/Reflection/MemberFinder.cs new file mode 100644 index 0000000..35c21a3 --- /dev/null +++ b/src/DynamicExpresso.Core/Reflection/MemberFinder.cs @@ -0,0 +1,132 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using DynamicExpresso.Resolution; + +namespace DynamicExpresso.Reflection +{ + internal class MemberFinder + { + private readonly ParserArguments _arguments; + private readonly BindingFlags _bindingCase; + private readonly MemberFilter _memberFilterCase; + + public MemberFinder(ParserArguments arguments) + { + _arguments = arguments; + _bindingCase = arguments.Settings.CaseInsensitive ? BindingFlags.IgnoreCase : BindingFlags.Default; + _memberFilterCase = arguments.Settings.CaseInsensitive ? Type.FilterNameIgnoreCase : Type.FilterName; + } + + public MemberInfo FindPropertyOrField(Type type, string memberName, bool staticAccess) + { + var flags = BindingFlags.Public | BindingFlags.DeclaredOnly | + (staticAccess ? BindingFlags.Static : BindingFlags.Instance) | _bindingCase; + + foreach (var t in SelfAndBaseTypes(type)) + { + var members = t.FindMembers(MemberTypes.Property | MemberTypes.Field, flags, _memberFilterCase, memberName); + if (members.Length != 0) + return members[0]; + } + return null; + } + + public MethodData[] FindMethods(Type type, string methodName, bool staticAccess, Expression[] args) + { + var flags = BindingFlags.Public | BindingFlags.DeclaredOnly | + (staticAccess ? BindingFlags.Static : BindingFlags.Instance) | _bindingCase; + foreach (var t in SelfAndBaseTypes(type)) + { + var members = t.FindMembers(MemberTypes.Method, flags, _memberFilterCase, methodName); + var applicableMethods = MethodResolution.FindBestMethod(members.Cast(), args); + + if (applicableMethods.Length > 0) + return applicableMethods; + } + + return new MethodData[0]; + } + + public MethodData FindInvokeMethod(Type type) + { + var flags = BindingFlags.Public | BindingFlags.DeclaredOnly | + BindingFlags.Instance | _bindingCase; + foreach (var t in SelfAndBaseTypes(type)) + { + var method = t.FindMembers(MemberTypes.Method, flags, _memberFilterCase, "Invoke") + .Cast() + .SingleOrDefault(); + + if (method != null) + return MethodData.Gen(method); + } + + return null; + } + + public MethodData[] FindExtensionMethods(string methodName, Expression[] args) + { + var matchMethods = _arguments.GetExtensionMethods(methodName); + + return MethodResolution.FindBestMethod(matchMethods, args); + } + + public MethodData[] FindIndexer(Type type, Expression[] args) + { + foreach (var t in SelfAndBaseTypes(type)) + { + var members = t.GetDefaultMembers(); + if (members.Length != 0) + { + var methods = members. + OfType(). + Select(p => (MethodData)new IndexerData(p)); + + var applicableMethods = MethodResolution.FindBestMethod(methods, args); + if (applicableMethods.Length > 0) + return applicableMethods; + } + } + + return new MethodData[0]; + } + + private static IEnumerable SelfAndBaseTypes(Type type) + { + if (type.IsInterface) + { + var types = new List(); + AddInterface(types, type); + + types.Add(typeof(object)); + + return types; + } + return SelfAndBaseClasses(type); + } + + private static IEnumerable SelfAndBaseClasses(Type type) + { + while (type != null) + { + yield return type; + type = type.BaseType; + } + } + + private static void AddInterface(List types, Type type) + { + if (!types.Contains(type)) + { + types.Add(type); + foreach (var t in type.GetInterfaces()) + { + AddInterface(types, t); + } + } + } + } +} diff --git a/src/DynamicExpresso.Core/Reflection/MethodData.cs b/src/DynamicExpresso.Core/Reflection/MethodData.cs new file mode 100644 index 0000000..7e56ff4 --- /dev/null +++ b/src/DynamicExpresso.Core/Reflection/MethodData.cs @@ -0,0 +1,27 @@ +using System.Linq.Expressions; +using System.Reflection; + +namespace DynamicExpresso.Reflection +{ + internal class MethodData + { + public MethodBase MethodBase; + public ParameterInfo[] Parameters; + public Expression[] PromotedParameters; + public bool HasParamsArray; + + public static MethodData Gen(MethodBase method) + { + return new MethodData + { + MethodBase = method, + Parameters = method.GetParameters() + }; + } + + public override string ToString() + { + return MethodBase.ToString(); + } + } +} diff --git a/src/DynamicExpresso.Core/Reflection/ReflectionExtensions.cs b/src/DynamicExpresso.Core/Reflection/ReflectionExtensions.cs index cc93333..d648873 100644 --- a/src/DynamicExpresso.Core/Reflection/ReflectionExtensions.cs +++ b/src/DynamicExpresso.Core/Reflection/ReflectionExtensions.cs @@ -7,6 +7,9 @@ namespace DynamicExpresso.Reflection { internal static class ReflectionExtensions { + public static readonly MethodInfo StringConcatMethod = GetStringConcatMethod(); + public static readonly MethodInfo ObjectToStringMethod = GetObjectToStringMethod(); + public static DelegateInfo GetDelegateInfo(Type delegateType, params string[] parametersNames) { MethodInfo method = delegateType.GetMethod("Invoke"); @@ -57,6 +60,28 @@ public DelegateInfo(Type returnType, Parameter[] parameters) public Parameter[] Parameters { get; private set; } } + private static MethodInfo GetStringConcatMethod() + { + var methodInfo = typeof(string).GetMethod("Concat", new[] { typeof(string), typeof(string) }); + if (methodInfo == null) + { + throw new Exception("String concat method not found"); + } + + return methodInfo; + } + + private static MethodInfo GetObjectToStringMethod() + { + var toStringMethod = typeof(object).GetMethod("ToString", Type.EmptyTypes); + if (toStringMethod == null) + { + throw new Exception("ToString method not found"); + } + + return toStringMethod; + } + public static Type GetFuncType(int parameterCount) { // +1 for the return type @@ -67,5 +92,19 @@ public static Type GetActionType(int parameterCount) { return typeof(Action<>).Assembly.GetType($"System.Action`{parameterCount}"); } + + public static bool HasParamsArrayType(ParameterInfo parameterInfo) + { + return parameterInfo.IsDefined(typeof(ParamArrayAttribute), false); + } + + public static Type GetParameterType(ParameterInfo parameterInfo) + { + var isParamsArray = HasParamsArrayType(parameterInfo); + var type = isParamsArray + ? parameterInfo.ParameterType.GetElementType() + : parameterInfo.ParameterType; + return type; + } } } diff --git a/src/DynamicExpresso.Core/Reflection/TypeUtils.cs b/src/DynamicExpresso.Core/Reflection/TypeUtils.cs new file mode 100644 index 0000000..5943433 --- /dev/null +++ b/src/DynamicExpresso.Core/Reflection/TypeUtils.cs @@ -0,0 +1,315 @@ +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.Linq; +using System.Linq.Expressions; + +namespace DynamicExpresso.Reflection +{ + internal static class TypeUtils + { + public static bool IsNullableType(Type type) + { + return Nullable.GetUnderlyingType(type) != null; + } + + public static bool IsDynamicType(Type type) + { + return typeof(IDynamicMetaObjectProvider).IsAssignableFrom(type); + } + + public static Type GetNonNullableType(Type type) + { + return IsNullableType(type) ? type.GetGenericArguments()[0] : type; + } + + public static string GetTypeName(Type type) + { + var baseType = GetNonNullableType(type); + var s = baseType.Name; + if (type != baseType) s += '?'; + return s; + } + + public static bool IsNumericType(Type type) + { + return GetNumericTypeKind(type) != 0; + } + + public static bool IsSignedIntegralType(Type type) + { + return GetNumericTypeKind(type) == 2; + } + + public static bool IsUnsignedIntegralType(Type type) + { + return GetNumericTypeKind(type) == 3; + } + + private static int GetNumericTypeKind(Type type) + { + type = GetNonNullableType(type); + if (type.IsEnum) return 0; + switch (Type.GetTypeCode(type)) + { + case TypeCode.Char: + case TypeCode.Single: + case TypeCode.Double: + case TypeCode.Decimal: + return 1; + case TypeCode.SByte: + case TypeCode.Int16: + case TypeCode.Int32: + case TypeCode.Int64: + return 2; + case TypeCode.Byte: + case TypeCode.UInt16: + case TypeCode.UInt32: + case TypeCode.UInt64: + return 3; + default: + return 0; + } + } + + public static bool IsCompatibleWith(Type source, Type target) + { + if (source == target) + { + return true; + } + + if (target.IsGenericParameter) + { + return true; + } + + if (!target.IsValueType) + { + return target.IsAssignableFrom(source); + } + var st = GetNonNullableType(source); + var tt = GetNonNullableType(target); + if (st != source && tt == target) return false; + var sc = st.IsEnum ? TypeCode.Object : Type.GetTypeCode(st); + var tc = tt.IsEnum ? TypeCode.Object : Type.GetTypeCode(tt); + switch (sc) + { + case TypeCode.SByte: + switch (tc) + { + case TypeCode.SByte: + case TypeCode.Int16: + case TypeCode.Int32: + case TypeCode.Int64: + case TypeCode.Single: + case TypeCode.Double: + case TypeCode.Decimal: + return true; + } + break; + case TypeCode.Byte: + switch (tc) + { + case TypeCode.Byte: + case TypeCode.Int16: + case TypeCode.UInt16: + case TypeCode.Int32: + case TypeCode.UInt32: + case TypeCode.Int64: + case TypeCode.UInt64: + case TypeCode.Single: + case TypeCode.Double: + case TypeCode.Decimal: + return true; + } + break; + case TypeCode.Int16: + switch (tc) + { + case TypeCode.Int16: + case TypeCode.Int32: + case TypeCode.Int64: + case TypeCode.Single: + case TypeCode.Double: + case TypeCode.Decimal: + return true; + } + break; + case TypeCode.UInt16: + switch (tc) + { + case TypeCode.UInt16: + case TypeCode.Int32: + case TypeCode.UInt32: + case TypeCode.Int64: + case TypeCode.UInt64: + case TypeCode.Single: + case TypeCode.Double: + case TypeCode.Decimal: + return true; + } + break; + case TypeCode.Int32: + switch (tc) + { + case TypeCode.Int32: + case TypeCode.Int64: + case TypeCode.Single: + case TypeCode.Double: + case TypeCode.Decimal: + return true; + } + break; + case TypeCode.UInt32: + switch (tc) + { + case TypeCode.UInt32: + case TypeCode.Int64: + case TypeCode.UInt64: + case TypeCode.Single: + case TypeCode.Double: + case TypeCode.Decimal: + return true; + } + break; + case TypeCode.Int64: + switch (tc) + { + case TypeCode.Int64: + case TypeCode.Single: + case TypeCode.Double: + case TypeCode.Decimal: + return true; + } + break; + case TypeCode.UInt64: + switch (tc) + { + case TypeCode.UInt64: + case TypeCode.Single: + case TypeCode.Double: + case TypeCode.Decimal: + return true; + } + break; + case TypeCode.Single: + switch (tc) + { + case TypeCode.Single: + case TypeCode.Double: + return true; + } + break; + default: + if (st == tt) return true; + break; + } + return false; + } + + // Return 1 if s -> t1 is a better conversion than s -> t2 + // Return -1 if s -> t2 is a better conversion than s -> t1 + // Return 0 if neither conversion is better + public static int CompareConversions(Type s, Type t1, Type t2) + { + if (t1 == t2) return 0; + if (s == t1) return 1; + if (s == t2) return -1; + + var assignableT1 = t1.IsAssignableFrom(s); + var assignableT2 = t2.IsAssignableFrom(s); + if (assignableT1 && !assignableT2) return 1; + if (assignableT2 && !assignableT1) return -1; + + var compatibleT1T2 = IsCompatibleWith(t1, t2); + var compatibleT2T1 = IsCompatibleWith(t2, t1); + if (compatibleT1T2 && !compatibleT2T1) return 1; + if (compatibleT2T1 && !compatibleT1T2) return -1; + + if (IsSignedIntegralType(t1) && IsUnsignedIntegralType(t2)) return 1; + if (IsSignedIntegralType(t2) && IsUnsignedIntegralType(t1)) return -1; + + return 0; + } + + /// + /// Returns null if is an Array type. Needed because the lookup methods fail with a if the array type is used. + /// Everything still miraculously works on the array if null is given for the type. + /// + /// + /// + public static Type RemoveArrayType(Type t) + { + if (t == null || t.IsArray) + { + return null; + } + return t; + } + + // from http://stackoverflow.com/a/1075059/209727 + public static Type FindAssignableGenericType(Type givenType, Type constructedGenericType) + { + var genericTypeDefinition = constructedGenericType.GetGenericTypeDefinition(); + var interfaceTypes = givenType.GetInterfaces(); + + foreach (var it in interfaceTypes) + { + if (it.IsGenericType && it.GetGenericTypeDefinition() == genericTypeDefinition) + { + return it; + } + } + + if (givenType.IsGenericType && givenType.GetGenericTypeDefinition() == genericTypeDefinition) + { + // the given type has the same generic type of the fully constructed generic type + // => check if the generic arguments are compatible (e.g. Nullable and Nullable: int is not compatible with DateTime) + var givenTypeGenericsArgs = givenType.GenericTypeArguments; + var constructedGenericsArgs = constructedGenericType.GenericTypeArguments; + if (givenTypeGenericsArgs.Zip(constructedGenericsArgs, (g, c) => TypeUtils.IsCompatibleWith(g, c)).Any(compatible => !compatible)) + return null; + + return givenType; + } + + var baseType = givenType.BaseType; + if (baseType == null) + return null; + + return FindAssignableGenericType(baseType, genericTypeDefinition); + } + + public static Type GetConcreteTypeForGenericMethod(Type type, List promotedArgs, MethodData method) + { + if (type.IsGenericType) + { + //Generic type + var genericArguments = type.GetGenericArguments(); + var concreteTypeParameters = new Type[genericArguments.Length]; + + for (var i = 0; i < genericArguments.Length; i++) + { + concreteTypeParameters[i] = GetConcreteTypeForGenericMethod(genericArguments[i], promotedArgs, method); + } + + return type.GetGenericTypeDefinition().MakeGenericType(concreteTypeParameters); + } + else if (type.ContainsGenericParameters) + { + //T case + //try finding an actual parameter for the generic + for (var i = 0; i < promotedArgs.Count; i++) + { + if (method.Parameters[i].ParameterType == type) + { + return promotedArgs[i].Type; + } + } + } + + return type;//already a concrete type + } + } +} diff --git a/src/DynamicExpresso.Core/Resolution/ExpressionUtils.cs b/src/DynamicExpresso.Core/Resolution/ExpressionUtils.cs new file mode 100644 index 0000000..c640ff2 --- /dev/null +++ b/src/DynamicExpresso.Core/Resolution/ExpressionUtils.cs @@ -0,0 +1,55 @@ +using System; +using System.Linq.Expressions; +using DynamicExpresso.Parsing; +using DynamicExpresso.Reflection; + +namespace DynamicExpresso.Resolution +{ + internal static class ExpressionUtils + { + public static Expression PromoteExpression(Expression expr, Type type, bool exact) + { + if (expr.Type == type) return expr; + if (expr is ConstantExpression) + { + var ce = (ConstantExpression)expr; + if (ce == ParserConstants.NullLiteralExpression) + { + if (type.ContainsGenericParameters) + return null; + if (!type.IsValueType || TypeUtils.IsNullableType(type)) + return Expression.Constant(null, type); + } + } + + if (expr is InterpreterExpression ie) + { + if (!ie.IsCompatibleWithDelegate(type)) + return null; + + if (!type.ContainsGenericParameters) + return ie.EvalAs(type); + + return expr; + } + + if (type.IsGenericType && !TypeUtils.IsNumericType(type)) + { + var genericType = TypeUtils.FindAssignableGenericType(expr.Type, type); + if (genericType != null) + return Expression.Convert(expr, genericType); + } + + if (TypeUtils.IsCompatibleWith(expr.Type, type) || expr is DynamicExpression) + { + if (type.IsValueType || exact) + { + return Expression.Convert(expr, type); + } + return expr; + } + + return null; + } + } +} diff --git a/src/DynamicExpresso.Core/Resolution/LateBinders.cs b/src/DynamicExpresso.Core/Resolution/LateBinders.cs new file mode 100644 index 0000000..6fc40b0 --- /dev/null +++ b/src/DynamicExpresso.Core/Resolution/LateBinders.cs @@ -0,0 +1,71 @@ +using System.Collections.ObjectModel; +using System.Linq; +using System.Linq.Expressions; +using System.Runtime.CompilerServices; +using DynamicExpresso.Reflection; +using Microsoft.CSharp.RuntimeBinder; + +namespace DynamicExpresso.Resolution +{ + internal class LateGetMemberCallSiteBinder : CallSiteBinder + { + private readonly string _propertyOrFieldName; + + public LateGetMemberCallSiteBinder(string propertyOrFieldName) + { + _propertyOrFieldName = propertyOrFieldName; + } + + public override Expression Bind(object[] args, ReadOnlyCollection parameters, LabelTarget returnLabel) + { + var binder = Binder.GetMember( + CSharpBinderFlags.None, + _propertyOrFieldName, + TypeUtils.RemoveArrayType(args[0]?.GetType()), + new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) } + ); + return binder.Bind(args, parameters, returnLabel); + } + } + + /// + /// Binds to a method invocation of an instance as late as possible. This allows the use of anonymous types on dynamic values. + /// + internal class LateInvokeMethodCallSiteBinder : CallSiteBinder + { + private readonly string _methodName; + + public LateInvokeMethodCallSiteBinder(string methodName) + { + _methodName = methodName; + } + + public override Expression Bind(object[] args, ReadOnlyCollection parameters, LabelTarget returnLabel) + { + var binderM = Binder.InvokeMember( + CSharpBinderFlags.None, + _methodName, + null, + TypeUtils.RemoveArrayType(args[0]?.GetType()), + parameters.Select(x => CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)) + ); + return binderM.Bind(args, parameters, returnLabel); + } + } + + /// + /// Binds to an items invocation of an instance as late as possible. This allows the use of anonymous types on dynamic values. + /// + internal class LateInvokeIndexCallSiteBinder : CallSiteBinder + { + public override Expression Bind(object[] args, ReadOnlyCollection parameters, LabelTarget returnLabel) + { + var binder = Binder.GetIndex( + CSharpBinderFlags.None, + TypeUtils.RemoveArrayType(args[0]?.GetType()), + parameters.Select(x => CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)) + ); + return binder.Bind(args, parameters, returnLabel); + } + } +} diff --git a/src/DynamicExpresso.Core/Resolution/MethodResolution.cs b/src/DynamicExpresso.Core/Resolution/MethodResolution.cs new file mode 100644 index 0000000..3d99d81 --- /dev/null +++ b/src/DynamicExpresso.Core/Resolution/MethodResolution.cs @@ -0,0 +1,333 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Linq; +using System.Reflection; +using System.Security; +using System.Text; +using DynamicExpresso.Exceptions; +using DynamicExpresso.Parsing; +using DynamicExpresso.Reflection; +using DynamicExpresso.Resources; + +namespace DynamicExpresso.Resolution +{ + internal static class MethodResolution + { + public static MethodData[] FindBestMethod(IEnumerable methods, Expression[] args) + { + return FindBestMethod(methods.Select(MethodData.Gen), args); + } + + public static MethodData[] FindBestMethod(IEnumerable methods, Expression[] args) + { + var applicable = methods. + Where(m => CheckIfMethodIsApplicableAndPrepareIt(m, args)). + ToArray(); + if (applicable.Length > 1) + { + var bestCandidates = applicable + .Where(m => applicable.All(n => m == n || MethodHasPriority(args, m, n))) + .ToArray(); + + // bestCandidates.Length == 0 means that no applicable method has priority + // we don't return bestCandidates to prevent callers from thinking no method was found + if (bestCandidates.Length > 0) + return bestCandidates; + } + + return applicable; + } + + public static bool CheckIfMethodIsApplicableAndPrepareIt(MethodData method, Expression[] args) + { + if (method.Parameters.Count(y => !y.HasDefaultValue && !ReflectionExtensions.HasParamsArrayType(y)) > args.Length) + return false; + + var promotedArgs = new List(); + var declaredWorkingParameters = 0; + + Type paramsArrayTypeFound = null; + List paramsArrayPromotedArgument = null; + + foreach (var currentArgument in args) + { + Type parameterType; + + if (paramsArrayTypeFound != null) + { + parameterType = paramsArrayTypeFound; + } + else + { + if (declaredWorkingParameters >= method.Parameters.Length) + { + return false; + } + + var parameterDeclaration = method.Parameters[declaredWorkingParameters]; + if (parameterDeclaration.IsOut) + { + return false; + } + + parameterType = parameterDeclaration.ParameterType; + + if (ReflectionExtensions.HasParamsArrayType(parameterDeclaration)) + { + paramsArrayTypeFound = parameterType; + } + + declaredWorkingParameters++; + } + + if (paramsArrayPromotedArgument == null && (paramsArrayTypeFound == null || args.Length == method.Parameters.Length)) + { + if (parameterType.IsGenericParameter) + { + // an interpreter expression can only be matched to a parameter of type Func + if (currentArgument is InterpreterExpression) + return false; + + promotedArgs.Add(currentArgument); + continue; + } + + var promoted = ExpressionUtils.PromoteExpression(currentArgument, parameterType, true); + if (promoted != null) + { + promotedArgs.Add(promoted); + continue; + } + } + + if (paramsArrayTypeFound != null) + { + var paramsArrayElementType = paramsArrayTypeFound.GetElementType(); + if (paramsArrayElementType.IsGenericParameter) + { + paramsArrayPromotedArgument = paramsArrayPromotedArgument ?? new List(); + paramsArrayPromotedArgument.Add(currentArgument); + continue; + } + + var promoted = ExpressionUtils.PromoteExpression(currentArgument, paramsArrayElementType, true); + if (promoted != null) + { + paramsArrayPromotedArgument = paramsArrayPromotedArgument ?? new List(); + paramsArrayPromotedArgument.Add(promoted); + continue; + } + } + + return false; + } + + if (paramsArrayPromotedArgument != null) + { + method.HasParamsArray = true; + var paramsArrayElementType = paramsArrayTypeFound.GetElementType(); + if (paramsArrayElementType == null) + throw ParseException.Create(-1, ErrorMessages.ParamsArrayTypeNotAnArray); + + if (paramsArrayElementType.IsGenericParameter) + { + var actualTypes = paramsArrayPromotedArgument.Select(_ => _.Type).Distinct().ToArray(); + if (actualTypes.Length != 1) + throw ParseException.Create(-1, ErrorMessages.MethodTypeParametersCantBeInferred, method.MethodBase); + + paramsArrayElementType = actualTypes[0]; + } + + promotedArgs.Add(Expression.NewArrayInit(paramsArrayElementType, paramsArrayPromotedArgument)); + } + + // Add default params, if needed. + promotedArgs.AddRange(method.Parameters.Skip(promotedArgs.Count).Select(x => + { + if (x.HasDefaultValue) + { + var parameterType = TypeUtils.GetConcreteTypeForGenericMethod(x.ParameterType, promotedArgs, method); + + return Expression.Constant(x.DefaultValue, parameterType); + } + + + if (ReflectionExtensions.HasParamsArrayType(x)) + { + method.HasParamsArray = true; + return Expression.NewArrayInit(x.ParameterType.GetElementType()); + } + + throw new Exception("No default value found!"); + })); + + method.PromotedParameters = promotedArgs.ToArray(); + + if (method.MethodBase != null && method.MethodBase.IsGenericMethodDefinition && + method.MethodBase is MethodInfo) + { + var genericMethod = MakeGenericMethod(method); + if (genericMethod == null) + return false; + + // we have all the type information we can get, update interpreter expressions and evaluate them + var actualMethodParameters = genericMethod.GetParameters(); + for (var i = 0; i < method.PromotedParameters.Length; i++) + { + if (method.PromotedParameters[i] is InterpreterExpression ie) + { + var actualParamInfo = actualMethodParameters[i]; + var lambdaExpr = ie.EvalAs(actualParamInfo.ParameterType); + if (lambdaExpr == null) + return false; + + method.PromotedParameters[i] = lambdaExpr; + + // we have inferred all types, update the method definition + genericMethod = MakeGenericMethod(method); + } + } + + method.MethodBase = genericMethod; + } + + return true; + } + + private static bool MethodHasPriority(Expression[] args, MethodData method, MethodData otherMethod) + { + var better = false; + + // check conversion from argument list to parameter list + for (int i = 0, m = 0, o = 0; i < args.Length; i++) + { + var arg = args[i]; + var methodParam = method.Parameters[m]; + var otherMethodParam = otherMethod.Parameters[o]; + var methodParamType = ReflectionExtensions.GetParameterType(methodParam); + var otherMethodParamType = ReflectionExtensions.GetParameterType(otherMethodParam); + + if (methodParamType.ContainsGenericParameters) + methodParamType = method.PromotedParameters[i].Type; + + if (otherMethodParamType.ContainsGenericParameters) + otherMethodParamType = otherMethod.PromotedParameters[i].Type; + + var c = TypeUtils.CompareConversions(arg.Type, methodParamType, otherMethodParamType); + if (c < 0) + return false; + if (c > 0) + better = true; + + if (!ReflectionExtensions.HasParamsArrayType(methodParam)) m++; + if (!ReflectionExtensions.HasParamsArrayType(otherMethodParam)) o++; + } + + if (better) + return true; + + if (method.MethodBase != null && + otherMethod.MethodBase != null && + !method.MethodBase.IsGenericMethod && + otherMethod.MethodBase.IsGenericMethod) + return true; + + if (!method.HasParamsArray && otherMethod.HasParamsArray) + return true; + + // if a method has a params parameter, it can have less parameters than the number of arguments + if (method.HasParamsArray && otherMethod.HasParamsArray && method.Parameters.Length > otherMethod.Parameters.Length) + return true; + + if (method is IndexerData indexer && otherMethod is IndexerData otherIndexer) + { + var declaringType = indexer.Indexer.DeclaringType; + var otherDeclaringType = otherIndexer.Indexer.DeclaringType; + + var isOtherIndexerIsInParentType = otherDeclaringType.IsAssignableFrom(declaringType); + if (isOtherIndexerIsInParentType) + { + var isIndexerIsInDescendantType = !declaringType.IsAssignableFrom(otherDeclaringType); + return isIndexerIsInDescendantType; + } + } + + return better; + } + + private static MethodInfo MakeGenericMethod(MethodData method) + { + var methodInfo = (MethodInfo)method.MethodBase; + var actualGenericArgs = ExtractActualGenericArguments( + method.Parameters.Select(p => p.ParameterType).ToArray(), + method.PromotedParameters.Select(p => p.Type).ToArray()); + + var genericArgs = methodInfo.GetGenericArguments() + .Select(p => actualGenericArgs.TryGetValue(p.Name, out var typ) ? typ : typeof(object)) + .ToArray(); + + MethodInfo genericMethod = null; + try + { + genericMethod = methodInfo.MakeGenericMethod(genericArgs); + } + catch (ArgumentException e) when (e.InnerException is VerificationException) + { + // this exception is thrown when a generic argument violates the generic constraints + return null; + } + + return genericMethod; + } + + private static Dictionary ExtractActualGenericArguments( + Type[] methodGenericParameters, + Type[] methodActualParameters) + { + var extractedGenericTypes = new Dictionary(); + + for (var i = 0; i < methodGenericParameters.Length; i++) + { + var requestedType = methodGenericParameters[i]; + var actualType = methodActualParameters[i]; + + if (requestedType.IsGenericParameter) + { + if (!actualType.IsGenericParameter) + extractedGenericTypes[requestedType.Name] = actualType; + } + else if (requestedType.IsArray && actualType.IsArray) + { + var innerGenericTypes = ExtractActualGenericArguments( + new[] { requestedType.GetElementType() }, + new[] { actualType.GetElementType() + }); + + foreach (var innerGenericType in innerGenericTypes) + extractedGenericTypes[innerGenericType.Key] = innerGenericType.Value; + } + else if (requestedType.ContainsGenericParameters) + { + if (actualType.IsGenericParameter) + { + extractedGenericTypes[actualType.Name] = requestedType; + } + else + { + var requestedInnerGenericArgs = requestedType.GetGenericArguments(); + var actualInnerGenericArgs = actualType.GetGenericArguments(); + if (requestedInnerGenericArgs.Length != actualInnerGenericArgs.Length) + continue; + + var innerGenericTypes = ExtractActualGenericArguments(requestedInnerGenericArgs, actualInnerGenericArgs); + foreach (var innerGenericType in innerGenericTypes) + extractedGenericTypes[innerGenericType.Key] = innerGenericType.Value; + } + } + } + + return extractedGenericTypes; + } + } +} diff --git a/test/DynamicExpresso.UnitTest/DetectIdentifiersTest.cs b/test/DynamicExpresso.UnitTest/DetectIdentifiersTest.cs index 27e8374..99d91f2 100644 --- a/test/DynamicExpresso.UnitTest/DetectIdentifiersTest.cs +++ b/test/DynamicExpresso.UnitTest/DetectIdentifiersTest.cs @@ -324,5 +324,22 @@ public void Dont_detect_members_with_at() Assert.AreEqual(1, detectedIdentifiers.UnknownIdentifiers.Count()); Assert.AreEqual("@class", detectedIdentifiers.UnknownIdentifiers.ElementAt(0)); } + + + [Test] + [TestCase("1L")] + [TestCase("2M")] + [TestCase("3.0D")] + [TestCase("4.0F")] + [TestCase("6.7e-8")] + [TestCase("9U")] + [TestCase("10ul")] + [TestCase("11lu")] + public void Dont_detect_numbers_with_suffix(string code) + { + var target = new Interpreter(InterpreterOptions.Default | InterpreterOptions.LambdaExpressions); + var detectedIdentifiers = target.DetectIdentifiers(code); + Assert.IsEmpty(detectedIdentifiers.UnknownIdentifiers); + } } } diff --git a/test/DynamicExpresso.UnitTest/DynamicExpresso.UnitTest.csproj b/test/DynamicExpresso.UnitTest/DynamicExpresso.UnitTest.csproj index 0a0e7ad..3df1cae 100644 --- a/test/DynamicExpresso.UnitTest/DynamicExpresso.UnitTest.csproj +++ b/test/DynamicExpresso.UnitTest/DynamicExpresso.UnitTest.csproj @@ -1,7 +1,7 @@ - net7.0;net5.0;netcoreapp3.1;net462 + net8.0;net7.0;net5.0;net462 false diff --git a/test/DynamicExpresso.UnitTest/GithubIssues.cs b/test/DynamicExpresso.UnitTest/GithubIssues.cs index 010292e..0edcc81 100644 --- a/test/DynamicExpresso.UnitTest/GithubIssues.cs +++ b/test/DynamicExpresso.UnitTest/GithubIssues.cs @@ -254,7 +254,7 @@ static bool GetGFunction2(string arg) Assert.Throws(() => interpreter.Eval("GFunction(arg)")); // GFunction1 is used - // because gFunc1.Method.GetParameters()[0].HasDefaultValue == true + // because gFunc1.Method.GetParameters()[0].HasDefaultValue == true // and gFunc2.Method.GetParameters()[0].HasDefaultValue == false Assert.False((bool)interpreter.Eval("GFunction()")); } @@ -819,6 +819,23 @@ public void GitHub_Issue_305() } #endregion + + [Test] + public void GitHub_Issue_311() + { + var a = "AABB"; + + var interpreter1 = new Interpreter(); + interpreter1.SetVariable("a", a); + Assert.AreEqual("AA", interpreter1.Eval("a.Substring(0, 2)")); + + var interpreter2 = new Interpreter().SetDefaultNumberType(DefaultNumberType.Decimal); + interpreter2.SetVariable("a", a); + // expected to throw because Substring is not defined for decimal + Assert.Throws(() => interpreter2.Eval("a.Substring(0, 2)")); + // It works if we cast to int + Assert.AreEqual("AA", interpreter2.Eval("a.Substring((int)0, (int)2)")); + } } internal static class GithubIssuesTestExtensionsMethods diff --git a/test/DynamicExpresso.UnitTest/MemberInvocationTest.cs b/test/DynamicExpresso.UnitTest/MemberInvocationTest.cs index e42727a..56abe7a 100644 --- a/test/DynamicExpresso.UnitTest/MemberInvocationTest.cs +++ b/test/DynamicExpresso.UnitTest/MemberInvocationTest.cs @@ -108,7 +108,7 @@ public void Indexer_Getter_MultiDimensional() target.SetVariable("x", x); var y = new MyTestService(); target.SetVariable("y", y); - + Assert.AreEqual(x[1, 2], target.Eval("x[1, 2]")); Assert.AreEqual(y[y.Today, 2], target.Eval("y[y.Today, 2]")); Assert.AreEqual(y[y.Today], target.Eval("y[y.Today]"));