diff --git a/src/FastExpressionCompiler/FastExpressionCompiler.cs b/src/FastExpressionCompiler/FastExpressionCompiler.cs index 21af4134..e29fb637 100644 --- a/src/FastExpressionCompiler/FastExpressionCompiler.cs +++ b/src/FastExpressionCompiler/FastExpressionCompiler.cs @@ -1,12044 +1,12356 @@ -// -/* -The MIT License (MIT) - -Copyright (c) 2016-2026 Maksim Volkau - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ - -// ReSharper disable CoVariantArrayConversion -#nullable disable - -//#define LIGHT_EXPRESSION - -// todo: @wip disable for the published version! -// #define TESTING // allows to test in Release in addition to the Debug - -#if DEBUG && NET6_0_OR_GREATER -#define DEBUG_INFO_LOCAL_VARIABLE_USAGE -#define DEMIT -#define INTERPRETATION_DIAGNOSTICS -#endif - -#if NET6_0_OR_GREATER -#define SUPPORTS_IL_GENERATOR_REUSE -#endif - -#if LIGHT_EXPRESSION -#define SUPPORTS_ARGUMENT_PROVIDER -#endif - -#if LIGHT_EXPRESSION -namespace FastExpressionCompiler.LightExpression -{ - using static FastExpressionCompiler.LightExpression.Expression; - using PE = FastExpressionCompiler.LightExpression.ParameterExpression; - using FastExpressionCompiler.LightExpression.ImTools; - using FastExpressionCompiler.LightExpression.ILDecoder; - using static FastExpressionCompiler.LightExpression.ImTools.SmallMap; -#else -namespace FastExpressionCompiler -{ - using static System.Linq.Expressions.Expression; - using PE = System.Linq.Expressions.ParameterExpression; - using FastExpressionCompiler.ImTools; - using FastExpressionCompiler.ILDecoder; - using static FastExpressionCompiler.ImTools.SmallMap; -#endif - using System; - using System.Collections; - using System.Collections.Generic; - using System.Linq; - using System.Linq.Expressions; - using System.Reflection; - using System.Reflection.Emit; - using System.Threading; - using System.Text; - using System.Runtime.CompilerServices; - using System.Runtime.InteropServices; - using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; - using static System.Environment; - using static CodePrinter; - - /// The flags for the compiler - [Flags] - public enum CompilerFlags : byte - { - /// The default flags: Invocation lambda is inlined, no debug info - Default = 0, - /// Prevents the inlining of the lambda in the Invocation expression to optimize for the multiple same lambda compiled once - NoInvocationLambdaInlining = 1, - /// Adds the Expression, ExpressionString, and CSharpString to the delegate closure for the debugging inspection - EnableDelegateDebugInfo = 1 << 1, - /// When the flag is set then instead of the returning `null` the specific exception is thrown*346 - ThrowOnNotSupportedExpression = 1 << 2, - /// Will try to Interpret arithmetic, logical, comparison expressions for the primitive types, - /// and emit the IL the result only instead of the whole computation. - DisableInterpreter = 1 << 4 - } - - /// FEC Not Supported exception - public sealed class NotSupportedExpressionException : InvalidOperationException - { - /// The reason - public readonly ExpressionCompiler.Result Reason; - /// Constructor - public NotSupportedExpressionException(ExpressionCompiler.Result reason) : base(reason.ToString()) => Reason = reason; - /// Constructor - public NotSupportedExpressionException(ExpressionCompiler.Result reason, string message) : base(reason + ": " + message) => Reason = reason; - } - - /// The interface is implemented by the compiled delegate Target if `CompilerFlags.EnableDelegateDebugInfo` is set. - public interface IDelegateDebugInfo - { - /// The lambda expression object that was compiled to the delegate - LambdaExpression Expression { get; } - - /// Delegate IL op-codes - ILInstruction[] ILInstructions { get; } - - /// Enumerate any nested lambdas in the delegate - [RequiresUnreferencedCode(Trimming.Message)] - IEnumerable EnumerateNestedLambdas(); - } - - /// Compiles expression to delegate ~20 times faster than Expression.Compile. - /// Partial to extend with your things when used as source file. - // ReSharper disable once PartialTypeWithSinglePart - [RequiresUnreferencedCode(Trimming.Message)] - public static partial class ExpressionCompiler - { - #region Expression.CompileFast overloads for Delegate, Func, and Action - - /// Compiles lambda expression to TDelegate type. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. - public static TDelegate CompileFast(this LambdaExpression lambdaExpr, - bool ifFastFailedReturnNull = false, CompilerFlags flags = CompilerFlags.Default) where TDelegate : class => - (TDelegate)(TryCompileBoundToFirstClosureParam( - typeof(TDelegate) == typeof(Delegate) ? lambdaExpr.Type : typeof(TDelegate), lambdaExpr.Body, -#if LIGHT_EXPRESSION - lambdaExpr, -#else - lambdaExpr.Parameters, -#endif - lambdaExpr.ReturnType, flags) ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys())); - - /// Compiles a static method to the passed IL Generator. - /// Could be used as alternative for `CompileToMethod` like this . - /// Check `IssueTests.Issue179_Add_something_like_LambdaExpression_CompileToMethod.cs` for example. - public static bool CompileFastToIL(this LambdaExpression lambdaExpr, ILGenerator il, CompilerFlags flags = CompilerFlags.Default) - { - if ((flags & CompilerFlags.EnableDelegateDebugInfo) != 0) - throw new NotSupportedException("The `CompilerFlags.EnableDelegateDebugInfo` is not supported because the debug info is gathered into the closure object which is not allowed for static lambda to be compiled to method."); - -#if LIGHT_EXPRESSION - var paramExprs = lambdaExpr; -#else - var paramExprs = lambdaExpr.Parameters; -#endif - var bodyExpr = lambdaExpr.Body; - - var closureInfo = new ClosureInfo(ClosureStatus.ShouldBeStaticMethod); - var nestedLambdas = new SmallList(); - if (!TryCollectBoundConstants(ref closureInfo, bodyExpr, paramExprs, null, ref nestedLambdas, flags)) - return false; - - if ((closureInfo.Status & ClosureStatus.HasClosure) != 0) - return false; - - var parent = lambdaExpr.ReturnType == typeof(void) ? ParentFlags.IgnoreResult : ParentFlags.LambdaCall; - if (!EmittingVisitor.TryEmit(bodyExpr, paramExprs, il, ref closureInfo, flags, parent)) - return false; - - il.Demit(OpCodes.Ret); - return true; - } - - /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. - public static Delegate CompileFast(this LambdaExpression lambdaExpr, bool ifFastFailedReturnNull = false, CompilerFlags flags = CompilerFlags.Default) => - (Delegate)TryCompileBoundToFirstClosureParam(lambdaExpr.Type, lambdaExpr.Body, -#if LIGHT_EXPRESSION - lambdaExpr, -#else - lambdaExpr.Parameters, -#endif - lambdaExpr.ReturnType, flags) ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); - - /// Returns the System expression itself or convert the System expression into Light Expression - public static Expression FromSysExpression(this System.Linq.Expressions.Expression lambdaExpr) where TDelegate : System.Delegate => -#if LIGHT_EXPRESSION - lambdaExpr.ToLightExpression(); -#else - lambdaExpr; -#endif - - /// Returns the System expression itself or convert the System expression into Light Expression - public static LambdaExpression FromSysExpression(this System.Linq.Expressions.LambdaExpression lambdaExpr) => -#if LIGHT_EXPRESSION - lambdaExpr.ToLightExpression(); -#else - lambdaExpr; -#endif - - /// Unifies Compile for System.Linq.Expressions and FEC.LightExpression - public static TDelegate CompileSys(this Expression lambdaExpr) where TDelegate : System.Delegate => - lambdaExpr -#if LIGHT_EXPRESSION - .ToLambdaExpression() -#endif - .Compile(); - - /// Unifies Compile for System.Linq.Expressions and FEC.LightExpression - public static Delegate CompileSys(this LambdaExpression lambdaExpr) => - lambdaExpr -#if LIGHT_EXPRESSION - .ToLambdaExpression() -#endif - .Compile(); - - /// Compiles lambda expression to TDelegate type. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. - public static TDelegate CompileFast(this Expression lambdaExpr, bool ifFastFailedReturnNull = false, - CompilerFlags flags = CompilerFlags.Default) where TDelegate : System.Delegate => - ((LambdaExpression)lambdaExpr).CompileFast(ifFastFailedReturnNull, flags); - - /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. - public static Func CompileFast(this Expression> lambdaExpr, bool ifFastFailedReturnNull = false, - CompilerFlags flags = CompilerFlags.Default) => - (Func)TryCompileBoundToFirstClosureParam(typeof(Func), lambdaExpr.Body, -#if LIGHT_EXPRESSION - lambdaExpr, -#else - lambdaExpr.Parameters, -#endif - typeof(R), flags) ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); - - /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. - public static Func CompileFast(this Expression> lambdaExpr, - bool ifFastFailedReturnNull = false, CompilerFlags flags = CompilerFlags.Default) => - (Func)TryCompileBoundToFirstClosureParam(typeof(Func), lambdaExpr.Body, -#if LIGHT_EXPRESSION - lambdaExpr, -#else - lambdaExpr.Parameters, -#endif - typeof(R), flags) ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); - - /// Compiles lambda expression to TDelegate type. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. - public static Func CompileFast(this Expression> lambdaExpr, - bool ifFastFailedReturnNull = false, CompilerFlags flags = CompilerFlags.Default) => - (Func)TryCompileBoundToFirstClosureParam(typeof(Func), lambdaExpr.Body, -#if LIGHT_EXPRESSION - lambdaExpr, -#else - lambdaExpr.Parameters, -#endif - typeof(R), flags) ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); - - /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. - public static Func CompileFast( - this Expression> lambdaExpr, bool ifFastFailedReturnNull = false, CompilerFlags flags = CompilerFlags.Default) => - (Func)TryCompileBoundToFirstClosureParam(typeof(Func), lambdaExpr.Body, -#if LIGHT_EXPRESSION - lambdaExpr, -#else - lambdaExpr.Parameters, -#endif - typeof(R), flags) - ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); - - /// Compiles lambda expression to TDelegate type. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. - public static Func CompileFast( - this Expression> lambdaExpr, bool ifFastFailedReturnNull = false, CompilerFlags flags = CompilerFlags.Default) => - (Func)TryCompileBoundToFirstClosureParam(typeof(Func), lambdaExpr.Body, -#if LIGHT_EXPRESSION - lambdaExpr, -#else - lambdaExpr.Parameters, -#endif - typeof(R), flags) - ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); - - /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. - public static Func CompileFast( - this Expression> lambdaExpr, bool ifFastFailedReturnNull = false, CompilerFlags flags = CompilerFlags.Default) => - (Func)TryCompileBoundToFirstClosureParam(typeof(Func), lambdaExpr.Body, -#if LIGHT_EXPRESSION - lambdaExpr, -#else - lambdaExpr.Parameters, -#endif - typeof(R), flags) - ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); - - /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. - public static Func CompileFast( - this Expression> lambdaExpr, bool ifFastFailedReturnNull = false, CompilerFlags flags = CompilerFlags.Default) => - (Func)TryCompileBoundToFirstClosureParam(typeof(Func), lambdaExpr.Body, -#if LIGHT_EXPRESSION - lambdaExpr, -#else - lambdaExpr.Parameters, -#endif - typeof(R), flags) - ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); - - /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. - public static Action CompileFast(this Expression lambdaExpr, bool ifFastFailedReturnNull = false, CompilerFlags flags = CompilerFlags.Default) => - (Action)TryCompileBoundToFirstClosureParam(typeof(Action), lambdaExpr.Body, -#if LIGHT_EXPRESSION - lambdaExpr, -#else - lambdaExpr.Parameters, -#endif - typeof(void), flags) ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); - - /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. - public static Action CompileFast(this Expression> lambdaExpr, - bool ifFastFailedReturnNull = false, CompilerFlags flags = CompilerFlags.Default) => - (Action)TryCompileBoundToFirstClosureParam(typeof(Action), lambdaExpr.Body, -#if LIGHT_EXPRESSION - lambdaExpr, -#else - lambdaExpr.Parameters, -#endif - typeof(void), flags) ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); - - /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. - public static Action CompileFast(this Expression> lambdaExpr, - bool ifFastFailedReturnNull = false, CompilerFlags flags = CompilerFlags.Default) => - (Action)TryCompileBoundToFirstClosureParam(typeof(Action), lambdaExpr.Body, -#if LIGHT_EXPRESSION - lambdaExpr, -#else - lambdaExpr.Parameters, -#endif - typeof(void), flags) ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); - - /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. - public static Action CompileFast(this Expression> lambdaExpr, - bool ifFastFailedReturnNull = false, CompilerFlags flags = CompilerFlags.Default) => - (Action)TryCompileBoundToFirstClosureParam(typeof(Action), lambdaExpr.Body, -#if LIGHT_EXPRESSION - lambdaExpr, -#else - lambdaExpr.Parameters, -#endif - typeof(void), flags) - ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); - - /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. - public static Action CompileFast( - this Expression> lambdaExpr, bool ifFastFailedReturnNull = false, CompilerFlags flags = CompilerFlags.Default) => - (Action)TryCompileBoundToFirstClosureParam(typeof(Action), lambdaExpr.Body, -#if LIGHT_EXPRESSION - lambdaExpr, -#else - lambdaExpr.Parameters, -#endif - typeof(void), flags) - ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); - - /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. - public static Action CompileFast( - this Expression> lambdaExpr, bool ifFastFailedReturnNull = false, CompilerFlags flags = CompilerFlags.Default) => - (Action)TryCompileBoundToFirstClosureParam(typeof(Action), lambdaExpr.Body, -#if LIGHT_EXPRESSION - lambdaExpr, -#else - lambdaExpr.Parameters, -#endif - typeof(void), flags) - ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); - - /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. - public static Action CompileFast( - this Expression> lambdaExpr, bool ifFastFailedReturnNull = false, CompilerFlags flags = CompilerFlags.Default) => - (Action)TryCompileBoundToFirstClosureParam(typeof(Action), lambdaExpr.Body, -#if LIGHT_EXPRESSION - lambdaExpr, -#else - lambdaExpr.Parameters, -#endif - typeof(void), flags) - ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); - - #endregion - - /// Tries to compile lambda expression to - public static TDelegate TryCompile(this LambdaExpression lambdaExpr, CompilerFlags flags = CompilerFlags.Default) - where TDelegate : class => - (TDelegate)TryCompileBoundToFirstClosureParam(typeof(TDelegate) == typeof(Delegate) ? lambdaExpr.Type : typeof(TDelegate), lambdaExpr.Body, -#if LIGHT_EXPRESSION - lambdaExpr, -#else - lambdaExpr.Parameters, -#endif - lambdaExpr.ReturnType, flags); - - /// Tries to compile lambda expression to - /// with the provided closure object and constant expressions (or lack there of) - - /// Constant expression should be the in order of Fields in closure object! - /// Note 1: Use it on your own risk - FEC won't verify the expression is compile-able with passed closure, it is up to you! - /// Note 2: The expression with NESTED LAMBDA IS NOT SUPPORTED! - /// Note 3: `Label` and `GoTo` are not supported in this case, because they need first round to collect out-of-order labels - public static TDelegate TryCompileWithPreCreatedClosure(this LambdaExpression lambdaExpr, - params ConstantExpression[] closureConstantsExprs) where TDelegate : class => - lambdaExpr.TryCompileWithPreCreatedClosure(closureConstantsExprs, CompilerFlags.Default); - - /// Tries to compile lambda expression to - /// with the provided closure object and constant expressions (or lack there of) - public static TDelegate TryCompileWithPreCreatedClosure(this LambdaExpression lambdaExpr, - ConstantExpression[] closureConstantsExprs, CompilerFlags flags) - where TDelegate : class - { - var closureConstants = new object[closureConstantsExprs.Length]; - for (var i = 0; i < closureConstants.Length; i++) - closureConstants[i] = closureConstantsExprs[i].Value; - - var closureInfo = new ClosureInfo(ClosureStatus.UserProvided | ClosureStatus.HasClosure, closureConstants); - return TryCompileWithPreCreatedClosure(lambdaExpr, ref closureInfo, flags); - } - - internal static TDelegate TryCompileWithPreCreatedClosure( - this LambdaExpression lambdaExpr, ref ClosureInfo closureInfo, CompilerFlags flags) where TDelegate : class - { -#if LIGHT_EXPRESSION - var closurePlusParamTypes = RentPooledOrNewClosureTypeToParamTypes(lambdaExpr); -#else - var closurePlusParamTypes = RentPooledOrNewClosureTypeToParamTypes(lambdaExpr.Parameters); -#endif - var method = new DynamicMethod(string.Empty, lambdaExpr.ReturnType, closurePlusParamTypes, - typeof(ExpressionCompiler), skipVisibility: true); - - var il = method.GetILGenerator(); - EmittingVisitor.EmitLoadConstantsAndNestedLambdasIntoVars(il, ref closureInfo); - - var parent = lambdaExpr.ReturnType == typeof(void) ? ParentFlags.IgnoreResult : ParentFlags.LambdaCall; - if (!EmittingVisitor.TryEmit(lambdaExpr.Body, -#if LIGHT_EXPRESSION - lambdaExpr, -#else - lambdaExpr.Parameters, -#endif - il, ref closureInfo, flags, parent)) - return null; - - il.Demit(OpCodes.Ret); - - var delegateType = typeof(TDelegate) != typeof(Delegate) ? typeof(TDelegate) : lambdaExpr.Type; - var dlg = (TDelegate)(object)method.CreateDelegate(delegateType, new ArrayClosure(closureInfo.Constants.Items)); - FreePooledClosureTypeAndParamTypes(closurePlusParamTypes); - return dlg; - } - - /// Tries to compile expression to "static" delegate, skipping the step of collecting the closure object. - public static TDelegate TryCompileWithoutClosure(this LambdaExpression lambdaExpr, - CompilerFlags flags = CompilerFlags.Default) where TDelegate : class - { - var closureInfo = new ClosureInfo(ClosureStatus.UserProvided); -#if LIGHT_EXPRESSION - var closurePlusParamTypes = RentPooledOrNewClosureTypeToParamTypes(lambdaExpr); -#else - var closurePlusParamTypes = RentPooledOrNewClosureTypeToParamTypes(lambdaExpr.Parameters); -#endif - var method = new DynamicMethod(string.Empty, lambdaExpr.ReturnType, closurePlusParamTypes, typeof(ArrayClosure), - skipVisibility: true); - - var il = method.GetILGenerator(); - if (!EmittingVisitor.TryEmit(lambdaExpr.Body, -#if LIGHT_EXPRESSION - lambdaExpr, -#else - lambdaExpr.Parameters, -#endif - il, ref closureInfo, flags, lambdaExpr.ReturnType == typeof(void) ? ParentFlags.IgnoreResult : ParentFlags.Empty)) - return null; - - il.Demit(OpCodes.Ret); - - var delegateType = typeof(TDelegate) != typeof(Delegate) ? typeof(TDelegate) : lambdaExpr.Type; - var dlg = (TDelegate)(object)method.CreateDelegate(delegateType, EmptyArrayClosure); - FreePooledClosureTypeAndParamTypes(closurePlusParamTypes); - return dlg; - } - - private static Delegate CompileNoArgsNew(NewExpression newExpr, Type delegateType, Type[] closurePlusParamTypes, Type returnType, CompilerFlags flags) - { - var method = new DynamicMethod(string.Empty, returnType, closurePlusParamTypes, typeof(ArrayClosure), true); - var il = DynamicMethodHacks.RentPooledOrNewILGenerator(method, returnType, closurePlusParamTypes, newStreamSize: 16); - il.Demit(OpCodes.Newobj, newExpr.Constructor); - if (returnType == typeof(void)) - il.Demit(OpCodes.Pop); - il.Demit(OpCodes.Ret); - - var closure = (flags & CompilerFlags.EnableDelegateDebugInfo) == 0 - ? EmptyArrayClosure - : new DebugArrayClosure(null, null, Lambda(newExpr, Tools.Empty())); - - var dlg = method.CreateDelegate(delegateType, closure); - DynamicMethodHacks.FreePooledILGenerator(method, il); - - if (closure is DebugArrayClosure diagClosure) - diagClosure.ILInstructions = dlg.Method.ReadAllInstructions(); - - return dlg; - } - -#if LIGHT_EXPRESSION - internal static object TryCompileBoundToFirstClosureParam(Type delegateType, Expression bodyExpr, IParameterProvider paramExprs, - Type returnType, CompilerFlags flags) - { - // There is no Return of the pooled parameter types here, - // because in the rarest case with the unused lambda arguments we may just exhaust the pooled instance - var closureAndParamTypes = RentPooledOrNewClosureTypeToParamTypes(paramExprs); - if (bodyExpr is NoArgsNewClassIntrinsicExpression newExpr) - return CompileNoArgsNew(newExpr, delegateType, closureAndParamTypes, returnType, flags); -#else - internal static object TryCompileBoundToFirstClosureParam(Type delegateType, Expression bodyExpr, IReadOnlyList paramExprs, - Type returnType, CompilerFlags flags) - { - var closureAndParamTypes = RentPooledOrNewClosureTypeToParamTypes(paramExprs); -#endif - // Try to avoid compilation altogether for Func delegates via Interpreter, see #468 - if (closureAndParamTypes.Length == 1 & returnType == typeof(bool) - && bodyExpr.Type.IsPrimitive // todo: @feat #496 nullable is not supported yet - && Interpreter.IsCandidateForInterpretation(bodyExpr) - && Interpreter.TryInterpretBool(out var result, bodyExpr, flags)) - return result ? Interpreter.TrueFunc : Interpreter.FalseFunc; - - Delegate compiledDelegate = null; - - // The method collects the info from the all nested lambdas deep down up-front and de-duplicates the lambdas as well. - var closureInfo = new ClosureInfo(ClosureStatus.ToBeCollected); - var collectResult = TryCollectInfo(ref closureInfo, bodyExpr, paramExprs, null, ref closureInfo.NestedLambdas, flags); - if (collectResult == Result.OK) - { - var constantsAndNestedLambdas = (closureInfo.Status & ClosureStatus.HasClosure) != 0 - ? closureInfo.GetArrayOfConstantsAndNestedLambdas() - : null; - - ArrayClosure closure; - if ((flags & CompilerFlags.EnableDelegateDebugInfo) == 0) - closure = constantsAndNestedLambdas == null ? EmptyArrayClosure : new ArrayClosure(constantsAndNestedLambdas); - else - { - var debugLambdaExpr = Lambda(delegateType, bodyExpr, paramExprs?.ToReadOnlyList() ?? Tools.Empty()); - closure = new DebugArrayClosure(null, constantsAndNestedLambdas, debugLambdaExpr); - } - - // note: @slow this is what System.Compiles does and which makes the compilation 10x slower, but the invocation become faster by a single branch instruction - // var method = new DynamicMethod(string.Empty, returnType, closurePlusParamTypes, true); - // this is FEC way, significantly faster compilation, but +1 branch instruction in the invocation - var dynMethod = new DynamicMethod(string.Empty, returnType, closureAndParamTypes, typeof(ArrayClosure), true); - - var il = DynamicMethodHacks.RentPooledOrNewILGenerator(dynMethod, returnType, closureAndParamTypes); - - if (closure.ConstantsAndNestedLambdas != null) - EmittingVisitor.EmitLoadConstantsAndNestedLambdasIntoVars(il, ref closureInfo); - - var parent = returnType == typeof(void) ? ParentFlags.IgnoreResult : ParentFlags.LambdaCall; - if (returnType.IsByRef) - parent |= ParentFlags.ReturnByRef; - - if (EmittingVisitor.TryEmit(bodyExpr, paramExprs, il, ref closureInfo, flags, parent)) - { - il.Demit(OpCodes.Ret); - compiledDelegate = dynMethod.CreateDelegate(delegateType, closure); - if (closure is DebugArrayClosure diagClosure) - diagClosure.ILInstructions = compiledDelegate.Method.ReadAllInstructions(); - } - - DynamicMethodHacks.FreePooledILGenerator(dynMethod, il); - } - FreePooledClosureTypeAndParamTypes(closureAndParamTypes); - - return compiledDelegate - ?? ((flags & CompilerFlags.ThrowOnNotSupportedExpression) == 0 ? null : NotSupportedCase(collectResult)); - } - - private static readonly Type[] _closureAsASingleParamType = { typeof(ArrayClosure) }; - private static readonly Type[][] _paramTypesPoolWithElem0OfLength1 = new Type[8][]; // todo: @perf @mem could we use this for other Type arrays? - -#if LIGHT_EXPRESSION - internal static Type[] RentPooledOrNewClosureTypeToParamTypes(IParameterProvider paramExprs) - { - var count = paramExprs.ParameterCount; -#else - internal static Type[] RentPooledOrNewClosureTypeToParamTypes(IReadOnlyList paramExprs) - { - var count = paramExprs.Count; -#endif - if (count == 0) - return _closureAsASingleParamType; - - var pooledOrNew = count < 8 ? Interlocked.Exchange(ref _paramTypesPoolWithElem0OfLength1[count], null) ?? new Type[count + 1] : new Type[count + 1]; - pooledOrNew[0] = typeof(ArrayClosure); - for (var i = 0; i < count; i++) - { - var paramExpr = paramExprs.GetParameter(i); // todo: @perf can we avoid calling virtual GetParameter() and maybe use intrinsic with NoByRef? - pooledOrNew[i + 1] = !paramExpr.IsByRef ? paramExpr.Type : paramExpr.Type.MakeByRefType(); - } - - return pooledOrNew; - } - - /// Renting the array of the passed parameter types - [MethodImpl((MethodImplOptions)256)] - public static Type[] RentPooledOrNewParamTypes(Type p0) - { - var pooledOrNew = Interlocked.Exchange(ref _paramTypesPoolWithElem0OfLength1[0], null) ?? new Type[1]; - pooledOrNew[0] = p0; - return pooledOrNew; - } - - /// Renting the array of the passed parameter types - [MethodImpl((MethodImplOptions)256)] - public static Type[] RentPooledOrNewParamTypes(Type p0, Type p1) - { - var pooledOrNew = Interlocked.Exchange(ref _paramTypesPoolWithElem0OfLength1[1], null) ?? new Type[2]; - pooledOrNew[0] = p0; - pooledOrNew[1] = p1; - return pooledOrNew; - } - - /// Renting the array of the passed parameter types - [MethodImpl((MethodImplOptions)256)] - public static Type[] RentPooledOrNewParamTypes(Type p0, Type p1, Type p2) - { - var pooledOrNew = Interlocked.Exchange(ref _paramTypesPoolWithElem0OfLength1[2], null) ?? new Type[3]; - pooledOrNew[0] = p0; - pooledOrNew[1] = p1; - pooledOrNew[2] = p2; - return pooledOrNew; - } - - /// Renting the array of the passed parameter types - [MethodImpl((MethodImplOptions)256)] - public static Type[] RentPooledOrNewParamTypes(Type p0, Type p1, Type p2, Type p3) - { - var pooledOrNew = Interlocked.Exchange(ref _paramTypesPoolWithElem0OfLength1[3], null) ?? new Type[4]; - pooledOrNew[0] = p0; - pooledOrNew[1] = p1; - pooledOrNew[2] = p2; - pooledOrNew[3] = p3; - return pooledOrNew; - } - - /// Renting the array of the passed parameter types - [MethodImpl((MethodImplOptions)256)] - public static Type[] RentPooledOrNewParamTypes(Type p0, Type p1, Type p2, Type p3, Type p4) - { - var pooledOrNew = Interlocked.Exchange(ref _paramTypesPoolWithElem0OfLength1[4], null) ?? new Type[5]; - pooledOrNew[0] = p0; - pooledOrNew[1] = p1; - pooledOrNew[2] = p2; - pooledOrNew[3] = p3; - pooledOrNew[4] = p4; - return pooledOrNew; - } - - /// Freeing to the pool the array of types of closure + plus the passed parameter types - [MethodImpl((MethodImplOptions)256)] - public static void FreePooledClosureTypeAndParamTypes(Type[] closurePlusParamTypes) - { - var paramCountOnly = closurePlusParamTypes.Length - 1; - if (paramCountOnly != 0 & paramCountOnly < 8) - Interlocked.Exchange(ref _paramTypesPoolWithElem0OfLength1[paramCountOnly], closurePlusParamTypes); - } - - /// Freeing to the pool the array of the passed parameter types - [MethodImpl((MethodImplOptions)256)] - public static void FreePooledParamTypes(Type[] paramTypes) - { - var paramCount = paramTypes.Length; - if (paramCount != 0 & paramCount < 8) - Interlocked.Exchange(ref _paramTypesPoolWithElem0OfLength1[paramCount - 1], paramTypes); - } - -#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member - - /// Collects the lambda info for the compilation - public sealed class NestedLambdaInfo - { - /// Compiled lambda - public object Lambda; // todo: @perf can we use the NestedLambdaInfo itself instead of NestedLambdaWithConstantsAndNestedLambdas and avoid duplication in memory? - - /// The nested lambdas and their info - public SmallList NestedLambdas; - - /// The lambda expression - public LambdaExpression LambdaExpression; - - /// Parameters not passed through lambda parameter list But used inside lambda body. - /// The top expression should Not contain not passed parameters. - public SmallList NonPassedParameters; - - /// If the N's bit is set, it means the parameter is mutated (assigned or passed by-ref), - /// where N is the index of the param in `NonPassedParameters` - public ulong NonPassedParamMutatedIndexBits; - - /// Index of the compiled lambda in the parent lambda closure array - public short LambdaVarIndex; - - /// Index of the variable which store the non-passed variables array before passing it to the closure constructor. - /// It used to assign the closed variables from the outside of the nested lambda - public short NonPassedParamsVarIndex; - - public NestedLambdaInfo(LambdaExpression lambdaExpression) => LambdaExpression = lambdaExpression; - - /// Compares 2 lambda expressions for equality - public bool HasTheSameLambdaExpression(LambdaExpression lambda) => // todo: @unclear parameters or is comparing the body is enough? - ReferenceEquals(LambdaExpression, lambda) || - ReferenceEquals(LambdaExpression.Body, lambda.Body) -#if LIGHT_EXPRESSION - && LambdaExpression.ParameterCount == lambda.ParameterCount -#endif - ; - - public override string ToString() => - $"Lambda: {(Lambda is NestedLambdaForNonPassedParams n ? "compiled+closure" : Lambda != null ? "compiled" : "null")}, Expr: {LambdaExpression.ToString()}"; - } - - [Flags] - public enum ClosureStatus : byte - { - ToBeCollected = 1, - UserProvided = 1 << 1, - HasClosure = 1 << 2, - ShouldBeStaticMethod = 1 << 3 - } - - [DebuggerDisplay("Target:{Target}, InlinedLambdaInvokeIndex:{InlinedLambdaInvokeIndex}, ReturnVariableIndexPlusOneAndIsDefined:{ReturnVariableIndexPlusOneAndIsDefined}")] - public struct LabelInfo - { - public object Target; - public short InlinedLambdaInvokeIndex; - public short ReturnVariableIndexPlusOneAndIsDefined; - public Label Label; - public Label ReturnLabel; - } - - /// Track the info required to build a closure object + some context information not directly related to closure. - public struct ClosureInfo - { - /// Tracks that the last emit was an address - public bool LastEmitIsAddress; - - // Tracks the current block nesting count in the stack of blocks in Collect and Emit phase - private ushort _blockCount; - - /// Tracks the use of the variables in the blocks stack per variable, - /// (uint) contains (ushort) BlockIndex in the upper bits and (ushort) VarIndex in the lower bits. - /// to determine if variable is the local variable and in what block it's defined - private SmallMap4, NoArrayPool>, RefEq> _varInBlock; - - /// The map of inlined invocations collected in TryCollect and then used in TryEmit - internal SmallMap4> InlinedLambdaInvocation; - - /// New or Call expressions containing the complex expression, e.g. inlined Lambda Invoke or Try with Finally - internal SmallMap4> ArgsContainingComplexExpression; - - internal bool HasComplexExpression; - - /// The stack for the lambda invocation and the labels bound to them - internal SmallList, NoArrayPool> LambdaInvokeStackLabels; - - /// Tracks of how many gotos, labels referencing the specific target, they may be the same gotos expression, - /// because the gotos may be reused multiple times in the big expression - internal SmallMap4> TargetToGotosAndLabels; - - /// This is required because we have the return from the nested lambda expression, - /// and when inlined in the parent lambda it is no longer the return but just a jump to the label. - internal short CurrentInlinedLambdaInvokeIndex; - - public ClosureStatus Status; - - /// Constant expressions to find an index (by reference) of constant expression from compiled expression. - public SmallList Constants; - - /// Constant usage count and variable index. - /// It is a separate collection from the Constants because we directly convert later into the closure array - public SmallList, NoArrayPool> ConstantUsageThenVarIndex; - - /// Parameters not passed through lambda parameter list But used inside lambda body. - /// The top expression should Not contain not passed parameters. - public SmallList NonPassedParameters; - - /// The nested lambdas and their info - public SmallList NestedLambdas; - - /// Populates the info - public ClosureInfo(ClosureStatus status) - { - Status = status; - Constants = new SmallList(); - LastEmitIsAddress = false; - CurrentInlinedLambdaInvokeIndex = -1; - } - - /// Populates info directly with provided closure object and constants. - public ClosureInfo(ClosureStatus status, object[] constValues) - { - Status = status; - - Constants = new SmallList(constValues ?? Tools.Empty()); - if (constValues != null) - ConstantUsageThenVarIndex.InitCount(constValues.Length); - - LastEmitIsAddress = false; - CurrentInlinedLambdaInvokeIndex = -1; - } - - [MethodImpl((MethodImplOptions)256)] - public bool ContainsConstantsOrNestedLambdas() => Constants.Count != 0 | NestedLambdas.Count != 0; - - public void AddConstantOrIncrementUsageCount(object value) - { - Status |= ClosureStatus.HasClosure; - var constItems = Constants.Items; - var constIndex = Constants.Count - 1; - while (constIndex != -1 && !ReferenceEquals(constItems[constIndex], value)) - --constIndex; - if (constIndex == -1) - { - Constants.Add(value); - ConstantUsageThenVarIndex.Add(1); - } - else - { - ++ConstantUsageThenVarIndex.GetSurePresentRef(constIndex); - } - } - - [RequiresUnreferencedCode(Trimming.Message)] - public void AddLabel(LabelTarget labelTarget, short inlinedLambdaInvokeIndex = -1) - { - // skip null labelTargets, e.g. it may be the case for LoopExpression.Continue - if (labelTarget == null) - return; - GetLabelOrInvokeIndexByTarget(ref LambdaInvokeStackLabels, labelTarget, out var found); - if (!found) - { - ref var label = ref LambdaInvokeStackLabels.AddDefaultAndGetRef(); - label.Target = labelTarget; - label.InlinedLambdaInvokeIndex = inlinedLambdaInvokeIndex; - } - } - - public short AddInlinedLambdaInvoke(InvocationExpression e) - { - var count = LambdaInvokeStackLabels.Count; - for (var i = 0; i < count; ++i) - if (LambdaInvokeStackLabels.GetSurePresentRef(i).Target == e) - return (short)i; - - ref var label = ref LambdaInvokeStackLabels.AddDefaultAndGetRef(); - label.Target = e; - return (short)count; - } - - public object[] GetArrayOfConstantsAndNestedLambdas() - { - var constCount = Constants.Count; - var nestedLambdasCount = NestedLambdas.Count; - if (constCount == 0) - { - if (nestedLambdasCount == 0) - return null; - - var lambdaObjects = new object[nestedLambdasCount]; - for (var i = 0; i < lambdaObjects.Length; i++) - lambdaObjects[i] = NestedLambdas.Items[i].Lambda; - return lambdaObjects; - } - - // if constants `count != 0` - var constItems = Constants.Items; - if (nestedLambdasCount == 0) - return constItems; - - var constPlusLambdaCount = constCount + nestedLambdasCount; - - if (constItems.Length < constPlusLambdaCount) - Array.Resize(ref constItems, constPlusLambdaCount); - - for (var i = 0; i < nestedLambdasCount; ++i) - constItems[constCount + i] = NestedLambdas.Items[i].Lambda; - - return constItems; - } - - /// Local variable index is not known in the collecting phase when we only need to decide if ParameterExpression is an actual parameter or variable - [RequiresUnreferencedCode(Trimming.Message)] - public void PushBlockWithVars(ParameterExpression blockVarExpr) => - PushVarInBlockMap(blockVarExpr, _blockCount++, 0); - - [RequiresUnreferencedCode(Trimming.Message)] - public void PushBlockWithVars(ParameterExpression blockVarExpr, int varIndex) => - PushVarInBlockMap(blockVarExpr, _blockCount++, (ushort)varIndex); - - [RequiresUnreferencedCode(Trimming.Message)] - public void PushBlockWithVars(IReadOnlyList blockVarExprs) - { - for (var i = 0; i < blockVarExprs.Count; i++) - PushVarInBlockMap(blockVarExprs[i], _blockCount, 0); - ++_blockCount; - } - - [RequiresUnreferencedCode(Trimming.Message)] - public void PushBlockAndConstructLocalVars(IReadOnlyList blockVarExprs, ILGenerator il) - { - for (var i = 0; i < blockVarExprs.Count; i++) - { - var varExpr = blockVarExprs[i]; - var varType = varExpr.Type; - if (varExpr.IsByRef && !varType.IsByRef) - varType = varType.MakeByRefType(); - PushVarInBlockMap(varExpr, _blockCount, (ushort)il.GetNextLocalVarIndex(varType)); - } - ++_blockCount; - } - - [MethodImpl((MethodImplOptions)256)] - private void PushVarInBlockMap(ParameterExpression pe, ushort blockIndex, ushort varIndex) - { - ref var blocks = ref _varInBlock.Map.AddOrGetValueRef(pe, out _); - if (blocks.Count == 0 || (blocks.GetLastSurePresentItem() >>> 16) != blockIndex) - blocks.Add((uint)(blockIndex << 16) | varIndex); - } - - public void PopBlock() - { - Debug.Assert(_blockCount > 0); - var varCount = _varInBlock.Map.Count; - for (var i = 0; i < varCount; ++i) - { - ref var varBlocks = ref _varInBlock.Map.GetSurePresentEntryRef(i); - if (varBlocks.Value.Count == _blockCount) - varBlocks.Value.RemoveLastSurePresentItem(); - } - --_blockCount; - } - - [MethodImpl((MethodImplOptions)256)] - public bool IsLocalVar(ParameterExpression varParamExpr) - { - ref var blocks = ref _varInBlock.Map.TryGetValueRef(varParamExpr, out var found); - return found && blocks.Count != 0; - } - - [MethodImpl((MethodImplOptions)256)] - public int GetDefinedLocalVarOrDefault(ParameterExpression varParamExpr) - { - ref var blocks = ref _varInBlock.Map.TryGetValueRef(varParamExpr, out var found); - return found && blocks.Count != 0 // rare case with the block count 0 may occur when we collected the block and vars, but not yet defined the variable for it - ? (int)(blocks.GetLastSurePresentItem() & ushort.MaxValue) - : -1; - } - } - - internal static ref LabelInfo GetLabelOrInvokeIndexByTarget(ref this TLabels labels, object labelTarget, out bool found) - where TLabels : struct, IIndexed - { - var count = labels.Count; - for (var i = 0; i < count; ++i) - { - ref var label = ref labels.GetSurePresentRef(i); - if (label.Target == labelTarget) - { - found = true; - return ref label; - } - } - found = false; - return ref RefTools.GetNullRef(); - } - - [MethodImpl((MethodImplOptions)256)] - private static Label GetOrDefineLabel(ref this LabelInfo label, ILGenerator il) - { - if ((label.ReturnVariableIndexPlusOneAndIsDefined & 1) == 0) - { - label.Label = il.DefineLabel(); - label.ReturnVariableIndexPlusOneAndIsDefined |= 1; - } - return label.Label; - } - - public static readonly ArrayClosure EmptyArrayClosure = new ArrayClosure(null); - - public static FieldInfo ArrayClosureArrayField = - typeof(ArrayClosure).GetField(nameof(ArrayClosure.ConstantsAndNestedLambdas)); - - public static FieldInfo ArrayClosureWithNonPassedParamsField = - typeof(ArrayClosureWithNonPassedParams).GetField(nameof(ArrayClosureWithNonPassedParams.NonPassedParams)); - - private static ConstructorInfo[] _nonPassedParamsArrayClosureCtors = typeof(ArrayClosureWithNonPassedParams).GetConstructors(); - - public static ConstructorInfo ArrayClosureWithNonPassedParamsAndConstantsCtor = _nonPassedParamsArrayClosureCtors[0]; - - public static ConstructorInfo ArrayClosureWithNonPassedParamsCtor = _nonPassedParamsArrayClosureCtors[1]; - - private static ConstructorInfo DebugArrayClosureCtor = typeof(DebugArrayClosure).GetConstructors()[0]; - - public static Result NotSupported_RuntimeVariables { get; private set; } - - public class ArrayClosure - { - // todo: @feature split into two to reduce copying - public readonly object[] ConstantsAndNestedLambdas; - public ArrayClosure(object[] constantsAndNestedLambdas) => ConstantsAndNestedLambdas = constantsAndNestedLambdas; - } - - internal static IEnumerable EnumerateNestedLambdas(object[] constantsAndNestedLambdas) - { - if (constantsAndNestedLambdas != null && constantsAndNestedLambdas.Length != 0) - // todo: @perf how to skip until the nested lambdas fast - foreach (var item in constantsAndNestedLambdas) - { - if (item is IDelegateDebugInfo diagInfo) - yield return diagInfo; - - var dlg = (item is NestedLambdaForNonPassedParams nestedLambda ? nestedLambda.NestedLambda : item) as Delegate; - if (dlg != null && dlg.Target is IDelegateDebugInfo dlgDebugInfo) - yield return dlgDebugInfo; - } - } - - /// Enumerate any nested lambdas in the delegate compiled with CompilerFlags.EnableDelegateDebugInfo flag - public static IEnumerable EnumerateNestedLambdas( - this Delegate fastCompiledDelegateWithDebugInfoFlag) => - fastCompiledDelegateWithDebugInfoFlag.Target is ArrayClosure closure ? EnumerateNestedLambdas(closure.ConstantsAndNestedLambdas) : []; - - [RequiresUnreferencedCode(Trimming.Message)] - public sealed class DebugArrayClosure : ArrayClosureWithNonPassedParams, IDelegateDebugInfo - { - public LambdaExpression Expression { get; internal set; } - - public ILInstruction[] ILInstructions { get; internal set; } - - public DebugArrayClosure(object[] nonPassedParams, object[] constantsAndNestedLambdas, - LambdaExpression expr, ILInstruction[] il = null) - : base(nonPassedParams, constantsAndNestedLambdas) - { - Expression = expr; - ILInstructions = il; - } - - [RequiresUnreferencedCode(Trimming.Message)] - public IEnumerable EnumerateNestedLambdas() => - ExpressionCompiler.EnumerateNestedLambdas(ConstantsAndNestedLambdas); - } - - // todo: @perf better to move the case with no constants to another class OR we can reuse ArrayClosure but now ConstantsAndNestedLambdas will hold NonPassedParams - public class ArrayClosureWithNonPassedParams : ArrayClosure - { - public readonly object[] NonPassedParams; - public ArrayClosureWithNonPassedParams(object[] nonPassedParams, object[] constantsAndNestedLambdas) : base(constantsAndNestedLambdas) => - NonPassedParams = nonPassedParams; - public ArrayClosureWithNonPassedParams(object[] nonPassedParams) : base(null) => - NonPassedParams = nonPassedParams; - } - - // todo: @perf this class is required until we (maybe) move to single constants list per lambda hierarchy - // Those two classes are required only if there are non-passed parameters, - // this class stores the context for creating the ArrayClosureWithNonPassedParams at the point of emitting the nested lambda. - // See the #437 and #353 for the context - public class NestedLambdaForNonPassedParams - { - public static FieldInfo NestedLambdaField = - typeof(NestedLambdaForNonPassedParams).GetField(nameof(NestedLambda)); - public static FieldInfo NonPassedParamsField = - typeof(NestedLambdaForNonPassedParams).GetField(nameof(NonPassedParams)); - -#pragma warning disable CS0649 - public readonly object NestedLambda; - public object[] NonPassedParams; -#pragma warning restore CS0649 - public NestedLambdaForNonPassedParams(object nestedLambda) => NestedLambda = nestedLambda; - } - - public class NestedLambdaForNonPassedParamsWithConstants : NestedLambdaForNonPassedParams - { - public static FieldInfo ConstantsAndNestedLambdasField = - typeof(NestedLambdaForNonPassedParamsWithConstants).GetField(nameof(ConstantsAndNestedLambdas)); - - public readonly object[] ConstantsAndNestedLambdas; - public NestedLambdaForNonPassedParamsWithConstants(object nestedLambda, object[] constantsAndNestedLambdas) - : base(nestedLambda) => ConstantsAndNestedLambdas = constantsAndNestedLambdas; - } - - public sealed class NestedLambdaForNonPassedParamsWithConstantsWithDebugInfo : NestedLambdaForNonPassedParamsWithConstants, IDelegateDebugInfo - { - public LambdaExpression Expression { get; } - public ILInstruction[] ILInstructions { get; internal set; } - public NestedLambdaForNonPassedParamsWithConstantsWithDebugInfo(object nestedLambda, object[] constantsAndNestedLambdas, LambdaExpression expr) - : base(nestedLambda, constantsAndNestedLambdas) => Expression = expr; - - [RequiresUnreferencedCode(Trimming.Message)] - public IEnumerable EnumerateNestedLambdas() => - ExpressionCompiler.EnumerateNestedLambdas(ConstantsAndNestedLambdas); - } - - internal static class CurryClosureFuncs - { - public static readonly MethodInfo[] Methods = typeof(CurryClosureFuncs).GetMethods(); - - // todo: @mem @perf can we avoid closure creation over `f` and `c`? - public static Func Curry(Func f, C c) => - () => f(c); - - public static Func Curry(Func f, C c) => - t1 => f(c, t1); - - public static Func Curry(Func f, C c) => - (t1, t2) => f(c, t1, t2); - - public static Func Curry(Func f, C c) => - (t1, t2, t3) => f(c, t1, t2, t3); - - public static Func Curry(Func f, C c) => - (t1, t2, t3, t4) => f(c, t1, t2, t3, t4); - - public static Func Curry(Func f, - C c) => (t1, t2, t3, t4, t5) => f(c, t1, t2, t3, t4, t5); - - public static Func - Curry(Func f, C c) => - (t1, t2, t3, t4, t5, t6) => f(c, t1, t2, t3, t4, t5, t6); - - public static Func - Curry(Func f, C c) => - (t1, t2, t3, t4, t5, t6, t7) => f(c, t1, t2, t3, t4, t5, t6, t7); - - public static Func - Curry(Func f, C c) => - (t1, t2, t3, t4, t5, t6, t7, t8) => f(c, t1, t2, t3, t4, t5, t6, t7, t8); - - public static Func - Curry(Func f, C c) => - (t1, t2, t3, t4, t5, t6, t7, t8, t9) => f(c, t1, t2, t3, t4, t5, t6, t7, t8, t9); - - public static Func - Curry(Func f, C c) => - (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10) => f(c, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10); - } - - internal static class CurryClosureActions - { - public static readonly MethodInfo[] Methods = typeof(CurryClosureActions).GetMethods(); - - public static Action Curry(Action a, C c) => - () => a(c); - - public static Action Curry(Action f, C c) => - t1 => f(c, t1); - - public static Action Curry(Action f, C c) => - (t1, t2) => f(c, t1, t2); - - public static Action Curry(Action f, C c) => - (t1, t2, t3) => f(c, t1, t2, t3); - - public static Action Curry(Action f, C c) => - (t1, t2, t3, t4) => f(c, t1, t2, t3, t4); - - public static Action Curry(Action f, - C c) => (t1, t2, t3, t4, t5) => f(c, t1, t2, t3, t4, t5); - - public static Action - Curry(Action f, C c) => - (t1, t2, t3, t4, t5, t6) => f(c, t1, t2, t3, t4, t5, t6); - - public static Action - Curry(Action f, C c) => - (t1, t2, t3, t4, t5, t6, t7) => f(c, t1, t2, t3, t4, t5, t6, t7); - - public static Action - Curry(Action f, C c) => - (t1, t2, t3, t4, t5, t6, t7, t8) => f(c, t1, t2, t3, t4, t5, t6, t7, t8); - - public static Action - Curry(Action f, C c) => - (t1, t2, t3, t4, t5, t6, t7, t8, t9) => f(c, t1, t2, t3, t4, t5, t6, t7, t8, t9); - - public static Action - Curry(Action f, C c) => - (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10) => f(c, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10); - } - - #region Collect Bound Constants - - /// Helps to identify constants as the one to be put into the Closure - public static bool IsClosureBoundConstant(object value, Type type) => - value is Delegate || type.IsArray || - !type.IsPrimitive && !type.IsEnum && value is string == false && value is Type == false && value is decimal == false; - - public enum Result - { - OK = 0, - ExpressionIsNull = 1, - ParameterIsNotVariableNorInPassedParameters = 2, - NestedLambdaCompileError = 102, - NotSupported_UnknownExpression = 1000, - /// Multi-dimensional array initializer is not supported - NotSupported_NewArrayInit_MultidimensionalArray = 1001, - /// Quote is not supported - NotSupported_Quote = 1002, - /// Dynamic is not supported - NotSupported_Dynamic = 1003, - /// RuntimeVariables is not supported - NotSupported_RuntimeVariables = 1004, - /// MemberInit MemberBinding is not supported - NotSupported_MemberInit_MemberBinding = 1005, - /// MemberInit ListBinding is not supported - NotSupported_MemberInit_ListBinding = 1006, - /// Goto of the Return kind from the TryCatch is not supported - NotSupported_Try_GotoReturnToTheFollowupLabel = 1007, - /// Not supported assignment target - NotSupported_Assign_Target = 1008, - /// TypeEqual is not supported - NotSupported_TypeEqual = 1009, - /// `when` in catch is not supported yet - NotSupported_ExceptionCatchFilter = 1010 - } - - /// Return value is ignored - [MethodImpl(MethodImplOptions.NoInlining)] - internal static T NotSupportedCase(Result reason) - { - if (reason == Result.OK) - { - Debug.WriteLine($"Not support case found in TryEmit phase because the TryCollect phase is {reason}"); - Debugger.Break(); - } - Debug.WriteLine($"Not supported case is found with the reason: {reason}"); - throw new NotSupportedExpressionException(reason); - } - - /// Wraps the call to `TryCollectInfo` for the compatibility and provide the root place to check the returned error code. - /// Important: The method collects the info from the nested lambdas up-front and de-duplicates the lambdas as well. - [MethodImpl((MethodImplOptions)256)] - public static bool TryCollectBoundConstants(ref ClosureInfo closure, Expression expr, -#if LIGHT_EXPRESSION - IParameterProvider paramExprs, // `paramExprs` are required for nested lambda compilation -#else - IReadOnlyList paramExprs, -#endif - NestedLambdaInfo nestedLambda, ref SmallList rootNestedLambdas, CompilerFlags flags) - { - var r = TryCollectInfo(ref closure, expr, paramExprs, nestedLambda, ref rootNestedLambdas, flags); - return r == Result.OK || (flags & CompilerFlags.ThrowOnNotSupportedExpression) != 0 && NotSupportedCase(r); - } - - /// Collects the information about closure constants, nested lambdas, non-passed parameters, goto labels and variables in blocks. - /// Returns `OK` result if everything is fine and other result for error. - public static Result TryCollectInfo(ref ClosureInfo closure, Expression expr, -#if LIGHT_EXPRESSION - IParameterProvider paramExprs, -#else - IReadOnlyList paramExprs, -#endif - NestedLambdaInfo nestedLambda, ref SmallList rootNestedLambdas, CompilerFlags flags) - { - var r = Result.OK; - while (true) - { - if (expr == null) - return Result.ExpressionIsNull; -#if LIGHT_EXPRESSION - if (expr.IsIntrinsic) - return expr.TryCollectInfo(flags, ref closure, paramExprs, nestedLambda, ref rootNestedLambdas); -#endif - switch (expr.NodeType) - { - case ExpressionType.Constant: -#if LIGHT_EXPRESSION - if (expr == NullConstant | expr == FalseConstant | expr == TrueConstant || expr is IntConstantExpression) - return r; -#endif - var constantExpr = (ConstantExpression)expr; - var value = constantExpr.Value; - if (value != null && IsClosureBoundConstant(value, value.GetType())) - closure.AddConstantOrIncrementUsageCount(value); - return Result.OK; - - case ExpressionType.Parameter: - { -#if LIGHT_EXPRESSION - var paramCount = paramExprs.ParameterCount; -#else - var paramCount = paramExprs.Count; -#endif - // if parameter is used BUT is not in passed parameters and not in local variables, - // it means parameter is provided by outer lambda and should be put in closure for current lambda - var p = paramCount - 1; - var parExpr = (PE)expr; - while (p != -1 && !ReferenceEquals(paramExprs.GetParameter(p), parExpr)) --p; - if (p == -1 && !closure.IsLocalVar(parExpr)) - { - if (nestedLambda == null) // means that we are in the root lambda - return Result.ParameterIsNotVariableNorInPassedParameters; - closure.Status |= ClosureStatus.HasClosure; - _ = nestedLambda.NonPassedParameters.GetIndexOrAdd(parExpr, default(RefEq)); - } - return Result.OK; - } - case ExpressionType.Call: - { - var callExpr = (MethodCallExpression)expr; - var callObjectExpr = callExpr.Object; - -#if SUPPORTS_ARGUMENT_PROVIDER - var callArgs = (IArgumentProvider)callExpr; -#else - var callArgs = callExpr.Arguments; -#endif - var argCount = callArgs.GetCount(); - if (argCount == 0) - { - if (callObjectExpr != null) - { - expr = callObjectExpr; - continue; - } - return Result.OK; - } - - if (callObjectExpr != null && - (r = TryCollectInfo(ref closure, callObjectExpr, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) - return r; - - var hasComplexExpression = false; - for (var i = 0; i < argCount; i++) - { - closure.HasComplexExpression = false; // reset the flag because we want to know the real result after the arg collection - if ((r = TryCollectInfo(ref closure, callArgs.GetArgument(i), paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) - return r; - // if any argument is complex, then thw whole call should be complex, - // because we cannot just store and restore a single argument, it should be done for all arguments - hasComplexExpression |= closure.HasComplexExpression; - } - - // propagate the value up the stack - if (hasComplexExpression) - { - closure.HasComplexExpression = true; - closure.ArgsContainingComplexExpression.Map.AddOrGetValueRef(callExpr, out _); - } - return r; - } - - case ExpressionType.MemberAccess: - var memberExpr = ((MemberExpression)expr).Expression; - if (memberExpr == null) - return r; - expr = memberExpr; - continue; - - case ExpressionType.New: - { - var newExpr = (NewExpression)expr; -#if SUPPORTS_ARGUMENT_PROVIDER - var ctorArgs = (IArgumentProvider)newExpr; -#else - var ctorArgs = newExpr.Arguments; -#endif - var argCount = ctorArgs.GetCount(); - if (argCount == 0) - return r; - - var hasComplexExpression = false; - for (var i = 0; i < argCount; i++) - { - closure.HasComplexExpression = false; - if ((r = TryCollectInfo(ref closure, ctorArgs.GetArgument(i), paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) - return r; - hasComplexExpression |= closure.HasComplexExpression; - } - - // pop the value up the stack - if (hasComplexExpression) - { - closure.HasComplexExpression = true; - closure.ArgsContainingComplexExpression.Map.AddOrGetEntryRef(newExpr, out _); - } - - return r; - } - case ExpressionType.NewArrayBounds: - case ExpressionType.NewArrayInit: - // todo: @feature multi-dimensional array initializers are not supported yet, they also are not supported by the hoisted expression - if (expr.NodeType == ExpressionType.NewArrayInit && expr.Type.GetArrayRank() > 1) - return Result.NotSupported_NewArrayInit_MultidimensionalArray; -#if LIGHT_EXPRESSION - var arrElems = (IArgumentProvider)expr; - var elemCount = arrElems.ArgumentCount; -#else - var arrElems = ((NewArrayExpression)expr).Expressions; - var elemCount = arrElems.Count; -#endif - if (elemCount == 0) - return r; - for (var i = 0; i < elemCount - 1; i++) - if ((r = TryCollectInfo(ref closure, arrElems.GetArgument(i), paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) - return r; - expr = arrElems.GetArgument(elemCount - 1); - continue; - - case ExpressionType.MemberInit: - return TryCollectMemberInitExprConstants( - ref closure, (MemberInitExpression)expr, paramExprs, nestedLambda, ref rootNestedLambdas, flags); - - case ExpressionType.ListInit: - return TryCollectListInitExprConstants( - ref closure, (ListInitExpression)expr, paramExprs, nestedLambda, ref rootNestedLambdas, flags); - - case ExpressionType.Lambda: - // Here we look if the lambda is already stored in the nested lambdas tree (Collected+Compiled), - // or if not found it Collects+Compiles the nested lambda here and adds to the nested lambda tree. - var nestedLambdaExpr = (LambdaExpression)expr; - -#if LIGHT_EXPRESSION // todo: @simplify can we do better? - var nestedParamExprs = (IParameterProvider)nestedLambdaExpr; -#else - var nestedParamExprs = nestedLambdaExpr.Parameters; -#endif - closure.Status |= ClosureStatus.HasClosure; - - // Look for the already collected lambdas starting from the root - if (rootNestedLambdas.Count != 0 && - FindAlreadyCompiledNestedLambdaInfoInLambdas(ref rootNestedLambdas, nestedLambdaExpr, out var compiledNestedLambda)) - { - if (nestedLambda != null) - nestedLambda.NestedLambdas.Add(compiledNestedLambda); - else - rootNestedLambdas.Add(compiledNestedLambda); - - if (compiledNestedLambda.NonPassedParameters.Count != 0 && - !PropagateNonPassedParamsToOuterLambda(ref closure, - nestedLambda, paramExprs, nestedParamExprs, ref compiledNestedLambda.NonPassedParameters)) - return Result.ParameterIsNotVariableNorInPassedParameters; - - return r; - } - - var nestedClosure = new ClosureInfo(ClosureStatus.ToBeCollected); - var newNestedLambda = new NestedLambdaInfo(nestedLambdaExpr); - - if (nestedLambda != null) - nestedLambda.NestedLambdas.Add(newNestedLambda); - else - rootNestedLambdas.Add(newNestedLambda); - - if ((r = TryCollectInfo(ref nestedClosure, nestedLambdaExpr.Body, nestedParamExprs, newNestedLambda, ref rootNestedLambdas, flags)) != Result.OK) - return r; - - if (newNestedLambda.NonPassedParameters.Count != 0 && - !PropagateNonPassedParamsToOuterLambda(ref closure, - nestedLambda, paramExprs, nestedParamExprs, ref newNestedLambda.NonPassedParameters)) - return Result.ParameterIsNotVariableNorInPassedParameters; - - if (!TryCompileNestedLambda(ref nestedClosure, newNestedLambda, flags)) - return Result.NestedLambdaCompileError; - - return r; - - case ExpressionType.Invoke: - { - var invokeExpr = (InvocationExpression)expr; -#if SUPPORTS_ARGUMENT_PROVIDER - var invokeArgs = (IArgumentProvider)invokeExpr; -#else - var invokeArgs = invokeExpr.Arguments; -#endif - var invokeArgCount = invokeArgs.GetCount(); - var invokedExpr = invokeExpr.Expression; - if ((flags & CompilerFlags.NoInvocationLambdaInlining) == 0 && invokedExpr is LambdaExpression lambdaExpr) - { - var oldIndex = closure.CurrentInlinedLambdaInvokeIndex; - closure.CurrentInlinedLambdaInvokeIndex = closure.AddInlinedLambdaInvoke(invokeExpr); - closure.HasComplexExpression = false; // switch off because we have entered the inlined lambda - - ref var inlinedExpr = ref closure.InlinedLambdaInvocation.Map.AddOrGetValueRef(invokeExpr, out var found); - if (!found) - inlinedExpr = CreateInlinedLambdaInvocationExpression(invokeArgs, invokeArgCount, lambdaExpr); - - if ((r = TryCollectInfo(ref closure, inlinedExpr, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) - return r; - - closure.HasComplexExpression = true; - closure.CurrentInlinedLambdaInvokeIndex = oldIndex; - return r; - } - - // No inlining, collect the normal way - if (invokeArgCount == 0) - { - expr = invokedExpr; - continue; - } - - if ((r = TryCollectInfo(ref closure, invokedExpr, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) - return r; - - var lastArgIndex = invokeArgCount - 1; - for (var i = 0; i < lastArgIndex; i++) - if ((r = TryCollectInfo(ref closure, invokeArgs.GetArgument(i), paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) - return r; - expr = invokeArgs.GetArgument(lastArgIndex); - continue; - } - case ExpressionType.Conditional: - var condExpr = (ConditionalExpression)expr; - if ((r = TryCollectInfo(ref closure, condExpr.Test, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK || - (r = TryCollectInfo(ref closure, condExpr.IfFalse, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) - return r; - expr = condExpr.IfTrue; - continue; - - case ExpressionType.Block: - var blockExpr = (BlockExpression)expr; - var blockExprs = blockExpr.Expressions; - var blockExprCount = blockExprs.Count; - if (blockExprCount == 0) - return r; // yeah, this is the real case - the block may not contain any expressions - - var varExprs = blockExpr.Variables; - var varExprCount = varExprs?.Count ?? 0; // todo: @perf optimize for an empty and a single variable - - if (varExprCount == 1 & blockExprCount == 2 && - blockExprs[0] is BinaryExpression st0 && st0.NodeType == ExpressionType.Assign && - blockExprs[1] is BinaryExpression st1 && st1.NodeType == ExpressionType.Assign && - st0.Left == blockExprs[0] && st1.Right == blockExprs[0]) - { - if ((r = TryCollectInfo(ref closure, st0.Right, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) - return r; - expr = st1.Left; - continue; - } - - if (varExprCount == 1) - closure.PushBlockWithVars(varExprs[0]); - else if (varExprCount != 0) - closure.PushBlockWithVars(varExprs); - - for (var i = 0; i < blockExprCount - 1; i++) - if ((r = TryCollectInfo(ref closure, blockExprs[i], paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) - return r; - - expr = blockExprs[blockExprCount - 1]; - if (varExprCount == 0) - continue; // in case of no variables we can collect the last expr without recursion - - if ((r = TryCollectInfo(ref closure, expr, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) - return r; - closure.PopBlock(); - return r; - - case ExpressionType.Loop: - var loopExpr = (LoopExpression)expr; - closure.AddLabel(loopExpr.BreakLabel); - closure.AddLabel(loopExpr.ContinueLabel); - expr = loopExpr.Body; - continue; - - case ExpressionType.Index: - var indexExpr = (IndexExpression)expr; -#if SUPPORTS_ARGUMENT_PROVIDER - var indexArgs = (IArgumentProvider)indexExpr; -#else - var indexArgs = indexExpr.Arguments; -#endif - var indexArgCount = indexArgs.GetCount(); - for (var i = 0; i < indexArgCount; i++) - if ((r = TryCollectInfo(ref closure, indexArgs.GetArgument(i), paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) - return r; - if (indexExpr.Object == null) - return r; - expr = indexExpr.Object; - continue; - - case ExpressionType.Try: - { - closure.HasComplexExpression = false; - r = TryCollectTryExprInfo(ref closure, (TryExpression)expr, paramExprs, nestedLambda, ref rootNestedLambdas, flags); - closure.HasComplexExpression = true; - return r; - } - case ExpressionType.Label: - var labelExpr = (LabelExpression)expr; - closure.AddLabel(labelExpr.Target, closure.CurrentInlinedLambdaInvokeIndex); - if (labelExpr.Target != null) - closure.TargetToGotosAndLabels.Map.AddOrGetValueRef(labelExpr.Target, out _).Item2++; - if (labelExpr.DefaultValue == null) - return r; - expr = labelExpr.DefaultValue; - continue; - - case ExpressionType.Goto: - var gotoExpr = (GotoExpression)expr; - if (gotoExpr.Target != null) - closure.TargetToGotosAndLabels.Map.AddOrGetValueRef(gotoExpr.Target, out _).Item1++; - if (gotoExpr.Value == null) - return r; - expr = gotoExpr.Value; - continue; - - case ExpressionType.Switch: - var switchExpr = ((SwitchExpression)expr); - if ((r = TryCollectInfo(ref closure, switchExpr.SwitchValue, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK || - switchExpr.DefaultBody != null && // todo: @check is the order of collection affects the result? - (r = TryCollectInfo(ref closure, switchExpr.DefaultBody, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) - return r; - - var switchCases = switchExpr.Cases; - if (switchCases.Count != 0) - { - for (var i = 0; i < switchCases.Count - 1; i++) - if ((r = TryCollectInfo(ref closure, switchCases[i].Body, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) - return r; - expr = switchCases[switchCases.Count - 1].Body; - continue; - } - return r; - - case ExpressionType.Extension: - expr = expr.Reduce(); - continue; - - case ExpressionType.Default: - return r; - - case ExpressionType.TypeIs: - case ExpressionType.TypeEqual: - expr = ((TypeBinaryExpression)expr).Expression; - continue; - - case ExpressionType.Quote: // todo: @feature - is not supported yet - return Result.NotSupported_Quote; - case ExpressionType.Dynamic: // todo: @feature - is not supported yet - return Result.NotSupported_Dynamic; - case ExpressionType.RuntimeVariables: // todo: @feature - is not supported yet - return NotSupported_RuntimeVariables; - - case ExpressionType.DebugInfo: // todo: @feature - is not supported yet - return r; // todo: @unclear - just ignoring the info for now - - default: - if (expr is UnaryExpression unaryExpr) - { - if (unaryExpr.Operand is null) - return r; - expr = unaryExpr.Operand; - continue; - } - - if (expr is BinaryExpression binaryExpr) - { - if ((r = TryCollectInfo(ref closure, binaryExpr.Left, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) - return r; - expr = binaryExpr.Right; - continue; - } - - return Result.NotSupported_UnknownExpression; - } - } - } - - private static Expression CreateInlinedLambdaInvocationExpression( -#if SUPPORTS_ARGUMENT_PROVIDER - IArgumentProvider invokeArgs, -#else - IReadOnlyList invokeArgs, -#endif - int invokeArgCount, LambdaExpression lambdaExpr) - { - // Check the actual lambda return type in case it differs from the Body type, - // e.g. often case for the Action lambdas where the Body type is ignored in favor of `void`. - var lambdaReturnType = lambdaExpr.ReturnType; - var lambdaBodyExpr = lambdaExpr.Body; - if (invokeArgCount == 0) - return lambdaReturnType == lambdaBodyExpr.Type ? lambdaBodyExpr : - lambdaReturnType == typeof(void) ? Block(typeof(void), lambdaBodyExpr) : - Convert(lambdaBodyExpr, lambdaReturnType); - - // To inline the lambda we will wrap its body into a block, parameters into the block variables, - // and the invocation arguments into the variable assignments, see #278. -#if LIGHT_EXPRESSION - var lambdaPars = (IParameterProvider)lambdaExpr; -#else - var lambdaPars = lambdaExpr.Parameters; -#endif - SmallList, NoArrayPool> inlinedBlockExprs = default; - SmallList, NoArrayPool> savedVars = default; - SmallList, NoArrayPool> savedVarsBlockExprs = default; - - for (var i = 0; i < invokeArgCount; i++) - { - var lambdaPar = lambdaPars.GetParameter(i); - var invokeArg = invokeArgs.GetArgument(i); - - // The case of reusing the parameters or variables in the different lambdas, - // see the test `NestedLambdaTests.Hmm_I_can_use_the_same_parameter_for_outer_and_nested_lambda` - // and the `Issue401_What_happens_if_inlined_invocation_of_lambda_overrides_the_same_parameter`. - if (lambdaPar == invokeArg) - { - var savedPar = Parameter(lambdaPar.Type, lambdaPar.Name + "_" + lambdaPar.GetHashCode().ToString()); - savedVars.Add(savedPar); - savedVarsBlockExprs.Add(Assign(savedPar, invokeArg)); - inlinedBlockExprs.Add(Assign(lambdaPar, savedPar)); - continue; - } - - inlinedBlockExprs.Add(Assign(lambdaPar, invokeArg)); - } - - inlinedBlockExprs.Add(lambdaBodyExpr); - -#if LIGHT_EXPRESSION - var inlinedBlock = lambdaReturnType == lambdaBodyExpr.Type - ? Block(lambdaPars.ToReadOnlyList(), in inlinedBlockExprs) - : Block(lambdaReturnType, lambdaPars.ToReadOnlyList(), in inlinedBlockExprs); - if (savedVars.Count != 0) - { - savedVarsBlockExprs.Add(inlinedBlock); - inlinedBlock = Block(savedVars.ToArray(), in savedVarsBlockExprs); - } -#else - var inlinedBlock = lambdaReturnType == lambdaBodyExpr.Type - ? Block(lambdaPars, inlinedBlockExprs.ToArray()) - : Block(lambdaReturnType, lambdaPars, inlinedBlockExprs.ToArray()); - if (savedVars.Count != 0) - { - savedVarsBlockExprs.Add(inlinedBlock); - inlinedBlock = Block(savedVars.ToArray(), savedVarsBlockExprs.ToArray()); - } -#endif - return inlinedBlock; - } - -#if LIGHT_EXPRESSION - private static bool PropagateNonPassedParamsToOuterLambda(ref ClosureInfo closure, NestedLambdaInfo lambda, - IParameterProvider paramExprs, IParameterProvider nestedLambdaParamExprs, ref SmallList nestedNonPassedParams) - { - var paramExprCount = paramExprs.ParameterCount; - var nestedLambdaParamExprCount = nestedLambdaParamExprs.ParameterCount; -#else - private static bool PropagateNonPassedParamsToOuterLambda(ref ClosureInfo closure, NestedLambdaInfo lambda, - IReadOnlyList paramExprs, IReadOnlyList nestedLambdaParamExprs, ref SmallList nestedNonPassedParams) - { - var paramExprCount = paramExprs.Count; - var nestedLambdaParamExprCount = nestedLambdaParamExprs.Count; -#endif - // If nested non passed parameter is not matched with any outer passed parameter, - // then we ensure it goes to the outer non passed parameter. - // But having the non-passed parameter in the root expression (nestedLambda == null) is invalid, and results in false. - for (var i = 0; i < nestedNonPassedParams.Count; i++) - { - var nestedNonPassedParam = nestedNonPassedParams.GetSurePresentRef(i); - - var isInNestedLambda = false; - if (nestedLambdaParamExprCount != 0) - for (var p = 0; !isInNestedLambda && p < nestedLambdaParamExprCount; ++p) - isInNestedLambda = ReferenceEquals(nestedLambdaParamExprs.GetParameter(p), nestedNonPassedParam); - - var isInLambda = false; - if (paramExprCount != 0) - for (var p = 0; !isInLambda && p < paramExprCount; ++p) - isInLambda = ReferenceEquals(paramExprs.GetParameter(p), nestedNonPassedParam); - - if (!isInNestedLambda & !isInLambda) - { - if (closure.IsLocalVar(nestedNonPassedParam)) - continue; - if (lambda == null) // means that we at the root level lambda, and non-passed parameter cannot be provided - return false; - _ = lambda.NonPassedParameters.GetIndexOrAdd(nestedNonPassedParam, default(RefEq)); - } - } - - return true; - } - - private static bool FindAlreadyCompiledNestedLambdaInfoInLambdas( - ref SmallList nestedLambdas, LambdaExpression lambdaExpr, out NestedLambdaInfo found) - { - var nestedLambdasCount = nestedLambdas.Count; - for (var i = 0; i < nestedLambdasCount; ++i) - { - var nestedLambda = nestedLambdas.Items[i]; - if (nestedLambda.HasTheSameLambdaExpression(lambdaExpr)) - { - found = nestedLambda; - return true; - } - - if (nestedLambda.NestedLambdas.Count != 0) - return FindAlreadyCompiledNestedLambdaInfoInLambdas(ref nestedLambda.NestedLambdas, lambdaExpr, out found); - } - - found = null; - return false; - } - - private static bool TryCompileNestedLambda(ref ClosureInfo nestedClosureInfo, NestedLambdaInfo nestedLambdaInfo, CompilerFlags flags) - { - // 1. Try to compile nested lambda in place - // 2. Check that parameters used in compiled lambda are passed or closed by outer lambda - // 3. Add the compiled lambda to closure of outer lambda for later invocation - var nestedLambdaExpr = nestedLambdaInfo.LambdaExpression; - var nestedReturnType = nestedLambdaExpr.ReturnType; - var nestedLambdaBody = nestedLambdaExpr.Body; -#if LIGHT_EXPRESSION - var nestedLambdaParamExprs = (IParameterProvider)nestedLambdaExpr; - - if (nestedLambdaBody is NoArgsNewClassIntrinsicExpression newExpr) - { - var paramTypes = RentPooledOrNewClosureTypeToParamTypes(nestedLambdaParamExprs); - nestedLambdaInfo.Lambda = CompileNoArgsNew(newExpr, nestedLambdaExpr.Type, paramTypes, nestedReturnType, flags); - FreePooledClosureTypeAndParamTypes(paramTypes); - return true; - } -#else - var nestedLambdaParamExprs = nestedLambdaExpr.Parameters; -#endif - // copy the nested lambdas and non-passed parameters to closure info to read them in TryEmit - nestedClosureInfo.NestedLambdas = nestedLambdaInfo.NestedLambdas; - nestedClosureInfo.NonPassedParameters = nestedLambdaInfo.NonPassedParameters; - - var constantsAndNestedLambdas = (nestedClosureInfo.Status & ClosureStatus.HasClosure) != 0 - ? nestedClosureInfo.GetArrayOfConstantsAndNestedLambdas() - : null; - - ArrayClosure nestedLambdaClosure = null; - var hasDebugInfo = (flags & CompilerFlags.EnableDelegateDebugInfo) != 0; - var hasNonPassedParameters = nestedLambdaInfo.NonPassedParameters.Count != 0; - if (!hasNonPassedParameters) - nestedLambdaClosure = !hasDebugInfo - ? (constantsAndNestedLambdas == null ? EmptyArrayClosure : new ArrayClosure(constantsAndNestedLambdas)) - : new DebugArrayClosure(null, constantsAndNestedLambdas, nestedLambdaExpr); - - var closurePlusParamTypes = RentPooledOrNewClosureTypeToParamTypes(nestedLambdaParamExprs); - - var method = new DynamicMethod(string.Empty, nestedReturnType, closurePlusParamTypes, typeof(ArrayClosure), true); - var il = DynamicMethodHacks.RentPooledOrNewILGenerator(method, nestedReturnType, closurePlusParamTypes); - - if (constantsAndNestedLambdas != null) - EmittingVisitor.EmitLoadConstantsAndNestedLambdasIntoVars(il, ref nestedClosureInfo); - - var parent = nestedReturnType == typeof(void) ? ParentFlags.IgnoreResult : ParentFlags.LambdaCall; - if (nestedReturnType.IsByRef) - parent |= ParentFlags.ReturnByRef; - - var emitOk = EmittingVisitor.TryEmit(nestedLambdaBody, nestedLambdaParamExprs, il, ref nestedClosureInfo, flags, parent); - if (emitOk) - { - il.Demit(OpCodes.Ret); - - // If we don't have closure then create a static or an open delegate to pass closure later in `TryEmitNestedLambda`, - // constructing the new closure with NonPassedParams and the rest of items stored in NestedLambdaWithConstantsAndNestedLambdas - var nestedLambda = nestedLambdaClosure != null - ? method.CreateDelegate(nestedLambdaExpr.Type, nestedLambdaClosure) - : method.CreateDelegate(Tools.GetFuncOrActionType(closurePlusParamTypes, nestedReturnType), null); - - nestedLambdaInfo.Lambda = !hasNonPassedParameters - ? nestedLambda - : !hasDebugInfo - ? constantsAndNestedLambdas == null - ? new NestedLambdaForNonPassedParams(nestedLambda) - : new NestedLambdaForNonPassedParamsWithConstants(nestedLambda, constantsAndNestedLambdas) - : new NestedLambdaForNonPassedParamsWithConstantsWithDebugInfo(nestedLambda, constantsAndNestedLambdas, nestedLambdaExpr); - - if (hasDebugInfo) - { - var ilInstructions = nestedLambda.Method.ReadAllInstructions(); - if (nestedLambdaClosure is DebugArrayClosure debugInfoClosure) - debugInfoClosure.ILInstructions = ilInstructions; - else - ((NestedLambdaForNonPassedParamsWithConstantsWithDebugInfo)nestedLambdaInfo.Lambda).ILInstructions = ilInstructions; - } - } - DynamicMethodHacks.FreePooledILGenerator(method, il); - FreePooledClosureTypeAndParamTypes(closurePlusParamTypes); - return emitOk; - } - - /// Return IDelegateDebugInfo if the delegate is fast compiled with `CompilerFlags.EnableDelegateDebugInfo` flag - public static IDelegateDebugInfo TryGetDebugInfo(this TDelegate d) - where TDelegate : Delegate => d?.Target as IDelegateDebugInfo; - -#if LIGHT_EXPRESSION - private static Result TryCollectMemberInitExprConstants(ref ClosureInfo closure, MemberInitExpression expr, - IParameterProvider paramExprs, NestedLambdaInfo nestedLambda, ref SmallList rootNestedLambdas, CompilerFlags flags) - { - var newExpr = expr.Expression; - var binds = (IArgumentProvider)expr; - var count = binds.ArgumentCount; -#else - private static Result TryCollectMemberInitExprConstants(ref ClosureInfo closure, MemberInitExpression expr, - IReadOnlyList paramExprs, NestedLambdaInfo nestedLambda, ref SmallList rootNestedLambdas, CompilerFlags flags) - { - var newExpr = expr.NewExpression; - var binds = expr.Bindings; - var count = binds.Count; -#endif - var r = Result.OK; - if ((r = TryCollectInfo(ref closure, newExpr, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) - return r; - - for (var i = 0; i < count; ++i) - { - var b = binds.GetArgument(i); - if (b.BindingType != MemberBindingType.Assignment) - return b.BindingType == MemberBindingType.MemberBinding ? Result.NotSupported_MemberInit_MemberBinding : Result.NotSupported_MemberInit_ListBinding; // todo: @feature MemberMemberBinding and the MemberListBinding is not supported yet. - - if ((r = TryCollectInfo(ref closure, ((MemberAssignment)b).Expression, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) - return r; - } - return r; - } - - private static Result TryCollectListInitExprConstants(ref ClosureInfo closure, ListInitExpression expr, -#if LIGHT_EXPRESSION - IParameterProvider paramExprs, -#else - IReadOnlyList paramExprs, -#endif - NestedLambdaInfo nestedLambda, ref SmallList rootNestedLambdas, CompilerFlags flags) - { - var newExpr = expr.NewExpression; - var inits = expr.Initializers; - var count = inits.Count; - - var r = Result.OK; - if ((r = TryCollectInfo(ref closure, newExpr, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) - return r; - - for (var i = 0; i < count; ++i) - { - var elemInit = inits.GetArgument(i); - var args = elemInit.Arguments; - var argCount = args.Count; - for (var a = 0; a < argCount; ++a) - if ((r = TryCollectInfo(ref closure, args.GetArgument(a), paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) - return r; - } - return r; - } - - private static Result TryCollectTryExprInfo(ref ClosureInfo closure, TryExpression tryExpr, -#if LIGHT_EXPRESSION - IParameterProvider paramExprs, -#else - IReadOnlyList paramExprs, -#endif - NestedLambdaInfo nestedLambda, ref SmallList rootNestedLambdas, CompilerFlags flags) - { - var r = Result.OK; - if ((r = TryCollectInfo(ref closure, tryExpr.Body, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) - return r; - - var catchBlocks = tryExpr.Handlers; - for (var i = 0; i < catchBlocks.Count; i++) - { - var catchBlock = catchBlocks[i]; - var catchExVar = catchBlock.Variable; - if (catchExVar != null) - { - closure.PushBlockWithVars(catchExVar); - if ((r = TryCollectInfo(ref closure, catchExVar, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) - return r; - } - - if (catchBlock.Filter != null && - (r = TryCollectInfo(ref closure, catchBlock.Filter, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) - return r; - - if ((r = TryCollectInfo(ref closure, catchBlock.Body, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) - return r; - - if (catchExVar != null) - closure.PopBlock(); - } - - var faultOrFinally = tryExpr.Fault ?? tryExpr.Finally; - if (faultOrFinally != null && - (r = TryCollectInfo(ref closure, faultOrFinally, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) - return r; - - return r; - } - - #endregion - - /// The minimal context-aware flags set by parent - [Flags] - public enum ParentFlags - { - /// Default is no flags - Empty = 0, - /// The result of expression is ignored and maybe popped out - IgnoreResult = 1 << 1, - /// Some parent is the call expression - Call = 1 << 2, - /// Any Parent Expression is a MemberExpression - MemberAccess = 1 << 3, - /// Some arithmetic operation - Arithmetic = 1 << 4, - /// Subject - Coalesce = 1 << 5, - /// Expression with instance object (method call or member access or array access) - InstanceAccess = 1 << 6, - /// Subject - DupIt = 1 << 7, - /// Subject - TryCatch = 1 << 8, - /// Combination`of InstanceAccess and Call - InstanceCall = Call | InstanceAccess, - /// Constructor - Ctor = 1 << 9, - /// Constructor call - CtorCall = Call | Ctor, - /// Indexer - IndexAccess = 1 << 10, - /// Invoking the inlined lambda (the default System.Expression behavior) - InlinedLambdaInvoke = 1 << 11, - /// Indicate if the part AT LEAST participates in the assignment on the left side, - /// it may also participate in the right side, e.g. ++x.Bar - AssignmentLeftValue = 1 << 12, - /// Indicates the ONLY right value of assignment, e.g. `p` in `foo.Bar += p` - AssignmentRightValue = 1 << 13, - /// Assigning the ref of the right value to the left, e.g. in `var a = ref b[1]` we are passing this flag for the `ref b[1]` - AssignmentByRef = 1 << 14, - /// Indicates the root lambda call - LambdaCall = 1 << 15, - /// ReturnByRef - ReturnByRef = 1 << 16, - /// The block result - BlockResult = 1 << 17, - } - - [MethodImpl((MethodImplOptions)256)] - public static bool IgnoresResult(this ParentFlags parent) => (parent & ParentFlags.IgnoreResult) != 0; - - [MethodImpl((MethodImplOptions)256)] - internal static bool EmitPopIfIgnoreResult(this ILGenerator il, ParentFlags parent) - { - if ((parent & ParentFlags.IgnoreResult) != 0) - il.Demit(OpCodes.Pop); - return true; - } - - [MethodImpl((MethodImplOptions)256)] - internal static bool TryEmitBoxOf(this ILGenerator il, Type sourceType) - { - if (sourceType.IsValueType) - il.Demit(OpCodes.Box, sourceType); - return true; - } - - [MethodImpl((MethodImplOptions)256)] - internal static bool TryEmitUnboxOf(this ILGenerator il, Type sourceType) - { - if (sourceType.IsValueType) - il.Demit(OpCodes.Unbox_Any, sourceType); - return true; - } - - /// Supports emitting of selected expressions, e.g. lambdaExpr are not supported yet. - /// When emitter find not supported expression it will return false from , so I could fallback - /// to normal and slow Expression.Compile. - [RequiresUnreferencedCode(Trimming.Message)] - public static class EmittingVisitor - { - // todo: @perf use UnsafeAccessAttribute - /// Get a type from handle - public static readonly MethodInfo GetTypeFromHandleMethod = - ((Func)Type.GetTypeFromHandle).Method; - private static readonly MethodInfo _objectEqualsMethod = - ((Func)object.Equals).Method; - - public static bool TryEmit(Expression expr, -#if LIGHT_EXPRESSION - IParameterProvider paramExprs, -#else - IReadOnlyList paramExprs, -#endif - ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent, int byRefIndex = -1) - { - var exprType = expr.Type; - while (true) - { - closure.LastEmitIsAddress = false; -#if LIGHT_EXPRESSION - if (expr.IsIntrinsic) - return expr.TryEmit(setup, ref closure, paramExprs, il, parent, byRefIndex); -#endif - var nodeType = expr.NodeType; - switch (nodeType) - { - case ExpressionType.Parameter: - return (parent & ParentFlags.IgnoreResult) != 0 || - TryEmitParameter((ParameterExpression)expr, paramExprs, il, ref closure, setup, parent, byRefIndex); - - case ExpressionType.TypeAs: - case ExpressionType.IsTrue: - case ExpressionType.IsFalse: - case ExpressionType.Increment: - case ExpressionType.Decrement: - case ExpressionType.Negate: - case ExpressionType.NegateChecked: - case ExpressionType.OnesComplement: - case ExpressionType.UnaryPlus: - case ExpressionType.Unbox: - return TryEmitSimpleUnaryExpression((UnaryExpression)expr, nodeType, paramExprs, il, ref closure, setup, parent); - - case ExpressionType.TypeIs: - case ExpressionType.TypeEqual: - return TryEmitTypeIsOrEqual((TypeBinaryExpression)expr, paramExprs, il, ref closure, setup, parent); - - case ExpressionType.Convert: - case ExpressionType.ConvertChecked: - return TryEmitConvert((UnaryExpression)expr, paramExprs, il, ref closure, setup, parent); - - case ExpressionType.ArrayIndex: - var arrIndexExpr = (BinaryExpression)expr; - return TryEmit(arrIndexExpr.Left, paramExprs, il, ref closure, setup, parent | ParentFlags.IndexAccess) - && TryEmit(arrIndexExpr.Right, paramExprs, il, ref closure, setup, parent | ParentFlags.IndexAccess) // #265 - && TryEmitArrayIndexGet(il, exprType, ref closure, parent); - - case ExpressionType.ArrayLength: - if (!TryEmit(((UnaryExpression)expr).Operand, paramExprs, il, ref closure, setup, parent)) - return false; - if ((parent & ParentFlags.IgnoreResult) == 0) - il.Demit(OpCodes.Ldlen); - return true; - - case ExpressionType.Constant: - return (parent & ParentFlags.IgnoreResult) != 0 || - TryEmitConstant((ConstantExpression)expr, exprType, il, ref closure, byRefIndex); - - case ExpressionType.Call: - return TryEmitMethodCall(expr, paramExprs, il, ref closure, setup, parent, byRefIndex); - - case ExpressionType.MemberAccess: - return TryEmitMemberGet((MemberExpression)expr, paramExprs, il, ref closure, setup, parent, byRefIndex); - - case ExpressionType.New: - return TryEmitNew(expr, paramExprs, il, ref closure, setup, parent); - - case ExpressionType.NewArrayBounds: - return EmitNewArrayBounds((NewArrayExpression)expr, paramExprs, il, ref closure, setup, parent); - - case ExpressionType.NewArrayInit: - return EmitNewArrayInit((NewArrayExpression)expr, paramExprs, il, ref closure, setup, parent); - - case ExpressionType.MemberInit: - return EmitMemberInit((MemberInitExpression)expr, paramExprs, il, ref closure, setup, parent); - - case ExpressionType.ListInit: - return TryEmitListInit((ListInitExpression)expr, paramExprs, il, ref closure, setup, parent); - - case ExpressionType.Lambda: - return TryEmitNestedLambda((LambdaExpression)expr, paramExprs, il, ref closure); - - case ExpressionType.Invoke: - return TryEmitInvoke((InvocationExpression)expr, paramExprs, il, ref closure, setup, parent); - - case ExpressionType.GreaterThan: - case ExpressionType.GreaterThanOrEqual: - case ExpressionType.LessThan: - case ExpressionType.LessThanOrEqual: - case ExpressionType.Equal: - case ExpressionType.NotEqual: - { - if (exprType.IsPrimitive && Interpreter.TryInterpretBool(out var boolResult, expr, setup)) - { - if ((parent & ParentFlags.IgnoreResult) == 0) - il.Demit(boolResult ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); - return true; - } - return TryEmitComparison(((BinaryExpression)expr).Left, ((BinaryExpression)expr).Right, exprType, nodeType, paramExprs, il, - ref closure, setup, parent); - } - case ExpressionType.Add: - case ExpressionType.Subtract: - case ExpressionType.Multiply: - case ExpressionType.Divide: - case ExpressionType.Modulo: - case ExpressionType.And: - case ExpressionType.Or: - case ExpressionType.ExclusiveOr: - case ExpressionType.LeftShift: - case ExpressionType.RightShift: - { - return exprType.IsPrimitive - && TryInterpretAndEmitResult(expr, il, parent, setup) - || TryEmitArithmetic(((BinaryExpression)expr).Left, ((BinaryExpression)expr).Right, nodeType, exprType, paramExprs, il, - ref closure, setup, parent); - } - // todo: @feature #472 add interpretation when those node types are supported - case ExpressionType.AddChecked: - case ExpressionType.SubtractChecked: - case ExpressionType.MultiplyChecked: - case ExpressionType.Power: - return TryEmitArithmetic(((BinaryExpression)expr).Left, ((BinaryExpression)expr).Right, nodeType, exprType, paramExprs, il, - ref closure, setup, parent); - - case ExpressionType.AndAlso: - case ExpressionType.OrElse: - { - if (!exprType.IsPrimitive) // todo: @feat #496 - { - Debug.WriteLine("Unsupported: Nullable in || or && (is invalid C# but valid expression) is not supported yet, see #480: " + expr); - return false; - } - if (Interpreter.TryInterpretBool(out var resultBool, expr, setup)) - { - if ((parent & ParentFlags.IgnoreResult) == 0) - il.Demit(resultBool ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); - return true; - } - return TryEmitLogicalOperator((BinaryExpression)expr, nodeType, paramExprs, il, ref closure, setup, parent); - } - case ExpressionType.Not: - { - if (!exprType.IsPrimitive) // todo: @feat #496 - { - Debug.WriteLine("Unsupported: Nullable in !x (is invalid C# but valid expression) is not supported yet, see #480: " + expr); - return false; - } - - if (Interpreter.TryInterpretBool(out var resultBool, expr, setup)) - { - if ((parent & ParentFlags.IgnoreResult) == 0) - il.Demit(resultBool ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); - return true; - } - return TryEmitNot((UnaryExpression)expr, paramExprs, il, ref closure, setup, parent); - } - case ExpressionType.Coalesce: - return TryEmitCoalesceOperator((BinaryExpression)expr, paramExprs, il, ref closure, setup, parent); - - case ExpressionType.Conditional: - var condExpr = (ConditionalExpression)expr; - var testExpr = condExpr.Test; - if (Interpreter.TryInterpretBool(out var testIsTrue, testExpr, setup)) - { - expr = testIsTrue ? condExpr.IfTrue : condExpr.IfFalse; - continue; // no recursion, just continue with the left or right side of condition - } - return TryEmitConditional(testExpr, condExpr.IfTrue, condExpr.IfFalse, paramExprs, il, ref closure, setup, parent); - - case ExpressionType.PostIncrementAssign: - case ExpressionType.PreIncrementAssign: - return TryEmitArithmeticAndOrAssign(((UnaryExpression)expr).Operand, null, exprType, ExpressionType.Add, - nodeType == ExpressionType.PostIncrementAssign, paramExprs, il, ref closure, setup, parent); - - case ExpressionType.PostDecrementAssign: - case ExpressionType.PreDecrementAssign: - return TryEmitArithmeticAndOrAssign(((UnaryExpression)expr).Operand, null, exprType, ExpressionType.Subtract, - nodeType == ExpressionType.PostDecrementAssign, paramExprs, il, ref closure, setup, parent); - - case ExpressionType.AddAssign: - case ExpressionType.AddAssignChecked: - case ExpressionType.SubtractAssign: - case ExpressionType.SubtractAssignChecked: - case ExpressionType.MultiplyAssign: - case ExpressionType.MultiplyAssignChecked: - case ExpressionType.DivideAssign: - case ExpressionType.ModuloAssign: - case ExpressionType.PowerAssign: - case ExpressionType.AndAssign: - case ExpressionType.OrAssign: - case ExpressionType.ExclusiveOrAssign: - case ExpressionType.LeftShiftAssign: - case ExpressionType.RightShiftAssign: - case ExpressionType.Assign: - var ba = (BinaryExpression)expr; - return TryEmitArithmeticAndOrAssign(ba.Left, ba.Right, exprType, - AssignToArithmeticOrSelf(nodeType), false, paramExprs, il, ref closure, setup, parent); - - case ExpressionType.Block: - { - var blockExpr = (BlockExpression)expr; - var blockVarExprs = blockExpr.Variables; - var blockVarCount = blockVarExprs?.Count ?? 0; - var statementExprs = blockExpr.Expressions; // Trim the expressions after the Throw - #196 - var statementCount = statementExprs.Count; - if (statementCount == 0) - return true; // yeah, it is a valid thing - - if (blockVarCount == 1 & statementCount == 2 && - statementExprs[0] is BinaryExpression st0 && st0.NodeType == ExpressionType.Assign && - statementExprs[1] is BinaryExpression st1 && st1.NodeType == ExpressionType.Assign && - st0.Left == blockVarExprs[0] && st1.Right == blockVarExprs[0]) - return TryEmitArithmeticAndOrAssign(st1.Left, st0.Right, st0.Left.Type, - ExpressionType.Assign, false, paramExprs, il, ref closure, setup, parent); - - if (blockVarCount != 0) - closure.PushBlockAndConstructLocalVars(blockVarExprs, il); - - expr = statementExprs[statementCount - 1]; // The last (result) statement in block will provide the result - - // Try to trim the statements from the end of the Block up to the Throw (if any), - // or to the prior Label as in the #442 case 2 - if (statementCount > 1) - { - var throwIndex = statementCount - 1; - for (; throwIndex != -1; --throwIndex) - { - var se = statementExprs[throwIndex]; - if (se.NodeType == ExpressionType.Label) - { - throwIndex = -1; // stop the search - break; - } - if (se.NodeType == ExpressionType.Throw) - break; - } - - // If we have a Throw and it is not the last one - if (throwIndex != -1 && throwIndex != statementCount - 1) - { - // Change the Throw return type to match the one for the Block, and adjust the statement count - expr = Expression.Throw(((UnaryExpression)statementExprs[throwIndex]).Operand, blockExpr.Type); - statementCount = throwIndex + 1; - } - } - - // handle the all statements in block excluding the last one - if (statementCount > 1) - { - for (var i = 0; i < statementCount - 1; i++) - { - var stExpr = statementExprs[i]; - if (stExpr.NodeType == ExpressionType.Default && stExpr.Type == typeof(void)) - continue; - - // This is basically the return pattern (see #237), so we don't care for the rest of expressions - if (stExpr is GotoExpression gt && gt.Kind == GotoExpressionKind.Return && - statementExprs[i + 1] is LabelExpression label && label.Target == gt.Target) - { - // But we cannot use the return pattern and eliminate the target label if we have more gotos referencing it, see #430 - var (gotos, labels) = closure.TargetToGotosAndLabels.Map.TryGetValueRef(label.Target, out var found); - if (found & gotos <= labels) - { - if ((parent & ParentFlags.TryCatch) != 0) - { - if ((setup & CompilerFlags.ThrowOnNotSupportedExpression) != 0) - throw new NotSupportedExpressionException(Result.NotSupported_Try_GotoReturnToTheFollowupLabel); - return false; // todo: @feature return from the TryCatch with the internal label is not supported, though it is an unlikely case - } - - // we are generating the return value and ensuring here that it is not popped-out - var gtOrLabelValue = gt.Value ?? label.DefaultValue; - if (gtOrLabelValue != null) - { - if (!TryEmit(gtOrLabelValue, paramExprs, il, ref closure, setup, parent & ~ParentFlags.IgnoreResult)) - return false; - - if ((parent & ParentFlags.InlinedLambdaInvoke) != 0) - { - ref var foundLabel = ref closure.LambdaInvokeStackLabels.GetLabelOrInvokeIndexByTarget(gt.Target, out var labelFound); - if (!labelFound || foundLabel.InlinedLambdaInvokeIndex == -1) - return false; - EmitGotoToReturnLabel(ref closure.LambdaInvokeStackLabels.GetSurePresentRef(foundLabel.InlinedLambdaInvokeIndex), il, gtOrLabelValue, OpCodes.Br); - } - else - { - // @hack (related to #237) if `IgnoreResult` set, that means the external/calling code won't planning on returning and - // emitting the double `OpCodes.Ret` (usually for not the last statement in block), so we can safely emit our own `Ret` here. - // And vice-versa, if `IgnoreResult` not set then the external code planning to emit `Ret` (the last block statement), - // so we should avoid it on our side. - if ((parent & ParentFlags.IgnoreResult) != 0) - il.Demit(OpCodes.Ret); - } - } - return true; - } - } - - if (!TryEmit(stExpr, paramExprs, il, ref closure, setup, parent | ParentFlags.IgnoreResult)) - return false; - } - } - - parent |= ParentFlags.BlockResult; - if (blockVarCount == 0) - continue; // OMG! no recursion, continue with the last expression - - if (!TryEmit(expr, paramExprs, il, ref closure, setup, parent)) - return false; - - closure.PopBlock(); - return true; - } - case ExpressionType.Loop: - return TryEmitLoop((LoopExpression)expr, paramExprs, il, ref closure, setup, parent); - - case ExpressionType.Try: - return TryEmitTryCatchFinallyBlock((TryExpression)expr, paramExprs, il, ref closure, setup, parent | ParentFlags.TryCatch); - - case ExpressionType.Throw: - { - var ok = true; - var throwOperand = ((UnaryExpression)expr).Operand; - if (throwOperand != null) - ok = TryEmit(throwOperand, paramExprs, il, ref closure, setup, parent & ~ParentFlags.IgnoreResult); - il.Demit(throwOperand != null ? OpCodes.Throw : OpCodes.Rethrow); - return ok; - } - - case ExpressionType.Default: - if (exprType != typeof(void) && (parent & ParentFlags.IgnoreResult) == 0) - EmitDefault(il, exprType); - return true; - - case ExpressionType.Index: - return TryEmitIndexGet((IndexExpression)expr, paramExprs, il, ref closure, setup, parent); - - case ExpressionType.Goto: - return TryEmitGoto((GotoExpression)expr, paramExprs, il, ref closure, setup, parent); - - case ExpressionType.Label: - return TryEmitLabel((LabelExpression)expr, paramExprs, il, ref closure, setup, parent); - - case ExpressionType.Switch: - return TryEmitSwitch((SwitchExpression)expr, paramExprs, il, ref closure, setup, parent); - - case ExpressionType.Extension: - expr = expr.Reduce(); - continue; - - case ExpressionType.DebugInfo: // todo: @feature - is not supported yet - return true; // todo: @unclear - just ignoring the info for now - - case ExpressionType.Quote: // todo: @feature - is not supported yet - default: - return false; - - } - } - } - -#if LIGHT_EXPRESSION - private static bool TryEmitNew(Expression expr, IParameterProvider paramExprs, ILGenerator il, ref ClosureInfo closure, - CompilerFlags setup, ParentFlags parent) -#else - private static bool TryEmitNew(Expression expr, IReadOnlyList paramExprs, ILGenerator il, ref ClosureInfo closure, - CompilerFlags setup, ParentFlags parent) -#endif - { - var flags = ParentFlags.CtorCall; - var newExpr = (NewExpression)expr; -#if SUPPORTS_ARGUMENT_PROVIDER - var argExprs = (IArgumentProvider)newExpr; -#else - var argExprs = newExpr.Arguments; -#endif - var argCount = argExprs.GetCount(); - var ctor = newExpr.Constructor; - if (argCount != 0) - { - var pars = ctor.GetParameters(); - - // If we have complex arguments then it is better to store them in the variables, then load them before calling ctor. - // Otherwise the stack may be broken by the long emit chain, and the previous argument result on stack - // may be hidden by the next argument interim stack additions, - // see the #488 for the details. - if (argCount == 1) - { - if (!TryEmit(argExprs.GetArgument(0), paramExprs, il, ref closure, setup, flags, pars[0].ParameterType.IsByRef ? 0 : -1)) - return false; - } - else - { - if (!closure.ArgsContainingComplexExpression.Map.ContainsKey(newExpr)) - { - for (var i = 0; i < argCount; ++i) - if (!TryEmit(argExprs.GetArgument(i), paramExprs, il, ref closure, setup, flags, pars[i].ParameterType.IsByRef ? i : -1)) - return false; - } - else - { - SmallList, NoArrayPool> argVars = default; - for (var i = 0; i < argCount; ++i) - { - var argExpr = argExprs.GetArgument(i); - var parType = pars[i].ParameterType; - if (!TryEmit(argExpr, paramExprs, il, ref closure, setup, flags, parType.IsByRef ? i : -1)) - return false; - argVars.Add(EmitStoreLocalVariable(il, parType)); - } - for (var i = 0; i < argCount; ++i) - EmitLoadLocalVariable(il, argVars[i]); - } - } - } - var newType = newExpr.Type; - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - if (ctor != null) - il.Demit(OpCodes.Newobj, ctor); - else if (newType.IsValueType) - { - ctor = newType.GetConstructor( - BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, - default, CallingConventions.Any, Tools.Empty(), default); - if (ctor != null) - il.Demit(OpCodes.Newobj, ctor); - else - EmitLoadLocalVariable(il, InitValueTypeVariable(il, newType)); - } - else - return false; - - closure.LastEmitIsAddress = - (parent & ParentFlags.InstanceAccess) != 0 & (parent & ParentFlags.IgnoreResult) == 0 && newType.IsValueType; - if (closure.LastEmitIsAddress) - EmitStoreAndLoadLocalVariableAddress(il, newType); - - if (parent.IgnoresResult()) - il.Demit(OpCodes.Pop); - - return true; - } - -#if LIGHT_EXPRESSION - private static bool TryEmitLoop(LoopExpression loopExpr, IParameterProvider paramExprs, ILGenerator il, ref ClosureInfo closure, - CompilerFlags setup, ParentFlags parent) -#else - private static bool TryEmitLoop(LoopExpression loopExpr, IReadOnlyList paramExprs, ILGenerator il, ref ClosureInfo closure, - CompilerFlags setup, ParentFlags parent) -#endif - { - // Loop expression itself does not leave a value on stack. - // If its body produces a value (e.g. a nested typed break/goto path), we need to ignore it before branching back to the loop head, see #498. - parent |= ParentFlags.IgnoreResult; - - // Mark the start of the loop body: - var loopBodyLabel = il.DefineLabel(); - il.DmarkLabel(loopBodyLabel); - - if (loopExpr.ContinueLabel != null) - { - ref var continueLabelInfo = ref closure.LambdaInvokeStackLabels.GetLabelOrInvokeIndexByTarget(loopExpr.ContinueLabel, out var foundLabel); - if (!foundLabel) - return false; - var continueLabel = continueLabelInfo.GetOrDefineLabel(il); - il.DmarkLabel(continueLabel); - } - - if (!TryEmit(loopExpr.Body, paramExprs, il, ref closure, setup, parent)) - return false; - - // If loop hasn't exited, jump back to start of its body: - il.Demit(OpCodes.Br, loopBodyLabel); - - if (loopExpr.BreakLabel != null) - { - ref var breakLabelInfo = ref closure.LambdaInvokeStackLabels.GetLabelOrInvokeIndexByTarget(loopExpr.BreakLabel, out var foundLabel); - if (!foundLabel) - return false; - var breakLabel = breakLabelInfo.GetOrDefineLabel(il); - il.DmarkLabel(breakLabel); - } - - return true; - } - - // similar code is used by the TryEmitArithmeticAndOrAssign, so don't forget to modify it as well - private static bool TryEmitIndexGet(IndexExpression indexExpr, -#if LIGHT_EXPRESSION - IParameterProvider paramExprs, -#else - IReadOnlyList paramExprs, -#endif - ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent) - { - var p = parent & ~ParentFlags.IgnoreResult | ParentFlags.IndexAccess; - - var objExpr = indexExpr.Object; - if (objExpr != null && - !TryEmit(objExpr, paramExprs, il, ref closure, setup, p | ParentFlags.InstanceAccess)) - return false; - -#if SUPPORTS_ARGUMENT_PROVIDER - var indexArgs = (IArgumentProvider)indexExpr; -#else - var indexArgs = indexExpr.Arguments; -#endif - var indexArgCount = indexArgs.GetCount(); - // Strip InstanceAccess when emitting arguments: they are parameters to the indexer - // getter method, not instances. Without this, a nested IndexExpression like - // `list[i][j]` leaks the outer InstanceAccess into the inner indexer's argument - // emission, causing value-type args (e.g. int) to be loaded by address (ldloca) - // instead of by value (ldloc), producing invalid IL. See #499. - var argParent = p & ~ParentFlags.InstanceAccess; - for (var i = 0; i < indexArgCount; i++) - if (!TryEmit(indexArgs.GetArgument(i), paramExprs, il, ref closure, setup, argParent, -1)) - return false; - - var indexerProp = indexExpr.Indexer; - return indexerProp != null - ? EmitMethodCallOrVirtualCallCheckForNull(il, indexerProp.GetMethod) - : indexArgCount == 1 - ? TryEmitArrayIndexGet(il, indexExpr.Type, ref closure, parent) // one-dimensional array - : EmitMethodCallOrVirtualCallCheckForNull(il, objExpr?.Type.FindMethod("Get")); // multi-dimensional array - } - -#if LIGHT_EXPRESSION - private static bool TryEmitLabel(LabelExpression expr, IParameterProvider paramExprs, ILGenerator il, ref ClosureInfo closure, - CompilerFlags setup, ParentFlags parent) -#else - private static bool TryEmitLabel(LabelExpression expr, IReadOnlyList paramExprs, ILGenerator il, ref ClosureInfo closure, - CompilerFlags setup, ParentFlags parent) -#endif - { - ref var labelInfo = ref closure.LambdaInvokeStackLabels.GetLabelOrInvokeIndexByTarget(expr.Target, out var foundLabel); - if (!foundLabel) - return false; - - var label = labelInfo.GetOrDefineLabel(il); - il.DmarkLabel(label); - - var defaultValue = expr.DefaultValue; - if (defaultValue != null && !TryEmit(defaultValue, paramExprs, il, ref closure, setup, parent)) - return false; - - var returnVariableIndexPlusOne = labelInfo.ReturnVariableIndexPlusOneAndIsDefined >>> 1; - if (returnVariableIndexPlusOne != 0) - { - // if the result value was ignored then we did not IL generate anything for the default value, - // and that in order means we do not have valid thing on stack to store here - so skip the store - if (defaultValue != null & ((parent & ParentFlags.IgnoreResult) == 0)) - EmitStoreLocalVariable(il, returnVariableIndexPlusOne - 1); - - il.DmarkLabel(labelInfo.ReturnLabel); - if (!parent.IgnoresResult()) - EmitLoadLocalVariable(il, returnVariableIndexPlusOne - 1); - } - return foundLabel; - } - - // For TryCatch get the variable for saving the result from the LabelInfo store the return expression result into the that variable. - // Emit OpCodes.Leave or OpCodes.Br to the special label with the result which should be marked after the label to jump over its default value - private static void EmitGotoToReturnLabel(ref LabelInfo labelInfo, ILGenerator il, Expression gotoValue, OpCode returnOpCode) - { - var returnVariableIndexPlusOne = labelInfo.ReturnVariableIndexPlusOneAndIsDefined >>> 1; - if (returnVariableIndexPlusOne == 0) - { - returnVariableIndexPlusOne = il.GetNextLocalVarIndex(gotoValue.Type) + 1; - labelInfo.ReturnVariableIndexPlusOneAndIsDefined = (short)(returnVariableIndexPlusOne << 1); - labelInfo.ReturnLabel = il.DefineLabel(); - } - EmitStoreLocalVariable(il, returnVariableIndexPlusOne - 1); - il.Demit(returnOpCode, labelInfo.ReturnLabel); - } - -#if LIGHT_EXPRESSION - private static bool TryEmitGoto(GotoExpression expr, IParameterProvider paramExprs, ILGenerator il, ref ClosureInfo closure, - CompilerFlags setup, ParentFlags parent) -#else - private static bool TryEmitGoto(GotoExpression expr, IReadOnlyList paramExprs, ILGenerator il, ref ClosureInfo closure, - CompilerFlags setup, ParentFlags parent) -#endif - { - ref var labelInfo = ref closure.LambdaInvokeStackLabels.GetLabelOrInvokeIndexByTarget(expr.Target, out var labelFound); - if (!labelFound) - { - if ((closure.Status & ClosureStatus.ToBeCollected) == 0) - return false; // if no collection cycle then the labels may be not collected - throw new InvalidOperationException($"Cannot jump, no labels found for the target `{expr.Target}`"); - } - var gotoValue = expr.Value; - if (gotoValue != null && - !TryEmit(gotoValue, paramExprs, il, ref closure, setup, parent & ~ParentFlags.IgnoreResult)) - return false; - - switch (expr.Kind) - { - case GotoExpressionKind.Break: - case GotoExpressionKind.Continue: - il.Demit(OpCodes.Br, labelInfo.GetOrDefineLabel(il)); - return true; - - case GotoExpressionKind.Goto: - if (gotoValue != null) - goto case GotoExpressionKind.Return; - - var gotoOpCode = (parent & ParentFlags.TryCatch) != 0 ? OpCodes.Leave : OpCodes.Br; - il.Demit(gotoOpCode, labelInfo.GetOrDefineLabel(il)); - return true; - - case GotoExpressionKind.Return: - if ((parent & ParentFlags.TryCatch) != 0) - { - if (gotoValue != null) - EmitGotoToReturnLabel(ref labelInfo, il, gotoValue, OpCodes.Leave); - else - il.Demit(OpCodes.Leave, labelInfo.GetOrDefineLabel(il)); // if there is no return value just leave to the original label - } - else if ((parent & ParentFlags.InlinedLambdaInvoke) != 0) - { - if (gotoValue != null) - { - var invokeIndex = labelInfo.InlinedLambdaInvokeIndex; - if (invokeIndex == -1) - return false; - EmitGotoToReturnLabel(ref closure.LambdaInvokeStackLabels.GetSurePresentRef(invokeIndex), il, gotoValue, OpCodes.Br); - } - } - else - il.Demit(OpCodes.Ret); - return true; - - default: - return false; - } - } - -#if LIGHT_EXPRESSION - private static bool TryEmitCoalesceOperator(BinaryExpression expr, IParameterProvider paramExprs, ILGenerator il, ref ClosureInfo closure, - CompilerFlags setup, ParentFlags parent) -#else - private static bool TryEmitCoalesceOperator(BinaryExpression expr, IReadOnlyList paramExprs, ILGenerator il, ref ClosureInfo closure, - CompilerFlags setup, ParentFlags parent) -#endif - { - var labelFalse = il.DefineLabel(); // todo: @perf define only if needed - var labelDone = il.DefineLabel(); - - var left = expr.Left; - var right = expr.Right; - - // we won't OpCodes.Pop inside the Coalesce as it may leave the Il in invalid state - instead we will pop at the end here (#284) - var flags = (parent & ~ParentFlags.IgnoreResult) | ParentFlags.Coalesce; - - if (!TryEmit(left, paramExprs, il, ref closure, setup, flags)) - return false; - - var exprType = expr.Type; - var leftType = left.Type; - if (leftType.IsValueType) - { - Debug.Assert(leftType.IsNullable(), "Expecting Nullable, it is the only ValueType comparable to null"); - - var varIndex = EmitStoreAndLoadLocalVariableAddress(il, leftType); - EmitMethodCall(il, leftType.GetNullableHasValueGetterMethod()); - - il.Demit(OpCodes.Brfalse, labelFalse); - - if (exprType == Nullable.GetUnderlyingType(leftType)) - { - // if the target expression type is of underlying nullable, and the left operand is not null, - // then extract its underlying value - EmitLoadLocalVariableAddress(il, varIndex); - il.Demit(OpCodes.Ldfld, leftType.GetNullableValueUnsafeAkaGetValueOrDefaultMethod()); - } - else - EmitLoadLocalVariable(il, varIndex); // loading the value (not address) to return it - - il.Demit(OpCodes.Br, labelDone); - il.DmarkLabel(labelFalse); - if (!TryEmit(right, paramExprs, il, ref closure, setup, flags)) - return false; - - il.DmarkLabel(labelDone); - } - else - { - il.Demit(OpCodes.Dup); // duplicate left, if it's not null, after the branch this value will be on the top of the stack - il.Demit(OpCodes.Brtrue, labelFalse); // automates the chain of the Ldnull, Ceq, Brfalse - il.Demit(OpCodes.Pop); // left is null, pop its value from the stack - - if (!TryEmit(right, paramExprs, il, ref closure, setup, flags)) - return false; - - if (right.Type != exprType) - il.TryEmitBoxOf(right.Type); - - if (left.Type == exprType) - il.DmarkLabel(labelFalse); - else - { - il.Demit(OpCodes.Br, labelDone); - il.DmarkLabel(labelFalse); // todo: @bug? should we insert the boxing for the Nullable value type before the Castclass - il.Demit(OpCodes.Castclass, exprType); - il.DmarkLabel(labelDone); - } - } - return il.EmitPopIfIgnoreResult(parent); - } - - /// Emit default op code for the type - public static void EmitDefault(ILGenerator il, Type type) - { - if (type.IsClass) - { - il.Demit(OpCodes.Ldnull); - return; - } - switch (Type.GetTypeCode(type)) - { - case TypeCode.Boolean: - case TypeCode.Char: - case TypeCode.Byte: - case TypeCode.SByte: - case TypeCode.Int16: - case TypeCode.UInt16: - case TypeCode.Int32: - case TypeCode.UInt32: - il.Demit(OpCodes.Ldc_I4_0); - break; - case TypeCode.Int64: - case TypeCode.UInt64: - il.Demit(OpCodes.Ldc_I4_0); - il.Demit(OpCodes.Conv_I8); - break; - case TypeCode.Single: - il.Demit(OpCodes.Ldc_R4, default(float)); - break; - case TypeCode.Double: - il.Demit(OpCodes.Ldc_R8, default(double)); - break; - default: - EmitLoadLocalVariable(il, InitValueTypeVariable(il, type)); - break; - } - } - -#if LIGHT_EXPRESSION - private static bool TryEmitTryCatchFinallyBlock(TryExpression tryExpr, IParameterProvider paramExprs, ILGenerator il, ref ClosureInfo closure, - CompilerFlags setup, ParentFlags parent) -#else - private static bool TryEmitTryCatchFinallyBlock(TryExpression tryExpr, IReadOnlyList paramExprs, ILGenerator il, ref ClosureInfo closure, - CompilerFlags setup, ParentFlags parent) -#endif - { -#if DEMIT - Debug.WriteLine("try {"); -#endif - il.BeginExceptionBlock(); - - if (!TryEmit(tryExpr.Body, paramExprs, il, ref closure, setup, parent)) - return false; - - var exprType = tryExpr.Type; - var returnsResult = exprType != typeof(void) && !parent.IgnoresResult(); - var resultVarIndex = -1; - - if (returnsResult) - EmitStoreLocalVariable(il, resultVarIndex = il.GetNextLocalVarIndex(exprType)); - - var catchBlocks = tryExpr.Handlers; - for (var i = 0; i < catchBlocks.Count; i++) - { - var catchBlock = catchBlocks[i]; - if (catchBlock.Filter != null) - { - if ((setup & CompilerFlags.ThrowOnNotSupportedExpression) != 0) - throw new NotSupportedExpressionException(Result.NotSupported_ExceptionCatchFilter); - return false; - } - - il.BeginCatchBlock(catchBlock.Test); - - // at the beginning of catch the Exception value is on the stack, - // we will store into local variable. - var exVarExpr = catchBlock.Variable; -#if DEMIT - Debug.WriteLine($"}} catch {{"); -#endif - if (exVarExpr != null) - { - // first, check if the exception variable was used before and supposed to be reused in the new catch - // (this is decided by creator of expression) - var exVarIndex = closure.GetDefinedLocalVarOrDefault(exVarExpr); - if (exVarIndex == -1) - exVarIndex = il.GetNextLocalVarIndex(exVarExpr.Type); - closure.PushBlockWithVars(exVarExpr, exVarIndex); - EmitStoreLocalVariable(il, exVarIndex); - } - - if (!TryEmit(catchBlock.Body, paramExprs, il, ref closure, setup, parent)) - return false; - - if (exVarExpr != null) - closure.PopBlock(); - - if (returnsResult) - EmitStoreLocalVariable(il, resultVarIndex); - } - - if (tryExpr.Fault != null) - { - var faultExpr = tryExpr.Fault; -#if DEMIT - Debug.WriteLine("} fault {" + faultExpr); -#endif - il.BeginFaultBlock(); - // it is important to ignore result for the fault block, because it should not return anything - if (!TryEmit(faultExpr, paramExprs, il, ref closure, setup, parent | ParentFlags.IgnoreResult)) - return false; - } - else if (tryExpr.Finally != null) - { - var finallyExpr = tryExpr.Finally; -#if DEMIT - Debug.WriteLine("} finally {" + finallyExpr); -#endif - il.BeginFinallyBlock(); - // it is important to ignore result for the finally block, because it should not return anything - if (!TryEmit(finallyExpr, paramExprs, il, ref closure, setup, parent | ParentFlags.IgnoreResult)) - return false; - } - - il.EndExceptionBlock(); - -#if DEMIT - Debug.WriteLine("}"); -#endif - - if (returnsResult) - EmitLoadLocalVariable(il, resultVarIndex); - - return true; - } - - public static bool TryEmitParameter(ParameterExpression paramExpr, -#if LIGHT_EXPRESSION - IParameterProvider paramExprs, -#else - IReadOnlyList paramExprs, -#endif - ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent, int byRefIndex = -1) - { - var paramExprCount = paramExprs.GetCount(); - var paramType = paramExpr.Type; - var isValueType = paramType.IsValueType; - var isParamOrVarByRef = paramExpr.IsByRef && !paramType.IsNullable(); // for the nullable part check the #461 cases with nullable - var isPassedRef = byRefIndex != -1; - - // Parameter may represent a variable, so first look if this is the case, - // and the variable is defined in the current block - var varIndex = closure.GetDefinedLocalVarOrDefault(paramExpr); - if (varIndex != -1) - { - // todo: @perf analyze if the variable is actually may be mutated in the nested lambda by being assigned or passed by ref - // Check if the variable is passed to the nested closure (#437), so it should be loaded from the nested closure NonPassedParams array - var nestedLambdasCount = closure.NestedLambdas.Count; - if (nestedLambdasCount != 0) - { - var nestedLambdas = closure.NestedLambdas.Items; - for (var nestedLambdaIndex = 0; nestedLambdaIndex < nestedLambdasCount; ++nestedLambdaIndex) - { - var lambdaInfo = nestedLambdas[nestedLambdaIndex]; - var nonPassedParamCount = lambdaInfo.NonPassedParameters.Count; - if (nonPassedParamCount != 0) - { - var varIndexInNonPassedParams = lambdaInfo.NonPassedParameters.TryGetIndex(paramExpr, default(RefEq)); - if (varIndexInNonPassedParams != -1 && - (lambdaInfo.NonPassedParamMutatedIndexBits & (1UL << varIndexInNonPassedParams)) != 0) - { - // Load the nested lambda item from the closure constants and nested lambdas array - var closureArrayItemIndex = closure.Constants.Count + nestedLambdaIndex; - EmitLoadClosureArrayItem(il, closureArrayItemIndex); - - // Check if the NonPassedArray field is being set (not null), - // otherwise the nested lambda is not yet emitted, and it is not expected for the variable to be set inside it - il.Demit(OpCodes.Ldfld, NestedLambdaForNonPassedParams.NonPassedParamsField); - - // Load the variable from the NonPassedParams array - EmitLoadConstantInt(il, varIndexInNonPassedParams); - il.Demit(OpCodes.Ldelem_Ref); - il.TryEmitUnboxOf(paramType); - return true; - } - } - } - } - - var valueTypeMemberButNotIndexAccess = isValueType & - // means the parameter is the instance for the called method or the instance for the member access, see #274, #283 - (parent & (ParentFlags.MemberAccess | ParentFlags.InstanceAccess)) != 0 & - ( - // but the variable is not used as an index for the method call or the member access - // `a[i].Foo()` -> false, see #281 - // `a[i].Bar` -> false, see #265 - // `a[i]` -> true, see #413 - (parent & ParentFlags.IndexAccess) == 0 | - (parent & (ParentFlags.Call | ParentFlags.MemberAccess)) == 0 - ); - - closure.LastEmitIsAddress = !isParamOrVarByRef & (isPassedRef | valueTypeMemberButNotIndexAccess); - - if (closure.LastEmitIsAddress) - EmitLoadLocalVariableAddress(il, varIndex); - else if (!isParamOrVarByRef) - EmitLoadLocalVariable(il, varIndex); - else - { - if (isParamOrVarByRef & isValueType & - (parent & ParentFlags.BlockResult) != 0 & - (parent & (ParentFlags.MemberAccess | ParentFlags.Call | ParentFlags.InstanceAccess | ParentFlags.Arithmetic)) == 0) - { - EmitLoadIndirectlyByRef(il, paramType); - return true; - } - - var byAddress = isParamOrVarByRef & isPassedRef & isValueType; - if (byAddress) - EmitStoreAndLoadLocalVariableAddress(il, varIndex); - else if ((parent & ParentFlags.InstanceCall) == ParentFlags.InstanceCall) - EmitLoadLocalVariable(il, varIndex); - else - EmitStoreAndLoadLocalVariable(il, varIndex); - - // Assume that the var is the last on the stack and just duplicating it, see #346 `Get_array_element_ref_and_increment_it` - // Do only when accessing the variable directly, and not the member and the array element by index - if (byAddress) - il.Demit(OpCodes.Dup); - else if ((parent & ParentFlags.InstanceAccess) == 0) - EmitLoadLocalVariable(il, varIndex); - - if (isValueType) - { - if ((parent & (ParentFlags.Arithmetic | ParentFlags.AssignmentRightValue)) != 0 & - (parent & (ParentFlags.MemberAccess | ParentFlags.InstanceAccess)) == 0) - EmitLoadIndirectlyByRef(il, paramType); - } - else - { - if ((parent & (ParentFlags.Coalesce | ParentFlags.MemberAccess | ParentFlags.IndexAccess | ParentFlags.AssignmentRightValue)) != 0) - il.Demit(OpCodes.Ldind_Ref); - } - } - return true; - } - - // If not variable then look if the parameter is passed to the current lambda - var paramIndex = paramExprCount - 1; - while (paramIndex != -1 && !ReferenceEquals(paramExprs.GetParameter(paramIndex), paramExpr)) --paramIndex; - if (paramIndex != -1) - { - if ((closure.Status & ClosureStatus.ShouldBeStaticMethod) == 0) - ++paramIndex; // shift parameter index by one, because the first one will be closure - - // means the parameter is the instance for what method is called or the instance for the member access, see #274, #283 - var valueTypeMemberButNotIndexAccess = isValueType & - // means the parameter is the instance for what method is called or the instance for the member access, see #274, #283 - (parent & (ParentFlags.MemberAccess | ParentFlags.InstanceAccess)) != 0 & - // see #352 for the arithmetic - (parent & ParentFlags.Arithmetic) == 0 & - ( - // but the variable is not used as an index for the method call or the member access - // `a[i].Foo()` -> false, see #281 - // `a[i].Bar` -> false, see #265 - // `a[i]` -> true, see #413 - (parent & ParentFlags.IndexAccess) == 0 | - (parent & (ParentFlags.Call | ParentFlags.MemberAccess)) == 0 - ); - - closure.LastEmitIsAddress = !isParamOrVarByRef & (isPassedRef | valueTypeMemberButNotIndexAccess); - - if (closure.LastEmitIsAddress) - EmitLoadArgAddress(il, paramIndex); - else - EmitLoadArg(il, paramIndex); - - // todo: @simplify as it is complex overall and EmitLoadIndirectlyByRef does the Ldind_Ref too - if (isParamOrVarByRef) - { - // todo: @wip requires a cleanup - if (isValueType) - { - // #248 - skip the cases with `ref param.Field` were we are actually want to load the `Field` address not the `param` - // this means the parameter is the argument to the method call and not the instance in the method call or member access - if (!isPassedRef & ( - ((parent & ParentFlags.Call) != 0 & (parent & ParentFlags.InstanceAccess) == 0) | - ((parent & ParentFlags.LambdaCall) != 0 & (parent & ParentFlags.ReturnByRef) == 0) - ) || - (parent & (ParentFlags.Arithmetic | ParentFlags.AssignmentRightValue)) != 0 & - (parent & (ParentFlags.MemberAccess | ParentFlags.InstanceAccess | ParentFlags.AssignmentLeftValue)) == 0) - EmitLoadIndirectlyByRef(il, paramType); - } - else - { - if (!isPassedRef & ( - (parent & (ParentFlags.Call | ParentFlags.LambdaCall)) != 0) || - (parent & (ParentFlags.Coalesce | ParentFlags.MemberAccess | ParentFlags.IndexAccess | ParentFlags.AssignmentRightValue)) != 0) - il.Demit(OpCodes.Ldind_Ref); - } - } - return true; - } - - if (isParamOrVarByRef) - { - EmitLoadLocalVariableAddress(il, byRefIndex); // todo: @bug? `closure.LastEmitIsAddress = true;` should we do it too as in above code with the variable - return true; - } - - // the only possibility that we are here is because we are in the nested lambda, - // and it uses the parameter or variable from the outer lambda - var nonPassedParamIndex = closure.NonPassedParameters.TryGetIndex(paramExpr, default(RefEq)); - if (nonPassedParamIndex == -1) - return false; - - // Load non-passed argument from Closure - closure object is always a first argument - il.Demit(OpCodes.Ldarg_0); - il.Demit(OpCodes.Ldfld, ArrayClosureWithNonPassedParamsField); - EmitLoadConstantInt(il, nonPassedParamIndex); - il.Demit(OpCodes.Ldelem_Ref); - return il.TryEmitUnboxOf(paramType); - } - -#if LIGHT_EXPRESSION - public static bool TryEmitNonByRefNonValueTypeParameter(ParameterExpression paramExpr, IParameterProvider paramExprs, ILGenerator il, ref ClosureInfo closure) - { - var paramExprCount = paramExprs.ParameterCount; -#else - public static bool TryEmitNonByRefNonValueTypeParameter(ParameterExpression paramExpr, IReadOnlyList paramExprs, ILGenerator il, ref ClosureInfo closure) - { - var paramExprCount = paramExprs.Count; -#endif - // if parameter is passed through, then just load it on stack - var paramType = paramExpr.Type; - var paramIndex = paramExprCount - 1; - while (paramIndex != -1 && !ReferenceEquals(paramExprs.GetParameter(paramIndex), paramExpr)) - --paramIndex; - if (paramIndex != -1) - { - if ((closure.Status & ClosureStatus.ShouldBeStaticMethod) == 0) - ++paramIndex; // shift parameter index by one, because the first one will be closure - if (closure.LastEmitIsAddress) - EmitLoadArgAddress(il, paramIndex); - else - EmitLoadArg(il, paramIndex); - return true; - } - - // the only possibility that we are here is because we are in the nested lambda, - // and it uses the parameter or variable from the outer lambda - var nonPassedParamIndex = closure.NonPassedParameters.TryGetIndex(paramExpr, default(RefEq)); - if (nonPassedParamIndex == -1) - return false; - - // Load non-passed argument from Closure - closure object is always a first argument - il.Demit(OpCodes.Ldarg_0); - il.Demit(OpCodes.Ldfld, ArrayClosureWithNonPassedParamsField); - EmitLoadConstantInt(il, nonPassedParamIndex); - il.Demit(OpCodes.Ldelem_Ref); - return true; - } - - private static bool TryEmitSimpleUnaryExpression(UnaryExpression expr, ExpressionType nodeType, -#if LIGHT_EXPRESSION - IParameterProvider paramExprs, -#else - IReadOnlyList paramExprs, -#endif - ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent) - { - var exprType = expr.Type; - - if (!TryEmit(expr.Operand, paramExprs, il, ref closure, setup, parent)) - return false; - - if (nodeType == ExpressionType.TypeAs) - { - il.TryEmitBoxOf(expr.Operand.Type); - il.Demit(OpCodes.Isinst, exprType); - if (exprType.IsValueType) - il.Demit(OpCodes.Unbox_Any, exprType); - } - else if (nodeType == ExpressionType.IsFalse) - { - var falseLabel = il.DefineLabel(); - var continueLabel = il.DefineLabel(); - il.Demit(OpCodes.Brfalse, falseLabel); - il.Demit(OpCodes.Ldc_I4_0); - il.Demit(OpCodes.Br, continueLabel); - il.DmarkLabel(falseLabel); - il.Demit(OpCodes.Ldc_I4_1); - il.DmarkLabel(continueLabel); - } - else if (nodeType == ExpressionType.Increment) - { - if (exprType.IsPrimitive) - { - if (!TryEmitNumberOne(il, exprType)) - return false; - il.Demit(OpCodes.Add); - } - else if (!EmitMethodCallCheckForNull(il, exprType.GetMethod("op_Increment"))) - return false; - } - else if (nodeType == ExpressionType.Decrement) - { - if (exprType.IsPrimitive) - { - if (!TryEmitNumberOne(il, exprType)) - return false; - il.Demit(OpCodes.Sub); - } - else if (!EmitMethodCallCheckForNull(il, exprType.GetMethod("op_Decrement"))) - return false; - } - else if (nodeType == ExpressionType.Negate | nodeType == ExpressionType.NegateChecked) - { - if (exprType.IsPrimitive) - il.Demit(OpCodes.Neg); - else if (!EmitMethodCallCheckForNull(il, exprType.GetMethod("op_UnaryNegation"))) - return false; - } - else if (nodeType == ExpressionType.OnesComplement) - il.Demit(OpCodes.Not); - else if (nodeType == ExpressionType.Unbox) - il.Demit(OpCodes.Unbox_Any, exprType); - // else if (nodeType == ExpressionType.IsTrue) { } - // else if (nodeType == ExpressionType.UnaryPlus) { } - - return il.EmitPopIfIgnoreResult(parent); - } - - private static bool TryEmitTypeIsOrEqual(TypeBinaryExpression expr, -#if LIGHT_EXPRESSION - IParameterProvider paramExprs, -#else - IReadOnlyList paramExprs, -#endif - ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent) - { - if (!TryEmit(expr.Expression, paramExprs, il, ref closure, setup, parent)) - return false; - if ((parent & ParentFlags.IgnoreResult) != 0) - return true; - if (expr.NodeType == ExpressionType.TypeIs) - { - il.Demit(OpCodes.Isinst, expr.TypeOperand); - il.Demit(OpCodes.Ldnull); - il.Demit(OpCodes.Cgt_Un); - return true; - } - if ((setup & CompilerFlags.ThrowOnNotSupportedExpression) != 0) - throw new NotSupportedExpressionException(Result.NotSupported_TypeEqual); - return false; - } - - private static bool TryEmitNot(UnaryExpression expr, -#if LIGHT_EXPRESSION - IParameterProvider paramExprs, -#else - IReadOnlyList paramExprs, -#endif - ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent) - { - var op = expr.Operand; - if (op.NodeType == ExpressionType.Equal) - return TryEmitComparison(((BinaryExpression)op).Left, ((BinaryExpression)op).Right, - expr.Type, ExpressionType.NotEqual, paramExprs, il, ref closure, setup, parent); - - if (!TryEmit(op, paramExprs, il, ref closure, setup, parent)) - return false; - - if ((parent & ParentFlags.IgnoreResult) != 0) - il.Demit(OpCodes.Pop); - else if (expr.Type == typeof(bool)) - EmitEqualToZeroOrNull(il); - else - il.Demit(OpCodes.Not); - return true; - } - - private static bool TryEmitConvert(UnaryExpression expr, -#if LIGHT_EXPRESSION - IParameterProvider paramExprs, -#else - IReadOnlyList paramExprs, -#endif - ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent) - { - var opExpr = expr.Operand; - var sourceType = opExpr.Type; - var targetType = expr.Type; - var underlyingNullableSourceType = Nullable.GetUnderlyingType(sourceType); - var underlyingNullableTargetType = Nullable.GetUnderlyingType(targetType); - - var convertMethod = expr.Method; - if (convertMethod == null) - { - // Try fast the special cases which does not require searching for the conversion operators in principle: - if (parent.IgnoresResult() && (sourceType == targetType || targetType.IsAssignableFrom(sourceType))) - return TryEmit(opExpr, paramExprs, il, ref closure, setup, parent & ~ParentFlags.InstanceAccess); - - // Emit the operand before going to the fast checks below - if (!TryEmit(opExpr, paramExprs, il, ref closure, setup, parent & ~ParentFlags.IgnoreResult & ~ParentFlags.InstanceAccess)) - return false; - - if (sourceType == targetType) - return il.EmitPopIfIgnoreResult(parent); - - if (targetType == underlyingNullableSourceType) - { - if (!closure.LastEmitIsAddress) - EmitStoreAndLoadLocalVariableAddress(il, sourceType); - EmitMethodCall(il, sourceType.GetNullableValueGetterMethod()); - return il.EmitPopIfIgnoreResult(parent); - } - - if (sourceType == underlyingNullableTargetType) - { - il.Demit(OpCodes.Newobj, targetType.GetNullableConstructor()); - return il.EmitPopIfIgnoreResult(parent); - } - - if (targetType == typeof(object) && sourceType.IsValueType) - { - il.Demit(OpCodes.Box, sourceType); - return il.EmitPopIfIgnoreResult(parent); - } - - if (sourceType == typeof(object) && targetType.IsValueType || - sourceType == typeof(Enum) && targetType.IsEnum // a special case, see the AutoMapper test `StringToEnumConverter.Should_work` - ) - { - il.Demit(OpCodes.Unbox_Any, targetType); - return il.EmitPopIfIgnoreResult(parent); - } - - // At least just check the assignability of the source to the target type, - // check only after the checks above for the ValueType or object Type, - // because their require additional boxing/unboxing operations - if (targetType.IsAssignableFrom(sourceType)) - { - if (sourceType.IsValueType && !targetType.IsValueType) - il.Demit(OpCodes.Box, sourceType); - il.Demit(OpCodes.Castclass, targetType); - return il.EmitPopIfIgnoreResult(parent); - } - } - - // Check implicit / explicit conversion operators on source and then on the target type, - // check their underlying nullable types, because Nullable does not contain conversion ops, - // also check inside that it is not primitive type, nor string or enum because those do no contain the conversion operators either. - // see #73, #451 for examples - Type methodReturnType = null; - Type methodParamType = null; - if (convertMethod != null) - { - methodReturnType = convertMethod.ReturnType; - - var mParams = convertMethod.GetParameters(); - Debug.Assert(mParams.Length == 1, $"Expected for the conversion operator to have a single param, but found {mParams.Length}"); - methodParamType = mParams[0].ParameterType; - - // todo: @wip check if we need to add the ParentFlags.Call here? - if (!TryEmit(opExpr, paramExprs, il, ref closure, setup, parent & ~ParentFlags.IgnoreResult & ~ParentFlags.InstanceAccess)) - return false; - } - else - { - // Lookup for the conversion method first in sourceType then in the targetType - Type underlyingOrNullableSourceType = null; - for (var lookupCount = 0; lookupCount < 2 & convertMethod == null; ++lookupCount) - { - var convOwnerType = lookupCount == 0 - ? underlyingNullableSourceType ?? sourceType - : underlyingNullableTargetType ?? targetType; - - if (convOwnerType == typeof(string) || convOwnerType.IsEnum || convOwnerType.IsPrimitive) - continue; - - var staticMethods = convOwnerType.GetMethods(BindingFlags.Static | BindingFlags.Public); - foreach (var m in staticMethods) - { - if (!m.IsSpecialName) - continue; - - // Method return type should be convertible to target type, - // and therefore it does not check for the method return type of Nullable - // because it cannot be coalesced to targetType without loss of information - methodReturnType = m.ReturnType; - if (methodReturnType != targetType && methodReturnType != underlyingNullableTargetType || - m.Name != "op_Implicit" && m.Name != "op_Explicit") - continue; - - var operatorParams = m.GetParameters(); - Debug.Assert(operatorParams.Length == 1, $"Expected for the conversion operator to have a single param, but found {operatorParams.Length}"); - - methodParamType = operatorParams[0].ParameterType; - if (methodParamType == sourceType) - { - convertMethod = m; - break; - } - - // This next check is only valid if the source type is the ValueType, so it can be either a Nullable struct or it can be wrapped in the Nullable - if (sourceType.IsValueType) - { - // Check for all variants of the source type which maybe either underlying nullable or nullable of the source type - // Calculate it once because less work is better. - underlyingOrNullableSourceType ??= underlyingNullableSourceType ?? sourceType.GetNullable(); - if (methodParamType == underlyingOrNullableSourceType) - { - convertMethod = m; - break; - } - } - } - } - } - - if (convertMethod != null) - { - Debug.Assert(methodParamType != null & methodReturnType != null, - "Expecting that actual source and return type are set for the found conversion operator method"); - - // For the both nullable source and target types, - // first check the source value for null and return null without calling the conversion method, otherwise call the conversion method - if (methodParamType == underlyingNullableSourceType & underlyingNullableTargetType != null) - { - var sourceVarIndex = EmitStoreAndLoadLocalVariableAddress(il, sourceType); - EmitMethodCall(il, sourceType.GetNullableHasValueGetterMethod()); - - var labelSourceHasValue = il.DefineLabel(); - il.Demit(OpCodes.Brtrue_S, labelSourceHasValue); // Jump to this label when the source has a value - - // Otherwise, emit and load the default nullable target without value, e.g. `default(Nullable)` - // then... the conversion is completed, so jumping to the done label - EmitLoadLocalVariable(il, InitValueTypeVariable(il, targetType)); - var labelConversionDone = il.DefineLabel(); - il.Demit(OpCodes.Br_S, labelConversionDone); - - // If the nullable source has the value, do the conversion - il.DmarkLabel(labelSourceHasValue); - EmitLoadLocalVariableAddress(il, sourceVarIndex); - il.Demit(OpCodes.Ldfld, sourceType.GetNullableValueUnsafeAkaGetValueOrDefaultMethod()); - - EmitMethodCallOrVirtualCall(il, convertMethod); - - // Wrap the conversion result into the target type only if the conversion method return the underlying target type, - // otherwise we done - if (methodReturnType == underlyingNullableTargetType) - il.Demit(OpCodes.Newobj, targetType.GetNullableConstructor()); - - il.DmarkLabel(labelConversionDone); - return il.EmitPopIfIgnoreResult(parent); - } - - if (methodParamType == underlyingNullableSourceType) - { - EmitStoreAndLoadLocalVariableAddress(il, sourceType); - EmitMethodCall(il, sourceType.GetNullableValueGetterMethod()); - } - else if (methodParamType != sourceType) // This is an unlikely case of Target(Source? source) - { - Debug.Assert(Nullable.GetUnderlyingType(methodParamType) == sourceType, "Expecting that the parameter type is the Nullable"); - il.Demit(OpCodes.Newobj, methodParamType.GetNullableConstructor()); - } - - EmitMethodCallOrVirtualCall(il, convertMethod); - - if (methodReturnType == underlyingNullableTargetType) - il.Demit(OpCodes.Newobj, targetType.GetNullableConstructor()); - - return il.EmitPopIfIgnoreResult(parent); - } - - if (underlyingNullableSourceType != null & underlyingNullableTargetType != null) - { - var sourceVarIndex = EmitStoreAndLoadLocalVariableAddress(il, sourceType); - EmitMethodCall(il, sourceType.GetNullableHasValueGetterMethod()); - - var labelSourceHasValue = il.DefineLabel(); - il.Demit(OpCodes.Brtrue_S, labelSourceHasValue); // Jump here when the source has a value - - // Otherwise, emit and load the default nullable target without value, e.g. `default(Nullable)` - // and... the conversion is completed, so jumping to the done label - EmitLoadLocalVariable(il, InitValueTypeVariable(il, targetType)); - var labelConversionDone = il.DefineLabel(); - il.Demit(OpCodes.Br_S, labelConversionDone); - - // If the nullable source has the value, do the conversion - il.DmarkLabel(labelSourceHasValue); - EmitLoadLocalVariableAddress(il, sourceVarIndex); - il.Demit(OpCodes.Ldfld, sourceType.GetNullableValueUnsafeAkaGetValueOrDefaultMethod()); - - if (!TryEmitPrimitiveValueConvert(underlyingNullableSourceType, underlyingNullableTargetType, il, expr.NodeType == ExpressionType.ConvertChecked)) - return false; - - il.Demit(OpCodes.Newobj, targetType.GetNullableConstructor()); - - // We done here. - il.DmarkLabel(labelConversionDone); - return il.EmitPopIfIgnoreResult(parent); - } - - if (underlyingNullableTargetType != null) // sourceType is NOT nullable here (checked above) - { - if (!underlyingNullableTargetType.IsEnum && - !TryEmitPrimitiveValueConvert(sourceType, underlyingNullableTargetType, il, isChecked: false)) - return false; - il.Demit(OpCodes.Newobj, targetType.GetNullableConstructor()); - } - else // targetType is NOT nullable here (checked above) - { - if (targetType.IsEnum) - { - targetType = Enum.GetUnderlyingType(targetType); - if (targetType == sourceType) - return il.EmitPopIfIgnoreResult(parent); - } - - // fixes #159 - if (underlyingNullableSourceType != null) - { - EmitStoreAndLoadLocalVariableAddress(il, sourceType); - EmitMethodCall(il, sourceType.GetNullableValueGetterMethod()); - } - - // Cast as the last resort and let's it fail if unlucky - if (!TryEmitPrimitiveValueConvert(underlyingNullableSourceType ?? sourceType, targetType, il, expr.NodeType == ExpressionType.ConvertChecked)) - { - il.TryEmitBoxOf(sourceType); - il.Demit(OpCodes.Castclass, targetType); - } - } - - return il.EmitPopIfIgnoreResult(parent); - } - - private static bool TryEmitPrimitiveValueConvert(Type sourceType, Type targetType, ILGenerator il, bool isChecked) - { - switch (Type.GetTypeCode(targetType)) - { - case TypeCode.SByte: - il.Demit(isChecked ? OpCodes.Conv_Ovf_I1 : OpCodes.Conv_I1); - break; - case TypeCode.Byte: - il.Demit(isChecked ? OpCodes.Conv_Ovf_U1 : OpCodes.Conv_U1); - break; - case TypeCode.Int16: - il.Demit(isChecked ? OpCodes.Conv_Ovf_I2 : OpCodes.Conv_I2); - break; - case TypeCode.Int32: - il.Demit(isChecked ? OpCodes.Conv_Ovf_I4 : OpCodes.Conv_I4); - break; - case TypeCode.Int64: - il.Demit(isChecked ? OpCodes.Conv_Ovf_I8 : OpCodes.Conv_I8); - break; - case TypeCode.Double: - if (sourceType.IsUnsigned()) - il.Demit(OpCodes.Conv_R_Un); - il.Demit(OpCodes.Conv_R8); - break; - case TypeCode.Single: - if (sourceType.IsUnsigned()) - il.Demit(OpCodes.Conv_R_Un); - il.Demit(OpCodes.Conv_R4); - break; - case TypeCode.UInt16: - case TypeCode.Char: - il.Demit(isChecked ? OpCodes.Conv_Ovf_U2 : OpCodes.Conv_U2); - break; - case TypeCode.UInt32: - il.Demit(isChecked ? OpCodes.Conv_Ovf_U4 : OpCodes.Conv_U4); - break; - case TypeCode.UInt64: - il.Demit(isChecked ? OpCodes.Conv_Ovf_U8 : OpCodes.Conv_U8); // should we consider if sourceType.IsUnsigned == false and using the OpCodes.Conv_I8 (seems like the System.Compile does it) - break; - default: - // todo: @feature for net7+ add Half, Int128, UInt128 - return false; - } - return true; - } - - public static bool TryEmitConstant(ConstantExpression expr, Type exprType, ILGenerator il, ref ClosureInfo closure, int byRefIndex = -1) - { - var ok = false; -#if LIGHT_EXPRESSION - if (expr == NullConstant) - { - il.Demit(OpCodes.Ldnull); - ok = true; - } - else if (expr == FalseConstant | expr == ZeroConstant) - { - il.Demit(OpCodes.Ldc_I4_0); - ok = true; - } - else if (expr == TrueConstant | expr == OneConstant) - { - il.Demit(OpCodes.Ldc_I4_1); - ok = true; - } - else if (expr is IntConstantExpression n) - { - EmitLoadConstantInt(il, (int)n.Value); - ok = true; - } -#endif - if (!ok) - { - var constValue = expr.Value; - if (constValue != null) - ok = TryEmitConstant(closure.ContainsConstantsOrNestedLambdas(), exprType, constValue.GetType(), constValue, il, ref closure, byRefIndex); - else if (exprType.IsValueType) - // null for a value type and yep, this is a proper way to emit the Nullable null - ok = EmitLoadLocalVariable(il, InitValueTypeVariable(il, exprType)); - else - { - il.Demit(OpCodes.Ldnull); - ok = true; - } - } - - if (ok & byRefIndex != -1) - EmitStoreAndLoadLocalVariableAddress(il, exprType); - return ok; - } - - [MethodImpl((MethodImplOptions)256)] - public static bool TryEmitNotNullConstant(bool considerClosure, object constValue, ILGenerator il, ref ClosureInfo closure, int byRefIndex = -1) - { - Debug.Assert(constValue != null, "Expecting that the constant value is not null here"); - var constType = constValue.GetType(); - var ok = TryEmitConstant(considerClosure, null, constType, constValue, il, ref closure, byRefIndex); - if (ok & byRefIndex != -1) - EmitStoreAndLoadLocalVariableAddress(il, constType); - return ok; - } - - public static bool TryEmitConstant(bool considerClosure, Type exprType, Type constType, object constValue, ILGenerator il, ref ClosureInfo closure, - int byRefIndex = -1, FieldInfo refField = null) - { - if (exprType == null) - exprType = constType; -#if LIGHT_EXPRESSION - if (considerClosure && (refField != null || IsClosureBoundConstant(constValue, constType))) -#else - if (considerClosure && IsClosureBoundConstant(constValue, constType)) -#endif - { - var constIndex = closure.Constants.TryGetIndex(constValue, default(RefEq)); - if (constIndex == -1) - return false; // todo: @check should we throw an exception instead? - - var varIndex = closure.ConstantUsageThenVarIndex[constIndex] - 1; - if (varIndex > 0) - EmitLoadLocalVariable(il, varIndex); - else - { - EmitLoadClosureArrayItem(il, constIndex); -#if LIGHT_EXPRESSION - // Handle the loading of the ref field if the constant usage is only once, - // for the multiple usages the field was loaded and saved into variable in `EmitLoadConstantsAndNestedLambdasIntoVars` - if (refField != null) - { - il.Demit(OpCodes.Ldfld, refField); - if (refField.FieldType != typeof(object)) - return true; // for typed constant we done, - // but the object ref field requires the normal constant treatment with unboxing of the ValueType or the cast - exprType = constType = ((ConstantExpression)constValue).Value.GetType(); - } -#endif - if (constType.IsValueType) - il.Demit(OpCodes.Unbox_Any, constType); -#if NETFRAMEWORK - else - // The cast is probably required only for the Full CLR, - // e.g. `Test_283_Case6_MappingSchemaTests_CultureInfo_VerificationException`. - // .NET Core does not seem to care about verifiability and it's faster without the explicit cast. - il.Demit(OpCodes.Castclass, constType); -#endif - } - } - else - { - if (constValue is string s) - { - il.Demit(s, OpCodes.Ldstr); - return true; - } - if (constValue is Type t) - { - il.Demit(OpCodes.Ldtoken, t); - return EmitMethodCall(il, GetTypeFromHandleMethod); - } - if (!TryEmitPrimitiveOrEnumOrDecimalConstant(il, constValue, constType)) - return false; - } - - if (exprType != constType && constType.IsValueType) - { - if (exprType.IsNullable()) - il.Demit(OpCodes.Newobj, exprType.GetNullableConstructor()); - else if (exprType == typeof(object)) - il.Demit(OpCodes.Box, constType); // using normal type for Enum instead of underlying type - } - return true; - } - - /// Emit the IL for the value of the primitive type. - public static bool TryEmitPrimitiveOrEnumOrDecimalConstant(ILGenerator il, object constValue, Type constType) - { - if (constType.IsEnum) - constType = Enum.GetUnderlyingType(constType); - - switch (Type.GetTypeCode(constType)) - { - case TypeCode.Boolean: - il.Demit((bool)constValue ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); // todo: @perf check for LightExpression - break; - case TypeCode.Char: - EmitLoadConstantInt(il, (char)constValue); - break; - case TypeCode.SByte: - EmitLoadConstantInt(il, (sbyte)constValue); - break; - case TypeCode.Byte: - EmitLoadConstantInt(il, (byte)constValue); - break; - case TypeCode.Int16: - EmitLoadConstantInt(il, (short)constValue); - break; - case TypeCode.UInt16: - EmitLoadConstantInt(il, (ushort)constValue); - break; - case TypeCode.Int32: - EmitLoadConstantInt(il, (int)constValue); - break; - case TypeCode.UInt32: - unchecked - { - EmitLoadConstantInt(il, (int)(uint)constValue); - } - break; - case TypeCode.Int64: - il.Demit(OpCodes.Ldc_I8, (long)constValue); - break; - case TypeCode.UInt64: - unchecked - { - il.Demit(OpCodes.Ldc_I8, (long)(ulong)constValue); - } - break; - case TypeCode.Double: - il.Demit(OpCodes.Ldc_R8, (double)constValue); - break; - case TypeCode.Single: - il.Demit(OpCodes.Ldc_R4, (float)constValue); - break; - case TypeCode.Decimal: - EmitDecimalConstant((decimal)constValue, il); - break; - // todo: @feature for net7 add Half, Int128, UInt128 - default: - if (constType == typeof(IntPtr)) - { - il.Demit(OpCodes.Ldc_I8, ((IntPtr)constValue).ToInt64()); - break; - } - else if (constType == typeof(UIntPtr)) - { - unchecked - { - il.Demit(OpCodes.Ldc_I8, (long)((UIntPtr)constValue).ToUInt64()); - } - break; - } - return false; - } - return true; - } - - // todo: @perf optimize using Type.TypeCode - internal static bool TryEmitNumberOne(ILGenerator il, Type type) - { - if (type == typeof(int) || type == typeof(char) || type == typeof(short) || - type == typeof(byte) || type == typeof(ushort) || type == typeof(sbyte) || - type == typeof(uint)) - il.Demit(OpCodes.Ldc_I4_1); - else if (type == typeof(long) || type == typeof(ulong) || - type == typeof(IntPtr) || type == typeof(UIntPtr)) - il.Demit(OpCodes.Ldc_I8, (long)1); - else if (type == typeof(float)) - il.Demit(OpCodes.Ldc_R4, 1f); - else if (type == typeof(double)) - il.Demit(OpCodes.Ldc_R8, 1d); - else - return false; - return true; - } - - [MethodImpl((MethodImplOptions)256)] - private static void EmitLoadClosureArrayItem(ILGenerator il, int i) - { - il.Demit(OpCodes.Ldloc_0);// SHOULD BE always at 0 location; load array field variable on the stack - EmitLoadConstantInt(il, i); - il.Demit(OpCodes.Ldelem_Ref); - } - - internal static void EmitLoadConstantsAndNestedLambdasIntoVars(ILGenerator il, ref ClosureInfo closure) - { - // todo: @perf load the field to `var` only if the constants are more than 1 - // Load constants array field from Closure and store it into the variable - il.Demit(OpCodes.Ldarg_0); - il.Demit(OpCodes.Ldfld, ArrayClosureArrayField); - EmitStoreLocalVariable(il, il.GetNextLocalVarIndex(typeof(object[]))); // always does Stloc_0, because it is done at start of the lambda emit - - // important that the constant will contain the nested lambdas as well in the same array after the actual constants, - // so the Count indicates where the constants end - var constItems = closure.Constants.Items; - var constCount = closure.Constants.Count; - - short varIndex; - for (var i = 0; i < constCount; i++) - { - ref var constUsage = ref closure.ConstantUsageThenVarIndex.GetSurePresentRef(i); - if (constUsage > 1) // todo: @perf should we proceed to do this or simplify and remove the usages for the closure info? - { - EmitLoadClosureArrayItem(il, i); - var constValue = constItems[i]; - var constType = constValue.GetType(); - if (constType.IsValueType) - il.Demit(OpCodes.Unbox_Any, constType); - - varIndex = (short)il.GetNextLocalVarIndex(constType); - constUsage = (short)(varIndex + 1); // to distinguish from the default 1 - EmitStoreLocalVariable(il, varIndex); - } - } - - var nestedLambdasCount = closure.NestedLambdas.Count; - if (nestedLambdasCount != 0) - { - var nestedLambdas = closure.NestedLambdas.Items; - for (var i = 0; i < nestedLambdasCount; i++) - { - var lambdaInfo = nestedLambdas[i]; - EmitLoadClosureArrayItem(il, constCount + i); - lambdaInfo.LambdaVarIndex = varIndex = (short)il.GetNextLocalVarIndex(lambdaInfo.Lambda.GetType()); - EmitStoreLocalVariable(il, varIndex); - } - } - } - - private static ConstructorInfo _decimalIntCtor, _decimalLongCtor; - private static FieldInfo _decimalZero, _decimalOne; - - private static void EmitDecimalConstant(decimal value, ILGenerator il) - { - if (value == 0 | value == 1) - { - // emit Decimal.Zero or Decimal.One instead of new Decimal(0) or new Decimal(1) - var field = value == 0 ? - _decimalZero ?? (_decimalZero = typeof(Decimal).GetField(nameof(Decimal.Zero))) : - _decimalOne ?? (_decimalOne = typeof(Decimal).GetField(nameof(Decimal.One))); - il.Demit(OpCodes.Ldsfld, field); - return; - } - - //check if decimal has decimal places, if not use shorter IL code (constructor from int or long) - if (value % 1 == 0) - { - if (value >= int.MinValue && value <= int.MaxValue) - { - EmitLoadConstantInt(il, decimal.ToInt32(value)); - il.Demit(OpCodes.Newobj, _decimalIntCtor ?? (_decimalIntCtor = typeof(decimal).FindSingleParamConstructor(typeof(int)))); - return; - } - - if (value >= long.MinValue && value <= long.MaxValue) - { - il.Demit(OpCodes.Ldc_I8, decimal.ToInt64(value)); - il.Demit(OpCodes.Newobj, _decimalLongCtor ?? (_decimalLongCtor = typeof(decimal).FindSingleParamConstructor(typeof(long)))); - return; - } - } - - if (value == decimal.MinValue) - { - il.Demit(OpCodes.Ldsfld, typeof(decimal).GetField(nameof(decimal.MinValue))); - return; - } - - if (value == decimal.MaxValue) - { - il.Demit(OpCodes.Ldsfld, typeof(decimal).GetField(nameof(decimal.MaxValue))); - return; - } - - var parts = decimal.GetBits(value); - var sign = (parts[3] & 0x80000000) != 0; - var scale = (byte)((parts[3] >> 16) & 0x7F); - - EmitLoadConstantInt(il, parts[0]); - EmitLoadConstantInt(il, parts[1]); - EmitLoadConstantInt(il, parts[2]); - - il.Demit(sign ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); - EmitLoadConstantInt(il, scale); - il.Demit(OpCodes.Conv_U1); - il.Demit(OpCodes.Newobj, _decimalCtor.Value); - } - - private static readonly Lazy _decimalCtor = new Lazy(() => - { - foreach (var ctor in typeof(decimal).GetTypeInfo().DeclaredConstructors) - if (ctor.GetParameters().Length == 5) - return ctor; - return null; - }); - - [MethodImpl((MethodImplOptions)256)] - private static int InitValueTypeVariable(ILGenerator il, Type exprType, int valueVarIndex) - { - Debug.Assert(valueVarIndex != -1); - EmitLoadLocalVariableAddress(il, valueVarIndex); - il.Demit(OpCodes.Initobj, exprType); - return valueVarIndex; - } - - // todo: @perf merge with EmitLoadLocalVariable - private static int InitValueTypeVariable(ILGenerator il, Type exprType) => - InitValueTypeVariable(il, exprType, il.GetNextLocalVarIndex(exprType)); - -#if LIGHT_EXPRESSION - private static bool EmitNewArrayBounds(NewArrayExpression expr, IParameterProvider paramExprs, - ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent) - { - var bounds = (IArgumentProvider)expr; - var boundCount = bounds.ArgumentCount; -#else - private static bool EmitNewArrayBounds(NewArrayExpression expr, IReadOnlyList paramExprs, - ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent) - { - var bounds = expr.Expressions; - var boundCount = bounds.Count; -#endif - if (boundCount == 1) - { - if (!TryEmit(bounds.GetArgument(0), paramExprs, il, ref closure, setup, parent)) - return false; - var elemType = expr.Type.GetElementType(); - if (elemType == null) - return false; - il.Demit(OpCodes.Newarr, elemType); - } - else - { - for (var i = 0; i < boundCount; i++) - if (!TryEmit(bounds.GetArgument(i), paramExprs, il, ref closure, setup, parent)) - return false; - il.Demit(OpCodes.Newobj, expr.Type.GetTypeInfo().DeclaredConstructors.GetFirst()); - } - return true; - } - -#if LIGHT_EXPRESSION - private static bool EmitNewArrayInit(NewArrayExpression expr, IParameterProvider paramExprs, - ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent) - { -#else - private static bool EmitNewArrayInit(NewArrayExpression expr, IReadOnlyList paramExprs, - ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent) - { -#endif - var arrayType = expr.Type; - if (arrayType.GetArrayRank() > 1) - return false; // todo: @feature multi-dimensional array initializers are not supported yet, they also are not supported by the hoisted expression - - var elemType = arrayType.GetElementType(); - if (elemType == null) - return false; - -#if LIGHT_EXPRESSION - var elems = (IArgumentProvider)expr; - var elemCount = elems.ArgumentCount; -#else - var elems = expr.Expressions; - var elemCount = elems.Count; -#endif - EmitLoadConstantInt(il, elemCount); // emit the length of the array calculated from the number of initializer elements - il.Demit(OpCodes.Newarr, elemType); - - var isElemOfValueType = elemType.IsValueType; - for (var i = 0; i < elemCount; i++) - { - il.Demit(OpCodes.Dup); - EmitLoadConstantInt(il, i); - if (isElemOfValueType) // loading element address for later copying of value into it. - { - il.Demit(OpCodes.Ldelema, elemType); - if (!TryEmit(elems.GetArgument(i), paramExprs, il, ref closure, setup, parent)) - return false; - il.Demit(OpCodes.Stobj, elemType); // store element of value type by array element address - } - else - { - if (!TryEmit(elems.GetArgument(i), paramExprs, il, ref closure, setup, parent)) - return false; - il.Demit(OpCodes.Stelem_Ref); - } - } - return true; - } - -#if LIGHT_EXPRESSION - private static bool EmitMemberInit(MemberInitExpression expr, IParameterProvider paramExprs, ILGenerator il, ref ClosureInfo closure, - CompilerFlags setup, ParentFlags parent) -#else - private static bool EmitMemberInit(MemberInitExpression expr, IReadOnlyList paramExprs, ILGenerator il, ref ClosureInfo closure, - CompilerFlags setup, ParentFlags parent) -#endif - { - var valueVarIndex = -1; - var exprType = expr.Type; - if (exprType.IsValueType) - valueVarIndex = il.GetNextLocalVarIndex(exprType); - - var newExpr = expr.NewExpression; -#if LIGHT_EXPRESSION - if (newExpr == null) - { - if (!TryEmit(expr.Expression, paramExprs, il, ref closure, setup, parent)) - return false; - if (valueVarIndex != -1) - EmitStoreLocalVariable(il, valueVarIndex); - } - else -#endif - { -#if SUPPORTS_ARGUMENT_PROVIDER - var argExprs = (IArgumentProvider)newExpr; -#else - var argExprs = newExpr.Arguments; -#endif - var argCount = argExprs.GetCount(); - if (argCount > 0) - { - var args = newExpr.Constructor.GetParameters(); - for (var i = 0; i < argCount; i++) - if (!TryEmit(argExprs.GetArgument(i), paramExprs, il, ref closure, setup, parent, - args[i].ParameterType.IsByRef ? i : -1)) - return false; - } - - if (newExpr.Constructor != null) - { - il.Demit(OpCodes.Newobj, newExpr.Constructor); - if (valueVarIndex != -1) - EmitStoreLocalVariable(il, valueVarIndex); - } - else if (valueVarIndex != -1) - InitValueTypeVariable(il, exprType, valueVarIndex); - else - return false; // null constructor and not a value type, better to fallback - } - -#if LIGHT_EXPRESSION - var bindings = (IArgumentProvider)expr; - var bindCount = bindings.ArgumentCount; -#else - var bindings = expr.Bindings; - var bindCount = bindings.Count; -#endif - for (var i = 0; i < bindCount; i++) - { - var binding = bindings.GetArgument(i); - if (binding.BindingType != MemberBindingType.Assignment) // todo: @feature is not supported yet - return false; - - if (valueVarIndex != -1) // load local value address, to set its members - EmitLoadLocalVariableAddress(il, valueVarIndex); - else - il.Demit(OpCodes.Dup); // duplicate member owner on stack - - if (!TryEmit(((MemberAssignment)binding).Expression, paramExprs, il, ref closure, setup, parent) || - !EmitMemberSet(il, binding.Member)) - return false; - } - - if (valueVarIndex != -1) - EmitLoadLocalVariable(il, valueVarIndex); - return true; - } - - [MethodImpl((MethodImplOptions)256)] - private static bool TryEmitPropertySet(ILGenerator il, PropertyInfo prop) - { - var method = prop.SetMethod; - return method != null && EmitMethodCallOrVirtualCall(il, method); - } - - [MethodImpl((MethodImplOptions)256)] - private static bool EmitFieldSet(ILGenerator il, FieldInfo field) - { - il.Demit(field.IsStatic ? OpCodes.Stsfld : OpCodes.Stfld, field); - return true; - } - - [MethodImpl((MethodImplOptions)256)] - private static bool EmitMemberSet(ILGenerator il, MemberInfo member) => - member is PropertyInfo pr ? TryEmitPropertySet(il, pr) : - member is FieldInfo field ? EmitFieldSet(il, field) : - false; - -#if LIGHT_EXPRESSION - private static bool TryEmitListInit(ListInitExpression expr, IParameterProvider paramExprs, ILGenerator il, ref ClosureInfo closure, - CompilerFlags setup, ParentFlags parent) -#else - private static bool TryEmitListInit(ListInitExpression expr, IReadOnlyList paramExprs, ILGenerator il, ref ClosureInfo closure, - CompilerFlags setup, ParentFlags parent) -#endif - { - var valueVarIndex = -1; - var exprType = expr.Type; - if (exprType.IsValueType) - valueVarIndex = il.GetNextLocalVarIndex(exprType); - - var newExpr = expr.NewExpression; -#if SUPPORTS_ARGUMENT_PROVIDER - var argExprs = (IArgumentProvider)newExpr; -#else - var argExprs = newExpr.Arguments; -#endif - var argCount = argExprs.GetCount(); - if (argCount > 0) - { - var args = newExpr.Constructor.GetParameters(); - for (var i = 0; i < argCount; i++) - if (!TryEmit(argExprs.GetArgument(i), paramExprs, il, ref closure, setup, parent, - args[i].ParameterType.IsByRef ? i : -1)) - return false; - } - - if (newExpr.Constructor != null) - { - il.Demit(OpCodes.Newobj, newExpr.Constructor); - if (valueVarIndex != -1) - EmitStoreLocalVariable(il, valueVarIndex); - } - else if (valueVarIndex != -1) - InitValueTypeVariable(il, exprType, valueVarIndex); - else - return false; // null constructor and not a value type, better to fallback - - var inits = expr.Initializers; - var initCount = inits.Count; - var ok = true; - - // see the TryEmitMethodCall for the reason of the callFlags - var callFlags = (parent - & ~(ParentFlags.IgnoreResult | ParentFlags.MemberAccess | ParentFlags.InstanceAccess | - ParentFlags.LambdaCall | ParentFlags.ReturnByRef)) - | ParentFlags.Call; - for (var i = 0; i < initCount; ++i) - { - if (valueVarIndex != -1) // load local value address, to set its members - EmitLoadLocalVariableAddress(il, valueVarIndex); - else - il.Demit(OpCodes.Dup); // duplicate member owner on stack - - var elemInit = inits.GetArgument(i); - var method = elemInit.AddMethod; - var methodParams = method.GetParameters(); -#if LIGHT_EXPRESSION - var addArgs = (IArgumentProvider)elemInit; - var addArgCount = elemInit.ArgumentCount; -#else - var addArgs = elemInit.Arguments; - var addArgCount = addArgs.Count; -#endif - for (var a = 0; ok && a < addArgCount; ++a) - ok = TryEmit(addArgs.GetArgument(a), paramExprs, il, ref closure, setup, callFlags, methodParams[a].ParameterType.IsByRef ? a : -1); - - if (!exprType.IsValueType) - ok = EmitMethodCallOrVirtualCall(il, method); - else if (!method.IsVirtual // #251 - no need for constrained or virtual call because it is already by-ref - || method.DeclaringType == exprType) - ok = EmitMethodCall(il, method); - else - { - il.Demit(OpCodes.Constrained, exprType); // todo: @clarify it is a value type so... can we de-virtualize the call? - ok = EmitVirtualMethodCall(il, method); - } - } - - if (valueVarIndex != -1) - EmitLoadLocalVariable(il, valueVarIndex); - return ok; - } - - // the `right = null` argument indicates the increment/decrement operation - private static bool TryEmitArithmeticAndOrAssign( - Expression left, Expression right, Type exprType, ExpressionType nodeType, bool isPost, -#if LIGHT_EXPRESSION - IParameterProvider paramExprs, -#else - IReadOnlyList paramExprs, -#endif - ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent) - { - // we need the result variable and the time of Post/Pre when to store it only if the result is not ignored, otherwise don't bother - var resultVar = -1; - if (!parent.IgnoresResult()) - resultVar = il.GetNextLocalVarIndex(exprType); - - switch (left.NodeType) - { - case ExpressionType.Parameter: - if (!isPost) - return TryEmitAssignToParameterOrVariable((ParameterExpression)left, right, - nodeType, isPost, exprType, paramExprs, il, ref closure, setup, parent, resultVar); - - // todo: @better split for now between the Increment/Decrement and the rest - var p = (ParameterExpression)left; -#if LIGHT_EXPRESSION - var paramExprCount = paramExprs.ParameterCount; -#else - var paramExprCount = paramExprs.Count; -#endif - var paramIndex = -1; - var localVarIndex = closure.GetDefinedLocalVarOrDefault(p); - if (localVarIndex != -1) - EmitLoadLocalVariable(il, localVarIndex); - else - { - paramIndex = paramExprCount - 1; - while (paramIndex != -1 && !ReferenceEquals(paramExprs.GetParameter(paramIndex), p)) --paramIndex; - if (paramIndex == -1) - return false; - if ((closure.Status & ClosureStatus.ShouldBeStaticMethod) == 0) - ++paramIndex; - EmitLoadArg(il, paramIndex); - if (p.IsByRef) - EmitLoadIndirectlyByRef(il, p.Type); - } - - if (resultVar != -1 & isPost) - EmitStoreAndLoadLocalVariable(il, resultVar); // for the post increment/decrement save the non-incremented value for the later further use - - EmitIncOrDec(il, nodeType == ExpressionType.Add); - - if (resultVar != -1 & !isPost) - EmitStoreAndLoadLocalVariable(il, resultVar); - - if (localVarIndex != -1) - EmitStoreLocalVariable(il, localVarIndex); // store incremented value into the local value; - else if (p.IsByRef) - { - var incrementedVar = il.GetNextLocalVarIndex(exprType); - EmitStoreLocalVariable(il, incrementedVar); - EmitLoadArg(il, paramIndex); - EmitLoadLocalVariable(il, incrementedVar); - EmitStoreIndirectlyByRef(il, exprType); - } - else - il.Demit(OpCodes.Starg_S, paramIndex); - break; - - case ExpressionType.ArrayIndex: - throw new InvalidOperationException("ArrayIndex is not supported for the left part of the assignment operation. Use Index instead."); - - case ExpressionType.MemberAccess: - case ExpressionType.Index: - var leftMemberExpr = left as MemberExpression; - var orLeftIndexExpr = left as IndexExpression; - - // return early for not supported types of left value to avoid multiple checks below - if (leftMemberExpr == null & orLeftIndexExpr == null) - return false; - - var indexArgCount = -1; -#if SUPPORTS_ARGUMENT_PROVIDER - IArgumentProvider indexArgs = null; -#else - IReadOnlyList indexArgs = null; -#endif - if (orLeftIndexExpr != null) - { -#if SUPPORTS_ARGUMENT_PROVIDER - indexArgs = (IArgumentProvider)orLeftIndexExpr; -#else - indexArgs = orLeftIndexExpr.Arguments; -#endif - indexArgCount = indexArgs.GetCount(); - if (indexArgCount > 4) - return false; // todo: @feature more than 4 index arguments are not supported, and probably not need to be supported - } - - var objExpr = leftMemberExpr != null ? leftMemberExpr.Expression : orLeftIndexExpr.Object; - - // Remove the InstanceCall because we need to operate on the (nullable) field value and not on `ref` to return the value. - // We may avoid it in case of not returning the value or PreIncrement/PreDecrement, but let's do less checks and branching. - var baseFlags = parent & - ~(ParentFlags.IgnoreResult | ParentFlags.InstanceCall | - ParentFlags.LambdaCall | ParentFlags.ReturnByRef); - var rightOnlyFlags = baseFlags | ParentFlags.AssignmentRightValue; - - - var memberOrIndexFlags = leftMemberExpr != null ? ParentFlags.MemberAccess : ParentFlags.IndexAccess; - var leftArLeastFlags = baseFlags | ParentFlags.AssignmentLeftValue | memberOrIndexFlags; - - var leftIsByAddress = false; - if (nodeType == ExpressionType.Assign) - { - Debug.Assert(right != null); - var rightType = right.Type; - - // if the right part is the block or alike, it is better from the complexity perspective - // to emit it first and then restore the assignment target from var and assign the value - var rightVar = -1; - if (right.NodeType.IsBlockLikeOrConditional() || - right.NodeType == ExpressionType.Invoke) - { - if (!TryEmit(right, paramExprs, il, ref closure, setup, rightOnlyFlags)) - return false; - if (closure.LastEmitIsAddress) - EmitLoadIndirectlyByRef(il, rightType); - rightVar = resultVar != -1 ? resultVar : il.GetNextLocalVarIndex(rightType); - EmitStoreLocalVariable(il, rightVar); - } - - // Emit the left-value instance and index(es) (for the index access) - if (leftMemberExpr != null) - { - if (objExpr != null && - !TryEmit(objExpr, paramExprs, il, ref closure, setup, leftArLeastFlags | ParentFlags.InstanceAccess)) - return false; - } - else - { - Debug.Assert(orLeftIndexExpr != null); - if (objExpr != null) - { - var isIndexerAMethodCall = indexArgCount > 1 | orLeftIndexExpr.Indexer != null; - var objFlags = isIndexerAMethodCall ? ParentFlags.InstanceCall : ParentFlags.InstanceAccess | ParentFlags.IndexAccess; - if (!TryEmit(objExpr, paramExprs, il, ref closure, setup, objFlags | ParentFlags.AssignmentLeftValue)) - return false; - } - for (var i = 0; i < indexArgCount; i++) - if (!TryEmit(indexArgs.GetArgument(i), paramExprs, il, ref closure, setup, baseFlags, -1)) - return false; - } - - // Load already emitted or emit the right-value normally after the left to be assigned - if (rightVar != -1) - { - EmitLoadLocalVariable(il, rightVar); - } - else - { - if (!TryEmit(right, paramExprs, il, ref closure, setup, rightOnlyFlags)) - return false; - if (resultVar != -1) - EmitStoreAndLoadLocalVariable(il, resultVar); - } - - if (leftMemberExpr != null) - { - if (!EmitMemberSet(il, leftMemberExpr.Member)) - return false; - } - else // if (leftIndexExpr != null) - { - var ok = orLeftIndexExpr.Indexer != null - ? EmitMethodCallOrVirtualCallCheckForNull(il, orLeftIndexExpr.Indexer.SetMethod) - : indexArgCount == 1 - ? TryEmitArrayIndexSet(il, orLeftIndexExpr.Type) // one-dimensional array - : EmitMethodCallOrVirtualCallCheckForNull(il, objExpr?.Type.FindMethod("Set")); // multi-dimensional array - if (!ok) - return false; - } - - if (resultVar != -1) - EmitLoadLocalVariable(il, resultVar); - return true; - } - - // Here we are at Arithmetic + Assign: - // 1. First loading the left part as a part of right assignment, - // 2. Loading the right value - // 3. Do arithmetic operation - // 4. Storing the result in the local variable - // 5. Loading the left value for assignment - // 6. Loading the stored arithmetic result - // 7. Assign the result - var leftOrRightNullableAreNullLabel = default(Label); - var leftType = left.Type; - if (leftMemberExpr != null) - { - if (!TryEmitMemberGet(leftMemberExpr, paramExprs, il, ref closure, setup, - leftArLeastFlags | ParentFlags.Arithmetic | ParentFlags.DupIt)) - return false; - } - else // if (leftIndexExpr != null) - { - // determine is the index essentially the method call to get/set value - var isIndexerAMethodCall = indexArgCount > 1 | orLeftIndexExpr.Indexer != null; - var objVar = -1; - var objVarByAddress = false; - if (objExpr != null) - { - var objFlags = isIndexerAMethodCall ? ParentFlags.InstanceCall : ParentFlags.InstanceAccess | ParentFlags.IndexAccess; - if (!TryEmit(objExpr, paramExprs, il, ref closure, setup, objFlags | ParentFlags.Arithmetic)) - return false; - - // required for calling the method on the value type parameter - var objType = objExpr.Type; - objVarByAddress = !closure.LastEmitIsAddress && objType.IsValueType && // todo: @better avoid ad-hocking with parameter here - (objExpr.NodeType != ExpressionType.Parameter || !((ParameterExpression)objExpr).IsByRef); - if (objVarByAddress) - objVar = EmitStoreAndLoadLocalVariableAddress(il, objType); - else - { - if (objExpr is ParameterExpression pe && pe.IsByRef) - objType = objType.MakeByRefType(); - objVar = EmitStoreAndLoadLocalVariable(il, objType); - } - } - - int indexArgVar0 = -1, indexArgVar1 = -1, indexArgVar2 = -1, indexArgVar3 = -1; // using stackalloc array? - for (var i = 0; i < indexArgCount; i++) - { - var indexArg = indexArgs.GetArgument(i); - if (!TryEmit(indexArg, paramExprs, il, ref closure, setup, baseFlags)) - return false; - var indexArgVar = EmitStoreAndLoadLocalVariable(il, indexArg.Type); - if (i == 0) indexArgVar0 = indexArgVar; - else if (i == 1) indexArgVar1 = indexArgVar; - else if (i == 2) indexArgVar2 = indexArgVar; - else if (i == 3) indexArgVar3 = indexArgVar; - } - - // repeat the load of the obj and index variables for the assignment here to avoid store and load of the right value - if (objExpr != null) - { - if (!objVarByAddress) - EmitLoadLocalVariable(il, objVar); - else - EmitLoadLocalVariableAddress(il, objVar); - } - - EmitLoadLocalVariable(il, indexArgVar0); // there is always at least one index argument - if (indexArgVar1 != -1) - { - EmitLoadLocalVariable(il, indexArgVar1); - if (indexArgVar2 != -1) - EmitLoadLocalVariable(il, indexArgVar2); - if (indexArgVar3 != -1) - EmitLoadLocalVariable(il, indexArgVar3); - } - - var ok = !isIndexerAMethodCall - ? TryEmitArrayIndexGet(il, orLeftIndexExpr.Type, ref closure, baseFlags) // one-dimensional array - : orLeftIndexExpr.Indexer != null - ? EmitMethodCallOrVirtualCallCheckForNull(il, orLeftIndexExpr.Indexer.GetMethod) - : EmitMethodCallOrVirtualCallCheckForNull(il, objExpr?.Type.FindMethod("Get")); // multi-dimensional array - if (!ok) - return false; - } - - if (leftIsByAddress = closure.LastEmitIsAddress) - EmitLoadIndirectlyByRef(il, leftType); // if the field is loaded by ref, it need to be loaded from the ref in order to do arithmetic operation on it - - var leftIsNullable = leftType.IsNullable(); - if (!leftIsNullable) - { - if (right == null) // optimization for the common increment/decrement case, indicated by using the null for the right argument - { - if (resultVar != -1 & isPost) - EmitStoreAndLoadLocalVariable(il, resultVar); // for the post increment/decrement save the non-incremented value before doing any operation on it - EmitIncOrDec(il, nodeType == ExpressionType.Add); - if (resultVar != -1 & !isPost) - EmitStoreAndLoadLocalVariable(il, resultVar); - } - else - { - var rightType = right.Type; - if (!TryEmit(right, paramExprs, il, ref closure, setup, rightOnlyFlags)) - return false; - - var rightIsNullable = rightType.IsNullable(); - if (rightIsNullable) // todo: @perf @clarify is it even possible to have left non-nullable and right nullable - { - var rightVar = EmitStoreAndLoadLocalVariableAddress(il, rightType); - il.Demit(OpCodes.Call, leftType.GetNullableHasValueGetterMethod()); - - il.Demit(OpCodes.Brfalse, leftOrRightNullableAreNullLabel = il.DefineLabel()); - - EmitLoadLocalVariableAddress(il, rightVar); - il.Demit(OpCodes.Ldfld, rightType.GetNullableValueUnsafeAkaGetValueOrDefaultMethod()); // unwrap right operand - } - - if (!TryEmitArithmeticOperation(leftType, rightType, nodeType, exprType, il)) - return false; - if (resultVar != -1) - EmitStoreAndLoadLocalVariable(il, resultVar); - } - } - else // if `leftIsNullable == true` - { - // Reuse the result variable for the field, - // so it may be returned as the original value of field if nullable is `null` and we jump to the return - var leftNullableVar = resultVar != -1 ? resultVar : il.GetNextLocalVarIndex(leftType); - EmitStoreLocalVariable(il, leftNullableVar); - - if (right == null) - { - EmitLoadLocalVariableAddress(il, leftNullableVar); - il.Demit(OpCodes.Call, leftType.GetNullableHasValueGetterMethod()); - - il.Demit(OpCodes.Brfalse, leftOrRightNullableAreNullLabel = il.DefineLabel()); - - EmitLoadLocalVariableAddress(il, leftNullableVar); - il.Demit(OpCodes.Ldfld, leftType.GetNullableValueUnsafeAkaGetValueOrDefaultMethod()); - - EmitIncOrDec(il, nodeType == ExpressionType.Add); - } - else - { - // emit the right expression immediately after the left and then just process their results - var rightType = right.Type; - if (!TryEmit(right, paramExprs, il, ref closure, setup, rightOnlyFlags)) - return false; - if (closure.LastEmitIsAddress) - EmitLoadIndirectlyByRef(il, rightType); - - var rightVar = EmitStoreLocalVariable(il, rightType); - - var rightIsNullable = rightType.IsNullable(); - if (!rightIsNullable) // todo: @perf @clarify if it is possible to have left nullable and right non-nullable - { - EmitLoadLocalVariableAddress(il, leftNullableVar); - il.Demit(OpCodes.Call, leftType.GetNullableHasValueGetterMethod()); - - il.Demit(OpCodes.Brfalse, leftOrRightNullableAreNullLabel = il.DefineLabel()); - - EmitLoadLocalVariableAddress(il, leftNullableVar); - il.Demit(OpCodes.Ldfld, leftType.GetNullableValueUnsafeAkaGetValueOrDefaultMethod()); - - EmitLoadLocalVariable(il, rightVar); - } - else - { - EmitLoadLocalVariableAddress(il, leftNullableVar); - il.Demit(OpCodes.Call, leftType.GetNullableHasValueGetterMethod()); - - EmitLoadLocalVariableAddress(il, rightVar); - il.Demit(OpCodes.Call, rightType.GetNullableHasValueGetterMethod()); - il.Demit(OpCodes.And); - il.Demit(OpCodes.Brfalse, leftOrRightNullableAreNullLabel = il.DefineLabel()); - - EmitLoadLocalVariableAddress(il, leftNullableVar); - il.Demit(OpCodes.Ldfld, leftType.GetNullableValueUnsafeAkaGetValueOrDefaultMethod()); // unwrap left operand - - EmitLoadLocalVariableAddress(il, rightVar); - il.Demit(OpCodes.Ldfld, rightType.GetNullableValueUnsafeAkaGetValueOrDefaultMethod()); // unwrap right operand - } - - if (!TryEmitArithmeticOperation(leftType, rightType, nodeType, exprType, il)) - return false; - } - - il.Demit(OpCodes.Newobj, leftType.GetNullableConstructor()); // wrap the result back into the nullable - if (resultVar != -1 & !isPost) - EmitStoreAndLoadLocalVariable(il, resultVar); - } - - if (leftIsByAddress) - EmitStoreIndirectlyByRef(il, leftType); - else if (leftMemberExpr != null) - { - if (!EmitMemberSet(il, leftMemberExpr.Member)) - return false; - } - else // if (leftIndexExpr != null) - { - var ok = orLeftIndexExpr.Indexer != null - ? EmitMethodCallOrVirtualCallCheckForNull(il, orLeftIndexExpr.Indexer.SetMethod) - : indexArgCount == 1 - ? TryEmitArrayIndexSet(il, orLeftIndexExpr.Type) // one-dimensional array - : EmitMethodCallOrVirtualCallCheckForNull(il, objExpr?.Type.FindMethod("Set")); // multi-dimensional array - if (!ok) - return false; - } - - if (leftIsNullable) - { - // todo: @perf @simplify avoid the Dup and the Pop for this case via storing and loading local var same as in `TryEmitArithmetic` - if (leftIsByAddress | objExpr != null) - { - var skipPopLeftDuppedInstance = il.DefineLabel(); - il.Demit(OpCodes.Br_S, skipPopLeftDuppedInstance); - il.DmarkLabel(leftOrRightNullableAreNullLabel); // jump here if nullables are null after checking them with `!HasValue` - - il.Demit(OpCodes.Pop); // pop the dupped instance address or the field address, or the array instance address - - if (orLeftIndexExpr != null) - { - il.Demit(OpCodes.Pop); // pop the first index argument which is always present - if (indexArgCount > 1) - { - il.Demit(OpCodes.Pop); - if (indexArgCount > 2) - { - il.Demit(OpCodes.Pop); - if (indexArgCount > 3) - il.Demit(OpCodes.Pop); // pop the 4th last supported index argument - } - } - } - - il.DmarkLabel(skipPopLeftDuppedInstance); - } - else - { - il.DmarkLabel(leftOrRightNullableAreNullLabel); // jump here if nullables are null after checking them with `!HasValue` - } - } - break; - default: - return false; - } - - if (resultVar != -1) - EmitLoadLocalVariable(il, resultVar); - return true; - } - - private static ExpressionType AssignToArithmeticOrSelf(ExpressionType nodeType) => nodeType switch - { - ExpressionType.AddAssign => ExpressionType.Add, - ExpressionType.AddAssignChecked => ExpressionType.AddChecked, - ExpressionType.SubtractAssign => ExpressionType.Subtract, - ExpressionType.SubtractAssignChecked => ExpressionType.SubtractChecked, - ExpressionType.MultiplyAssign => ExpressionType.Multiply, - ExpressionType.MultiplyAssignChecked => ExpressionType.MultiplyChecked, - ExpressionType.DivideAssign => ExpressionType.Divide, - ExpressionType.ModuloAssign => ExpressionType.Modulo, - ExpressionType.PowerAssign => ExpressionType.Power, - ExpressionType.AndAssign => ExpressionType.And, - ExpressionType.OrAssign => ExpressionType.Or, - ExpressionType.ExclusiveOrAssign => ExpressionType.ExclusiveOr, - ExpressionType.LeftShiftAssign => ExpressionType.LeftShift, - ExpressionType.RightShiftAssign => ExpressionType.RightShift, - _ => nodeType - }; - - // the null `right` means the increment/decrement operation - private static bool TryEmitAssignToParameterOrVariable( - ParameterExpression left, Expression right, ExpressionType nodeType, bool isPost, Type exprType, -#if LIGHT_EXPRESSION - IParameterProvider paramExprs, -#else - IReadOnlyList paramExprs, -#endif - ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent, int resultVar = -1) - { -#if LIGHT_EXPRESSION - var paramExprCount = paramExprs.ParameterCount; -#else - var paramExprCount = paramExprs.Count; -#endif - var ok = false; - var flags = parent & ~ParentFlags.IgnoreResult; - - // First look if the left value is the local variable (in the current block) then store the right value in it. - var leftLocalVar = closure.GetDefinedLocalVarOrDefault(left); - if (leftLocalVar != -1) - { - if (resultVar != -1 & isPost) - { - EmitLoadLocalVariable(il, leftLocalVar); - EmitStoreLocalVariable(il, resultVar); // for the post increment/decrement save the non-incremented value for the later further use - } - - var isLeftByRef = left.IsByRef; - if (nodeType == ExpressionType.Assign) - { - var varFlags = flags | ParentFlags.AssignmentRightValue; - if (isLeftByRef) - varFlags |= ParentFlags.AssignmentByRef; - ok = TryEmit(right, paramExprs, il, ref closure, setup, varFlags); - if (resultVar != -1 & !isPost) - EmitStoreAndLoadLocalVariable(il, resultVar); - if (!isLeftByRef) - EmitStoreLocalVariable(il, leftLocalVar); - } - else - { - ok = TryEmitArithmetic(left, right, nodeType, exprType, paramExprs, il, ref closure, setup, flags | ParentFlags.AssignmentLeftValue); - if (resultVar != -1 & !isPost) - EmitStoreAndLoadLocalVariable(il, resultVar); - if (isLeftByRef) - EmitStoreIndirectlyByRef(il, exprType); - else - EmitStoreLocalVariable(il, leftLocalVar); - } - - // assigning the new value into the already closed variable - it enables the recursive nested lambda calls, see #353 - var nestedLambdasCount = closure.NestedLambdas.Count; - for (var i = 0; i < nestedLambdasCount; ++i) - EmitStoreAssignedLeftVarIntoClosureArray(il, closure.NestedLambdas.Items[i], left, leftLocalVar); - - if (resultVar != -1) - EmitLoadLocalVariable(il, resultVar); - return ok; - } - - // If not the variable, then look if it is the passed parameter - yes it is bad but you can assign to the parameter - var paramIndex = paramExprCount - 1; - while (paramIndex != -1 && !ReferenceEquals(paramExprs.GetParameter(paramIndex), left)) --paramIndex; - if (paramIndex != -1) - { - if ((closure.Status & ClosureStatus.ShouldBeStaticMethod) == 0) - ++paramIndex; // shift parameter index by one, because the first one will be closure - - var isLeftByRef = left.IsByRef; - if (isLeftByRef) - EmitLoadArg(il, paramIndex); - - if (resultVar != -1 & isPost) - EmitStoreAndLoadLocalVariable(il, resultVar); // for the post increment/decrement save the non-incremented value for the later further use - - ok = nodeType == ExpressionType.Assign - ? TryEmit(right, paramExprs, il, ref closure, setup, flags) - : TryEmitArithmetic(left, right, nodeType, exprType, paramExprs, il, ref closure, setup, flags); - - if (resultVar != -1 & !isPost) - EmitStoreAndLoadLocalVariable(il, resultVar); - - if (isLeftByRef) - EmitStoreIndirectlyByRef(il, left.Type); - else - il.Demit(OpCodes.Starg_S, paramIndex); - - if (resultVar != -1) - EmitLoadLocalVariable(il, resultVar); - return ok; - } - - // check that it is a captured parameter by closure - var nonPassedParamIndex = closure.NonPassedParameters.TryGetIndex(left, default(RefEq)); - if (nonPassedParamIndex == -1) - return false; - - if (nodeType == ExpressionType.Assign) - { - if (!TryEmit(right, paramExprs, il, ref closure, setup, flags)) - return false; - if (right is ParameterExpression rp && rp.IsByRef) - EmitLoadIndirectlyByRef(il, rp.Type); - - var rightVar = resultVar != -1 ? resultVar : il.GetNextLocalVarIndex(exprType); - EmitStoreLocalVariable(il, rightVar); - - // load array field and param item index - il.Demit(OpCodes.Ldarg_0); // load closure as it is always an argument zero - il.Demit(OpCodes.Ldfld, ArrayClosureWithNonPassedParamsField); - EmitLoadConstantInt(il, nonPassedParamIndex); - - EmitLoadLocalVariable(il, rightVar); - - il.TryEmitBoxOf(exprType); - il.Demit(OpCodes.Stelem_Ref); // put the variable into array - - // assigning the new value into the already closed variable - it enables the recursive nested lambda calls, see #353 - var nestedLambdasCount = closure.NestedLambdas.Count; - for (var i = 0; i < nestedLambdasCount; ++i) - EmitStoreAssignedLeftVarIntoClosureArray(il, closure.NestedLambdas.Items[i], left, rightVar); - - if (resultVar != -1) - EmitLoadLocalVariable(il, rightVar); - return true; - } - - if (resultVar != -1 & isPost) - { - il.Demit(OpCodes.Ldarg_0); // load closure as it is always an argument zero - il.Demit(OpCodes.Ldfld, ArrayClosureWithNonPassedParamsField); - EmitLoadConstantInt(il, nonPassedParamIndex); - il.Demit(OpCodes.Ldelem_Ref); // load the variable from array - if (exprType.IsValueType) - il.Demit(OpCodes.Unbox_Any, exprType); -#if NETFRAMEWORK - else - il.Demit(OpCodes.Castclass, exprType); -#endif - EmitStoreLocalVariable(il, resultVar); - } - - // todo: @perf optimize for the increment/decrement case - if (!TryEmitArithmetic(left, right, nodeType, exprType, paramExprs, il, ref closure, setup, flags)) - return false; - - var arithmeticResultVar = resultVar != -1 ? resultVar : il.GetNextLocalVarIndex(exprType); - EmitStoreLocalVariable(il, arithmeticResultVar); - - il.Demit(OpCodes.Ldarg_0); // load closure as it is always an argument zero - il.Demit(OpCodes.Ldfld, ArrayClosureWithNonPassedParamsField); - EmitLoadConstantInt(il, nonPassedParamIndex); - - EmitLoadLocalVariable(il, arithmeticResultVar); - - il.TryEmitBoxOf(exprType); - il.Demit(OpCodes.Stelem_Ref); // put the variable into array - - if (resultVar != -1 & !isPost) - EmitLoadLocalVariable(il, arithmeticResultVar); - return true; - } - - private static void EmitStoreAssignedLeftVarIntoClosureArray(ILGenerator il, NestedLambdaInfo nestedLambdaInfo, ParameterExpression assignedLeftVar, int assignedLeftVarIndex) - { - if (nestedLambdaInfo.NonPassedParamsVarIndex == 0) - return; - var nonPassedParIndex = nestedLambdaInfo.NonPassedParameters.TryGetIndex(assignedLeftVar, default(RefEq)); - if (nonPassedParIndex == -1) - return; - EmitLoadLocalVariable(il, nestedLambdaInfo.NonPassedParamsVarIndex); - EmitLoadConstantInt(il, nonPassedParIndex); - EmitLoadLocalVariable(il, assignedLeftVarIndex); - il.TryEmitBoxOf(assignedLeftVar.Type); - il.Demit(OpCodes.Stelem_Ref); // put the variable into non-passed parameters (variables) array - } - - private static void EmitLoadIndirectlyByRef(ILGenerator il, Type type) - { - if (type.IsEnum) - type = Enum.GetUnderlyingType(type); - switch (Type.GetTypeCode(type)) - { - case TypeCode.Boolean: il.Demit(OpCodes.Ldind_U1); break; - case TypeCode.Char: il.Demit(OpCodes.Ldind_U1); break; - case TypeCode.Byte: il.Demit(OpCodes.Ldind_U1); break; - case TypeCode.SByte: il.Demit(OpCodes.Ldind_I1); break; - case TypeCode.Int16: il.Demit(OpCodes.Ldind_I2); break; - case TypeCode.Int32: il.Demit(OpCodes.Ldind_I4); break; - case TypeCode.Int64: il.Demit(OpCodes.Ldind_I8); break; - case TypeCode.Double: il.Demit(OpCodes.Ldind_R8); break; - case TypeCode.Single: il.Demit(OpCodes.Ldind_R4); break; - case TypeCode.UInt16: il.Demit(OpCodes.Ldind_U2); break; - case TypeCode.UInt32: il.Demit(OpCodes.Ldind_U4); break; - case TypeCode.UInt64: il.Demit(OpCodes.Ldobj, type); break; - case TypeCode.String: il.Demit(OpCodes.Ldind_Ref); break; - default: - if (type == typeof(IntPtr) | type == typeof(UIntPtr)) - il.Demit(OpCodes.Ldind_I); - else if (type.IsValueType) - il.Demit(OpCodes.Ldobj, type); - else - il.Demit(OpCodes.Ldind_Ref); - break; - } - } - - private static void EmitStoreIndirectlyByRef(ILGenerator il, Type type) - { - if (type.IsEnum) - type = Enum.GetUnderlyingType(type); - switch (Type.GetTypeCode(type)) - { - case TypeCode.Boolean: il.Demit(OpCodes.Stind_I1); break; - case TypeCode.Char: il.Demit(OpCodes.Stind_I1); break; - case TypeCode.Byte: il.Demit(OpCodes.Stind_I1); break; - case TypeCode.SByte: il.Demit(OpCodes.Stind_I1); break; - case TypeCode.Int16: il.Demit(OpCodes.Stind_I2); break; - case TypeCode.Int32: il.Demit(OpCodes.Stind_I4); break; - case TypeCode.Int64: il.Demit(OpCodes.Stind_I8); break; - case TypeCode.Double: il.Demit(OpCodes.Stind_R8); break; - case TypeCode.Single: il.Demit(OpCodes.Stind_R4); break; - case TypeCode.String: il.Demit(OpCodes.Stind_Ref); break; - case TypeCode.UInt16: il.Demit(OpCodes.Stind_I2); break; - case TypeCode.UInt32: il.Demit(OpCodes.Stind_I4); break; - case TypeCode.UInt64: il.Demit(OpCodes.Stind_I8); break; - default: - if (type == typeof(IntPtr) || type == typeof(UIntPtr)) - il.Demit(OpCodes.Stind_I); - else if (type.IsValueType) - il.Demit(OpCodes.Stobj, type); - else - il.Demit(OpCodes.Stind_Ref); - break; - } - } - - private static bool TryEmitArrayIndexGet(ILGenerator il, Type type, ref ClosureInfo closure, ParentFlags parent) - { - if (!type.IsValueType) - { - il.Demit(OpCodes.Ldelem_Ref); - return true; - } - - // access the value type by address when it is used later for the member access, as instance in the method call, assigned to the left; - if ((parent & (ParentFlags.MemberAccess | ParentFlags.InstanceAccess | ParentFlags.AssignmentByRef)) != 0) - { - il.Demit(OpCodes.Ldelema, type); - closure.LastEmitIsAddress = true; - return true; - } - // todo: @perf @simplify convert to switch on TypeCode - if (type == typeof(Int32)) - il.Demit(OpCodes.Ldelem_I4); - else if (type == typeof(Int64)) - il.Demit(OpCodes.Ldelem_I8); - else if (type == typeof(Int16)) - il.Demit(OpCodes.Ldelem_I2); - else if (type == typeof(SByte)) - il.Demit(OpCodes.Ldelem_I1); - else if (type == typeof(Single)) - il.Demit(OpCodes.Ldelem_R4); - else if (type == typeof(Double)) - il.Demit(OpCodes.Ldelem_R8); - else if (type == typeof(IntPtr)) - il.Demit(OpCodes.Ldelem_I); - else if (type == typeof(UIntPtr)) - il.Demit(OpCodes.Ldelem_I); - else if (type == typeof(Byte)) - il.Demit(OpCodes.Ldelem_U1); - else if (type == typeof(UInt16)) - il.Demit(OpCodes.Ldelem_U2); - else if (type == typeof(UInt32)) - il.Demit(OpCodes.Ldelem_U4); - else - il.Demit(OpCodes.Ldelem, type); - return true; - } - - private static bool TryEmitArrayIndexSet(ILGenerator il, Type elementType) - { - if (!elementType.IsValueType) - { - il.Demit(OpCodes.Stelem_Ref); - return true; - } - - if (elementType == typeof(Int32)) - il.Demit(OpCodes.Stelem_I4); - else if (elementType == typeof(Int64)) - il.Demit(OpCodes.Stelem_I8); - else if (elementType == typeof(Int16)) - il.Demit(OpCodes.Stelem_I2); - else if (elementType == typeof(SByte)) - il.Demit(OpCodes.Stelem_I1); - else if (elementType == typeof(Single)) - il.Demit(OpCodes.Stelem_R4); - else if (elementType == typeof(Double)) - il.Demit(OpCodes.Stelem_R8); - else if (elementType == typeof(IntPtr)) - il.Demit(OpCodes.Stelem_I); - else if (elementType == typeof(UIntPtr)) - il.Demit(OpCodes.Stelem_I); - else - il.Demit(OpCodes.Stelem, elementType); - return true; - } - - private static bool TryEmitMethodCall(Expression expr, -#if LIGHT_EXPRESSION - IParameterProvider paramExprs, -#else - IReadOnlyList paramExprs, -#endif - ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent, int byRefIndex = -1) - { - var flags = ParentFlags.Call; - var callExpr = (MethodCallExpression)expr; - var objExpr = callExpr.Object; - var method = callExpr.Method; - var methodParams = method.GetParameters(); // todo: @perf @mem find how to avoid the call, look at `NewNoByRefArgs` expressions as example - - var objIsValueType = false; - var loadObjByAddress = false; - if (objExpr != null) - { - if (!TryEmit(objExpr, paramExprs, il, ref closure, setup, flags | ParentFlags.InstanceAccess)) - return false; - objIsValueType = objExpr.Type.IsValueType; - loadObjByAddress = objIsValueType && objExpr.NodeType != ExpressionType.Parameter && !closure.LastEmitIsAddress; - } - - var parCount = methodParams.Length; - if (parCount == 0) - { - if (loadObjByAddress) - EmitStoreAndLoadLocalVariableAddress(il, objExpr.Type); - } - else - { -#if SUPPORTS_ARGUMENT_PROVIDER - var callArgs = (IArgumentProvider)callExpr; -#else - var callArgs = callExpr.Arguments; -#endif - if (!closure.ArgsContainingComplexExpression.Map.ContainsKey(callExpr)) - { - if (loadObjByAddress) - EmitStoreAndLoadLocalVariableAddress(il, objExpr.Type); - - for (var i = 0; i < parCount; i++) - { - var argExpr = callArgs.GetArgument(i); - var parType = methodParams[i].ParameterType; - if (!TryEmit(argExpr, paramExprs, il, ref closure, setup, flags, parType.IsByRef ? i : -1)) - return false; - } - } - else - { - // don't forget to store the object into the variable first, before emitting the arguments - var objVar = objExpr == null ? -1 : EmitStoreLocalVariable(il, objExpr.Type); - - SmallList, NoArrayPool> argVars = default; - for (var i = 0; i < methodParams.Length; i++) - { - var argExpr = callArgs.GetArgument(i); - var parType = methodParams[i].ParameterType; - if (!TryEmit(argExpr, paramExprs, il, ref closure, setup, flags, parType.IsByRef ? i : -1)) - return false; - argVars.Add(EmitStoreLocalVariable(il, parType)); - } - - // restore the object and the args from the variables in the proper order to emit the call - if (objExpr != null) - { - if (loadObjByAddress) - EmitLoadLocalVariableAddress(il, objVar); - else - EmitLoadLocalVariable(il, objVar); - } - - for (var i = 0; i < methodParams.Length; i++) - EmitLoadLocalVariable(il, argVars[i]); - } - } - - var ok = true; - if (!objIsValueType) - ok = EmitMethodCallOrVirtualCall(il, method); - else if (method.DeclaringType != typeof(Enum) && - (!method.IsVirtual || - method.DeclaringType == objExpr.Type || - objExpr is ParameterExpression pe && pe.IsByRef)) - ok = EmitMethodCall(il, method); - else - { - il.Demit(OpCodes.Constrained, objExpr.Type); - ok = EmitVirtualMethodCall(il, method); - } - - if (byRefIndex != -1) - EmitStoreAndLoadLocalVariableAddress(il, method.ReturnType); - - if (parent.IgnoresResult() && method.ReturnType != typeof(void)) - il.Demit(OpCodes.Pop); - - closure.LastEmitIsAddress = false; - return ok; - } - - public static bool TryEmitMemberGet(MemberExpression expr, -#if LIGHT_EXPRESSION - IParameterProvider paramExprs, -#else - IReadOnlyList paramExprs, -#endif - ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent, int byRefIndex = -1) - { - var objExpr = expr.Expression; - if (expr.Member is PropertyInfo prop) - { - if (objExpr != null) - { - var p = (parent | ParentFlags.InstanceCall) - // removing ParentFlags.MemberAccess here because we are calling the method instead of accessing the field - & ~(ParentFlags.IgnoreResult | ParentFlags.MemberAccess | ParentFlags.DupIt | - ParentFlags.LambdaCall | ParentFlags.ReturnByRef); - - if (!TryEmit(objExpr, paramExprs, il, ref closure, setup, p)) - return false; - - if ((parent & ParentFlags.DupIt) != 0) // just duplicate the whatever is emitted for object - il.Demit(OpCodes.Dup); - else - // Value type special treatment to load address of value instance in order to call a method. - // For the parameters, we will skip the address loading because the `LastEmitIsAddress == true` for `Ldarga`, - // so the condition here will be skipped - if (!closure.LastEmitIsAddress && objExpr.Type.IsValueType && !(objExpr is PE pe && pe.IsByRef)) - EmitStoreAndLoadLocalVariableAddress(il, objExpr.Type); - } - - closure.LastEmitIsAddress = false; - return EmitMethodCallOrVirtualCall(il, prop.GetMethod); - } - - if (expr.Member is FieldInfo field) - { - if (objExpr != null) - { - var p = (parent | ParentFlags.InstanceAccess | ParentFlags.MemberAccess) - & ~ParentFlags.IgnoreResult & ~ParentFlags.DupIt; - - if (!TryEmit(objExpr, paramExprs, il, ref closure, setup, p)) - return false; - - if (parent.IgnoresResult()) - { - il.Demit(OpCodes.Pop); // pop the obj value - it is emitted only for the side effects - return true; - } - - // #248 indicates that expression is argument passed by ref to Call - var isByAddress = byRefIndex != -1; - - // we are assigning to the field of ValueType so we need its address `val.Bar += 1`, #352 - if ((parent & ParentFlags.AssignmentLeftValue) != 0 && objExpr.Type.IsValueType) - isByAddress = true; - else - // if the field is not used as an index, #302 - // or if the field is not accessed from the just constructed object `new Widget().DodgyValue`, #333 - if (((parent & ParentFlags.InstanceAccess) != 0 & - (parent & ParentFlags.IndexAccess) == 0) && field.FieldType.IsValueType) - isByAddress = true; - - // we don't need to duplicate the instance if we are working with the field address to save to it directly, - // so the field address should be Dupped instead for loading and then storing by-ref for assignment (see below). - // (don't forget to Pop if the assignment should be skipped for nullable with `null` value) - if ((parent & ParentFlags.DupIt) != 0 & - (!isByAddress | (parent & ParentFlags.AssignmentLeftValue) == 0)) - il.Demit(OpCodes.Dup); - - closure.LastEmitIsAddress = isByAddress; - if (!isByAddress) - { - if (objExpr.Type.IsEnum) - EmitStoreAndLoadLocalVariableAddress(il, objExpr.Type); - il.Demit(OpCodes.Ldfld, field); - } - else - { - il.Demit(OpCodes.Ldflda, field); - if ((parent & ParentFlags.AssignmentLeftValue) != 0) - il.Demit(OpCodes.Dup); - } - } - else if (field.IsLiteral) - { - if (parent.IgnoresResult()) - return true; // do nothing - var fieldValue = field.GetValue(null); - if (fieldValue != null) - return TryEmitConstant(false, null, field.FieldType, fieldValue, il, ref closure); - il.Demit(OpCodes.Ldnull); - } - else - { - if (parent.IgnoresResult()) - return true; // do nothing - il.Demit(OpCodes.Ldsfld, field); - } - return true; - } - return false; - } - - // ReSharper disable once FunctionComplexityOverflow -#if LIGHT_EXPRESSION - private static bool TryEmitNestedLambda(LambdaExpression lambdaExpr, IParameterProvider outerParamExprs, ILGenerator il, ref ClosureInfo closure) - { - var outerParamExprCount = outerParamExprs.ParameterCount; -#else - private static bool TryEmitNestedLambda(LambdaExpression lambdaExpr, IReadOnlyList outerParamExprs, ILGenerator il, ref ClosureInfo closure) - { - var outerParamExprCount = outerParamExprs.Count; -#endif - // First, find in closed compiled lambdas the one corresponding to the current lambda expression. - // Situation with not found lambda is not possible/exceptional, - // it means that we somehow skipped the lambda expression while collecting closure info. - var outerNestedLambdasCount = closure.NestedLambdas.Count; - var outerNestedLambdas = closure.NestedLambdas.Items; - var i = outerNestedLambdasCount - 1; - while (i != -1 && !outerNestedLambdas[i].HasTheSameLambdaExpression(lambdaExpr)) --i; - if (i == -1) - return false; - - ref var nestedLambdaInfo = ref outerNestedLambdas[i]; - EmitLoadLocalVariable(il, nestedLambdaInfo.LambdaVarIndex); - - // If lambda does not use any outer parameters to be set in closure, then we're done - var nonPassedParams = nestedLambdaInfo.NonPassedParameters; - if (nonPassedParams.Count == 0) - return true; - - //------------------------------------------------------------------- - // For the lambda with non-passed parameters (or variables) in closure - - // Emit the NonPassedParams array for the non-passed parameters and variables - var nonPassedParamsCount = nonPassedParams.Count; - EmitLoadConstantInt(il, nonPassedParamsCount); // load the length of array - il.Demit(OpCodes.Newarr, typeof(object)); - var nonPassedParamsVarIndex = EmitStoreAndLoadLocalVariable(il, typeof(object[])); - nestedLambdaInfo.NonPassedParamsVarIndex = (short)nonPassedParamsVarIndex; - - // Store the NonPassedParams back into the NestedLambda wrapper for the #437. - // Also, it is needed to be able to assign the closed variable after the closure is passed to the lambda, - // e.g. `var x = 1; var f = () => x + 1; x = 2; f();` expects 3, not 2 - il.Demit(OpCodes.Stfld, NestedLambdaForNonPassedParams.NonPassedParamsField); - - // Populate the NonPassedParams array - for (var nestedParamIndex = 0; nestedParamIndex < nonPassedParamsCount; ++nestedParamIndex) - { - // todo: @wip move this code to where the assignment and by-ref parameter passing - Debug.Assert(nestedParamIndex < 64, "Assume that we don't have more than 64 mutated non-passed parameters"); - if (nonPassedParamsCount < 64) - nestedLambdaInfo.NonPassedParamMutatedIndexBits |= 1UL << nestedParamIndex; - - // Load the array and index where to store the item - EmitLoadLocalVariable(il, nonPassedParamsVarIndex); - EmitLoadConstantInt(il, nestedParamIndex); - - var nestedParam = nonPassedParams.GetSurePresentRef(nestedParamIndex); - var outerParamIndex = outerParamExprCount - 1; - while (outerParamIndex != -1 && !ReferenceEquals(outerParamExprs.GetParameter(outerParamIndex), nestedParam)) - --outerParamIndex; - if (outerParamIndex != -1) // load parameter from input outer params - { - // Add `+1` to index because the `0` index is for the closure argument - EmitLoadArg(il, outerParamIndex + 1); - il.TryEmitBoxOf(nestedParam.Type); - } - else // load parameter from outer closure or from the local variables - { - var outerLocalVarIndex = closure.GetDefinedLocalVarOrDefault(nestedParam); - if (outerLocalVarIndex != -1) // it's a local variable - { - EmitLoadLocalVariable(il, outerLocalVarIndex); - il.TryEmitBoxOf(nestedParam.Type); - } - else // it's a parameter from the outer closure - { - var outerNonPassedParamIndex = closure.NonPassedParameters.TryGetIndex(nestedParam, default(RefEq)); - if (outerNonPassedParamIndex == -1) - return false; // impossible, return error code 2 the same as in TryCollectInfo - - // Load the parameter from outer closure `Items` array - il.Demit(OpCodes.Ldarg_0); // closure is always a first argument - il.Demit(OpCodes.Ldfld, ArrayClosureWithNonPassedParamsField); - EmitLoadConstantInt(il, outerNonPassedParamIndex); - il.Demit(OpCodes.Ldelem_Ref); - } - } - - // Store the item into nested lambda array - il.Demit(OpCodes.Stelem_Ref); - } - - // Load the actual lambda delegate on stack - EmitLoadLocalVariable(il, nestedLambdaInfo.LambdaVarIndex); - il.Demit(OpCodes.Ldfld, NestedLambdaForNonPassedParams.NestedLambdaField); - - // Load the nonPassedParams as a first argument of closure - EmitLoadLocalVariable(il, nonPassedParamsVarIndex); - - // Load the constants as a second argument and call the closure constructor - var lambda = nestedLambdaInfo.Lambda; - if (lambda is NestedLambdaForNonPassedParamsWithConstants) - { - EmitLoadLocalVariable(il, nestedLambdaInfo.LambdaVarIndex); - il.Demit(OpCodes.Ldfld, NestedLambdaForNonPassedParamsWithConstants.ConstantsAndNestedLambdasField); - il.Demit(OpCodes.Newobj, ArrayClosureWithNonPassedParamsAndConstantsCtor); - } - else - il.Demit(OpCodes.Newobj, ArrayClosureWithNonPassedParamsCtor); - - // Call the `Curry` method with the nested lambda and closure to produce a closed lambda with the expected signature - var lambdaType = (lambda is NestedLambdaForNonPassedParams lp ? lp.NestedLambda : lambda).GetType(); - var lambdaTypeArgs = lambdaType.GetGenericArguments(); - var nestedLambdaExpr = nestedLambdaInfo.LambdaExpression; - var closureMethod = nestedLambdaExpr.ReturnType == typeof(void) - ? CurryClosureActions.Methods[lambdaTypeArgs.Length - 1].MakeGenericMethod(lambdaTypeArgs) - : CurryClosureFuncs.Methods[lambdaTypeArgs.Length - 2].MakeGenericMethod(lambdaTypeArgs); - - var ok = EmitMethodCall(il, closureMethod); - - // Convert to the original possibly custom delegate type, see #308 - if (closureMethod.ReturnType != nestedLambdaExpr.Type) - { - il.Demit(OpCodes.Ldftn, closureMethod.ReturnType.FindDelegateInvokeMethod()); - il.Demit(OpCodes.Newobj, nestedLambdaExpr.Type.GetConstructors()[0]); - } - - return ok; - } - -#if LIGHT_EXPRESSION - private static bool TryEmitInvoke(InvocationExpression expr, IParameterProvider paramExprs, ILGenerator il, ref ClosureInfo closure, - CompilerFlags setup, ParentFlags parent) - { - var paramCount = paramExprs.ParameterCount; -#else - private static bool TryEmitInvoke(InvocationExpression expr, IReadOnlyList paramExprs, ILGenerator il, ref ClosureInfo closure, - CompilerFlags setup, ParentFlags parent) - { - var paramCount = paramExprs.Count; -#endif -#if SUPPORTS_ARGUMENT_PROVIDER - var argExprs = (IArgumentProvider)expr; -#else - var argExprs = expr.Arguments; -#endif - var argCount = argExprs.GetCount(); - var invokedExpr = expr.Expression; - if ((setup & CompilerFlags.NoInvocationLambdaInlining) == 0 && invokedExpr is LambdaExpression lambdaExpr) - { - parent |= ParentFlags.InlinedLambdaInvoke; - - ref var inlinedExpr = ref closure.InlinedLambdaInvocation.Map.AddOrGetValueRef(expr, out var found); - Debug.Assert(found, "The invocation expression should be collected in TryCollectInfo but it is not"); - if (!found) - return false; - - if (!TryEmit(inlinedExpr, paramExprs, il, ref closure, setup, parent)) - return false; - - if ((parent & ParentFlags.IgnoreResult) == 0 && inlinedExpr.Type != typeof(void)) - { - // find if the variable with the result is exist in the label infos - ref var label = ref closure.LambdaInvokeStackLabels.GetLabelOrInvokeIndexByTarget(expr, out var labelFound); - if (labelFound) - { - var returnVariableIndexPlusOne = label.ReturnVariableIndexPlusOneAndIsDefined >>> 1; - if (returnVariableIndexPlusOne != 0) - { - il.DmarkLabel(label.ReturnLabel); - EmitLoadLocalVariable(il, returnVariableIndexPlusOne - 1); - } - } - } - return true; - } - - if (!TryEmit(invokedExpr, paramExprs, il, ref closure, setup, parent & ~ParentFlags.IgnoreResult)) // removing the IgnoreResult temporary because we need "full" lambda emit and we will re-apply the IgnoreResult later at the end of the method - return false; - - //if (lambda is ConstantExpression lambdaConst) // todo: @perf opportunity to optimize - // delegateInvokeMethod = ((Delegate)lambdaConst.Value).GetMethodInfo(); - //else - var delegateInvokeMethod = invokedExpr.Type.FindDelegateInvokeMethod(); // todo: @perf bad thingy - if (argCount != 0) - { - var useResult = parent & ~ParentFlags.IgnoreResult & ~ParentFlags.InstanceAccess; - var args = delegateInvokeMethod.GetParameters(); // todo: @perf avoid this if possible - for (var i = 0; i < args.Length; ++i) - { - var argExpr = argExprs.GetArgument(i); - if (!TryEmit(argExpr, paramExprs, il, ref closure, setup, useResult, args[i].ParameterType.IsByRef ? i : -1)) - return false; - } - } - - EmitMethodCall(il, delegateInvokeMethod); - if ((parent & ParentFlags.IgnoreResult) != 0 && delegateInvokeMethod.ReturnType != typeof(void)) - il.Demit(OpCodes.Pop); - - return true; - } - - private static Label[][] _labelPool = null; - - private struct TestValueAndMultiTestCaseIndex - { - public long Value; - public int CaseBodyIdx; - public int MultiTestValCaseBodyIdxPlusOne; // 0 means not multi-test case, otherwise index+1 - } - - private static long ConvertValueObjectToLong(object valObj) - { - Debug.Assert(valObj != null); - var type = valObj.GetType(); - type = type.IsEnum ? Enum.GetUnderlyingType(type) : type; - return Type.GetTypeCode(type) switch - { - TypeCode.Char => (long)(char)valObj, - TypeCode.SByte => (long)(sbyte)valObj, - TypeCode.Byte => (long)(byte)valObj, - TypeCode.Int16 => (long)(short)valObj, - TypeCode.UInt16 => (long)(ushort)valObj, - TypeCode.Int32 => (long)(int)valObj, - TypeCode.UInt32 => (long)(uint)valObj, - TypeCode.Int64 => (long)valObj, - TypeCode.UInt64 => (long)(ulong)valObj, - _ => 0 // unreachable - }; - } - -#if LIGHT_EXPRESSION - private static bool TryEmitSwitch(SwitchExpression expr, IParameterProvider paramExprs, ILGenerator il, ref ClosureInfo closure, - CompilerFlags setup, ParentFlags parent) -#else - private static bool TryEmitSwitch(SwitchExpression expr, IReadOnlyList paramExprs, ILGenerator il, ref ClosureInfo closure, - CompilerFlags setup, ParentFlags parent) -#endif - { - var switchValueExpr = expr.SwitchValue; - var customEqualMethod = expr.Comparison; - var cases = expr.Cases; - var caseCount = cases.Count; - var defaultBody = expr.DefaultBody; - - // Optimization for the single case - if (caseCount == 1 & defaultBody != null) - { - var cs0 = cases[0]; - if (cs0.TestValues.Count == 1) - { - Expression testExpr; - if (customEqualMethod == null) - { - // todo: @perf avoid creation of the additional expression - testExpr = Equal(switchValueExpr, cs0.TestValues[0]); - if (Interpreter.TryInterpretBool(out var testResult, testExpr, setup)) - return TryEmit(testResult ? cs0.Body : defaultBody, paramExprs, il, ref closure, setup, parent); - } - else - testExpr = Call(customEqualMethod, switchValueExpr, cs0.TestValues[0]); - - return TryEmitConditional(testExpr, cs0.Body, defaultBody, paramExprs, il, ref closure, setup, parent); - } - } - - var switchValueType = switchValueExpr.Type; - var switchValueParent = parent & ~ParentFlags.IgnoreResult & ~ParentFlags.InstanceAccess; - - // Check the pre-requisite conditions and emit OpCodes.Switch if possible, see #398 - const int minSwitchTableSize = 3; - Debug.Assert(minSwitchTableSize >= 3); // The later code will assume at least so few of the cases count - if (customEqualMethod == null && caseCount >= minSwitchTableSize && switchValueType.IsIntegerOrUnderlyingInteger()) - { - var multipleTestValuesLabelsId = 0; -#if TESTING || DEBUG - SmallList, NoArrayPool> switchValues = default; -#else - SmallList, NoArrayPool> switchValues = default; -#endif - for (var caseIndex = 0; caseIndex < caseCount; ++caseIndex) - { - var testValues = cases[caseIndex].TestValues; - var testValueCount = testValues.Count; - var id = testValueCount > 1 ? ++multipleTestValuesLabelsId : 0; - - for (var v = 0; v < testValueCount; ++v) - { - var testValExpr = testValues[v]; - if (testValExpr is not ConstantExpression && testValExpr is not DefaultExpression) - { - Debug.Assert(false, $"Not supported non-constant,non-default switch case value expression: `{testValExpr}`"); - return false; - } - var testValueLong = testValExpr is ConstantExpression constExpr ? ConvertValueObjectToLong(constExpr.Value) : 0L; - - // Adding a free slot for the new or for the shifted max value - ref var freeValRef = ref switchValues.AddDefaultAndGetRef(); // the default value is (0,0) - var lastIndex = switchValues.Count - 1; - while (true) - { - --lastIndex; - if (lastIndex == -1) - { - freeValRef.Value = testValueLong; - freeValRef.CaseBodyIdx = caseIndex; - freeValRef.MultiTestValCaseBodyIdxPlusOne = id; - break; - } - - ref var valRef = ref switchValues.GetSurePresentRef(lastIndex); - Debug.Assert(valRef.Value != testValueLong, "Duplicate test value in switch case"); - - // Shift current value further to get space for the smaller new value - if (testValueLong < valRef.Value) - { - freeValRef = valRef; // shift the value by copying it into the free slot - freeValRef = ref valRef; // set the free slot reference to the current value - } - else - { - lastIndex = 0; // stop searching - } - } - } - } - - // Let's analyze the switch values for the starting value and the gaps. - // We require no more than nonContValueCountMax non-continuous values and no larger gap than the valueGapMin for the rest, - // to consider the values for the switch table. Because otherwise the table will be filled with gaps and - // will become too large for the final optimization goal. - // Note that we cannot check the gaps earlier when collecting the values because the gaps may be filled at the end. - const int valueGapMin = 4; - const int nonContValueCountMax = 3; - var contValueCount = 0; - var valuesConditionsMet = true; - var actualSwitchTableSize = 1; // start from the 1 and then add the gaps - - var firstTestValueIdx = 0; - var switchValuesCount = switchValues.Count; - var lastTestValueIdx = switchValuesCount - 1; - - // Check for the 1 or 2 outliers with the big gap only if there are 1/2 + minSwitchTableSize, otherwise the table is too small and just count the outlier toward the nonContValueCountMax gaps. - if (switchValuesCount >= 1 + minSwitchTableSize) - { - if (switchValues.GetSurePresentRef(1).Value - switchValues.GetSurePresentRef(0).Value > valueGapMin) - firstTestValueIdx = 1; - - if (firstTestValueIdx == 0 || switchValuesCount >= 2 + minSwitchTableSize) { - if (switchValues.GetSurePresentRef(lastTestValueIdx).Value - switchValues.GetSurePresentRef(lastTestValueIdx - 1).Value > valueGapMin) - --lastTestValueIdx; - } - } - Debug.Assert(lastTestValueIdx - firstTestValueIdx + 1 >= minSwitchTableSize, "Postcondition: switch table between outliers should be of enough size"); - var prevSwitchValue = switchValues.GetSurePresentRef(firstTestValueIdx).Value; - for (var i = firstTestValueIdx + 1; i <= lastTestValueIdx; ++i) - { - var currValue = switchValues.GetSurePresentRef(i).Value; - var valueGap = currValue - prevSwitchValue; - Debug.Assert(valueGap > 0, $"The values should be in increased order and non-equal but found {currValue} <= {prevSwitchValue}"); - - if (valueGap > valueGapMin || - valueGap > 1 && ++contValueCount > nonContValueCountMax) - { - valuesConditionsMet = false; - break; - } - - prevSwitchValue = currValue; - actualSwitchTableSize += (int)valueGap; - } - - if (valuesConditionsMet) - { - Debug.Assert(actualSwitchTableSize >= lastTestValueIdx - firstTestValueIdx + 1, // take outliers into account - $"The switch table size should be at least as large as the case count, but found {actualSwitchTableSize} < {lastTestValueIdx - firstTestValueIdx + 1}"); - - var endOfSwitchLabel = il.DefineLabel(); - var defaultBodyLabel = defaultBody == null ? endOfSwitchLabel : il.DefineLabel(); - -#if TESTING || DEBUG - SmallList, NoArrayPool> sameCaseLabelIndexes = default; -#else - SmallList, NoArrayPool> sameCaseLabelIndexes = default; -#endif - sameCaseLabelIndexes.InitCount(multipleTestValuesLabelsId); // may stay empty if id is 0 - -#if TESTING || DEBUG - // Test the pool at work - _labelPool ??= new Label[8][]; -#endif - // Let define and collect the labels that we will be using for switch table and optional outliers - ProvidedArrayPool> labelPool = default; - labelPool.Init(_labelPool, 8); - var switchTableLabels = labelPool.RentExactOrNew(actualSwitchTableSize); - - // Define outliers labels - Label minOutlierLabel = default, maxOutlierLabel = default; - int minOutlierMultiTestCaseBodyIdx = -1, maxOutlierMultiTestCaseBodyIdx = -1; - int minOutlierCaseBodyIdx = -1, maxOutlierCaseBodyIdx = -1; - if (firstTestValueIdx == 1) - { - ref var minOutlier = ref switchValues.GetSurePresentRef(0); - minOutlierCaseBodyIdx = minOutlier.CaseBodyIdx; - minOutlierMultiTestCaseBodyIdx = minOutlier.MultiTestValCaseBodyIdxPlusOne - 1; - minOutlierLabel = il.DefineLabel(); // define the label that later can be shared with others in switch table or with max outlier - } - if (lastTestValueIdx == switchValuesCount - 2) - { - ref var maxOutlier = ref switchValues.GetSurePresentRef(switchValuesCount - 1); - maxOutlierCaseBodyIdx = maxOutlier.CaseBodyIdx; - maxOutlierMultiTestCaseBodyIdx = maxOutlier.MultiTestValCaseBodyIdxPlusOne - 1; - maxOutlierLabel = maxOutlierMultiTestCaseBodyIdx != -1 && maxOutlierMultiTestCaseBodyIdx == minOutlierMultiTestCaseBodyIdx - ? minOutlierLabel : il.DefineLabel(); // Check if the max outlier share the label with some and if this some is the min outlier - } - - // Let's start with the continuous values from the start - var firstTestValue = switchValues.GetSurePresentRef(firstTestValueIdx).Value; - prevSwitchValue = firstTestValue - 1; // init the prev value to by 1 before the first for calc purposes - var switchTableIndex = 0; - for (var v = firstTestValueIdx; v <= lastTestValueIdx; ++v) - { - var currSwitchVal = switchValues.GetSurePresentRef(v); - var currSwitchValue = currSwitchVal.Value; - var valueGap = currSwitchValue - prevSwitchValue; - if (valueGap > 1) - { - var endOfGap = switchTableIndex + valueGap - 1; - while (switchTableIndex < endOfGap) - switchTableLabels[switchTableIndex++] = defaultBodyLabel; // the label will be same as endOfSwitchLabel if no default body present - } - - var caseIndex = currSwitchVal.MultiTestValCaseBodyIdxPlusOne - 1; - if (caseIndex != -1) - { - Debug.Assert(caseIndex < sameCaseLabelIndexes.Count, "Invalid MultiTestValCaseBodyIdxPlusOne in switch case values"); - ref var labelIndexPlusOneRef = ref sameCaseLabelIndexes.GetSurePresentRef(caseIndex); - if (labelIndexPlusOneRef > 0) - { - switchTableLabels[switchTableIndex] = switchTableLabels[labelIndexPlusOneRef - 1]; - } - else if (caseIndex == minOutlierMultiTestCaseBodyIdx | caseIndex == maxOutlierMultiTestCaseBodyIdx) - { - switchTableLabels[switchTableIndex] = caseIndex == minOutlierMultiTestCaseBodyIdx ? minOutlierLabel : maxOutlierLabel; - labelIndexPlusOneRef = switchTableIndex + 1; - } - else - { - switchTableLabels[switchTableIndex] = il.DefineLabel(); - labelIndexPlusOneRef = switchTableIndex + 1; - } - } - else - switchTableLabels[switchTableIndex] = il.DefineLabel(); - - prevSwitchValue = currSwitchVal.Value; - ++switchTableIndex; - } - - // Emit the switch value here only after we sure about proceeding with switch table - if (!TryEmit(switchValueExpr, paramExprs, il, ref closure, setup, switchValueParent, -1)) - return false; - - // Emit the min outlier before the switch table - var switchValVar = -1; - if (firstTestValueIdx == 1) - { - // store and use the switch value over in case of the outlier, otherwise just use whatever is on stack from the prev TryEmit - EmitStoreAndLoadLocalVariable(il, switchValVar = il.GetNextLocalVarIndex(switchValueType)); - EmitLoadConstantLong(il, switchValues.GetSurePresentRef(0).Value); // Load the min outlier - il.Demit(OpCodes.Beq, minOutlierLabel); - EmitLoadLocalVariable(il, switchValVar); // Load the switch var on stack for the next operation - } - else if (lastTestValueIdx == switchValuesCount - 2) // if the max outlier is defined, store and load the switch value - { - EmitStoreAndLoadLocalVariable(il, switchValVar = il.GetNextLocalVarIndex(switchValueType)); - } - - // Before emitting switch we need to normalize the switch value to start from zero - if (firstTestValue != 0) - { - EmitLoadConstantLong(il, firstTestValue); - il.Demit(firstTestValue > 0 ? OpCodes.Sub : OpCodes.Add); - } - - // Emit the switch instruction - il.DemitSwitch(switchTableLabels); - - // Emit the max outlier after the switch table - if (lastTestValueIdx == switchValuesCount - 2) - { - Debug.Assert(switchValVar != -1); - EmitLoadLocalVariable(il, switchValVar); - EmitLoadConstantLong(il, switchValues.GetSurePresentRef(switchValuesCount - 1).Value); // Load the max outlier - il.Demit(OpCodes.Beq, maxOutlierLabel); // If switch value matches the outlier break to its label - } - - // For the values OUTSIDE of switch table range, immediately branch to the default or to the end of the switch - il.Demit(OpCodes.Br, defaultBodyLabel); // the label is the same as endOfSwitchLabel if no default body present - - for (var i = 0; i < caseCount; ++i) - { - var cs = cases[i]; - if (i == minOutlierCaseBodyIdx | i == maxOutlierCaseBodyIdx) - il.DmarkLabel(i == minOutlierCaseBodyIdx ? minOutlierLabel : maxOutlierLabel); - else - { - // First test value is enough to find the corresponding label in switch table to mark the case body - // because all test values share the same case body and the same label, so the first test value will do. - var testValExpr = cs.TestValues[0]; - var testValue = testValExpr is ConstantExpression constExpr ? ConvertValueObjectToLong(constExpr.Value) : 0L; - var labelIndex = (int)(testValue - firstTestValue); - il.DmarkLabel(switchTableLabels[labelIndex]); - } - - if (!TryEmit(cs.Body, paramExprs, il, ref closure, setup, parent)) - return false; - il.Demit(OpCodes.Br, endOfSwitchLabel); - } - - labelPool.ReuseIfPossible(switchTableLabels); - labelPool.MergeInto(ref _labelPool); - - if (defaultBody != null) - { - il.DmarkLabel(defaultBodyLabel); - if (!TryEmit(defaultBody, paramExprs, il, ref closure, setup, parent)) - return false; - } - - il.DmarkLabel(endOfSwitchLabel); - return true; - } - } - - var switchValueIsNullable = switchValueType.IsNullable(); - Type switchNullableUnderlyingValueType = null; - MethodInfo switchNullableHasValueMethod = null; - FieldInfo switchNullableUnsafeValueField = null; - if (switchValueIsNullable) - { - switchNullableUnderlyingValueType = Nullable.GetUnderlyingType(switchValueType); - switchNullableHasValueMethod = switchValueType.GetNullableHasValueGetterMethod(); - switchNullableUnsafeValueField = switchValueType.GetNullableValueUnsafeAkaGetValueOrDefaultMethod(); - } - - var checkType = switchNullableUnderlyingValueType ?? switchValueType; - var equalityMethod = customEqualMethod != null - ? customEqualMethod - : !checkType.IsPrimitive && !checkType.IsEnum - ? FindBinaryOperandMethod("op_Equality", switchValueType, switchValueType, switchValueType, typeof(bool)) - ?? _objectEqualsMethod - : null; - - var isEqualityMethodForUnderlyingNullable = false; - int param0ByRefIndex = -1, param1ByRefIndex = -1; - if (equalityMethod != null) - { - switchValueParent |= ParentFlags.Call; - var paramInfos = equalityMethod.GetParameters(); - Debug.Assert(paramInfos.Length == 2); - var paramType = paramInfos[0].ParameterType; - isEqualityMethodForUnderlyingNullable = paramType == switchNullableUnderlyingValueType; - if (paramType.IsByRef) - param0ByRefIndex = 0; - if (paramInfos[1].ParameterType.IsByRef) - param1ByRefIndex = 1; - } - - // Emit the switch value once and store it in the local variable for comparison in cases below - if (!TryEmit(switchValueExpr, paramExprs, il, ref closure, setup, switchValueParent, param0ByRefIndex)) - return false; - - if (caseCount == 0) // see #440 - { - il.Demit(OpCodes.Pop); // remove the switch value result - return defaultBody == null || TryEmit(defaultBody, paramExprs, il, ref closure, setup, parent); - } - - var switchValueVar = EmitStoreLocalVariable(il, switchValueType); - - var switchEndLabel = il.DefineLabel(); - -#if TESTING || DEBUG - // Check the heap/pool part of the labels in debug mode - SmallList, ProvidedArrayPool>> caseLabels = default; -#else - SmallList, ProvidedArrayPool>> caseLabels = default; -#endif - caseLabels.Pool.Init(_labelPool, 8); - - for (var caseIndex = 0; caseIndex < caseCount; ++caseIndex) - { - var cs = cases[caseIndex]; - var caseBodyLabel = il.DefineLabel(); - caseLabels.Add(caseBodyLabel); - - foreach (var caseTestValue in cs.TestValues) - { - if (!switchValueIsNullable) - { - EmitLoadLocalVariable(il, switchValueVar); - if (!TryEmit(caseTestValue, paramExprs, il, ref closure, setup, switchValueParent, param1ByRefIndex)) - return false; - if (equalityMethod == null) - { - il.Demit(OpCodes.Beq, caseBodyLabel); - continue; - } - - if (!EmitMethodCall(il, equalityMethod)) - return false; - il.Demit(OpCodes.Brtrue, caseBodyLabel); - continue; - } - - if (equalityMethod != null & !isEqualityMethodForUnderlyingNullable) - { - EmitLoadLocalVariable(il, switchValueVar); - if (!TryEmit(caseTestValue, paramExprs, il, ref closure, setup, switchValueParent, param1ByRefIndex) || - !EmitMethodCall(il, equalityMethod)) - return false; - il.Demit(OpCodes.Brtrue, caseBodyLabel); - continue; - } - - if (equalityMethod == null) - { - // short-circuit the comparison with the null, if the switch value has value == false the let's do a Brfalse - if (caseTestValue is ConstantExpression r && r.Value == null) - { - EmitLoadLocalVariableAddress(il, switchValueVar); - EmitMethodCall(il, switchNullableHasValueMethod); - il.Demit(OpCodes.Brfalse, caseBodyLabel); - continue; - } - } - - // Compare the switch value with the case value via Ceq or comparison method and then compare the HasValue of both - EmitLoadLocalVariableAddress(il, switchValueVar); - il.Demit(OpCodes.Ldfld, switchNullableUnsafeValueField); - if (!TryEmit(caseTestValue, paramExprs, il, ref closure, setup, switchValueParent, param1ByRefIndex)) - return false; - var caseValueVar = EmitStoreAndLoadLocalVariableAddress(il, switchValueType); - il.Demit(OpCodes.Ldfld, switchNullableUnsafeValueField); - if (equalityMethod == null) - il.Demit(OpCodes.Ceq); - else if (!EmitMethodCall(il, equalityMethod)) - return false; - - EmitLoadLocalVariableAddress(il, switchValueVar); - EmitMethodCall(il, switchNullableHasValueMethod); - EmitLoadLocalVariableAddress(il, caseValueVar); - EmitMethodCall(il, switchNullableHasValueMethod); - il.Demit(OpCodes.Ceq); - - il.Demit(OpCodes.And); // both the Nullable values and HashValue results need to be true - il.Demit(OpCodes.Brtrue, caseBodyLabel); - } - } - - if (defaultBody != null && !TryEmit(defaultBody, paramExprs, il, ref closure, setup, parent)) - return false; - il.Demit(OpCodes.Br, switchEndLabel); - - for (var caseIndex = 0; caseIndex < caseCount; ++caseIndex) - { - il.DmarkLabel(caseLabels[caseIndex]); - var cs = cases[caseIndex]; - if (!TryEmit(cs.Body, paramExprs, il, ref closure, setup, parent)) - return false; - - il.Demit(OpCodes.Br, switchEndLabel); - } - - caseLabels.FreePooled(); - caseLabels.Pool.MergeInto(ref _labelPool); - - il.DmarkLabel(switchEndLabel); - return true; - } - - // todo: @perf cache found method, because for some cases there many methods to search from, e.g. 157 methods in BigInteger - private static MethodInfo FindBinaryOperandMethod( - string methodName, Type sourceType, Type leftOpType, Type rightOpType, Type resultType) - { - var methods = sourceType.GetMethods(); - for (var i = 0; i < methods.Length; i++) - { - var m = methods[i]; - if (m.IsSpecialName && m.IsStatic && m.Name == methodName && m.ReturnType == resultType) - { - var ps = m.GetParameters(); - if (ps.Length == 2 && ps[0].ParameterType == leftOpType && ps[1].ParameterType == rightOpType) - return m; - } - } - return null; - } - - private static bool TryEmitComparison( - Expression left, Expression right, Type exprType, ExpressionType nodeType, -#if LIGHT_EXPRESSION - IParameterProvider paramExprs, -#else - IReadOnlyList paramExprs, -#endif - ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent) - { - var leftType = left.Type; - var leftIsNullable = leftType.IsNullable(); - var rightType = right.Type; - - // If one operand is `null` then the equality comparison can be simplified to `ldnull, ceq` - var rightIsNull = IsNullContainingExpression(right); - var comparingToRightNull = rightIsNull & rightType.IsClass; - - // Coalesce the right object type to the more specific left type - if (comparingToRightNull & rightType == typeof(object)) - rightType = leftType; - - var leftIsNull = IsNullContainingExpression(left); - var comparingToLeftNull = leftIsNull & leftType.IsClass; - if (!comparingToRightNull && comparingToLeftNull & leftType == typeof(object)) - leftType = rightType; - - var operandParent = parent & ~ParentFlags.IgnoreResult & ~ParentFlags.InstanceAccess; - - // short-circuit the comparison with null on the right - var isEqualityOp = nodeType == ExpressionType.Equal | nodeType == ExpressionType.NotEqual; - if (isEqualityOp) - { - if (leftIsNullable & rightIsNull) - { - if (!TryEmit(left, paramExprs, il, ref closure, setup, operandParent)) - return false; - - // See #341 `Nullable_decimal_parameter_with_decimal_constant_comparison_cases` - if (!closure.LastEmitIsAddress && !(left is ParameterExpression p && p.IsByRef)) // Nullable type does not track IsByRef for some reason, so we check the param explicitly, see #461 `Case_equal_nullable_and_object_null` - EmitStoreAndLoadLocalVariableAddress(il, leftType); - - EmitMethodCall(il, leftType.GetNullableHasValueGetterMethod()); - if (nodeType == ExpressionType.Equal) - EmitEqualToZeroOrNull(il); - return il.EmitPopIfIgnoreResult(parent); - } - - if (leftIsNull && rightType.IsNullable()) - { - if (!TryEmit(right, paramExprs, il, ref closure, setup, operandParent)) - return false; - - if (!closure.LastEmitIsAddress && !(right is ParameterExpression p && p.IsByRef)) - EmitStoreAndLoadLocalVariableAddress(il, rightType); - - EmitMethodCall(il, rightType.GetNullableHasValueGetterMethod()); - if (nodeType == ExpressionType.Equal) - EmitEqualToZeroOrNull(il); - return il.EmitPopIfIgnoreResult(parent); - } - } - - var lVarIndex = -1; - var rightIsComplexExpression = false; - // just load the `null` later when done with the right operand, without need for go to nested TryEmit call - // and store, load the left result for the complex expressions, see `IsComplexExpression` and #422 - if (!leftIsNull) - { - if (!TryEmit(left, paramExprs, il, ref closure, setup, operandParent)) - return false; - - // save the left result to restore it later after the complex expression, see #422 - if (rightIsComplexExpression = right.IsComplexExpression()) - lVarIndex = EmitStoreLocalVariable(il, leftType); - else if (leftIsNullable) - { - lVarIndex = EmitStoreAndLoadLocalVariableAddress(il, leftType); - il.Demit(OpCodes.Ldfld, leftType.GetNullableValueUnsafeAkaGetValueOrDefaultMethod()); - leftType = Nullable.GetUnderlyingType(leftType); - } - } - - if (rightIsNull) - il.Demit(OpCodes.Ldnull); - else if (!TryEmit(right, paramExprs, il, ref closure, setup, operandParent)) - return false; - - if (comparingToLeftNull | comparingToRightNull || - (leftType != rightType && leftType.IsClass && rightType.IsClass && - (leftType == typeof(object) | rightType == typeof(object)))) - { - // If the operation is not Equal or NotEqual then comparison with null is not possible - if (!isEqualityOp) - return false; - - if (leftIsNull) - il.Demit(OpCodes.Ldnull); - else if (rightIsComplexExpression) - EmitLoadLocalVariable(il, lVarIndex); // the order of comparison does not matter, because equality ops are commutative - - il.Demit(OpCodes.Ceq); - if (nodeType == ExpressionType.NotEqual) - EmitEqualToZeroOrNull(il); - - return il.EmitPopIfIgnoreResult(parent); - } - - var rVarIndex = -1; - if (rightIsComplexExpression) - { - rVarIndex = EmitStoreLocalVariable(il, rightType); - if (!leftIsNullable) - EmitLoadLocalVariable(il, lVarIndex); - else - { - EmitLoadLocalVariableAddress(il, lVarIndex); - il.Demit(OpCodes.Ldfld, leftType.GetNullableValueUnsafeAkaGetValueOrDefaultMethod()); - leftType = Nullable.GetUnderlyingType(leftType); - } - - if (!rightType.IsNullable()) - EmitLoadLocalVariable(il, rVarIndex); - else - { - EmitLoadLocalVariableAddress(il, rVarIndex); - il.Demit(OpCodes.Ldfld, rightType.GetNullableValueUnsafeAkaGetValueOrDefaultMethod()); - rightType = Nullable.GetUnderlyingType(rightType); - } - } - else if (leftIsNull) - { - // here we're handling only non-nullable right, the nullable right with null left is handled above - rVarIndex = EmitStoreLocalVariable(il, rightType); - il.Demit(OpCodes.Ldnull); - EmitLoadLocalVariable(il, rVarIndex); - } - else if (rightType.IsNullable()) - { - rVarIndex = EmitStoreAndLoadLocalVariableAddress(il, rightType); - il.Demit(OpCodes.Ldfld, rightType.GetNullableValueUnsafeAkaGetValueOrDefaultMethod()); - rightType = Nullable.GetUnderlyingType(rightType); - } - - if (!leftType.IsPrimitive && !leftType.IsEnum) - { - var methodName - = nodeType == ExpressionType.Equal ? "op_Equality" - : nodeType == ExpressionType.NotEqual ? "op_Inequality" - : nodeType == ExpressionType.GreaterThan ? "op_GreaterThan" - : nodeType == ExpressionType.GreaterThanOrEqual ? "op_GreaterThanOrEqual" - : nodeType == ExpressionType.LessThan ? "op_LessThan" - : nodeType == ExpressionType.LessThanOrEqual ? "op_LessThanOrEqual" - : null; - if (methodName == null) - return false; - - var method = FindBinaryOperandMethod(methodName, leftType, leftType, rightType, typeof(bool)); - if (method == null & leftType != rightType) - method = FindBinaryOperandMethod(methodName, rightType, leftType, rightType, typeof(bool)); - if (method != null) - { - var ok = EmitMethodCall(il, method); - if (leftIsNullable) - goto nullableCheck; - return ok; - } - - if (!isEqualityOp) - return false; // todo: @unclear what is the alternative? - - EmitMethodCall(il, _objectEqualsMethod); - if (nodeType == ExpressionType.NotEqual) // invert result for not equal - EmitEqualToZeroOrNull(il); - - if (leftIsNullable) - goto nullableCheck; - - return il.EmitPopIfIgnoreResult(parent); - } - - // handle primitives comparison - switch (nodeType) - { - case ExpressionType.Equal: - il.Demit(OpCodes.Ceq); - break; - case ExpressionType.NotEqual: - il.Demit(OpCodes.Ceq); - EmitEqualToZeroOrNull(il); - break; - case ExpressionType.LessThan: - il.Demit(OpCodes.Clt); - break; - case ExpressionType.GreaterThan: - il.Demit(OpCodes.Cgt); - break; - case ExpressionType.GreaterThanOrEqual: - // simplifying by using the LessThen (Clt) and comparing with negative outcome (Ceq 0) - if (leftType.IsUnsigned() && rightType.IsUnsigned() || - (leftType.IsFloatingPoint() || rightType.IsFloatingPoint())) - il.Demit(OpCodes.Clt_Un); - else - il.Demit(OpCodes.Clt); - EmitEqualToZeroOrNull(il); - break; - case ExpressionType.LessThanOrEqual: - // simplifying by using the GreaterThen (Cgt) and comparing with negative outcome (Ceq 0) - if (leftType.IsUnsigned() && rightType.IsUnsigned() || - (leftType.IsFloatingPoint() || rightType.IsFloatingPoint())) - il.Demit(OpCodes.Cgt_Un); - else - il.Demit(OpCodes.Cgt); - EmitEqualToZeroOrNull(il); - break; - - default: - return false; - } - - nullableCheck: - if (leftIsNullable) - { - var leftNullableHasValueGetterMethod = left.Type.GetNullableHasValueGetterMethod(); // asking from the left.Type because leftType now is set to the underlying type - - EmitLoadLocalVariableAddress(il, lVarIndex); - EmitMethodCall(il, leftNullableHasValueGetterMethod); - - var isLiftedToNull = exprType == typeof(bool?); - var leftHasValueVar = -1; - if (isLiftedToNull) - EmitStoreAndLoadLocalVariable(il, leftHasValueVar = il.GetNextLocalVarIndex(typeof(bool))); - - // ReSharper disable once AssignNullToNotNullAttribute - EmitLoadLocalVariableAddress(il, rVarIndex); - EmitMethodCall(il, leftNullableHasValueGetterMethod); - - var rightHasValueVar = -1; - if (isLiftedToNull) - EmitStoreAndLoadLocalVariable(il, rightHasValueVar = il.GetNextLocalVarIndex(typeof(bool))); - - switch (nodeType) - { - case ExpressionType.Equal: - il.Demit(OpCodes.Ceq); // compare both HasValue calls - il.Demit(OpCodes.And); // both results need to be true - break; - - case ExpressionType.NotEqual: - il.Demit(OpCodes.Ceq); - EmitEqualToZeroOrNull(il); - il.Demit(OpCodes.Or); - break; - - case ExpressionType.LessThan: - case ExpressionType.GreaterThan: - case ExpressionType.LessThanOrEqual: - case ExpressionType.GreaterThanOrEqual: - // left.HasValue `and` right.HasValue - il.Demit(OpCodes.And); - // `and` the prev result of comparison operation - il.Demit(OpCodes.And); - break; - - default: - return false; - } - - if (isLiftedToNull) - { - var resultLabel = il.DefineLabel(); - var isNullLabel = il.DefineLabel(); - EmitLoadLocalVariable(il, leftHasValueVar); - il.Demit(OpCodes.Brfalse, isNullLabel); - EmitLoadLocalVariable(il, rightHasValueVar); - il.Demit(OpCodes.Brtrue, resultLabel); - il.DmarkLabel(isNullLabel); - il.Demit(OpCodes.Pop); - il.Demit(OpCodes.Ldnull); - il.DmarkLabel(resultLabel); - } - } - - return il.EmitPopIfIgnoreResult(parent); - } - - private static bool TryEmitArithmetic(Expression left, Expression right, ExpressionType nodeType, Type exprType, -#if LIGHT_EXPRESSION - IParameterProvider paramExprs, -#else - IReadOnlyList paramExprs, -#endif - ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent) - { - var flags = (parent - & ~(ParentFlags.IgnoreResult | ParentFlags.InstanceCall | - ParentFlags.LambdaCall | ParentFlags.ReturnByRef)) - | ParentFlags.Arithmetic; - - var noNullableValueLabel = default(Label); - var leftType = left.Type; - var leftIsNullable = leftType.IsNullable(); - var leftVar = -1; - var leftValueVar = -1; - if (leftIsNullable) - { - noNullableValueLabel = il.DefineLabel(); - if (!TryEmit(left, paramExprs, il, ref closure, setup, flags | ParentFlags.InstanceCall)) - return false; - - leftVar = EmitStoreAndLoadLocalVariableAddress(il, leftType); - EmitMethodCall(il, leftType.GetNullableHasValueGetterMethod()); - il.Demit(OpCodes.Brfalse, noNullableValueLabel); - - EmitLoadLocalVariableAddress(il, leftVar); - il.Demit(OpCodes.Ldfld, leftType.GetNullableValueUnsafeAkaGetValueOrDefaultMethod()); - leftValueVar = EmitStoreLocalVariable(il, Nullable.GetUnderlyingType(leftType)); - } - else if (!TryEmit(left, paramExprs, il, ref closure, setup, flags)) - return false; - - var rightIsNullable = false; - if (right == null) // indicates the increment/decrement operation - { - EmitIncOrDec(il, nodeType == ExpressionType.Add); - } - else - { - // Stores the left value for later to restore it after the complex right emit, - // it prevents the problems in cases of right being a block, try-catch, etc. - // see `Using_try_finally_as_arithmetic_operand_use_void_block_in_finally` - var rightType = right.Type; - if (leftValueVar == -1 && right.IsComplexExpression()) - leftValueVar = EmitStoreLocalVariable(il, leftType); - - var rightVar = -1; - var rightValueVar = -1; - rightIsNullable = rightType.IsNullable(); - if (rightIsNullable) - { - if (!TryEmit(right, paramExprs, il, ref closure, setup, flags | ParentFlags.InstanceCall)) - return false; - - rightVar = EmitStoreAndLoadLocalVariableAddress(il, rightType); - - EmitMethodCall(il, rightType.GetNullableHasValueGetterMethod()); - il.Demit(OpCodes.Brfalse, noNullableValueLabel); - - EmitLoadLocalVariableAddress(il, rightVar); - il.Demit(OpCodes.Ldfld, rightType.GetNullableValueUnsafeAkaGetValueOrDefaultMethod()); - rightValueVar = EmitStoreLocalVariable(il, Nullable.GetUnderlyingType(rightType)); - } - else if (!TryEmit(right, paramExprs, il, ref closure, setup, flags)) - return false; - - // Means that it was complex right and the result of the left operation was stored - // and should be restored now, so the left and right go in order before the arithmetic operation - if (leftValueVar != -1) - { - if (rightValueVar == -1) - rightValueVar = EmitStoreLocalVariable(il, rightType); - EmitLoadLocalVariable(il, leftValueVar); - EmitLoadLocalVariable(il, rightValueVar); - } - - if (!TryEmitArithmeticOperation(leftType, rightType, nodeType, exprType, il)) - return false; - } - - if (leftIsNullable | rightIsNullable) - { - var valueLabel = il.DefineLabel(); - il.Demit(OpCodes.Br, valueLabel); - - il.DmarkLabel(noNullableValueLabel); - - if (exprType.IsNullable()) - { - EmitLoadLocalVariable(il, InitValueTypeVariable(il, exprType)); - var endLabel = il.DefineLabel(); - il.Demit(OpCodes.Br_S, endLabel); - il.DmarkLabel(valueLabel); - il.Demit(OpCodes.Newobj, exprType.GetNullableConstructor()); - il.DmarkLabel(endLabel); - } - else - { - il.Demit(OpCodes.Ldc_I4_0); - il.DmarkLabel(valueLabel); - } - } - - il.EmitPopIfIgnoreResult(parent); - return true; - } - - private static MethodInfo _stringStringConcatMethod, _stringObjectConcatMethod; - private static MethodInfo GetStringConcatMethod(Type paraType) - { - var methods = typeof(string).GetMethods(); - for (var i = 0; i < methods.Length; i++) - { - var m = methods[i]; - if (m.IsStatic && m.Name == "Concat" && - m.GetParameters().Length == 2 && m.GetParameters()[0].ParameterType == paraType) - return m; - } - return null; - } - - private static bool TryEmitArithmeticOperation(Type leftType, Type rightType, ExpressionType arithmeticNodeType, Type exprType, ILGenerator il) - { - if (!exprType.IsPrimitive) - { - if (exprType.IsNullable()) - exprType = Nullable.GetUnderlyingType(exprType); - - if (!exprType.IsPrimitive) - { - var opMethodName = arithmeticNodeType.GetArithmeticBinaryOperatorMethodName(); - if (opMethodName == null) - return false; // todo: @feature should return specific error - - MethodInfo method = null; - if (exprType != typeof(string)) - { - // Note, that the result operation Type may be different from the operand Type, - // e.g. `TimeSpan op_Subtraction(DateTime, DateTime)`, that mean we should look - // for the specific method in the operand types, then in the result (expr) type. - method = FindBinaryOperandMethod(opMethodName, leftType, leftType, rightType, exprType); - if (method == null & leftType != rightType) - method = FindBinaryOperandMethod(opMethodName, rightType, leftType, rightType, exprType); - if (method == null & leftType != exprType & rightType != exprType) - method = FindBinaryOperandMethod(opMethodName, exprType, leftType, rightType, exprType); - // todo: @feature should return specific error - return method != null && EmitMethodCall(il, method); - } - - method = leftType != rightType | leftType != typeof(string) - ? _stringObjectConcatMethod ?? (_stringObjectConcatMethod = GetStringConcatMethod(typeof(object))) - : _stringStringConcatMethod ?? (_stringStringConcatMethod = GetStringConcatMethod(typeof(string))); - - return method != null && EmitMethodCallOrVirtualCall(il, method); - } - } - - var opCode = arithmeticNodeType switch - { - ExpressionType.Add => OpCodes.Add, - ExpressionType.AddChecked => exprType.IsUnsigned() ? OpCodes.Add_Ovf_Un : OpCodes.Add_Ovf, - ExpressionType.Subtract => OpCodes.Sub, - ExpressionType.SubtractChecked => exprType.IsUnsigned() ? OpCodes.Sub_Ovf_Un : OpCodes.Sub_Ovf, - ExpressionType.Multiply => OpCodes.Mul, - ExpressionType.MultiplyChecked => exprType.IsUnsigned() ? OpCodes.Mul_Ovf_Un : OpCodes.Mul_Ovf, - ExpressionType.Divide => OpCodes.Div, - ExpressionType.Modulo => OpCodes.Rem, - ExpressionType.And => OpCodes.And, - ExpressionType.Or => OpCodes.Or, - ExpressionType.ExclusiveOr => OpCodes.Xor, - ExpressionType.LeftShift => OpCodes.Shl, - ExpressionType.RightShift => exprType.IsUnsigned() ? OpCodes.Shr_Un : OpCodes.Shr, - ExpressionType.Power => OpCodes.Call, - _ => throw new NotSupportedException("Unsupported arithmetic operation: " + arithmeticNodeType) - }; - - if (opCode.Equals(OpCodes.Call)) - il.Demit(OpCodes.Call, typeof(Math).FindMethod("Pow")); - else - il.Demit(opCode); - return true; - } - - private static bool TryEmitLogicalOperator(BinaryExpression expr, ExpressionType nodeType, -#if LIGHT_EXPRESSION - IParameterProvider paramExprs, -#else - IReadOnlyList paramExprs, -#endif - ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent) - { - if (!TryEmit(expr.Left, paramExprs, il, ref closure, setup, parent)) - return false; - - var labelSkipRight = il.DefineLabel(); - il.Demit(nodeType == ExpressionType.AndAlso ? OpCodes.Brfalse : OpCodes.Brtrue, labelSkipRight); - - if (!TryEmit(expr.Right, paramExprs, il, ref closure, setup, parent)) - return false; - - var labelDone = il.DefineLabel(); - il.Demit(OpCodes.Br, labelDone); - - il.DmarkLabel(labelSkipRight); // label the second branch - il.Demit(nodeType == ExpressionType.AndAlso ? OpCodes.Ldc_I4_0 : OpCodes.Ldc_I4_1); - il.DmarkLabel(labelDone); - - return true; - } - - private static bool IsNullContainingExpression(Expression expr) => - expr is DefaultExpression ld && (ld.Type.IsClass || ld.Type.IsNullable()) || - expr is ConstantExpression lc && lc.Value == null; - - private static bool TryEmitConditional( - Expression testExpr, Expression ifTrueExpr, Expression ifFalseExpr, - // Type type, // todo: @wip what about the type, what if it is a void? -#if LIGHT_EXPRESSION - IParameterProvider paramExprs, -#else - IReadOnlyList paramExprs, -#endif - ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent) - { - testExpr = Tools.TryReduceConditionalTest(testExpr); - var testNodeType = testExpr.NodeType; - - // Detect a simplistic case when we can use `Brtrue` or `Brfalse`. - // We are checking the negative result to go into the `IfFalse` branch, - // because for `IfTrue` we don't need to jump and just need to proceed emitting the `IfTrue` expression - // - // The cases: - // `x == true` => `Brfalse` - // `x != true` => `Brtrue` - // `x == false` => `Brtrue` - // `x != false` => `Brfalse` - // `x == null` => `Brtrue` - // `x != null` => `Brfalse` - // `x == 0` => `Brtrue` - // `x != 0` => `Brfalse` - - var useBrFalseOrTrue = -1; // 0 - is comparison with Zero (0, null, false), 1 - is comparison with (true) - Type nullOfValueType = null; - if (testExpr is BinaryExpression tb && - (testNodeType == ExpressionType.Equal | testNodeType == ExpressionType.NotEqual)) - { - var testLeftExpr = tb.Left; - var testRightExpr = tb.Right; - - Expression oppositeTestExpr = null; - var sideConstExpr = testRightExpr as ConstantExpression ?? testLeftExpr as ConstantExpression; - if (sideConstExpr != null) - { - oppositeTestExpr = sideConstExpr == testLeftExpr ? testRightExpr : testLeftExpr; - var sideConstVal = sideConstExpr.Value; - if (sideConstVal == null) // todo: @perf we need to optimize for the Default as well - { - useBrFalseOrTrue = 0; - if (oppositeTestExpr.Type.IsNullable()) - nullOfValueType = oppositeTestExpr.Type; - } - else if (sideConstVal is bool boolConst) - useBrFalseOrTrue = boolConst ? 1 : 0; - else if (sideConstVal is int intConst && intConst == 0) - useBrFalseOrTrue = 0; // Brtrue does not work for `1`, you need to use Beq, or similar - else if (sideConstVal is byte bytConst && bytConst == 0) - useBrFalseOrTrue = 0; - } - else - { - var sideDefaultExpr = testRightExpr as DefaultExpression ?? testLeftExpr as DefaultExpression; - if (sideDefaultExpr != null) - { - oppositeTestExpr = sideDefaultExpr == testLeftExpr ? testRightExpr : testLeftExpr; - var testSideType = sideDefaultExpr.Type; - // except decimal, because its 0 is Decimal.Zero a struct and is not working with Brtrue/Brfalse - if (testSideType.IsPrimitiveWithZeroDefaultExceptDecimal()) - useBrFalseOrTrue = 0; - else if (testSideType.IsClass || testSideType.IsNullable()) - { - useBrFalseOrTrue = 0; - if (oppositeTestExpr.Type.IsNullable()) - nullOfValueType = oppositeTestExpr.Type; - } - } - } - - if (useBrFalseOrTrue != -1 && - !TryEmit(oppositeTestExpr, paramExprs, il, ref closure, setup, parent & ~ParentFlags.IgnoreResult)) - return false; - } - - if (useBrFalseOrTrue == -1 && - !TryEmit(testExpr, paramExprs, il, ref closure, setup, parent & ~ParentFlags.IgnoreResult)) - return false; - - if (nullOfValueType != null) - { - if (!closure.LastEmitIsAddress) - EmitStoreAndLoadLocalVariableAddress(il, nullOfValueType); - EmitMethodCall(il, nullOfValueType.GetNullableHasValueGetterMethod()); - } - - var labelIfFalse = il.DefineLabel(); - - // todo: @perf try to recognize the patterns like `b == 1` and replace Ceq, Brtrue with Beq, and respectively Ceq, Brfalse with Bne_Un - // for this we need to inline here the logic from the TryEmitComparison. - if ((testNodeType == ExpressionType.Equal & useBrFalseOrTrue == 0) || - (testNodeType == ExpressionType.NotEqual & useBrFalseOrTrue == 1)) - il.Demit(OpCodes.Brtrue, labelIfFalse); - else - il.Demit(OpCodes.Brfalse, labelIfFalse); - - if (!TryEmit(ifTrueExpr, paramExprs, il, ref closure, setup, parent)) - return false; - - if (ifFalseExpr.NodeType == ExpressionType.Default && ifFalseExpr.Type == typeof(void)) - il.DmarkLabel(labelIfFalse); - else - { - var labelDone = il.DefineLabel(); - il.Demit(OpCodes.Br, labelDone); - il.DmarkLabel(labelIfFalse); - if (!TryEmit(ifFalseExpr, paramExprs, il, ref closure, setup, parent)) - return false; - il.DmarkLabel(labelDone); - } - return true; - } - - [MethodImpl((MethodImplOptions)256)] - public static void EmitEqualToZeroOrNull(ILGenerator il) - { - il.Demit(OpCodes.Ldc_I4_0); // OpCodes.Not does not work here because it is a bitwise operation - il.Demit(OpCodes.Ceq); - } - - /// Get the advantage of the optimized specialized EmitCall method - [MethodImpl((MethodImplOptions)256)] - public static bool EmitMethodCallOrVirtualCall(ILGenerator il, MethodInfo method) - { - il.Demit(method.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, method); - // todo: @feature EmitCall is specifically for the varags method and not for normal C# conventions methods, - // for those you need to call Emit(OpCodes.Call|Callvirt, methodInfo). - // So for now the varargs methods are not supported yet. - return (method.CallingConvention & CallingConventions.VarArgs) == 0; - } - - [MethodImpl((MethodImplOptions)256)] - public static bool EmitVirtualMethodCall(ILGenerator il, MethodInfo method) - { - il.Demit(OpCodes.Callvirt, method); - // todo: @feature EmitCall is specifically for the varags method and not for normal C# conventions methods, - // for those you need to call Emit(OpCodes.Call|Callvirt, methodInfo). - // So for now the varargs methods are not supported yet. - return (method.CallingConvention & CallingConventions.VarArgs) == 0; - } - - [MethodImpl((MethodImplOptions)256)] - public static bool EmitMethodCall(ILGenerator il, MethodInfo method) - { - il.Demit(OpCodes.Call, method); - // todo: @feature EmitCall is specifically for the varags method and not for normal C# conventions methods, - // for those you need to call Emit(OpCodes.Call|Callvirt, methodInfo). - // So for now the varargs methods are not supported yet. - return (method.CallingConvention & CallingConventions.VarArgs) == 0; - } - - /// Same as EmitMethodCall which checks the method for null first, and returns false if it is null. - [MethodImpl((MethodImplOptions)256)] - public static bool EmitMethodCallCheckForNull(ILGenerator il, MethodInfo method) => - method != null && EmitMethodCall(il, method); - - /// Same as EmitMethodCallOrVirtualCall which checks the method for null first, and returns false if it is null. - [MethodImpl((MethodImplOptions)256)] - public static bool EmitMethodCallOrVirtualCallCheckForNull(ILGenerator il, MethodInfo method) => - method != null && EmitMethodCallOrVirtualCall(il, method); - - /// Efficiently emit the int constant - [MethodImpl((MethodImplOptions)256)] - public static void EmitLoadConstantInt(ILGenerator il, int i) - { - switch (i) - { - case -1: il.Demit(OpCodes.Ldc_I4_M1); break; - case 0: il.Demit(OpCodes.Ldc_I4_0); break; - case 1: il.Demit(OpCodes.Ldc_I4_1); break; - case 2: il.Demit(OpCodes.Ldc_I4_2); break; - case 3: il.Demit(OpCodes.Ldc_I4_3); break; - case 4: il.Demit(OpCodes.Ldc_I4_4); break; - case 5: il.Demit(OpCodes.Ldc_I4_5); break; - case 6: il.Demit(OpCodes.Ldc_I4_6); break; - case 7: il.Demit(OpCodes.Ldc_I4_7); break; - case 8: il.Demit(OpCodes.Ldc_I4_8); break; - default: - if (i > -129 && i < 128) - il.Demit(OpCodes.Ldc_I4_S, (sbyte)i); - else - il.Demit(OpCodes.Ldc_I4, i); - break; - } - } - - /// Efficiently emit the long constant based on its value as int or long - [MethodImpl((MethodImplOptions)256)] - public static void EmitLoadConstantLong(ILGenerator il, long i) - { - if (i >= int.MinValue && i <= int.MaxValue) - EmitLoadConstantInt(il, (int)i); - else - il.Demit(OpCodes.Ldc_I8, i); - } - - [MethodImpl((MethodImplOptions)256)] - private static void EmitLoadLocalVariableAddress(ILGenerator il, int location) - { - if ((uint)location <= byte.MaxValue) - il.Demit(OpCodes.Ldloca_S, (byte)location); - else - il.Demit(OpCodes.Ldloca, (short)location); - } - - /// Load local variable on stack - [MethodImpl((MethodImplOptions)256)] - public static bool EmitLoadLocalVariable(ILGenerator il, int location) - { - if (location == 0) - il.Demit(OpCodes.Ldloc_0); - else if (location == 1) - il.Demit(OpCodes.Ldloc_1); - else if (location == 2) - il.Demit(OpCodes.Ldloc_2); - else if (location == 3) - il.Demit(OpCodes.Ldloc_3); - else if ((uint)location <= byte.MaxValue) - il.Demit(OpCodes.Ldloc_S, (byte)location); - else - il.Demit(OpCodes.Ldloc, (short)location); - return true; - } - - [MethodImpl((MethodImplOptions)256)] - private static bool EmitIncOrDec(ILGenerator il, bool isInc = false) - { - il.Demit(OpCodes.Ldc_I4_1); - il.Demit(isInc ? OpCodes.Add : OpCodes.Sub); - return true; - } - - /// Store the variable location on stack - [MethodImpl((MethodImplOptions)256)] - public static void EmitStoreLocalVariable(ILGenerator il, int location) - { - if (location == 0) - il.Demit(OpCodes.Stloc_0); - else if (location == 1) - il.Demit(OpCodes.Stloc_1); - else if (location == 2) - il.Demit(OpCodes.Stloc_2); - else if (location == 3) - il.Demit(OpCodes.Stloc_3); - else if ((uint)location <= byte.MaxValue) - il.Demit(OpCodes.Stloc_S, (byte)location); - else - il.Demit(OpCodes.Stloc, (short)location); - } - - /// Get and store the variable of the type on stack - [MethodImpl((MethodImplOptions)256)] - public static int EmitStoreLocalVariable(ILGenerator il, Type type) - { - var location = il.GetNextLocalVarIndex(type); - EmitStoreLocalVariable(il, location); - return location; - } - - /// Stores and loads the variable - [MethodImpl((MethodImplOptions)256)] - public static void EmitStoreAndLoadLocalVariable(ILGenerator il, int location) - { - if (location == 0) - { - il.Demit(OpCodes.Stloc_0); - il.Demit(OpCodes.Ldloc_0); - } - else if (location == 1) - { - il.Demit(OpCodes.Stloc_1); - il.Demit(OpCodes.Ldloc_1); - } - else if (location == 2) - { - il.Demit(OpCodes.Stloc_2); - il.Demit(OpCodes.Ldloc_2); - } - else if (location == 3) - { - il.Demit(OpCodes.Stloc_3); - il.Demit(OpCodes.Ldloc_3); - } - else if ((uint)location <= byte.MaxValue) - { - il.Demit(OpCodes.Stloc_S, (byte)location); - il.Demit(OpCodes.Ldloc_S, (byte)location); - } - else - { - il.Demit(OpCodes.Stloc, (short)location); - il.Demit(OpCodes.Ldloc, (short)location); - } - } - - /// Stores and loads the variable, and returns it - public static int EmitStoreAndLoadLocalVariable(ILGenerator il, Type t) - { - var location = il.GetNextLocalVarIndex(t); - EmitStoreAndLoadLocalVariable(il, location); - return location; - } - - [MethodImpl((MethodImplOptions)256)] - private static void EmitStoreAndLoadLocalVariableAddress(ILGenerator il, int location) - { - // #if DEBUG - // var ilLengthField = typeof(ILGenerator).GetField("m_length", BindingFlags.Instance | BindingFlags.NonPublic); - // var ilStreamField = typeof(ILGenerator).GetField("m_ILStream", BindingFlags.Instance | BindingFlags.NonPublic); - // var ilLength = (int)ilLengthField.GetValue(il); - // var ilStream = (byte[])ilStreamField.GetValue(il); - - // var ilMaxMidStackField = typeof(ILGenerator).GetField("m_maxMidStack", BindingFlags.Instance | BindingFlags.NonPublic); - // var ilMaxMidStackCurField = typeof(ILGenerator).GetField("m_maxMidStackCur", BindingFlags.Instance | BindingFlags.NonPublic); - // var ilMaxMidStack = (int)ilMaxMidStackField.GetValue(il); - // var ilMaxMidStackCur = (int)ilMaxMidStackCurField.GetValue(il); - // #endif - if (location == 0) - { - // todo: @perf - // the internal code for this is - // - // EnsureCapacity(3); - // InternalEmit(opcode); - // EnsureCapacity(4); - // InternalEmit(opcode); - // m_ILStream[m_length++] = (byte)arg; - // - // which translates to -> - // - // if (m_length + 7 >= m_ILStream.Length) - // IncreaseCapacity(7); - // // No stack change here cause 1st op decrease stack by 1 and second increase by 1 - // m_ILStream[m_length++] = (byte)OpCodes.Stloc_0.Value; - // m_ILStream[m_length++] = (byte)OpCodes.Ldloca_S.Value; - // m_ILStream[m_length++] = (byte)0; // we may no need it - // - il.Demit(OpCodes.Stloc_0); - il.Demit(OpCodes.Ldloca_S, (byte)0); - } - else if (location == 1) - { - // todo: @perf we may introduce the EmitOne, EmitBatchNonStackModified(OpCode store, OpCode load, byte value), etc. method overloads - // - // if (ilLength + 7 < ilStream.Length) - // { - // ilStream[ilLength++] = (byte)OpCodes.Stloc_1.Value; - // if (ilMaxMidStackCur + 1 > ilMaxMidStack) - // ilMaxMidStackField.SetValue(il, ilMaxMidStackCur + 1); - // ilStream[ilLength++] = (byte)OpCodes.Ldloca_S.Value; - // ilStream[ilLength++] = (byte)1; - // ilLengthField.SetValue(il, ilLength); - // } - // else - // { - il.Demit(OpCodes.Stloc_1); - il.Demit(OpCodes.Ldloca_S, (byte)1); - // } - } - else if (location == 2) - { - il.Demit(OpCodes.Stloc_2); - il.Demit(OpCodes.Ldloca_S, (byte)2); - } - else if (location == 3) - { - il.Demit(OpCodes.Stloc_3); - il.Demit(OpCodes.Ldloca_S, (byte)3); - } - else if ((uint)location <= byte.MaxValue) - { - il.Demit(OpCodes.Stloc_S, (byte)location); - il.Demit(OpCodes.Ldloca_S, (byte)location); - } - else - { - il.Demit(OpCodes.Stloc, (short)location); - il.Demit(OpCodes.Ldloca, (short)location); - } - } - - private static int EmitStoreAndLoadLocalVariableAddress(ILGenerator il, Type type) - { - var location = il.GetNextLocalVarIndex(type); - EmitStoreAndLoadLocalVariableAddress(il, location); - return location; - } - - [MethodImpl((MethodImplOptions)256)] - private static void EmitLoadArg(ILGenerator il, int paramIndex) - { - if (paramIndex == 0) - il.Demit(OpCodes.Ldarg_0); - else if (paramIndex == 1) - il.Demit(OpCodes.Ldarg_1); - else if (paramIndex == 2) - il.Demit(OpCodes.Ldarg_2); - else if (paramIndex == 3) - il.Demit(OpCodes.Ldarg_3); - else if ((uint)paramIndex <= byte.MaxValue) - il.Demit(OpCodes.Ldarg_S, (byte)paramIndex); - else - il.Demit(OpCodes.Ldarg, (short)paramIndex); - } - - [MethodImpl((MethodImplOptions)256)] - private static void EmitLoadArgAddress(ILGenerator il, int paramIndex) - { - if ((uint)paramIndex <= byte.MaxValue) - il.Demit(OpCodes.Ldarga_S, (byte)paramIndex); - else - il.Demit(OpCodes.Ldarga, (short)paramIndex); - } - - /// Tries to interpret and emit the result IL - /// In case of exception return false, to allow FEC emit normally and throw in the invocation phase - public static bool TryInterpretAndEmitResult(Expression expr, ILGenerator il, ParentFlags parent, CompilerFlags flags) - { - var type = expr.Type; - Debug.Assert(type.IsPrimitive); - if ((flags & CompilerFlags.DisableInterpreter) != 0) - return false; - - var typeCode = Type.GetTypeCode(type); - try - { - switch (typeCode) - { - case TypeCode.Boolean: - var resultBool = false; - if (!Interpreter.TryInterpretBool(ref resultBool, expr, expr.NodeType)) - return false; - il.Demit(resultBool ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); - break; - - case TypeCode.Int32: - int resultInt = 0; - if (!Interpreter.TryInterpretInt(ref resultInt, expr, expr.NodeType)) - return false; - if ((parent & ParentFlags.IgnoreResult) == 0) - EmitLoadConstantInt(il, resultInt); - break; - case TypeCode.Decimal: - decimal resultDec = default; - if (!Interpreter.TryInterpretDecimal(ref resultDec, expr, expr.NodeType)) - return false; - if ((parent & ParentFlags.IgnoreResult) == 0) - EmitDecimalConstant(resultDec, il); - break; - default: - Interpreter.PValue resultVal = default; - if (!Interpreter.TryInterpretPrimitiveValue(ref resultVal, expr, typeCode, expr.NodeType)) - return false; - if ((parent & ParentFlags.IgnoreResult) == 0) - switch (typeCode) - { - case TypeCode.Char: - EmitLoadConstantInt(il, resultVal.CharValue); - break; - case TypeCode.SByte: - EmitLoadConstantInt(il, resultVal.SByteValue); - break; - case TypeCode.Byte: - EmitLoadConstantInt(il, resultVal.ByteValue); - break; - case TypeCode.Int16: - EmitLoadConstantInt(il, resultVal.Int16Value); - break; - case TypeCode.UInt16: - EmitLoadConstantInt(il, resultVal.UInt16Value); - break; - case TypeCode.Int32: - EmitLoadConstantInt(il, resultVal.Int32Value); - break; - case TypeCode.UInt32: - unchecked - { - EmitLoadConstantInt(il, (int)resultVal.UInt32Value); - } - break; - case TypeCode.Int64: - il.Demit(OpCodes.Ldc_I8, resultVal.Int64Value); - break; - case TypeCode.UInt64: - unchecked - { - il.Demit(OpCodes.Ldc_I8, (long)resultVal.UInt64Value); - } - break; - case TypeCode.Single: - il.Demit(OpCodes.Ldc_R4, resultVal.SingleValue); - break; - case TypeCode.Double: - il.Demit(OpCodes.Ldc_R8, resultVal.DoubleValue); - break; - default: Interpreter.UnreachableCase(typeCode); break; - } - break; - } - return true; - } - catch - { - // ignore exception and return the false and rethrow the exception in the invocation time - return false; - } - } - } - - /// Interpreter - public static class Interpreter - { - /// Always returns true - public static readonly Func TrueFunc = static () => true; - /// Always returns false - public static readonly Func FalseFunc = static () => false; - - /// Return value should be ignored - [MethodImpl(MethodImplOptions.NoInlining)] - internal static void UnreachableCase(T @case, - [CallerMemberName] string caller = "", [CallerLineNumber] int line = -1) - { -#if INTERPRETATION_DIAGNOSTICS - Console.WriteLine($"Unreachable switch case detected `{@case}` at `{caller}`:{line}"); - Debugger.Break(); -#endif - throw new InvalidCastException($"Unreachable switch case detected `{@case}` at `{caller}`:{line}"); - } - - /// Return value should be ignored - [MethodImpl(MethodImplOptions.NoInlining)] - private static R UnreachableCase(T @case, R result, - [CallerMemberName] string caller = "", [CallerLineNumber] int line = -1) - { -#if INTERPRETATION_DIAGNOSTICS - Console.WriteLine($"Unreachable switch case detected `{@case}` at `{caller}`:{line}"); - Debugger.Break(); -#endif - throw new InvalidCastException($"Unreachable switch case detected `{@case}` at `{caller}`:{line}"); - } - - /// Operation accepting IComparable inputs and producing bool output - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsComparison(ExpressionType nodeType) => - nodeType == ExpressionType.Equal | - nodeType == ExpressionType.NotEqual | - nodeType == ExpressionType.GreaterThan | - nodeType == ExpressionType.GreaterThanOrEqual | - nodeType == ExpressionType.LessThan | - nodeType == ExpressionType.LessThanOrEqual; - - /// Operation accepting the same primitive type inputs (or of the coalescing types) and producing the "same" primitive type output - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsArithmeticBinary(ExpressionType nodeType) => - nodeType == ExpressionType.Add | - nodeType == ExpressionType.Subtract | - nodeType == ExpressionType.Multiply | - nodeType == ExpressionType.Divide | - nodeType == ExpressionType.Modulo | - nodeType == ExpressionType.Power | - nodeType == ExpressionType.LeftShift | - nodeType == ExpressionType.RightShift | - nodeType == ExpressionType.And | - nodeType == ExpressionType.Or | - nodeType == ExpressionType.ExclusiveOr; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void NegatePrimitiveValue(ref PValue value, TypeCode code) - { - switch (code) - { - case TypeCode.Char: value.CharValue = (char)-value.CharValue; break; - case TypeCode.SByte: value.SByteValue = (sbyte)-value.SByteValue; break; - case TypeCode.Byte: value.Int16Value = (short)-value.ByteValue; break; - case TypeCode.Int16: value.Int16Value = (short)-value.Int16Value; break; - case TypeCode.UInt16: value.Int32Value = (int)-value.UInt16Value; break; - case TypeCode.Int32: value.Int32Value = -value.Int32Value; break; - // Negate can not be applied to the UInt32 - case TypeCode.UInt32: UnreachableCase(code); break; - case TypeCode.Single: value.SingleValue = -value.SingleValue; break; - case TypeCode.Double: value.DoubleValue = -value.DoubleValue; break; - default: UnreachableCase(code); break; - } - } - - internal static void ConvertPrimitiveValueFromTo(ref PValue value, TypeCode fromCode, TypeCode toCode) - { - switch (toCode) - { - case TypeCode.SByte: - switch (fromCode) - { - case TypeCode.Char: break; - case TypeCode.SByte: break; - case TypeCode.Byte: value.SByteValue = (sbyte)value.ByteValue; break; - case TypeCode.Int16: value.SByteValue = (sbyte)value.Int16Value; break; - case TypeCode.UInt16: value.SByteValue = (sbyte)value.UInt16Value; break; - case TypeCode.Int32: value.SByteValue = (sbyte)value.Int32Value; break; - case TypeCode.UInt32: value.SByteValue = (sbyte)value.UInt32Value; break; - case TypeCode.Int64: value.SByteValue = (sbyte)value.Int64Value; break; - case TypeCode.UInt64: value.SByteValue = (sbyte)value.UInt64Value; break; - case TypeCode.Single: value.SByteValue = (sbyte)value.SingleValue; break; - case TypeCode.Double: value.SByteValue = (sbyte)value.DoubleValue; break; - default: UnreachableCase(fromCode); break; - } - break; - case TypeCode.Byte: - switch (fromCode) - { - case TypeCode.Char: value.ByteValue = (byte)value.CharValue; break; - case TypeCode.SByte: value.ByteValue = (byte)value.SByteValue; break; - case TypeCode.Byte: break; - case TypeCode.Int16: value.ByteValue = (byte)value.Int16Value; break; - case TypeCode.UInt16: value.ByteValue = (byte)value.UInt16Value; break; - case TypeCode.Int32: value.ByteValue = (byte)value.Int32Value; break; - case TypeCode.UInt32: value.ByteValue = (byte)value.UInt32Value; break; - case TypeCode.Int64: value.ByteValue = (byte)value.Int64Value; break; - case TypeCode.UInt64: value.ByteValue = (byte)value.UInt64Value; break; - case TypeCode.Single: value.ByteValue = (byte)value.SingleValue; break; - case TypeCode.Double: value.ByteValue = (byte)value.DoubleValue; break; - default: UnreachableCase(fromCode); break; - } - break; - case TypeCode.Int16: - switch (fromCode) - { - case TypeCode.Char: value.Int16Value = (short)value.CharValue; break; - case TypeCode.SByte: value.Int16Value = (short)value.SByteValue; break; - case TypeCode.Byte: value.Int16Value = (short)value.ByteValue; break; - case TypeCode.Int16: break; - case TypeCode.UInt16: value.Int16Value = (short)value.UInt16Value; break; - case TypeCode.Int32: value.Int16Value = (short)value.Int32Value; break; - case TypeCode.UInt32: value.Int16Value = (short)value.UInt32Value; break; - case TypeCode.Int64: value.Int16Value = (short)value.Int64Value; break; - case TypeCode.UInt64: value.Int16Value = (short)value.UInt64Value; break; - case TypeCode.Single: value.Int16Value = (short)value.SingleValue; break; - case TypeCode.Double: value.Int16Value = (short)value.DoubleValue; break; - default: UnreachableCase(fromCode); break; - } - break; - case TypeCode.UInt16: - switch (fromCode) - { - case TypeCode.Char: value.UInt16Value = (ushort)value.CharValue; break; - case TypeCode.SByte: value.UInt16Value = (ushort)value.SByteValue; break; - case TypeCode.Byte: value.UInt16Value = (ushort)value.ByteValue; break; - case TypeCode.Int16: value.UInt16Value = (ushort)value.Int16Value; break; - case TypeCode.UInt16: break; - case TypeCode.Int32: value.UInt16Value = (ushort)value.Int32Value; break; - case TypeCode.UInt32: value.UInt16Value = (ushort)value.UInt32Value; break; - case TypeCode.Int64: value.UInt16Value = (ushort)value.Int64Value; break; - case TypeCode.UInt64: value.UInt16Value = (ushort)value.UInt64Value; break; - case TypeCode.Single: value.UInt16Value = (ushort)value.SingleValue; break; - case TypeCode.Double: value.UInt16Value = (ushort)value.DoubleValue; break; - default: UnreachableCase(fromCode); break; - } - break; - case TypeCode.Int32: - switch (fromCode) - { - case TypeCode.Char: value.Int32Value = (int)value.CharValue; break; - case TypeCode.SByte: value.Int32Value = (int)value.SByteValue; break; - case TypeCode.Byte: value.Int32Value = (int)value.ByteValue; break; - case TypeCode.Int16: value.Int32Value = (int)value.Int16Value; break; - case TypeCode.UInt16: value.Int32Value = (int)value.UInt16Value; break; - case TypeCode.Int32: break; - case TypeCode.UInt32: value.Int32Value = (int)value.UInt32Value; break; - case TypeCode.Int64: value.Int32Value = (int)value.Int64Value; break; - case TypeCode.UInt64: value.Int32Value = (int)value.UInt64Value; break; - case TypeCode.Single: value.Int32Value = (int)value.SingleValue; break; - case TypeCode.Double: value.Int32Value = (int)value.DoubleValue; break; - default: UnreachableCase(fromCode); break; - } - break; - case TypeCode.UInt32: - switch (fromCode) - { - case TypeCode.Char: value.UInt32Value = (uint)value.CharValue; break; - case TypeCode.SByte: value.UInt32Value = (uint)value.SByteValue; break; - case TypeCode.Byte: value.UInt32Value = (uint)value.ByteValue; break; - case TypeCode.Int16: value.UInt32Value = (uint)value.Int16Value; break; - case TypeCode.UInt16: value.UInt32Value = (uint)value.UInt16Value; break; - case TypeCode.Int32: value.UInt32Value = (uint)value.Int32Value; break; - case TypeCode.UInt32: break; - case TypeCode.Int64: value.UInt32Value = (uint)value.Int64Value; break; - case TypeCode.UInt64: value.UInt32Value = (uint)value.UInt64Value; break; - case TypeCode.Single: value.UInt32Value = (uint)value.SingleValue; break; - case TypeCode.Double: value.UInt32Value = (uint)value.DoubleValue; break; - default: UnreachableCase(fromCode); break; - } - break; - case TypeCode.Int64: - switch (fromCode) - { - case TypeCode.Char: value.Int64Value = (long)value.CharValue; break; - case TypeCode.SByte: value.Int64Value = (long)value.SByteValue; break; - case TypeCode.Byte: value.Int64Value = (long)value.ByteValue; break; - case TypeCode.Int16: value.Int64Value = (long)value.Int16Value; break; - case TypeCode.UInt16: value.Int64Value = (long)value.UInt16Value; break; - case TypeCode.Int32: value.Int64Value = (long)value.Int32Value; break; - case TypeCode.UInt32: value.Int64Value = (long)value.UInt32Value; break; - case TypeCode.Int64: break; - case TypeCode.UInt64: value.Int64Value = (long)value.UInt64Value; break; - case TypeCode.Single: value.Int64Value = (long)value.SingleValue; break; - case TypeCode.Double: value.Int64Value = (long)value.DoubleValue; break; - default: UnreachableCase(fromCode); break; - } - break; - case TypeCode.UInt64: - switch (fromCode) - { - case TypeCode.Char: value.UInt64Value = (ulong)value.CharValue; break; - case TypeCode.SByte: value.UInt64Value = (ulong)value.SByteValue; break; - case TypeCode.Byte: value.UInt64Value = (ulong)value.ByteValue; break; - case TypeCode.Int16: value.UInt64Value = (ulong)value.Int16Value; break; - case TypeCode.UInt16: value.UInt64Value = (ulong)value.UInt16Value; break; - case TypeCode.Int32: value.UInt64Value = (ulong)value.Int32Value; break; - case TypeCode.UInt32: value.UInt64Value = (ulong)value.UInt32Value; break; - case TypeCode.Int64: value.UInt64Value = (ulong)value.Int64Value; break; - case TypeCode.UInt64: break; - case TypeCode.Single: value.UInt64Value = (ulong)value.SingleValue; break; - case TypeCode.Double: value.UInt64Value = (ulong)value.DoubleValue; break; - default: UnreachableCase(fromCode); break; - } - break; - case TypeCode.Single: - switch (fromCode) - { - case TypeCode.Char: value.SingleValue = (float)value.CharValue; break; - case TypeCode.SByte: value.SingleValue = (float)value.SByteValue; break; - case TypeCode.Byte: value.SingleValue = (float)value.ByteValue; break; - case TypeCode.Int16: value.SingleValue = (float)value.Int16Value; break; - case TypeCode.UInt16: value.SingleValue = (float)value.UInt16Value; break; - case TypeCode.Int32: value.SingleValue = (float)value.Int32Value; break; - case TypeCode.UInt32: value.SingleValue = (float)value.UInt32Value; break; - case TypeCode.Int64: value.SingleValue = (float)value.Int64Value; break; - case TypeCode.UInt64: value.SingleValue = (float)value.UInt64Value; break; - case TypeCode.Single: break; - case TypeCode.Double: value.SingleValue = (float)value.DoubleValue; break; - default: UnreachableCase(fromCode); break; - } - break; - case TypeCode.Double: - switch (fromCode) - { - case TypeCode.Char: value.DoubleValue = (double)value.CharValue; break; - case TypeCode.SByte: value.DoubleValue = (double)value.SByteValue; break; - case TypeCode.Byte: value.DoubleValue = (double)value.ByteValue; break; - case TypeCode.Int16: value.DoubleValue = (double)value.Int16Value; break; - case TypeCode.UInt16: value.DoubleValue = (double)value.UInt16Value; break; - case TypeCode.Int32: value.DoubleValue = (double)value.Int32Value; break; - case TypeCode.UInt32: value.DoubleValue = (double)value.UInt32Value; break; - case TypeCode.Int64: value.DoubleValue = (double)value.Int64Value; break; - case TypeCode.UInt64: value.DoubleValue = (double)value.UInt64Value; break; - case TypeCode.Single: value.DoubleValue = (double)value.SingleValue; break; - case TypeCode.Double: break; - default: UnreachableCase(fromCode); break; - } - break; - } - } - - [DebuggerDisplay("{Code}")] - [StructLayout(LayoutKind.Explicit)] - internal struct PValue - { - [FieldOffset(0)] - public char CharValue; - [FieldOffset(0)] - public sbyte SByteValue; - [FieldOffset(0)] - public byte ByteValue; - [FieldOffset(0)] - public short Int16Value; - [FieldOffset(0)] - public ushort UInt16Value; - [FieldOffset(0)] - public int Int32Value; - [FieldOffset(0)] - public uint UInt32Value; - [FieldOffset(0)] - public long Int64Value; - [FieldOffset(0)] - public ulong UInt64Value; - [FieldOffset(0)] - public float SingleValue; - [FieldOffset(0)] - public double DoubleValue; - public PValue(char value) => CharValue = value; - public PValue(sbyte value) => SByteValue = value; - public PValue(byte value) => ByteValue = value; - public PValue(short value) => Int16Value = value; - public PValue(ushort value) => UInt16Value = value; - public PValue(int value) => Int32Value = value; - public PValue(uint value) => UInt32Value = value; - public PValue(long value) => Int64Value = value; - public PValue(ulong value) => UInt64Value = value; - public PValue(float value) => SingleValue = value; - public PValue(double value) => DoubleValue = value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static bool TryUnboxToPrimitiveValue(ref PValue value, object boxedValue, TypeCode code) - { - switch (code) - { - case TypeCode.Char: value.CharValue = (char)boxedValue; break; - case TypeCode.SByte: value.SByteValue = (sbyte)boxedValue; break; - case TypeCode.Byte: value.ByteValue = (byte)boxedValue; break; - case TypeCode.Int16: value.Int16Value = (short)boxedValue; break; - case TypeCode.UInt16: value.UInt16Value = (ushort)boxedValue; break; - case TypeCode.Int32: value.Int32Value = (int)boxedValue; break; - case TypeCode.UInt32: value.UInt32Value = (uint)boxedValue; break; - case TypeCode.Int64: value.Int64Value = (long)boxedValue; break; - case TypeCode.UInt64: value.UInt64Value = (ulong)boxedValue; break; - case TypeCode.Single: value.SingleValue = (float)boxedValue; break; - case TypeCode.Double: value.DoubleValue = (double)boxedValue; break; - default: return false; - } - return true; - } - - // todo: @perf think how to avoid this boxing thing altogether, maybe do not expose it at all to force client to handle the union values - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static object BoxPrimitiveValue(ref PValue value, TypeCode code) => code switch - { - TypeCode.Char => value.CharValue, - TypeCode.SByte => value.SByteValue, - TypeCode.Byte => value.ByteValue, - TypeCode.Int16 => value.Int16Value, - TypeCode.UInt16 => value.UInt16Value, - TypeCode.Int32 => value.Int32Value, - TypeCode.UInt32 => value.UInt32Value, - TypeCode.Int64 => value.Int64Value, - TypeCode.UInt64 => value.UInt64Value, - TypeCode.Single => value.SingleValue, - TypeCode.Double => value.DoubleValue, - _ => UnreachableCase(code, (object)null) - }; - - internal static bool ComparePrimitiveValues(ref PValue left, ref PValue right, TypeCode code, ExpressionType nodeType) - { - switch (nodeType) - { - case ExpressionType.GreaterThan: - return code switch - { - TypeCode.Char => left.CharValue > right.CharValue, - TypeCode.SByte => left.SByteValue > right.SByteValue, - TypeCode.Byte => left.ByteValue > right.ByteValue, - TypeCode.Int16 => left.Int16Value > right.Int16Value, - TypeCode.UInt16 => left.UInt16Value > right.UInt16Value, - TypeCode.Int32 => left.Int32Value > right.Int32Value, - TypeCode.UInt32 => left.UInt32Value > right.UInt32Value, - TypeCode.Int64 => left.Int64Value > right.Int64Value, - TypeCode.UInt64 => left.UInt64Value > right.UInt64Value, - TypeCode.Single => left.SingleValue > right.SingleValue, - TypeCode.Double => left.DoubleValue > right.DoubleValue, - _ => UnreachableCase(code, false) - }; - case ExpressionType.GreaterThanOrEqual: - return code switch - { - TypeCode.Char => left.CharValue >= right.CharValue, - TypeCode.SByte => left.SByteValue >= right.SByteValue, - TypeCode.Byte => left.ByteValue >= right.ByteValue, - TypeCode.Int16 => left.Int16Value >= right.Int16Value, - TypeCode.UInt16 => left.UInt16Value >= right.UInt16Value, - TypeCode.Int32 => left.Int32Value >= right.Int32Value, - TypeCode.UInt32 => left.UInt32Value >= right.UInt32Value, - TypeCode.Int64 => left.Int64Value >= right.Int64Value, - TypeCode.UInt64 => left.UInt64Value >= right.UInt64Value, - TypeCode.Single => left.SingleValue >= right.SingleValue, - TypeCode.Double => left.DoubleValue >= right.DoubleValue, - _ => UnreachableCase(code, false) - }; - case ExpressionType.LessThan: - return code switch - { - TypeCode.Char => left.CharValue < right.CharValue, - TypeCode.SByte => left.SByteValue < right.SByteValue, - TypeCode.Byte => left.ByteValue < right.ByteValue, - TypeCode.Int16 => left.Int16Value < right.Int16Value, - TypeCode.UInt16 => left.UInt16Value < right.UInt16Value, - TypeCode.Int32 => left.Int32Value < right.Int32Value, - TypeCode.UInt32 => left.UInt32Value < right.UInt32Value, - TypeCode.Int64 => left.Int64Value < right.Int64Value, - TypeCode.UInt64 => left.UInt64Value < right.UInt64Value, - TypeCode.Single => left.SingleValue < right.SingleValue, - TypeCode.Double => left.DoubleValue < right.DoubleValue, - _ => UnreachableCase(code, false) - }; - case ExpressionType.LessThanOrEqual: - return code switch - { - TypeCode.Char => left.CharValue <= right.CharValue, - TypeCode.SByte => left.SByteValue <= right.SByteValue, - TypeCode.Byte => left.ByteValue <= right.ByteValue, - TypeCode.Int16 => left.Int16Value <= right.Int16Value, - TypeCode.UInt16 => left.UInt16Value <= right.UInt16Value, - TypeCode.Int32 => left.Int32Value <= right.Int32Value, - TypeCode.UInt32 => left.UInt32Value <= right.UInt32Value, - TypeCode.Int64 => left.Int64Value <= right.Int64Value, - TypeCode.UInt64 => left.UInt64Value <= right.UInt64Value, - TypeCode.Single => left.SingleValue <= right.SingleValue, - TypeCode.Double => left.DoubleValue <= right.DoubleValue, - _ => UnreachableCase(code, false) - }; - default: return UnreachableCase(nodeType, false); - } - } - - /// Puts the result to the `left` para meter - internal static void DoArithmeticForPrimitiveValues(ref PValue left, ref PValue right, TypeCode code, ExpressionType nodeType) - { - switch (nodeType) - { - case ExpressionType.Add: - switch (code) - { - case TypeCode.Char: left.CharValue += right.CharValue; break; - // System Expression does not define the Add for sbyte and byte, but let's keep it here because it is allowed in C# and LightExpression - case TypeCode.SByte: left.SByteValue += right.SByteValue; break; - case TypeCode.Byte: left.ByteValue += right.ByteValue; break; - // the rest - case TypeCode.Int16: left.Int16Value += right.Int16Value; break; - case TypeCode.UInt16: left.UInt16Value += right.UInt16Value; break; - case TypeCode.Int32: left.Int32Value += right.Int32Value; break; - case TypeCode.UInt32: left.UInt32Value += right.UInt32Value; break; - case TypeCode.Int64: left.Int64Value += right.Int64Value; break; - case TypeCode.UInt64: left.UInt64Value += right.UInt64Value; break; - case TypeCode.Single: left.SingleValue += right.SingleValue; break; - case TypeCode.Double: left.DoubleValue += right.DoubleValue; break; - default: UnreachableCase(code); break; - } - break; - case ExpressionType.Subtract: - switch (code) - { - case TypeCode.Char: left.CharValue -= right.CharValue; break; - case TypeCode.SByte: left.SByteValue -= right.SByteValue; break; - case TypeCode.Byte: left.ByteValue -= right.ByteValue; break; - case TypeCode.Int16: left.Int16Value -= right.Int16Value; break; - case TypeCode.UInt16: left.UInt16Value -= right.UInt16Value; break; - case TypeCode.Int32: left.Int32Value -= right.Int32Value; break; - case TypeCode.UInt32: left.UInt32Value -= right.UInt32Value; break; - case TypeCode.Int64: left.Int64Value -= right.Int64Value; break; - case TypeCode.UInt64: left.UInt64Value -= right.UInt64Value; break; - case TypeCode.Single: left.SingleValue -= right.SingleValue; break; - case TypeCode.Double: left.DoubleValue -= right.DoubleValue; break; - default: UnreachableCase(code); break; - } - break; - case ExpressionType.Multiply: - switch (code) - { - case TypeCode.Char: left.CharValue *= right.CharValue; break; - case TypeCode.SByte: left.SByteValue *= right.SByteValue; break; - case TypeCode.Byte: left.ByteValue *= right.ByteValue; break; - case TypeCode.Int16: left.Int16Value *= right.Int16Value; break; - case TypeCode.UInt16: left.UInt16Value *= right.UInt16Value; break; - case TypeCode.Int32: left.Int32Value *= right.Int32Value; break; - case TypeCode.UInt32: left.UInt32Value *= right.UInt32Value; break; - case TypeCode.Int64: left.Int64Value *= right.Int64Value; break; - case TypeCode.UInt64: left.UInt64Value *= right.UInt64Value; break; - case TypeCode.Single: left.SingleValue *= right.SingleValue; break; - case TypeCode.Double: left.DoubleValue *= right.DoubleValue; break; - default: UnreachableCase(code); break; - } - break; - case ExpressionType.Divide: - switch (code) - { - case TypeCode.Char: left.CharValue /= right.CharValue; break; - case TypeCode.SByte: left.SByteValue /= right.SByteValue; break; - case TypeCode.Byte: left.ByteValue /= right.ByteValue; break; - case TypeCode.Int16: left.Int16Value /= right.Int16Value; break; - case TypeCode.UInt16: left.UInt16Value /= right.UInt16Value; break; - case TypeCode.Int32: left.Int32Value /= right.Int32Value; break; - case TypeCode.UInt32: left.UInt32Value /= right.UInt32Value; break; - case TypeCode.Int64: left.Int64Value /= right.Int64Value; break; - case TypeCode.UInt64: left.UInt64Value /= right.UInt64Value; break; - case TypeCode.Single: left.SingleValue /= right.SingleValue; break; - case TypeCode.Double: left.DoubleValue /= right.DoubleValue; break; - default: UnreachableCase(code); break; - } - break; - case ExpressionType.Modulo: - switch (code) - { - case TypeCode.Char: left.CharValue %= right.CharValue; break; - case TypeCode.SByte: left.SByteValue %= right.SByteValue; break; - case TypeCode.Byte: left.ByteValue %= right.ByteValue; break; - case TypeCode.Int16: left.Int16Value %= right.Int16Value; break; - case TypeCode.UInt16: left.UInt16Value %= right.UInt16Value; break; - case TypeCode.Int32: left.Int32Value %= right.Int32Value; break; - case TypeCode.UInt32: left.UInt32Value %= right.UInt32Value; break; - case TypeCode.Int64: left.Int64Value %= right.Int64Value; break; - case TypeCode.UInt64: left.UInt64Value %= right.UInt64Value; break; - case TypeCode.Single: left.SingleValue %= right.SingleValue; break; - case TypeCode.Double: left.DoubleValue %= right.DoubleValue; break; - default: UnreachableCase(code); break; - } - break; - case ExpressionType.And: - switch (code) - { - case TypeCode.Char: left.CharValue &= right.CharValue; break; - case TypeCode.SByte: left.SByteValue &= right.SByteValue; break; - case TypeCode.Byte: left.ByteValue &= right.ByteValue; break; - case TypeCode.Int16: left.Int16Value &= right.Int16Value; break; - case TypeCode.UInt16: left.UInt16Value &= right.UInt16Value; break; - case TypeCode.Int32: left.Int32Value &= right.Int32Value; break; - case TypeCode.UInt32: left.UInt32Value &= right.UInt32Value; break; - case TypeCode.Int64: left.Int64Value &= right.Int64Value; break; - case TypeCode.UInt64: left.UInt64Value &= right.UInt64Value; break; - default: UnreachableCase(code); break; - } - break; - case ExpressionType.Or: - switch (code) - { - case TypeCode.Char: left.CharValue |= right.CharValue; break; - case TypeCode.SByte: left.SByteValue |= right.SByteValue; break; - case TypeCode.Byte: left.ByteValue |= right.ByteValue; break; - case TypeCode.Int16: left.Int16Value |= right.Int16Value; break; - case TypeCode.UInt16: left.UInt16Value |= right.UInt16Value; break; - case TypeCode.Int32: left.Int32Value |= right.Int32Value; break; - case TypeCode.UInt32: left.UInt32Value |= right.UInt32Value; break; - case TypeCode.Int64: left.Int64Value |= right.Int64Value; break; - case TypeCode.UInt64: left.UInt64Value |= right.UInt64Value; break; - default: UnreachableCase(code); break; - } - break; - case ExpressionType.ExclusiveOr: - switch (code) - { - case TypeCode.Char: left.CharValue ^= right.CharValue; break; - case TypeCode.SByte: left.SByteValue ^= right.SByteValue; break; - case TypeCode.Byte: left.ByteValue ^= right.ByteValue; break; - case TypeCode.Int16: left.Int16Value ^= right.Int16Value; break; - case TypeCode.UInt16: left.UInt16Value ^= right.UInt16Value; break; - case TypeCode.Int32: left.Int32Value ^= right.Int32Value; break; - case TypeCode.UInt32: left.UInt32Value ^= right.UInt32Value; break; - case TypeCode.Int64: left.Int64Value ^= right.Int64Value; break; - case TypeCode.UInt64: left.UInt64Value ^= right.UInt64Value; break; - default: UnreachableCase(code); break; - } - break; - case ExpressionType.LeftShift: - switch (code) - { - case TypeCode.Char: left.CharValue <<= right.CharValue; break; - case TypeCode.SByte: left.SByteValue <<= right.SByteValue; break; - case TypeCode.Byte: left.ByteValue <<= right.ByteValue; break; - case TypeCode.Int16: left.Int16Value <<= right.Int16Value; break; - case TypeCode.UInt16: left.UInt16Value <<= right.UInt16Value; break; - case TypeCode.Int32: left.Int32Value <<= right.Int32Value; break; - case TypeCode.UInt32: left.UInt32Value <<= (int)right.UInt32Value; break; - case TypeCode.Int64: left.Int64Value <<= (int)right.Int64Value; break; - case TypeCode.UInt64: left.UInt64Value <<= (int)right.UInt64Value; break; - default: UnreachableCase(code); break; - } - break; - case ExpressionType.RightShift: - switch (code) - { - case TypeCode.Char: left.CharValue >>= right.CharValue; break; - case TypeCode.SByte: left.SByteValue >>= right.SByteValue; break; - case TypeCode.Byte: left.ByteValue >>= right.ByteValue; break; - case TypeCode.Int16: left.Int16Value >>= right.Int16Value; break; - case TypeCode.UInt16: left.UInt16Value >>= right.UInt16Value; break; - case TypeCode.Int32: left.Int32Value >>= right.Int32Value; break; - case TypeCode.UInt32: left.UInt32Value >>= (int)right.UInt32Value; break; - case TypeCode.Int64: left.Int64Value >>= (int)right.Int64Value; break; - case TypeCode.UInt64: left.UInt64Value >>= (int)right.UInt64Value; break; - default: UnreachableCase(code); break; - } - break; - default: UnreachableCase(nodeType); break; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static bool TrySetPrimitiveValueToDefault(ref PValue value, TypeCode code) - { - switch (code) - { - case TypeCode.Char: value.CharValue = default; break; - case TypeCode.SByte: value.SByteValue = default; break; - case TypeCode.Byte: value.ByteValue = default; break; - case TypeCode.Int16: value.Int16Value = default; break; - case TypeCode.UInt16: value.UInt16Value = default; break; - case TypeCode.Int32: value.Int32Value = default; break; - case TypeCode.UInt32: value.UInt32Value = default; break; - case TypeCode.Int64: value.Int64Value = default; break; - case TypeCode.UInt64: value.UInt64Value = default; break; - case TypeCode.Single: value.SingleValue = default; break; - case TypeCode.Double: value.DoubleValue = default; break; - default: return false; - } - return true; - } - - /// Fast, mostly negative check to skip or proceed with interpretation. - /// Depending on the context you may avoid calling it because you know the interpreted expression beforehand, - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsCandidateForInterpretation(Expression expr) - { - var nodeType = expr.NodeType; - return - nodeType == ExpressionType.Constant | - nodeType == ExpressionType.Default | - nodeType == ExpressionType.Convert | - nodeType == ExpressionType.Not | - nodeType == ExpressionType.Negate | - expr is BinaryExpression; - // todo: @wip include conditional? - } - -#if INTERPRETATION_DIAGNOSTICS - - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Used in diagnostics only")] - [UnconditionalSuppressMessage("Trimming", "IL2075:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Used in diagnostics only")] - private static void CollectCallingTestName() - { - var stackTrace = new StackTrace(); - var frames = stackTrace.GetFrames(); - - // Skip this method and its immediate caller, and start from the outer callers - var found = false; - for (int i = 3; i < frames.Length; ++i) - { - var frame = frames[i]; - var method = frame.GetMethod(); - var type = method?.DeclaringType; - if (type == null) - continue; - - var ifaces = type.GetInterfaces(); - if (ifaces.Length == 0) - continue; - - foreach (var iface in ifaces) - if (iface.Name.Contains("Test")) - { - found = true; - break; - } - - if (found) - { - found = true; - Console.WriteLine($"//Interpretation in: {type.Name}.{method.Name}"); - break; // collect the first found thing in stack trace - } - } - - if (!found) - { - var methodTrace = string.Join("; ", frames.Skip(3).Select(f => f.GetMethod().Name).ToArray()); - Console.WriteLine($"//Interpretation in: not found in stack trace: {methodTrace}"); - } - } -#endif - - /// Wraps `TryInterpretPrimitive` in the try catch block. - /// In case of exception FEC will emit the whole computation to throw exception in the invocation phase - public static bool TryInterpretBool(out bool result, Expression expr, CompilerFlags flags) - { - var exprType = expr.Type; - Debug.Assert(exprType.IsPrimitive, // todo: @feat nullables are not supported yet // || Nullable.GetUnderlyingType(exprType)?.IsPrimitive == true, - "Can only reduce the boolean for the expressions of primitive types but found " + expr.Type); - result = false; - if ((flags & CompilerFlags.DisableInterpreter) != 0) - return false; - try - { - var ok = TryInterpretBool(ref result, expr, expr.NodeType); -#if INTERPRETATION_DIAGNOSTICS - if (ok) CollectCallingTestName(); -#endif - return ok; - } - catch - { - // ignore exception and return the false and rethrow the exception in the invocation time - return false; - } - } - - // todo: @perf try split to `TryInterpretBinary` overload to streamline the calls for TryEmitConditional and similar - /// Tries to interpret the expression of the Primitive type of Constant, Convert, Logical, Comparison, Arithmetic. - internal static bool TryInterpretBool(ref bool resultBool, Expression expr, ExpressionType nodeType) - { - // what operations are supported, to get the boolean result? - // yes: not, logical, comparison, default - // not: negate, arithmetic, convert to bool - if (nodeType == ExpressionType.Not) - { - var operandExpr = ((UnaryExpression)expr).Operand; - if (operandExpr.NodeType == ExpressionType.Conditional) - operandExpr = Tools.TryReduceConditional((ConditionalExpression)operandExpr); - if (operandExpr is ConstantExpression co) - resultBool = (bool)co.Value; - else if (!TryInterpretBool(ref resultBool, operandExpr, operandExpr.NodeType)) - return false; - - resultBool = !resultBool; - return true; - } - - if (nodeType == ExpressionType.AndAlso | - nodeType == ExpressionType.OrElse) - { - var binaryExpr = (BinaryExpression)expr; - - // Interpreting the left part as the first candidate for the result - var left = binaryExpr.Left; - if (left.NodeType == ExpressionType.Conditional) - left = Tools.TryReduceConditional((ConditionalExpression)left); - if (left is ConstantExpression lc) - resultBool = (bool)lc.Value; - else if (!TryInterpretBool(ref resultBool, left, left.NodeType)) - return false; - - // Short circuit the interpretation, because this is an actual logic of these logical operations - if (resultBool & nodeType == ExpressionType.OrElse || - !resultBool & nodeType == ExpressionType.AndAlso) - return true; - - // If the first part is not enough to decide of the expression result, go right - var right = binaryExpr.Right; - if (right.NodeType == ExpressionType.Conditional) - right = Tools.TryReduceConditional((ConditionalExpression)right); - if (right is ConstantExpression rc) - { - resultBool = (bool)rc.Value; - return true; - } - return TryInterpretBool(ref resultBool, right, right.NodeType); - } - - if (nodeType == ExpressionType.Equal | - nodeType == ExpressionType.NotEqual) - { - var binaryExpr = (BinaryExpression)expr; - var right = binaryExpr.Right; - var left = binaryExpr.Left; - var leftCode = Type.GetTypeCode(left.Type); - if (leftCode == TypeCode.Boolean) - { - var leftBool = false; - if (left is ConstantExpression lc) - leftBool = (bool)lc.Value; - else if (!TryInterpretBool(ref leftBool, left, left.NodeType)) - return false; - - var rightBool = false; - if (right is ConstantExpression rc) - rightBool = (bool)rc.Value; - else if (!TryInterpretBool(ref rightBool, right, right.NodeType)) - return false; - - resultBool = nodeType == ExpressionType.Equal ? leftBool == rightBool : leftBool != rightBool; - return true; - } - if (leftCode == TypeCode.Int32) - { - var leftInt = 0; - if (left is ConstantExpression lc) - leftInt = (int)lc.Value; - else if (!TryInterpretInt(ref leftInt, left, left.NodeType)) - return false; - - var rightInt = 0; - if (right is ConstantExpression rc) - rightInt = (int)rc.Value; - else if (!TryInterpretInt(ref rightInt, right, right.NodeType)) - return false; - - resultBool = nodeType == ExpressionType.Equal ? leftInt == rightInt : leftInt != rightInt; - return true; - } - if (leftCode == TypeCode.Decimal) - { - decimal decimalLeft = default; - if (left is ConstantExpression lc) - decimalLeft = (decimal)lc.Value; - else if (!TryInterpretDecimal(ref decimalLeft, left, left.NodeType)) - return false; - - decimal rightDec = default; - if (right is ConstantExpression rc) - rightDec = (decimal)rc.Value; - else if (!TryInterpretDecimal(ref rightDec, right, right.NodeType)) - return false; - - resultBool = nodeType == ExpressionType.Equal ? decimalLeft == rightDec : decimalLeft != rightDec; - return true; - } - // not a bool, int, or decimal - { - PValue leftVal = default; - if (left is ConstantExpression lc && !TryUnboxToPrimitiveValue(ref leftVal, lc.Value, leftCode) || - !TryInterpretPrimitiveValue(ref leftVal, left, leftCode, left.NodeType)) - return false; - - PValue rightVal = default; - if (right is ConstantExpression rc && !TryUnboxToPrimitiveValue(ref rightVal, rc.Value, leftCode) || - !TryInterpretPrimitiveValue(ref rightVal, right, leftCode, right.NodeType)) - return false; - - resultBool = leftCode switch - { - TypeCode.Char => leftVal.CharValue == rightVal.CharValue, - TypeCode.SByte => leftVal.SByteValue == rightVal.SByteValue, - TypeCode.Byte => leftVal.ByteValue == rightVal.ByteValue, - TypeCode.Int16 => leftVal.Int16Value == rightVal.Int16Value, - TypeCode.UInt16 => leftVal.UInt16Value == rightVal.UInt16Value, - TypeCode.Int32 => leftVal.Int32Value == rightVal.Int32Value, - TypeCode.UInt32 => leftVal.UInt32Value == rightVal.UInt32Value, - TypeCode.Int64 => leftVal.Int64Value == rightVal.Int64Value, - TypeCode.UInt64 => leftVal.UInt64Value == rightVal.UInt64Value, - TypeCode.Single => leftVal.SingleValue == rightVal.SingleValue, - TypeCode.Double => leftVal.DoubleValue == rightVal.DoubleValue, - _ => UnreachableCase(leftCode, false), - }; - resultBool = nodeType == ExpressionType.Equal ? resultBool : !resultBool; - return true; - } - } - - if (nodeType == ExpressionType.GreaterThan | - nodeType == ExpressionType.GreaterThanOrEqual | - nodeType == ExpressionType.LessThan | - nodeType == ExpressionType.LessThanOrEqual) - { - var binaryExpr = (BinaryExpression)expr; - var left = binaryExpr.Left; - var right = binaryExpr.Right; - var leftCode = Type.GetTypeCode(left.Type); - Debug.Assert(leftCode != TypeCode.Boolean, "Boolean values are not comparable by less or greater"); - - if (leftCode == TypeCode.Int32) - { - int intLeft = 0; - if (left is ConstantExpression lc) - intLeft = (int)lc.Value; - else if (!TryInterpretInt(ref intLeft, left, left.NodeType)) - return false; - - int rightInt = 0; - if (right is ConstantExpression rc) - rightInt = (int)rc.Value; - else if (!TryInterpretInt(ref rightInt, right, right.NodeType)) - return false; - - resultBool = nodeType switch - { - ExpressionType.GreaterThan => intLeft > rightInt, - ExpressionType.GreaterThanOrEqual => intLeft >= rightInt, - ExpressionType.LessThan => intLeft < rightInt, - ExpressionType.LessThanOrEqual => intLeft <= rightInt, - _ => UnreachableCase(nodeType, false), - }; - return true; - } - if (leftCode == TypeCode.Decimal) - { - decimal leftDec = default; - if (left is ConstantExpression lc) - leftDec = (decimal)lc.Value; - else if (!TryInterpretDecimal(ref leftDec, left, left.NodeType)) - return false; - - decimal rightDec = default; - if (right is ConstantExpression rc) - rightDec = (decimal)rc.Value; - else if (!TryInterpretDecimal(ref rightDec, right, right.NodeType)) - return false; - - resultBool = nodeType switch - { - ExpressionType.GreaterThan => leftDec > rightDec, - ExpressionType.GreaterThanOrEqual => leftDec >= rightDec, - ExpressionType.LessThan => leftDec < rightDec, - ExpressionType.LessThanOrEqual => leftDec <= rightDec, - _ => UnreachableCase(nodeType, false), - }; - return true; - } - // not a bool, int, or decimal - { - PValue leftVal = default; - if (left is ConstantExpression lc && !TryUnboxToPrimitiveValue(ref leftVal, lc.Value, leftCode) || - !TryInterpretPrimitiveValue(ref leftVal, left, leftCode, left.NodeType)) - return false; - - PValue rightVal = default; - if (right is ConstantExpression rc && !TryUnboxToPrimitiveValue(ref rightVal, rc.Value, leftCode) || - !TryInterpretPrimitiveValue(ref rightVal, right, leftCode, right.NodeType)) - return false; - - resultBool = ComparePrimitiveValues(ref leftVal, ref rightVal, leftCode, nodeType); - return true; - } - } - - if (expr is ConstantExpression constExpr) - { - resultBool = (bool)constExpr.Value; - return true; - } - - if (nodeType == ExpressionType.Default) - { - resultBool = false; - return true; - } - - return false; - } - - /// Tries to interpret the expression of the Primitive type of Constant, Convert, Logical, Comparison, Arithmetic. - internal static bool TryInterpretDecimal(ref decimal resultDec, Expression expr, ExpressionType nodeType) - { - // What operations are supported, to get the decimal result: - // yes: arithmetic, negate, default, convert - // no: not, logical, comparison - - if (IsArithmeticBinary(nodeType)) - { - var binaryExpr = (BinaryExpression)expr; - var left = binaryExpr.Left; - if (left is ConstantExpression lc) - resultDec = (decimal)lc.Value; - else if (!TryInterpretDecimal(ref resultDec, left, left.NodeType)) - return false; - - decimal rightDec = default; - var right = binaryExpr.Right; - if (right is ConstantExpression rc) - rightDec = (decimal)rc.Value; - else if (!TryInterpretDecimal(ref rightDec, right, right.NodeType)) - return false; - - switch (nodeType) - { - case ExpressionType.Add: resultDec += rightDec; break; - case ExpressionType.Subtract: resultDec -= rightDec; break; - case ExpressionType.Multiply: resultDec *= rightDec; break; - case ExpressionType.Divide: resultDec /= rightDec; break; - case ExpressionType.Modulo: resultDec %= rightDec; break; - default: UnreachableCase(nodeType); break; - } - return true; - } - - if (nodeType == ExpressionType.Negate) - { - var operandExpr = ((UnaryExpression)expr).Operand; - if (operandExpr is ConstantExpression co) - resultDec = (decimal)co.Value; - else if (!TryInterpretDecimal(ref resultDec, operandExpr, operandExpr.NodeType)) - return false; - - resultDec = -resultDec; - return true; - } - - if (expr is ConstantExpression constExpr) - { - resultDec = (decimal)constExpr.Value; - return true; - } - - if (nodeType == ExpressionType.Default) - { - resultDec = default; - return true; - } - - if (nodeType == ExpressionType.Convert) - { - var operandExpr = ((UnaryExpression)expr).Operand; - var operandCode = Type.GetTypeCode(operandExpr.Type); - Debug.Assert(operandCode != TypeCode.Boolean, - "Operand may be a decimal but cannot be bool, because there is no conversation from bool to the types in UValue"); - - PValue operandVal = default; - if (operandExpr is ConstantExpression co && !TryUnboxToPrimitiveValue(ref operandVal, co.Value, operandCode) || - !TryInterpretPrimitiveValue(ref operandVal, operandExpr, operandCode, operandExpr.NodeType)) - return false; - - resultDec = operandCode switch - { - TypeCode.Char => operandVal.CharValue, - TypeCode.SByte => operandVal.SByteValue, - TypeCode.Byte => operandVal.ByteValue, - TypeCode.Int16 => operandVal.Int16Value, - TypeCode.UInt16 => operandVal.UInt16Value, - TypeCode.Int32 => operandVal.Int32Value, - TypeCode.UInt32 => operandVal.UInt32Value, - TypeCode.Int64 => operandVal.Int64Value, - TypeCode.UInt64 => operandVal.UInt64Value, - TypeCode.Single => (decimal)operandVal.SingleValue, - TypeCode.Double => (decimal)operandVal.DoubleValue, - _ => UnreachableCase(operandCode, default(decimal)), - }; - return true; - } - - return false; - } - - /// Tries to interpret the expression of the Primitive type of Constant, Convert, Logical, Comparison, Arithmetic. - /// Returns `false` if it failed to do so. - internal static bool TryInterpretInt(ref int resultInt, Expression expr, ExpressionType nodeType) - { - // What is supported for the int result - // yes: arithmetic, convert, default - // no: not, logical, comparison - if (IsArithmeticBinary(nodeType)) - { - var binaryExpr = (BinaryExpression)expr; - var left = binaryExpr.Left; - if (left is ConstantExpression lc) - resultInt = (int)lc.Value; - else if (!TryInterpretInt(ref resultInt, left, left.NodeType)) - return false; - - int rightVal = 0; - var right = binaryExpr.Right; - if (right is ConstantExpression rc) - rightVal = (int)rc.Value; - else if (!TryInterpretInt(ref rightVal, right, right.NodeType)) - return false; - - resultInt = nodeType switch - { - ExpressionType.Add => resultInt + rightVal, - ExpressionType.Subtract => resultInt - rightVal, - ExpressionType.Multiply => resultInt * rightVal, - ExpressionType.Divide => resultInt / rightVal, - ExpressionType.Modulo => resultInt % rightVal, - ExpressionType.LeftShift => resultInt << rightVal, - ExpressionType.RightShift => resultInt >> rightVal, - ExpressionType.And => resultInt & rightVal, - ExpressionType.Or => resultInt | rightVal, - ExpressionType.ExclusiveOr => resultInt ^ rightVal, - ExpressionType.Power => (int)Math.Pow(resultInt, rightVal), - _ => UnreachableCase(nodeType, 0), - }; - return true; - } - - if (nodeType == ExpressionType.Negate) - { - var operandExpr = ((UnaryExpression)expr).Operand; - if (operandExpr is ConstantExpression co) - resultInt = (int)co.Value; - else if (!TryInterpretInt(ref resultInt, operandExpr, operandExpr.NodeType)) - return false; - - resultInt = -resultInt; - return true; - } - - if (expr is ConstantExpression constExpr) - { - resultInt = (int)constExpr.Value; - return true; - } - - if (nodeType == ExpressionType.Default) - { - resultInt = 0; - return true; - } - - if (nodeType == ExpressionType.Convert) - { - var operandExpr = ((UnaryExpression)expr).Operand; - var operandCode = Type.GetTypeCode(operandExpr.Type); - Debug.Assert(operandCode != TypeCode.Boolean, - "Operand may be a decimal but cannot be bool, because there is no conversation from bool to the types in PValue"); - - if (operandCode != TypeCode.Decimal) - { - PValue operandVal = default; - if (operandExpr is ConstantExpression co && !TryUnboxToPrimitiveValue(ref operandVal, co.Value, operandCode) || - !TryInterpretPrimitiveValue(ref operandVal, operandExpr, operandCode, operandExpr.NodeType)) - return false; - - resultInt = operandCode switch - { - TypeCode.Char => operandVal.CharValue, - TypeCode.SByte => operandVal.SByteValue, - TypeCode.Byte => operandVal.ByteValue, - TypeCode.Int16 => operandVal.Int16Value, - TypeCode.UInt16 => operandVal.UInt16Value, - TypeCode.Int32 => operandVal.Int32Value, - TypeCode.UInt32 => (int)operandVal.UInt32Value, - TypeCode.Int64 => (int)operandVal.Int64Value, - TypeCode.UInt64 => (int)operandVal.UInt64Value, - TypeCode.Single => (int)operandVal.SingleValue, - TypeCode.Double => (int)operandVal.DoubleValue, - _ => UnreachableCase(operandCode, 0), - }; - return true; - } - // then for the decimal - { - decimal resultDec = default; - if (operandExpr is ConstantExpression co) - resultDec = (decimal)co.Value; - else if (!TryInterpretDecimal(ref resultDec, operandExpr, operandExpr.NodeType)) - return false; - - resultInt = (int)resultDec; - return true; - } - } - return false; - } - - /// Tries to interpret the expression of the Primitive type of Constant, Convert, Logical, Comparison, Arithmetic. - /// Returns `false` if it is failed to do so. - internal static bool TryInterpretPrimitiveValue(ref PValue result, Expression expr, TypeCode exprCode, ExpressionType nodeType) - { - // What is supported for the non-boolean, non-decimal result - // yes: arithmetic, convert, default - // no: not, logical, comparison - if (IsArithmeticBinary(nodeType)) - { - var binaryExpr = (BinaryExpression)expr; - var left = binaryExpr.Left; - var leftCode = Type.GetTypeCode(left.Type); - if (left is ConstantExpression lc && !TryUnboxToPrimitiveValue(ref result, lc.Value, leftCode) || - !TryInterpretPrimitiveValue(ref result, left, leftCode, left.NodeType)) - return false; - - PValue rightVal = default; - var right = binaryExpr.Right; - // Using the leftCode to interpret the right part of the binary expression, - // because for supported operations left and right types are the same - if (right is ConstantExpression rc && !TryUnboxToPrimitiveValue(ref rightVal, rc.Value, leftCode) || - !TryInterpretPrimitiveValue(ref rightVal, right, leftCode, right.NodeType)) - return false; - - DoArithmeticForPrimitiveValues(ref result, ref rightVal, leftCode, nodeType); - return true; - } - - if (nodeType == ExpressionType.Negate) - { - var operandExpr = ((UnaryExpression)expr).Operand; - var operandCode = Type.GetTypeCode(operandExpr.Type); - if (operandExpr is ConstantExpression co && !TryUnboxToPrimitiveValue(ref result, co.Value, operandCode) || - !TryInterpretPrimitiveValue(ref result, operandExpr, operandCode, operandExpr.NodeType)) - return false; - - NegatePrimitiveValue(ref result, operandCode); - return true; - } - - if (expr is ConstantExpression constExpr) - return TryUnboxToPrimitiveValue(ref result, constExpr.Value, exprCode); - - if (nodeType == ExpressionType.Default) - return TrySetPrimitiveValueToDefault(ref result, exprCode); - - if (nodeType == ExpressionType.Convert) - { - var operandExpr = ((UnaryExpression)expr).Operand; - var operandCode = Type.GetTypeCode(operandExpr.Type); - Debug.Assert(operandCode != TypeCode.Boolean, - "Operand may be a decimal but cannot be bool, because there is no conversation from bool to the types in PValue"); - - if (operandCode != TypeCode.Decimal) - { - if (operandExpr is ConstantExpression co && !TryUnboxToPrimitiveValue(ref result, co.Value, operandCode) || - !TryInterpretPrimitiveValue(ref result, operandExpr, operandCode, operandExpr.NodeType)) - return false; - - if (exprCode != operandCode) - ConvertPrimitiveValueFromTo(ref result, operandCode, exprCode); - return true; - } - // then for the decimal - { - decimal operandDec = default; - if (operandExpr is ConstantExpression co) - operandDec = (decimal)co.Value; - else if (!TryInterpretDecimal(ref operandDec, operandExpr, operandExpr.NodeType)) - return false; - - switch (exprCode) - { - case TypeCode.Char: result.CharValue = (char)operandDec; break; - case TypeCode.SByte: result.SByteValue = (sbyte)operandDec; break; - case TypeCode.Byte: result.ByteValue = (byte)operandDec; break; - case TypeCode.Int16: result.Int16Value = (short)operandDec; break; - case TypeCode.UInt16: result.UInt16Value = (ushort)operandDec; break; - case TypeCode.Int32: result.Int32Value = (int)operandDec; break; - case TypeCode.UInt32: result.UInt32Value = (uint)operandDec; break; - case TypeCode.Int64: result.Int64Value = (long)operandDec; break; - case TypeCode.UInt64: result.UInt64Value = (ulong)operandDec; break; - case TypeCode.Single: result.SingleValue = (float)operandDec; break; - case TypeCode.Double: result.DoubleValue = (double)operandDec; break; - default: - // todo: @feature #472 support conversion to nullable, put nullable marker into PValue or something - return false; - } - return true; - } - } - return false; - } - } - } - - /// - /// Helpers targeting the performance. Extensions method names may be a bit funny (non standard), - /// in order to prevent conflicts with YOUR helpers with standard names - /// - public static class Tools - { - public static Expression AsExpr(this object obj) => obj as Expression ?? Constant(obj); - public static Expression[] AsExprs(this object[] obj) - { - var exprs = new Expression[obj.Length]; - for (var i = 0; i < obj.Length; i++) - exprs[i] = obj[i].AsExpr(); - return exprs; - } - - /// Returns true if class is compiler generated. Checking for CompilerGeneratedAttribute - /// is not enough, because this attribute is not applied for classes generated from "async/await". - [MethodImpl((MethodImplOptions)256)] - public static bool IsCompilerGenerated(this Type type) => - type.Name[0] == '<'; // consider the types with obstruct names like `<>blah` as compiler-generated - - [MethodImpl((MethodImplOptions)256)] - internal static bool IsUnsigned(this Type type) => - type == typeof(byte) || - type == typeof(ushort) || - type == typeof(uint) || - type == typeof(ulong); - - [MethodImpl((MethodImplOptions)256)] - internal static bool IsFloatingPoint(this Type type) => - type == typeof(float) || - type == typeof(double); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static bool IsInteger(this Type type) => Type.GetTypeCode(type) switch - { - TypeCode.Char => true, - TypeCode.SByte => true, - TypeCode.Byte => true, - TypeCode.Int16 => true, - TypeCode.UInt16 => true, - TypeCode.Int32 => true, - TypeCode.UInt32 => true, - TypeCode.Int64 => true, - TypeCode.UInt64 => true, - _ => false - }; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static bool IsIntegerOrUnderlyingInteger(this Type type) => - type.IsPrimitive && type.IsInteger() || - type.IsEnum && Enum.GetUnderlyingType(type).IsInteger(); - - internal static bool IsPrimitiveWithZeroDefaultExceptDecimal(this Type type) - { - switch (Type.GetTypeCode(type)) - { - case TypeCode.Boolean: - case TypeCode.Char: - case TypeCode.SByte: - 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: - return true; - default: - return false; - } - } - - [RequiresUnreferencedCode(Trimming.Message)] - [MethodImpl((MethodImplOptions)256)] - public static bool IsNullable(this Type type) => - (type.IsValueType & type.IsGenericType) && type.GetGenericTypeDefinition() == typeof(Nullable<>); - - [RequiresUnreferencedCode(Trimming.Message)] - [MethodImpl((MethodImplOptions)256)] - public static Type GetUnderlyingNullableTypeUnsafe(this Type type) => type.GetGenericArguments()[0]; - - [RequiresUnreferencedCode(Trimming.Message)] - [MethodImpl((MethodImplOptions)256)] - public static Type GetNonNullableOrSelf(this Type type) => type.IsNullable() ? type.GetGenericArguments()[0] : type; - - [RequiresUnreferencedCode(Trimming.Message)] - [MethodImpl((MethodImplOptions)256)] - public static Type GetNullable(this Type type) => typeof(Nullable<>).MakeGenericType(type); - - public static string GetArithmeticBinaryOperatorMethodName(this ExpressionType nodeType) => - nodeType switch - { - ExpressionType.Add => "op_Addition", - ExpressionType.AddChecked => "op_Addition", - ExpressionType.Subtract => "op_Subtraction", - ExpressionType.SubtractChecked => "op_Subtraction", - ExpressionType.Multiply => "op_Multiply", - ExpressionType.MultiplyChecked => "op_Multiply", - ExpressionType.Divide => "op_Division", - ExpressionType.Modulo => "op_Modulus", - _ => null - }; - - internal static bool IsAssignNodeType(this ExpressionType nodeType) => nodeType switch - { - ExpressionType.Assign => true, - ExpressionType.PowerAssign => true, - ExpressionType.AndAssign => true, - ExpressionType.OrAssign => true, - ExpressionType.AddAssign => true, - ExpressionType.ExclusiveOrAssign => true, - ExpressionType.AddAssignChecked => true, - ExpressionType.SubtractAssign => true, - ExpressionType.SubtractAssignChecked => true, - ExpressionType.MultiplyAssign => true, - ExpressionType.MultiplyAssignChecked => true, - ExpressionType.DivideAssign => true, - ExpressionType.LeftShiftAssign => true, - ExpressionType.RightShiftAssign => true, - ExpressionType.ModuloAssign => true, - _ => false - }; - - [MethodImpl((MethodImplOptions)256)] - internal static bool IsBracedBlockLike(this ExpressionType nodeType) => - nodeType == ExpressionType.Try | - nodeType == ExpressionType.Switch | - nodeType == ExpressionType.Block | - nodeType == ExpressionType.Loop; - - - [MethodImpl((MethodImplOptions)256)] - internal static bool IsBlockLikeOrConditional(this ExpressionType nodeType) => - nodeType == ExpressionType.Conditional | nodeType == ExpressionType.Coalesce || - IsBracedBlockLike(nodeType); - - [MethodImpl((MethodImplOptions)256)] - internal static bool IsReturnable(this Expression expr) - { - var nodeType = expr.NodeType; - return expr.Type != typeof(void) && - nodeType != ExpressionType.Goto & nodeType != ExpressionType.Label & nodeType != ExpressionType.Throw && - !IsBracedBlockLike(nodeType); - } - - internal static Expression StripConvertRecursively(this Expression expr) => - expr is UnaryExpression convert && convert.NodeType == ExpressionType.Convert - ? StripConvertRecursively(convert.Operand) - : expr; - - internal static bool IsComplexExpression(this Expression expr) - { - expr = expr.StripConvertRecursively(); - return expr.NodeType == ExpressionType.Invoke - || expr.NodeType.IsBlockLikeOrConditional(); - } - - internal static bool IsConstantOrDefault(this Expression expr) - { - var nodeType = StripConvertRecursively(expr).NodeType; - return nodeType == ExpressionType.Constant - | nodeType == ExpressionType.Default; - } - - internal static bool IsParamOrConstantOrDefault(this Expression expr) - { - var nodeType = StripConvertRecursively(expr).NodeType; - return nodeType == ExpressionType.Parameter - | nodeType == ExpressionType.Constant - | nodeType == ExpressionType.Default; - } - - internal static string GetCSharpName(this MemberInfo m) - { - var name = m.Name; - if (m is FieldInfo fi && m.DeclaringType.IsValueType) - { - // btw, `fi.IsSpecialName` returns `false` :/ - if (name[0] == '<') // a backing field for the properties in struct, e.g. k__BackingField - { - var end = name.IndexOf('>'); - if (end > 1) - name = name.Substring(1, end - 1); - } - } - return name; - } - - [RequiresUnreferencedCode(Trimming.Message)] - internal static MethodInfo FindMethod(this Type type, string methodName) - { - var methods = type.GetMethods(); - for (var i = 0; i < methods.Length; i++) - if (methods[i].Name == methodName) - return methods[i]; - return type.BaseType?.FindMethod(methodName); - } - - internal static MethodInfo DelegateTargetGetterMethod = - typeof(Delegate).GetProperty(nameof(Delegate.Target)).GetMethod; - - [MethodImpl((MethodImplOptions)256)] - internal static MethodInfo FindDelegateInvokeMethod( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] this Type type) => - type.GetMethod("Invoke"); - - [RequiresUnreferencedCode(Trimming.Message)] - internal static class NullableReflected where T : struct - { - public static readonly Type NullableType = typeof(T?); - public static readonly MethodInfo ValueGetterMethod = - NullableType.GetProperty("Value").GetMethod; - public static readonly MethodInfo HasValueGetterMethod = - NullableType.GetProperty("HasValue").GetMethod; - public static readonly FieldInfo ValueField = - NullableType.GetField("value", BindingFlags.Instance | BindingFlags.NonPublic); - public static readonly ConstructorInfo Constructor = - NullableType.GetConstructors()[0]; - } - - [RequiresUnreferencedCode(Trimming.Message)] - [MethodImpl((MethodImplOptions)256)] - internal static MethodInfo GetNullableValueGetterMethod(this Type type) => - type == typeof(int?) ? NullableReflected.ValueGetterMethod : - type == typeof(double?) ? NullableReflected.ValueGetterMethod : - type.GetProperty("Value").GetMethod; - - [RequiresUnreferencedCode(Trimming.Message)] - [MethodImpl((MethodImplOptions)256)] - internal static MethodInfo GetNullableHasValueGetterMethod(this Type type) => - type == typeof(int?) ? NullableReflected.HasValueGetterMethod : - type == typeof(double?) ? NullableReflected.HasValueGetterMethod : - type.GetProperty("HasValue").GetMethod; - - [RequiresUnreferencedCode(Trimming.Message)] - [MethodImpl((MethodImplOptions)256)] - internal static FieldInfo GetNullableValueUnsafeAkaGetValueOrDefaultMethod(this Type type) => - type == typeof(int?) ? NullableReflected.ValueField : - type == typeof(double?) ? NullableReflected.ValueField : - type.GetField("value", BindingFlags.Instance | BindingFlags.NonPublic); - - [RequiresUnreferencedCode(Trimming.Message)] - [MethodImpl((MethodImplOptions)256)] - internal static ConstructorInfo GetNullableConstructor(this Type type) => - type == typeof(int?) ? NullableReflected.Constructor : - type == typeof(double?) ? NullableReflected.Constructor : - type.GetConstructors()[0]; - - /// Finds the implicit or explicit conversion operator inType from the sourceType to targetType, - /// otherwise returns null - [RequiresUnreferencedCode(Trimming.Message)] - public static MethodInfo FindConvertOperator(this Type inType, Type sourceType, Type targetType) - { - Debug.Assert(!inType.IsNullable(), "Should not be called for the nullable type"); - Debug.Assert(!inType.IsPrimitive, "Should not be called for the primitive type"); - Debug.Assert(!inType.IsEnum, "Should not be called for the enum type"); - - // note: remember that if inType.IsPrimitive it does contain the explicit or implicit conversion operators at all - if (sourceType == typeof(object) | targetType == typeof(object)) - return null; - - // conversion operators should be declared as static and public - var methods = inType.GetMethods(BindingFlags.Static | BindingFlags.Public); - foreach (var m in methods) - if (m.IsSpecialName && m.ReturnType == targetType) - { - var n = m.Name; - if ((n == "op_Implicit" || n == "op_Explicit") && - m.GetParameters()[0].ParameterType == sourceType) - return m; - } - - return null; - } - - [RequiresUnreferencedCode(Trimming.Message)] - internal static ConstructorInfo FindSingleParamConstructor(this Type type, Type paramType) - { - var ctors = type.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); - for (var i = 0; i < ctors.Length; i++) - { - var ctor = ctors[i]; - var parameters = ctor.GetParameters(); - if (parameters.Length == 1 && parameters[0].ParameterType == paramType) - return ctor; - } - - return null; - } - - public static T[] AsArray(this IEnumerable xs) - { - if (xs is T[] array) - return array; - return xs == null ? null : xs.ToArray(); - } - - internal static IList AsList(this IEnumerable source) => - source == null ? Empty() : source as IList ?? source.ToList(); - - internal static bool TryGetIndex(this IList items, out int index, T item, int count, - TEq eq = default) where TEq : struct, IEq - { - for (var i = 0; (uint)i < count; ++i) - if (eq.Equals(items[i], item)) - { - index = i; - return true; - } - index = -1; - return false; - } - - private static class EmptyArray - { - public static readonly T[] Value = new T[0]; - } - - public static T[] Empty() => EmptyArray.Value; - - public static Type[] GetParamTypes(IReadOnlyList paramExprs) - { - if (paramExprs == null) - return Empty(); - - var count = paramExprs.Count; - if (count == 0) - return Empty(); - - if (count == 1) - return new[] { paramExprs[0].IsByRef ? paramExprs[0].Type.MakeByRefType() : paramExprs[0].Type }; - - var paramTypes = new Type[count]; - for (var i = 0; i < paramTypes.Length; i++) - { - var parameterExpr = paramExprs[i]; - paramTypes[i] = parameterExpr.IsByRef ? parameterExpr.Type.MakeByRefType() : parameterExpr.Type; - } - - return paramTypes; - } - - public static Type GetFuncOrActionType(Type returnType) => - returnType == typeof(void) ? typeof(Action) : typeof(Func<>).MakeGenericType(returnType); - - public static Type GetFuncOrActionType(Type p, Type returnType) => - returnType == typeof(void) ? typeof(Action<>).MakeGenericType(p) : typeof(Func<,>).MakeGenericType(p, returnType); - - public static Type GetFuncOrActionType(Type p0, Type p1, Type returnType) => - returnType == typeof(void) ? typeof(Action<,>).MakeGenericType(p0, p1) : typeof(Func<,,>).MakeGenericType(p0, p1, returnType); - - public static Type GetFuncOrActionType(Type p0, Type p1, Type p2, Type returnType) => - returnType == typeof(void) ? typeof(Action<,,>).MakeGenericType(p0, p1, p2) : typeof(Func<,,,>).MakeGenericType(p0, p1, p2, returnType); - - public static Type GetFuncOrActionType(Type p0, Type p1, Type p2, Type p3, Type returnType) => - returnType == typeof(void) ? typeof(Action<,,,>).MakeGenericType(p0, p1, p2, p3) : typeof(Func<,,,,>).MakeGenericType(p0, p1, p2, p3, returnType); - - public static Type GetFuncOrActionType(Type p0, Type p1, Type p2, Type p3, Type p4, Type returnType) => - returnType == typeof(void) ? typeof(Action<,,,,>).MakeGenericType(p0, p1, p2, p3, p4) : typeof(Func<,,,,,>).MakeGenericType(p0, p1, p2, p3, p4, returnType); - - public static Type GetFuncOrActionType(Type p0, Type p1, Type p2, Type p3, Type p4, Type p5, Type returnType) => - returnType == typeof(void) ? typeof(Action<,,,,,>).MakeGenericType(p0, p1, p2, p3, p4, p5) : typeof(Func<,,,,,,>).MakeGenericType(p0, p1, p2, p3, p4, p5, returnType); - - [RequiresUnreferencedCode(Trimming.Message)] - public static Type GetFuncOrActionType(Type[] paramTypes, Type returnType) - { - if (returnType == typeof(void)) - { - if (paramTypes.Length == 0) - return typeof(Action); - - return GetAction(paramTypes.Length).MakeGenericType(paramTypes); - } - - Type funcType = GetFunc(paramTypes.Length); - Type[] typeParams = new Type[paramTypes.Length + 1]; // todo: @perf could we Rent the array? - Array.Copy(paramTypes, typeParams, paramTypes.Length); - typeParams[paramTypes.Length] = returnType; - return funcType.MakeGenericType(typeParams); - - static Type GetAction(int length) - { - return length switch - { - 1 => typeof(Action<>), - 2 => typeof(Action<,>), - 3 => typeof(Action<,,>), - 4 => typeof(Action<,,,>), - 5 => typeof(Action<,,,,>), - 6 => typeof(Action<,,,,,>), - 7 => typeof(Action<,,,,,,>), - 8 => typeof(Action<,,,,,,,>), - 9 => typeof(Action<,,,,,,,,>), - 10 => typeof(Action<,,,,,,,,,>), - 11 => typeof(Action<,,,,,,,,,,>), - 12 => typeof(Action<,,,,,,,,,,,>), - 13 => typeof(Action<,,,,,,,,,,,,>), - 14 => typeof(Action<,,,,,,,,,,,,,>), - 15 => typeof(Action<,,,,,,,,,,,,,,>), - 16 => typeof(Action<,,,,,,,,,,,,,,,>), - _ => throw new NotSupportedException($"Action with so many ({length}) parameters is not supported!") - }; - } - - static Type GetFunc(int length) - { - return length switch - { - 0 => typeof(Func<>), - 1 => typeof(Func<,>), - 2 => typeof(Func<,,>), - 3 => typeof(Func<,,,>), - 4 => typeof(Func<,,,,>), - 5 => typeof(Func<,,,,,>), - 6 => typeof(Func<,,,,,,>), - 7 => typeof(Func<,,,,,,,>), - 8 => typeof(Func<,,,,,,,,>), - 9 => typeof(Func<,,,,,,,,,>), - 10 => typeof(Func<,,,,,,,,,,>), - 11 => typeof(Func<,,,,,,,,,,,>), - 12 => typeof(Func<,,,,,,,,,,,,>), - 13 => typeof(Func<,,,,,,,,,,,,,>), - 14 => typeof(Func<,,,,,,,,,,,,,,>), - 15 => typeof(Func<,,,,,,,,,,,,,,,>), - 16 => typeof(Func<,,,,,,,,,,,,,,,,>), - _ => throw new NotSupportedException($"Func with so many ({length}) parameters is not supported!") - }; - } - } - - public static T GetFirst(this IEnumerable source) - { - // This is pretty much Linq.FirstOrDefault except it does not need to check - // if source is IPartition (but should it?) - - if (source is IList list) - return list.Count == 0 ? default : list[0]; - var items = source.GetEnumerator(); - return items.MoveNext() ? items.Current : default; - } - - public static T GetFirst(this T[] source) => source.Length == 0 ? default : source[0]; - - public static Expression TryReduceConditionalTest(Expression testExpr) - { - // removing Not by turning Equal -> NotEqual, NotEqual -> Equal - if (testExpr.NodeType == ExpressionType.Not) - { - // simplify the not `==` -> `!=`, `!=` -> `==` - var op = TryReduceConditionalTest(((UnaryExpression)testExpr).Operand); - var nodeType = op.NodeType; - if (nodeType == ExpressionType.Equal) // ensures that it is a BinaryExpression - { - var binOp = (BinaryExpression)op; - return NotEqual(binOp.Left, binOp.Right); - } - else if (nodeType == ExpressionType.NotEqual) // ensures that it is a BinaryExpression - { - var binOp = (BinaryExpression)op; - return Equal(binOp.Left, binOp.Right); - } - } - else if (testExpr is BinaryExpression b) - { - var nodeType = b.NodeType; - if (nodeType == ExpressionType.OrElse | nodeType == ExpressionType.Or) - { - if (b.Left is ConstantExpression lc && lc.Value is bool lcb) - return lcb ? lc : TryReduceConditionalTest(b.Right); - - if (b.Right is ConstantExpression rc && rc.Value is bool rcb && !rcb) - return TryReduceConditionalTest(b.Left); - } - else if (nodeType == ExpressionType.AndAlso | nodeType == ExpressionType.And) - { - if (b.Left is ConstantExpression lc && lc.Value is bool lcb) - return !lcb ? lc : TryReduceConditionalTest(b.Right); - - if (b.Right is ConstantExpression rc && rc.Value is bool rcb && rcb) - return TryReduceConditionalTest(b.Left); - } - } - - return testExpr; - } - - // Does a low-hanging fruit reductions for now @date-20260227 - public static Expression TryReduceConditional(ConditionalExpression condExpr) - { - var testExpr = TryReduceConditionalTest(condExpr.Test); - if (testExpr is BinaryExpression bi && (bi.NodeType == ExpressionType.Equal || bi.NodeType == ExpressionType.NotEqual)) - { - if (bi.Left is ConstantExpression lc && bi.Right is ConstantExpression rc) - { -#if INTERPRETATION_DIAGNOSTICS - Console.WriteLine("//Reduced Conditional in Interpretation: " + condExpr); -#endif - var equals = Equals(lc.Value, rc.Value); - return bi.NodeType == ExpressionType.Equal - ? (equals ? condExpr.IfTrue : condExpr.IfFalse) - : (equals ? condExpr.IfFalse: condExpr.IfTrue); - } - } - - return testExpr is ConstantExpression constExpr && constExpr.Value is bool testBool - ? (testBool ? condExpr.IfTrue : condExpr.IfFalse) - : condExpr; - } - } - - [RequiresUnreferencedCode(Trimming.Message)] - public static class ILGeneratorTools - { - /// Configuration option to disable the pooling - public static bool DisableILGeneratorPooling; - /// Configuration option to disable the ILGenerator Emit debug output - public static bool DisableDemit; - -#if DEMIT - [MethodImpl((MethodImplOptions)256)] - public static void Demit(this ILGenerator il, OpCode opcode, [CallerMemberName] string emitterName = "", [CallerLineNumber] int emitterLine = 0) - { - il.Emit(opcode); - if (DisableDemit) return; - Debug.WriteLine($"{opcode} -- {emitterName}:{emitterLine}"); - } - - [MethodImpl((MethodImplOptions)256)] - public static void Demit(this ILGenerator il, OpCode opcode, Type type, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) - { - il.Emit(opcode, type); - if (DisableDemit) return; - Debug.WriteLine($"{opcode} {type.ToCode(stripNamespace: true)} -- {emitterName}:{emitterLine}"); - } - - [MethodImpl((MethodImplOptions)256)] - public static void Demit(this ILGenerator il, OpCode opcode, FieldInfo value, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) - { - il.Emit(opcode, value); - if (DisableDemit) return; - - var declType = value.DeclaringType?.ToCode(stripNamespace: true) ?? ""; - var fieldType = value.FieldType.ToCode(stripNamespace: true); - Debug.WriteLine($"{opcode} {fieldType} {declType}.{value.Name} -- {emitterName}:{emitterLine}"); - } - - [MethodImpl((MethodImplOptions)256)] - public static void Demit(this ILGenerator il, OpCode opcode, MethodInfo value, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) - { - il.Emit(opcode, value); - if (DisableDemit) return; - - var declType = value.DeclaringType?.ToCode(stripNamespace: true) ?? ""; - var retType = value.ReturnType.ToCode(stripNamespace: true); - var signature = value.ToString(); - var paramStart = signature.IndexOf('('); - var paramsInParens = paramStart == -1 ? "()" : signature.Substring(paramStart); - Debug.WriteLine($"{opcode} {retType} {declType}.{value.Name}{paramsInParens} -- {emitterName}:{emitterLine}"); - } - - [MethodImpl((MethodImplOptions)256)] - public static void Demit(this ILGenerator il, OpCode opcode, ConstructorInfo value, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) - { - il.Emit(opcode, value); - if (DisableDemit) return; - - var declType = value.DeclaringType?.ToCode(stripNamespace: true) ?? ""; - var signature = value.ToString(); - var paramStart = signature.IndexOf('('); - var paramsInParens = paramStart == -1 ? "()" : signature.Substring(paramStart); - Debug.WriteLine($"{opcode} {declType}{paramsInParens} -- {emitterName}:{emitterLine}"); - } - - [MethodImpl((MethodImplOptions)256)] - public static void Demit(this ILGenerator il, OpCode opcode, Label value, - [CallerArgumentExpression("value")] string valueName = null, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) - { - il.Emit(opcode, value); - if (DisableDemit) return; - Debug.WriteLine($"{opcode} {valueName ?? value.ToString()} -- {emitterName}:{emitterLine}"); - } - - [MethodImpl((MethodImplOptions)256)] - public static void DemitSwitch(this ILGenerator il, Label[] gotoLabels, - [CallerArgumentExpression("gotoLabels")] string valueName = null, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) - { - il.Emit(OpCodes.Switch, gotoLabels); - if (DisableDemit) return; - // Use GetHashCode to identify the labels, because it returns Id. Id is not used directly because Id is exposed since .NET 9+ only - Debug.WriteLine($"{OpCodes.Switch} {valueName}=[{string.Join(",", gotoLabels.Select(l => l.GetHashCode())).ToString()}] -- {emitterName}:{emitterLine}"); - } - - [MethodImpl((MethodImplOptions)256)] - public static void DmarkLabel(this ILGenerator il, Label value, - [CallerArgumentExpression("value")] string valueName = null, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) - { - il.MarkLabel(value); - if (DisableDemit) return; - Debug.WriteLine($"MarkLabel: {valueName ?? value.ToString()} -- {emitterName}:{emitterLine}"); - } - - [MethodImpl((MethodImplOptions)256)] - public static void Demit(this ILGenerator il, OpCode opcode, byte value, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) - { - il.Emit(opcode, value); - if (DisableDemit) return; - Debug.WriteLine($"{opcode} {value} -- {emitterName}:{emitterLine}"); - } - - [MethodImpl((MethodImplOptions)256)] - public static void Demit(this ILGenerator il, OpCode opcode, sbyte value, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) - { - il.Emit(opcode, value); - if (DisableDemit) return; - Debug.WriteLine($"{opcode} {value} -- {emitterName}:{emitterLine}"); - } - - [MethodImpl((MethodImplOptions)256)] - public static void Demit(this ILGenerator il, OpCode opcode, short value, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) - { - il.Emit(opcode, value); - if (DisableDemit) return; - Debug.WriteLine($"{opcode} {value} -- {emitterName}:{emitterLine}"); - } - - [MethodImpl((MethodImplOptions)256)] - public static void Demit(this ILGenerator il, OpCode opcode, int value, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) - { - il.Emit(opcode, value); - if (DisableDemit) return; - Debug.WriteLine($"{opcode} {value} -- {emitterName}:{emitterLine}"); - } - - [MethodImpl((MethodImplOptions)256)] - public static void Demit(this ILGenerator il, OpCode opcode, long value, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) - { - il.Emit(opcode, value); - if (DisableDemit) return; - Debug.WriteLine($"{opcode} {value} -- {emitterName}:{emitterLine}"); - } - - [MethodImpl((MethodImplOptions)256)] - public static void Demit(this ILGenerator il, OpCode opcode, float value, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) - { - il.Emit(opcode, value); - if (DisableDemit) return; - Debug.WriteLine($"{opcode} {value} -- {emitterName}:{emitterLine}"); - } - - [MethodImpl((MethodImplOptions)256)] - public static void Demit(this ILGenerator il, OpCode opcode, double value, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) - { - il.Emit(opcode, value); - if (DisableDemit) return; - Debug.WriteLine($"{opcode} {value} -- {emitterName}:{emitterLine}"); - } - - [MethodImpl((MethodImplOptions)256)] - public static void Demit(this ILGenerator il, string value, OpCode opcode, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) - { - il.Emit(opcode, value); - if (DisableDemit) return; - Debug.WriteLine($"{opcode} {value} -- {emitterName}:{emitterLine}"); - } - -#else // no DEMIT! :-) - - [MethodImpl((MethodImplOptions)256)] - public static void Demit(this ILGenerator il, OpCode opcode) => il.Emit(opcode); - - [MethodImpl((MethodImplOptions)256)] - public static void Demit(this ILGenerator il, OpCode opcode, Type value) => il.Emit(opcode, value); - - [MethodImpl((MethodImplOptions)256)] - public static void Demit(this ILGenerator il, OpCode opcode, FieldInfo value) => il.Emit(opcode, value); - - [MethodImpl((MethodImplOptions)256)] - public static void Demit(this ILGenerator il, OpCode opcode, MethodInfo value) => il.Emit(opcode, value); - - [MethodImpl((MethodImplOptions)256)] - public static void Demit(this ILGenerator il, OpCode opcode, ConstructorInfo value) => il.Emit(opcode, value); - - [MethodImpl((MethodImplOptions)256)] - public static void Demit(this ILGenerator il, OpCode opcode, Label value) => il.Emit(opcode, value); - - [MethodImpl((MethodImplOptions)256)] - public static void DemitSwitch(this ILGenerator il, Label[] gotoLabels) => il.Emit(OpCodes.Switch, gotoLabels); - - [MethodImpl((MethodImplOptions)256)] - public static void DmarkLabel(this ILGenerator il, Label value) => il.MarkLabel(value); - - [MethodImpl((MethodImplOptions)256)] - public static void Demit(this ILGenerator il, OpCode opcode, byte value) => il.Emit(opcode, value); - - [MethodImpl((MethodImplOptions)256)] - public static void Demit(this ILGenerator il, OpCode opcode, sbyte value) => il.Emit(opcode, value); - - [MethodImpl((MethodImplOptions)256)] - public static void Demit(this ILGenerator il, OpCode opcode, short value) => il.Emit(opcode, value); - - [MethodImpl((MethodImplOptions)256)] - public static void Demit(this ILGenerator il, OpCode opcode, int value) => il.Emit(opcode, value); - - [MethodImpl((MethodImplOptions)256)] - public static void Demit(this ILGenerator il, OpCode opcode, long value) => il.Emit(opcode, value); - - [MethodImpl((MethodImplOptions)256)] - public static void Demit(this ILGenerator il, OpCode opcode, float value) => il.Emit(opcode, value); - - [MethodImpl((MethodImplOptions)256)] - public static void Demit(this ILGenerator il, OpCode opcode, double value) => il.Emit(opcode, value); - - [MethodImpl((MethodImplOptions)256)] - public static void Demit(this ILGenerator il, string value, OpCode opcode) => il.Emit(opcode, value); -#endif - } - - /// Reflecting the internal methods to access the more performant for defining the local variable - [RequiresUnreferencedCode(Trimming.Message)] - public static class DynamicMethodHacks - { - [ThreadStatic] - internal static ILGenerator _pooledILGenerator; - -#if SUPPORTS_IL_GENERATOR_REUSE - /// Get new or pool and configure existing DynamicILGenerator - [MethodImpl((MethodImplOptions)256)] - public static ILGenerator RentPooledOrNewILGenerator(DynamicMethod dynMethod, Type returnType, Type[] paramTypes, - // the default ILGenerator size is 64 in .NET 8.0+ - int newStreamSize = 64) - { - var reuseILGenerator = DynamicMethodHacks.ReuseDynamicILGenerator; - if (reuseILGenerator != null) - { - var pooledIL = _pooledILGenerator; - _pooledILGenerator = null; - if (pooledIL != null) - { - reuseILGenerator(dynMethod, pooledIL, returnType, paramTypes); - return pooledIL; - } - else - { - Debug.WriteLine("Unexpected: using a New ILGenerator instead of the pooled one"); - } - } - return dynMethod.GetILGenerator(newStreamSize); - } - - /// Should be called only after call to DynamicMethod.CreateDelegate - [MethodImpl((MethodImplOptions)256)] - public static void FreePooledILGenerator(DynamicMethod dynMethod, ILGenerator il) - { - if (DynamicMethodHacks.ReuseDynamicILGenerator != null) - _pooledILGenerator = il; - } -#else - /// Get new or pool and configure existing DynamicILGenerator - [MethodImpl((MethodImplOptions)256)] - public static ILGenerator RentPooledOrNewILGenerator(DynamicMethod dynMethod, Type returnType, Type[] paramTypes, - int newStreamSize = 64) => - dynMethod.GetILGenerator(newStreamSize); - - /// Should be called only after call to DynamicMethod.CreateDelegate - [MethodImpl((MethodImplOptions)256)] - public static void FreePooledILGenerator(DynamicMethod dynMethod, ILGenerator il) { /* do nothing */ } -#endif - - internal static readonly Func GetNextLocalVarLocation; - - internal static int PostInc(ref int i) => i++; - - internal static Action ReuseDynamicILGenerator; - -#pragma warning disable CS0649 // Warning is about the unused field, but it is used by the generated ReuseDynamicILGenerator - [ThreadStatic] - internal static SignatureHelper _pooledSignatureHelper; -#pragma warning restore CS0649 - - internal static FieldInfo ILGeneratorField; - internal static Type DynamicILGeneratorType; - - static DynamicMethodHacks() - { - const BindingFlags instanceNonPublic = BindingFlags.Instance | BindingFlags.NonPublic; - const BindingFlags instancePublic = BindingFlags.Instance | BindingFlags.Public; - const BindingFlags staticNonPublic = BindingFlags.Static | BindingFlags.NonPublic; - const BindingFlags staticPublic = BindingFlags.Static | BindingFlags.Public; - - ILGeneratorField = typeof(DynamicMethod).GetField("_ilGenerator", instanceNonPublic); - if (ILGeneratorField == null) - return; // nothing to do here - - DynamicILGeneratorType = ILGeneratorField.FieldType; - - // Avoid demit polluting the output of the the initialization phase - var prevDemitValue = ILGeneratorTools.DisableDemit; - ILGeneratorTools.DisableDemit = true; - - if (!ILGeneratorTools.DisableILGeneratorPooling) - { - // ## 1. Reuse the DynamicILGenerator - // - // Reference code - NET. v8, v9: - /* - public ILGenerator GetILGenerator(int streamSize) - { - if (_ilGenerator == null) - { - byte[] methodSignature = SignatureHelper.GetMethodSigHelper( - null, CallingConvention, ReturnType, null, null, _parameterTypes, null, null).GetSignature(true); - _ilGenerator = new DynamicILGenerator(this, methodSignature, streamSize); - } - return _ilGenerator; - } - - internal sealed class DynamicScope - { - internal readonly List m_tokens = new List { null }; - public int GetTokenFor(byte[] signature) - { - m_tokens.Add(signature); - return m_tokens.Count - 1 | (int)MetadataTokenType.Signature; - } - } - - internal DynamicScope m_scope; - private readonly int m_methodSigToken; - - public DynamicILGenerator( - DynamicMethod method, - byte[] methodSignature, - int streamSize) - { - m_scope = new DynamicScope(); - m_methodSigToken = m_scope.GetTokenFor(methodSignature); - - m_ScopeTree = new ScopeTree(); - m_ILStream = new byte[Math.Max(size, DefaultSize)]; - - m_localSignature = SignatureHelper.GetLocalVarSigHelper((method as RuntimeMethodBuilder)?.GetTypeBuilder().Module); - m_methodBuilder = method; // set to the new DynamicMethod - } - */ - var m_ScopeTreeField = DynamicILGeneratorType.GetField("m_ScopeTree", instanceNonPublic); - if (m_ScopeTreeField == null) - goto endOfReuse; - - var ScopeTreeCtor = m_ScopeTreeField.FieldType.GetConstructor(instanceNonPublic, null, Type.EmptyTypes, null); - if (ScopeTreeCtor == null) - goto endOfReuse; - - var DynamicILGeneratorScopeField = DynamicILGeneratorType.GetField("m_scope", instanceNonPublic); - if (DynamicILGeneratorScopeField == null) - goto endOfReuse; - - var DynamicILGeneratorScopeType = DynamicILGeneratorScopeField.FieldType; - var DynamicScopeTokensField = DynamicILGeneratorScopeType.GetField("m_tokens", instanceNonPublic); - if (DynamicScopeTokensField == null) - goto endOfReuse; - - var DynamicScopeTokensItem = DynamicScopeTokensField.FieldType.GetProperty("Item"); - Debug.Assert(DynamicScopeTokensItem != null, "DynamicScopeTokens.Item should not be null"); - - var getMethodSigHelperParams = ExpressionCompiler.RentPooledOrNewParamTypes(typeof(Module), typeof(Type), typeof(Type[])); - var GetMethodSigHelperMethod = typeof(SignatureHelper).GetMethod("GetMethodSigHelper", staticPublic, null, getMethodSigHelperParams, null); - ExpressionCompiler.FreePooledParamTypes(getMethodSigHelperParams); - if (GetMethodSigHelperMethod == null) - goto endOfReuse; - - var GetLocalVarSigHelperMethod = typeof(SignatureHelper).GetMethod("GetLocalVarSigHelper", staticPublic, null, Type.EmptyTypes, null); - if (GetLocalVarSigHelperMethod == null) - goto endOfReuse; - - var getSignatureParams = ExpressionCompiler.RentPooledOrNewParamTypes(typeof(bool)); - var SignatureHelper_GetSignatureMethod = typeof(SignatureHelper).GetMethod("GetSignature", instanceNonPublic, null, getSignatureParams, null); - ExpressionCompiler.FreePooledParamTypes(getSignatureParams); - if (SignatureHelper_GetSignatureMethod == null) - goto endOfReuse; - - var getTokenForParams = ExpressionCompiler.RentPooledOrNewParamTypes(typeof(byte[])); - var GetTokenForMethod = DynamicILGeneratorScopeType.GetMethod("GetTokenFor", instancePublic, null, getTokenForParams, null); - ExpressionCompiler.FreePooledParamTypes(getTokenForParams); - if (GetTokenForMethod == null) - goto endOfReuse; - - var MethodSigTokenField = DynamicILGeneratorType.GetField("m_methodSigToken", instanceNonPublic); - if (MethodSigTokenField == null) - goto endOfReuse; - - var dynMethodParamTypes = ExpressionCompiler.RentPooledOrNewParamTypes( - typeof(ExpressionCompiler.ArrayClosure), typeof(DynamicMethod), typeof(ILGenerator), typeof(Type), typeof(Type[])); - - var dynMethod = new DynamicMethod(string.Empty, typeof(void), dynMethodParamTypes, typeof(ExpressionCompiler.ArrayClosure), true); - - var il = dynMethod.GetILGenerator(512); // precalculated size to avoid waste - - var baseFields = DynamicILGeneratorType.BaseType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly); - foreach (var field in baseFields) - { - var fieldName = field.Name; - if (fieldName == "m_localSignature") - { - il.Demit(OpCodes.Ldarg_2); - il.Demit(OpCodes.Call, GetLocalVarSigHelperMethod); - il.Demit(OpCodes.Stfld, field); - continue; - } - - // m_ScopeTree = new ScopeTree(); - if (fieldName == "m_ScopeTree") - { - il.Demit(OpCodes.Ldarg_2); - il.Demit(OpCodes.Newobj, ScopeTreeCtor); - il.Demit(OpCodes.Stfld, field); - continue; - } - - // m_methodBuilder = method; // dynamicMethod - if (fieldName == "m_methodBuilder") - { - il.Demit(OpCodes.Ldarg_2); - il.Demit(OpCodes.Ldarg_1); - il.Demit(OpCodes.Stfld, field); - continue; - } - - if (fieldName == "m_ILStream") - continue; // reuse the previous il stream - - il.Demit(OpCodes.Ldarg_2); - ExpressionCompiler.EmittingVisitor.EmitDefault(il, field.FieldType); - il.Demit(OpCodes.Stfld, field); - } - - // var scope = new DynamicScope(); - var dynamicScopeCtor = DynamicILGeneratorScopeType.GetConstructor(Type.EmptyTypes); - if (dynamicScopeCtor == null) - goto endOfReuse; - il.Emit(OpCodes.Newobj, dynamicScopeCtor); - var scopeVar = il.DeclareLocal(DynamicILGeneratorScopeType).LocalIndex; - ExpressionCompiler.EmittingVisitor.EmitStoreLocalVariable(il, scopeVar); - - /* - private byte[] m_signature; // todo: @perf keep it, because it would be copied anyway - private int m_currSig; // index into m_signature buffer for next available byte - private int m_sizeLoc; // index into m_signature buffer to put m_argCount (will be NO_SIZE_IN_SIG if no arg count is needed) - private ModuleBuilder? m_module; - private bool m_sigDone; - private int m_argCount; // tracking number of arguments in the signature - - internal static SignatureHelper GetMethodSigHelper( - Module? scope, CallingConventions callingConvention, int cGenericParam, - Type? returnType, Type[]? requiredReturnTypeCustomModifiers, Type[]? optionalReturnTypeCustomModifiers, - Type[]? parameterTypes, Type[][]? requiredParameterTypeCustomModifiers, Type[][]? optionalParameterTypeCustomModifiers) - { - SignatureHelper sigHelp; - MdSigCallingConvention intCall; - - // not needed, always provided - returnType ??= typeof(void); - - // not needed, always CallingConventions.Standard - intCall = MdSigCallingConvention.Default; - if ((callingConvention & CallingConventions.VarArgs) == CallingConventions.VarArgs) - intCall = MdSigCallingConvention.Vararg; - - // not needed, always 0 - if (cGenericParam > 0) - { - intCall |= MdSigCallingConvention.Generic; - } - - // not needed, can be externalized as a const, precalculate the `unchecked((byte)CallingConventions.Standard) & Mask)` - const byte Mask = (byte)(CallingConventions.HasThis | CallingConventions.ExplicitThis); - intCall = (MdSigCallingConvention)((byte)intCall | (unchecked((byte)callingConvention) & Mask)); - - sigHelp = new SignatureHelper(scope, intCall, cGenericParam, returnType, - requiredReturnTypeCustomModifiers, optionalReturnTypeCustomModifiers); - - // m_signature = new byte[32]; - // m_currSig = 0; - // m_module = mod as ModuleBuilder; - // m_argCount = 0; - // m_sigDone = false; - // m_sizeLoc = NO_SIZE_IN_SIG; - - sigHelp.AddArguments(parameterTypes, requiredParameterTypeCustomModifiers, optionalParameterTypeCustomModifiers); - - return sigHelp; - } - */ - // pseudo code to reuse the pooled SignatureHelper - /* - var signatureHelper = _pooledSignatureHelper; - _pooledSignatureHelper = null; - if (signatureHelper == null) - signatureBytes = SignatureHelper.GetMethodSigHelper(null, returnType, paramTypes).GetSignature(true); - else - { - // no need to set, can reuse the previous value - // m_signature = new byte[32]; - // signatureHelper.m_module = null; - signatureHelper.m_currSig = 0; - signatureHelper.m_argCount = 0; - signatureHelper.m_sigDone = false; - signatureHelper.m_sizeLoc = -1; - signatureHelper.AddOneArgTypeHelper(returnType, null, null); - signatureHelper.AddArguments(paramTypes, null, null); - - var signatureBytes = signatureHelper.GetSignature(true); - _pooledSignatureHelper = signatureHelper; - } - */ - var sigBytesVar = il.DeclareLocal(typeof(byte[])).LocalIndex; - - var pooledSignatureHelperField = typeof(DynamicMethodHacks).GetField(nameof(_pooledSignatureHelper), staticNonPublic); - Debug.Assert(pooledSignatureHelperField != null, "_pooledSignatureHelper field not found!"); - - il.Emit(OpCodes.Ldsfld, pooledSignatureHelperField); - var sigHelperVar = il.DeclareLocal(typeof(SignatureHelper)).LocalIndex; - ExpressionCompiler.EmittingVisitor.EmitStoreAndLoadLocalVariable(il, sigHelperVar); // loading it here to do a Brfalse below, after setting the field to null - il.Emit(OpCodes.Ldnull); // set the pooled instance to null - il.Emit(OpCodes.Stsfld, pooledSignatureHelperField); - - var labelSigHelperNull = il.DefineLabel(); - il.Emit(OpCodes.Brfalse, labelSigHelperNull); - - // signatureHelper.m_currSig = 0 - ExpressionCompiler.EmittingVisitor.EmitLoadLocalVariable(il, sigHelperVar); - il.Emit(OpCodes.Ldc_I4_0); - var m_currSig = typeof(SignatureHelper).GetField("m_currSig", instanceNonPublic); - if (m_currSig == null) - goto endOfReuse; - il.Emit(OpCodes.Stfld, m_currSig); - - //signatureHelper.m_argCount = 0; - ExpressionCompiler.EmittingVisitor.EmitLoadLocalVariable(il, sigHelperVar); - il.Emit(OpCodes.Ldc_I4_0); - var m_argCount = typeof(SignatureHelper).GetField("m_argCount", instanceNonPublic); - if (m_argCount == null) - goto endOfReuse; - il.Emit(OpCodes.Stfld, m_argCount); - - // signatureHelper.m_sigDone = false; - ExpressionCompiler.EmittingVisitor.EmitLoadLocalVariable(il, sigHelperVar); - il.Emit(OpCodes.Ldc_I4_0); - var m_sigDone = typeof(SignatureHelper).GetField("m_sigDone", instanceNonPublic); - if (m_sigDone == null) - goto endOfReuse; - il.Emit(OpCodes.Stfld, m_sigDone); - - // signatureHelper.m_sizeLoc = -1; - ExpressionCompiler.EmittingVisitor.EmitLoadLocalVariable(il, sigHelperVar); - il.Emit(OpCodes.Ldc_I4_M1); - var m_sizeLoc = typeof(SignatureHelper).GetField("m_sizeLoc", instanceNonPublic); - if (m_sizeLoc == null) - goto endOfReuse; - il.Emit(OpCodes.Stfld, m_sizeLoc); - - // signatureHelper.AddOneArgTypeHelper(returnType, null, null); - ExpressionCompiler.EmittingVisitor.EmitLoadLocalVariable(il, sigHelperVar); - il.Emit(OpCodes.Ldarg_3); // load return type - il.Emit(OpCodes.Ldnull); // load required return type custom modifiers - il.Emit(OpCodes.Ldnull); // load optional return type custom modifiers - var addOneArgTypeHelperParams = ExpressionCompiler.RentPooledOrNewParamTypes(typeof(Type), typeof(Type[]), typeof(Type[])); - var AddOneArgTypeHelperMethod = typeof(SignatureHelper).GetMethod("AddOneArgTypeHelper", instanceNonPublic, null, addOneArgTypeHelperParams, null); - ExpressionCompiler.FreePooledParamTypes(addOneArgTypeHelperParams); - if (AddOneArgTypeHelperMethod == null) - goto endOfReuse; - il.Emit(OpCodes.Call, AddOneArgTypeHelperMethod); - - // signatureHelper.AddArguments(paramTypes, null, null); - ExpressionCompiler.EmittingVisitor.EmitLoadLocalVariable(il, sigHelperVar); - il.Emit(OpCodes.Ldarg_S, 4); // load parameter types arrays - il.Emit(OpCodes.Ldnull); // load required parameter type custom modifiers - il.Emit(OpCodes.Ldnull); // load optional parameter type custom modifiers - - var addArgumentsParams = ExpressionCompiler.RentPooledOrNewParamTypes(typeof(Type[]), typeof(Type[][]), typeof(Type[][])); - var AddArgumentsMethod = typeof(SignatureHelper).GetMethod("AddArguments", instancePublic, null, addArgumentsParams, null); - ExpressionCompiler.FreePooledParamTypes(addArgumentsParams); - if (AddArgumentsMethod == null) - goto endOfReuse; - il.Emit(OpCodes.Call, AddArgumentsMethod); - - // signatureBytes = signatureHelper.GetSignature(true); - ExpressionCompiler.EmittingVisitor.EmitLoadLocalVariable(il, sigHelperVar); - il.Emit(OpCodes.Ldc_I4_1); // load true - il.Emit(OpCodes.Call, SignatureHelper_GetSignatureMethod); - ExpressionCompiler.EmittingVisitor.EmitStoreLocalVariable(il, sigBytesVar); - - // free the signature helper to the pool - ExpressionCompiler.EmittingVisitor.EmitLoadLocalVariable(il, sigHelperVar); - il.Emit(OpCodes.Stsfld, pooledSignatureHelperField); - - // done - var labelSigHelperDone = il.DefineLabel(); - il.Emit(OpCodes.Br, labelSigHelperDone); - - // Standard handling: - // signatureBytes = SignatureHelper.GetMethodSigHelper(null, returnType, paramTypes).GetSignature(true); - il.MarkLabel(labelSigHelperNull); - - il.Emit(OpCodes.Ldnull); // for the module - il.Emit(OpCodes.Ldarg_3); // load return type - il.Emit(OpCodes.Ldarg_S, 4); // load parameter types arrays - il.Emit(OpCodes.Call, GetMethodSigHelperMethod); - il.Emit(OpCodes.Ldc_I4_1); // load true - il.Emit(OpCodes.Call, SignatureHelper_GetSignatureMethod); - ExpressionCompiler.EmittingVisitor.EmitStoreLocalVariable(il, sigBytesVar); - - // todo: @perf GetSignature(true) will copy internal byte buffer almost always, see - /* - internal byte[] GetSignature(bool appendEndOfSig) - { - // Chops the internal signature to the appropriate length. Adds the - // end token to the signature and marks the signature as finished so that - // no further tokens can be added. Return the full signature in a trimmed array. - if (!m_sigDone) - { - if (appendEndOfSig) - AddElementType(CorElementType.ELEMENT_TYPE_END); - SetNumberOfSignatureElements(true); - m_sigDone = true; - } - - // This case will only happen if the user got the signature through - // InternalGetSignature first and then called GetSignature. - if (m_signature.Length > m_currSig) - { - byte[] temp = new byte[m_currSig]; - Array.Copy(m_signature, temp, m_currSig); - m_signature = temp; - } - - return m_signature; - } - */ - - il.MarkLabel(labelSigHelperDone); - - // m_methodSigToken = scope.GetTokenFor(methodSignature); - il.Emit(OpCodes.Ldarg_2); - ExpressionCompiler.EmittingVisitor.EmitLoadLocalVariable(il, scopeVar); - ExpressionCompiler.EmittingVisitor.EmitLoadLocalVariable(il, sigBytesVar); - il.Emit(OpCodes.Call, GetTokenForMethod); - il.Emit(OpCodes.Stfld, MethodSigTokenField); - - // m_scope = scope; - il.Emit(OpCodes.Ldarg_2); - ExpressionCompiler.EmittingVisitor.EmitLoadLocalVariable(il, scopeVar); - il.Emit(OpCodes.Stfld, DynamicILGeneratorScopeField); - - // store the reused ILGenerator to - il.Emit(OpCodes.Ldarg_1); - il.Emit(OpCodes.Ldarg_2); - il.Emit(OpCodes.Stfld, ILGeneratorField); - - il.Emit(OpCodes.Ret); - - ReuseDynamicILGenerator = (Action) - dynMethod.CreateDelegate(typeof(Action), ExpressionCompiler.EmptyArrayClosure); - - // Put the first used ILGenerator into the pool, let's not waste it from te get go - _pooledILGenerator = il; - - ExpressionCompiler.FreePooledParamTypes(dynMethodParamTypes); - endOfReuse:; - } - { - // ## 2. Get Next Local Variable Index/Location - - // The original ILGenerator methods we are trying to hack without allocating the `LocalBuilder` - /* - public virtual LocalBuilder DeclareLocal(Type localType) - { - return this.DeclareLocal(localType, false); - } - - public virtual LocalBuilder DeclareLocal(Type localType, bool pinned) - { - MethodBuilder methodBuilder = this.m_methodBuilder as MethodBuilder; - if ((MethodInfo)methodBuilder == (MethodInfo)null) - throw new NotSupportedException(); - if (methodBuilder.IsTypeCreated()) - throw new InvalidOperationException(SR.InvalidOperation_TypeHasBeenCreated); - if (localType == (Type)null) - throw new ArgumentNullException(nameof(localType)); - if (methodBuilder.m_bIsBaked) - throw new InvalidOperationException(SR.InvalidOperation_MethodBaked); - this.m_localSignature.AddArgument(localType, pinned); - LocalBuilder localBuilder = new LocalBuilder(this.m_localCount, localType, (MethodInfo)methodBuilder, pinned); - ++this.m_localCount; - return localBuilder; - } - */ -#if NET10_0_OR_GREATER - // In .NET 10+, use UnsafeAccessorType to directly access RuntimeILGenerator's private fields - // without the need for reflection-based DynamicMethod generation at startup - GetNextLocalVarLocation = static (il, t) => - { - GetMLocalSignature(il).AddArgument(t, false); - return PostInc(ref GetMLocalCount(il)); - }; -#else - // Let's try to acquire the more efficient less allocating method - var m_localSignatureField = DynamicILGeneratorType.GetField("m_localSignature", instanceNonPublic); - if (m_localSignatureField == null) - goto endOfGetNextVar; - - var m_localCountField = DynamicILGeneratorType.GetField("m_localCount", instanceNonPublic); - if (m_localCountField == null) - goto endOfGetNextVar; - - // looking for the `SignatureHelper.AddArgument(Type argument, bool pinned)` - var typeAndBoolParamTypes = ExpressionCompiler.RentPooledOrNewParamTypes(typeof(Type), typeof(bool)); - var SignatureHelper_AddArgumentMethod = typeof(SignatureHelper).GetMethod("AddArgument", typeAndBoolParamTypes); - ExpressionCompiler.FreePooledParamTypes(typeAndBoolParamTypes); - if (SignatureHelper_AddArgumentMethod == null) - goto endOfGetNextVar; - - // our own helper - always available - var postIncMethod = typeof(DynamicMethodHacks).GetMethod(nameof(PostInc), staticNonPublic); - Debug.Assert(postIncMethod != null, "PostInc method not found!"); - - var paramTypes = ExpressionCompiler.RentPooledOrNewParamTypes(typeof(ExpressionCompiler.ArrayClosure), typeof(ILGenerator), typeof(Type)); - var dynMethod = new DynamicMethod(string.Empty, typeof(int), paramTypes, typeof(ExpressionCompiler.ArrayClosure), true); - - // it does not use the pooled il generator here, to isolate this variable hack from the il generator pooling hack and for the better problem diagnostics - var il = dynMethod.GetILGenerator(32); - - // emitting `il.m_localSignature.AddArgument(type);` - il.Emit(OpCodes.Ldarg_1); // load `il` argument (arg_0 is the empty closure object) - il.Emit(OpCodes.Ldfld, m_localSignatureField); - il.Emit(OpCodes.Ldarg_2); // load `type` argument - il.Emit(OpCodes.Ldc_I4_0); // load `pinned: false` argument - il.Emit(OpCodes.Call, SignatureHelper_AddArgumentMethod); - - // emitting `return PostInc(ref il.LocalCount);` - il.Emit(OpCodes.Ldarg_1); // load `il` argument - il.Emit(OpCodes.Ldflda, m_localCountField); - il.Emit(OpCodes.Call, postIncMethod); - - il.Emit(OpCodes.Ret); - - GetNextLocalVarLocation = (Func) - dynMethod.CreateDelegate(typeof(Func), ExpressionCompiler.EmptyArrayClosure); - - ExpressionCompiler.FreePooledParamTypes(paramTypes); - endOfGetNextVar:; -#endif - } - - // Restore the demit - ILGeneratorTools.DisableDemit = prevDemitValue; - - // ## 3 TBD - // - // todo: @perf do batch Emit by manually calling `EnsureCapacity` once then `InternalEmit` multiple times - // todo: @perf Replace the `Emit(opcode, int)` with the more specialized `Emit(opcode)`, `Emit(opcode, byte)` or `Emit(opcode, short)` - // avoiding internal check for Ldc_I4, Ldarg, Ldarga, Starg then call `PutInteger4` only if needed see https://source.dot.net/#System.Private.CoreLib/src/System/Reflection/Emit/ILGenerator.cs,690f350859394132 - // var ensureCapacityMethod = ilGenTypeInfo.GetDeclaredMethod("EnsureCapacity"); - // var internalEmitMethod = ilGenTypeInfo.GetDeclaredMethod("InternalEmit"); - // var putInteger4Method = ilGenTypeInfo.GetDeclaredMethod("PutInteger4"); - } - - -#if DEBUG_INFO_LOCAL_VARIABLE_USAGE - [ThreadStatic] - public static SmallMap8> LocalVarUsage; -#endif - // todo: @perf add the map of the used local variables that can be reused, e.g. we are getting the variable used in the local scope but then we may return them into POOL and reuse (many of int variable can be reuses, say for indexes) - /// Efficiently returns the next variable index, hopefully without unnecessary allocations. - [MethodImpl((MethodImplOptions)256)] - public static int GetNextLocalVarIndex(this ILGenerator il, Type t) - { -#if DEBUG_INFO_LOCAL_VARIABLE_USAGE - try - { - ref var varUsage = ref LocalVarUsage.Map.AddOrGetValueRef(t, out var found); - if (!found) - varUsage = 1; - else - ++varUsage; - } - catch (Exception ex) - { - Debug.WriteLine("Error tracking the local variable usage: " + ex); - } -#endif - return GetNextLocalVarLocation?.Invoke(il, t) ?? il.DeclareLocal(t).LocalIndex; - } - - // todo: @perf add MultiOpCodes emit to save on the EnsureCapacity calls - // todo: @perf create EmitMethod without additional GetParameters call - /* - // original code: - public override void Emit(OpCode opcode, MethodInfo meth) - { - ArgumentNullException.ThrowIfNull(meth); - - int stackchange = 0; - int token; - DynamicMethod? dynMeth = meth as DynamicMethod; - if (dynMeth == null) - { - RuntimeMethodInfo? rtMeth = meth as RuntimeMethodInfo; - if (rtMeth == null) - throw new ArgumentException(SR.Argument_MustBeRuntimeMethodInfo, nameof(meth)); - - RuntimeType declaringType = rtMeth.GetRuntimeType(); - if (declaringType != null && (declaringType.IsGenericType || declaringType.IsArray)) - token = GetTokenFor(rtMeth, declaringType); - else - token = GetTokenFor(rtMeth); - } - else - { - // rule out not allowed operations on DynamicMethods - if (opcode.Equals(OpCodes.Ldtoken) || opcode.Equals(OpCodes.Ldftn) || opcode.Equals(OpCodes.Ldvirtftn)) - { - throw new ArgumentException(SR.Argument_InvalidOpCodeOnDynamicMethod); - } - token = GetTokenFor(dynMeth); - } - - EnsureCapacity(7); - InternalEmit(opcode); - - if (opcode.StackBehaviourPush == StackBehaviour.Varpush - && meth.ReturnType != typeof(void)) - { - stackchange++; - } - if (opcode.StackBehaviourPop == StackBehaviour.Varpop) - { - stackchange -= meth.GetParametersNoCopy().Length; - } - // Pop the "this" parameter if the method is non-static, - // and the instruction is not newobj/ldtoken/ldftn. - if (!meth.IsStatic && - !(opcode.Equals(OpCodes.Newobj) || opcode.Equals(OpCodes.Ldtoken) || opcode.Equals(OpCodes.Ldftn))) - { - stackchange--; - } - - UpdateStackSize(opcode, stackchange); - PutInteger4(token); - } - - // stripped down code for not generic method and not array method: - public override void Emit(OpCode opcode, MethodInfo meth, int paramCount) - { - m_scope.m_tokens.Add(((RuntimeMethodInfo)meth).MethodHandle); - var token = m_scope.m_tokens.Count - 1 | (int)MetadataTokenType.MethodDef; // MethodDef is 0x06000000 - - // Guarantees an array capable of holding at least size elements. - if (m_length + size >= m_ILStream.Length) - IncreaseCapacity(7); - - m_ILStream[m_length++] = (byte)opcode.Value; - UpdateStackSize(opcode, 0); - - int stackchange = 0; - if (meth.ReturnType != typeof(void)) - stackchange++; - stackchange -= paramCount; - if (!meth.IsStatic) - stackchange--; - - UpdateStackSize(opcode, stackchange); - - BinaryPrimitives.WriteInt32LittleEndian(m_ILStream.AsSpan(m_length), token); - m_length += 4; - } - */ - -#if NET10_0_OR_GREATER - // UnsafeAccessorType methods for accessing private fields of the non-public RuntimeILGenerator class. - // RuntimeILGenerator is the internal base class of DynamicILGenerator that holds the core IL generation state. - // Using UnsafeAccessorType avoids reflection at call time and is compatible with AOT compilation. - - /// Gets a ref to the m_localSignature field of the ILGenerator (declared in the internal RuntimeILGenerator). - [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "m_localSignature")] - internal static extern ref SignatureHelper GetMLocalSignature( - [UnsafeAccessorType("System.Reflection.Emit.RuntimeILGenerator")] object il); - - /// Gets a ref to the m_localCount field of the ILGenerator (declared in the internal RuntimeILGenerator). - [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "m_localCount")] - internal static extern ref int GetMLocalCount( - [UnsafeAccessorType("System.Reflection.Emit.RuntimeILGenerator")] object il); - - /// Gets a ref to the m_length field of the ILGenerator (declared in the internal RuntimeILGenerator). - [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "m_length")] - internal static extern ref int GetMLength( - [UnsafeAccessorType("System.Reflection.Emit.RuntimeILGenerator")] object il); - - /// Gets a ref to the m_ILStream field of the ILGenerator (declared in the internal RuntimeILGenerator). - [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "m_ILStream")] - internal static extern ref byte[] GetMILStream( - [UnsafeAccessorType("System.Reflection.Emit.RuntimeILGenerator")] object il); - - /// Calls the internal UpdateStackSize method on the ILGenerator (declared in the internal RuntimeILGenerator). - [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "UpdateStackSize")] - internal static extern void UpdateStackSize( - [UnsafeAccessorType("System.Reflection.Emit.RuntimeILGenerator")] object il, - OpCode opcode, int stackchange); - - /// Gets a ref to the m_tokens field on a DynamicScope instance (internal type). - [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "m_tokens")] - internal static extern ref System.Collections.Generic.List GetMTokens( - [UnsafeAccessorType("System.Reflection.Emit.DynamicScope")] object scope); -#endif - } - - [RequiresUnreferencedCode(Trimming.Message)] - public static class ToExpressionPrinter - { - /// - /// Prints the expression in its constructing syntax - - /// helpful to get the expression from the debug session and put into it the code for the test. - /// - public static string ToExpressionString(this Expression expr, ObjectToCode notRecognizedToCode = null) => - expr.ToExpressionString(out var _, out var _, out var _, notRecognizedToCode: notRecognizedToCode); - - // todo: @api There should be a version returning StringBuilder the same as for ToCSharpString - /// - /// Prints the expression in its constructing syntax - - /// helpful to get the expression from the debug session and put into it the code for the test. - /// In addition, returns the gathered expressions, parameters ad labels. - /// - public static string ToExpressionString(this Expression expr, - out List paramsExprs, out List uniqueExprs, out List lts, - bool stripNamespace = false, Func printType = null, int indentSpaces = 2, ObjectToCode notRecognizedToCode = null) - { - var sb = new StringBuilder(1024); - sb.Append("var expr = "); - paramsExprs = new List(); - uniqueExprs = new List(); - lts = new List(); - sb = expr.CreateExpressionString(sb, paramsExprs, uniqueExprs, lts, 2, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(';'); - - if (lts.Count > 0) - sb.Insert(0, $"var l = new LabelTarget[{lts.Count}]; // the labels{NewLine}"); - if (uniqueExprs.Count > 0) - sb.Insert(0, $"var e = new Expression[{uniqueExprs.Count}]; // the unique expressions{NewLine}"); - if (paramsExprs.Count > 0) - sb.Insert(0, $"var p = new ParameterExpression[{paramsExprs.Count}]; // the parameter expressions{NewLine}"); - - return sb.ToString(); - } - - // Searches first for the expression reference in the `uniqueExprs` and adds the reference to expression by index, - // otherwise delegates to `CreateExpressionCodeString` - internal static StringBuilder ToExpressionString(this Expression expr, StringBuilder sb, - List paramsExprs, List uniqueExprs, List lts, - int lineIndent, bool stripNamespace, Func printType, int indentSpaces, ObjectToCode notRecognizedToCode) - { - if (expr is ParameterExpression p) - return p.ToExpressionString(sb, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - - if (uniqueExprs.TryGetIndex(out var i, expr, uniqueExprs.Count, default(RefEq))) - return sb.Append("e[").Append(i) - // output expression type and kind to help to understand what is it - .Append(" // ").Append(expr.NodeType.ToString()).Append(" of ") - .Append(expr.Type.ToCode(stripNamespace, printType)) - .NewLineIndent(lineIndent).Append("]"); - - uniqueExprs.Add(expr); - sb.Append("e[").Append(uniqueExprs.Count - 1).Append("]="); - return expr.CreateExpressionString(sb, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - } - - internal static StringBuilder ToExpressionString(this ParameterExpression pe, StringBuilder sb, - List paramsExprs, List uniqueExprs, List lts, - int lineIndent, bool stripNamespace, Func printType, int indentSpaces, ObjectToCode notRecognizedToCode) - { - if (paramsExprs.TryGetIndex(out var i, pe, paramsExprs.Count, default(RefEq))) - { - PrintContext ctx = default; - return sb - .Append("p[").Append(i) - .Append(" // (") - .Append(!pe.Type.IsPrimitive && pe.Type.IsValueType ? "[struct] " : string.Empty) - .Append(pe.Type.ToCode(stripNamespace, printType)) - .Append(' ') - .AppendName(pe, pe.Name, pe.Type.ToCode(stripNamespace, printType), ref ctx, pe.GetHashCode()).Append(')') // todo: @wip #434 but for ToExpressionString - .NewLineIndent(lineIndent) - .Append(']'); - } - paramsExprs.Add(pe); - sb.Append("p[").Append(paramsExprs.Count - 1).Append("]="); - return pe.CreateExpressionString(sb, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - } - - internal static StringBuilder ToExpressionString(this LabelTarget lt, StringBuilder sb, List labelTargets, - int lineIndent, bool stripNamespace, Func printType) - { - if (labelTargets.TryGetIndex(out var i, lt, labelTargets.Count, default(RefEq))) - { - PrintContext ctx = default; - return sb.Append("l[").Append(i) - .Append(" // (").AppendName(lt, lt.Name, lt.Type.ToCode(stripNamespace, printType), ref ctx, lt.GetHashCode()).Append(')') - .NewLineIndent(lineIndent).Append(']'); - } - labelTargets.Add(lt); - sb.Append("l[").Append(labelTargets.Count - 1).Append("]=Label("); - sb.AppendTypeOf(lt.Type, stripNamespace, printType); - - return (lt.Name != null ? sb.Append(", \"").Append(lt.Name).Append("\"") : sb).Append(")"); - } - - private static StringBuilder ToExpressionString(this IReadOnlyList bs, StringBuilder sb, - List paramsExprs, List uniqueExprs, List lts, - int lineIndent, bool stripNamespace, Func printType, int indentSpaces, ObjectToCode notRecognizedToCode) - { - if (bs.Count == 0) - return sb.Append("new CatchBlock[0]"); - for (var i = 0; i < bs.Count; i++) - bs[i].ToExpressionString((i > 0 ? sb.Append(',') : sb).NewLineIndent(lineIndent), - paramsExprs, uniqueExprs, lts, lineIndent + indentSpaces, stripNamespace, printType, indentSpaces, notRecognizedToCode); - return sb; - } - - private static StringBuilder ToExpressionString(this CatchBlock b, StringBuilder sb, - List paramsExprs, List uniqueExprs, List lts, - int lineIndent, bool stripNamespace, Func printType, int indentSpaces, ObjectToCode notRecognizedToCode) - { - sb.Append("MakeCatchBlock("); - sb.NewLineIndent(lineIndent).AppendTypeOf(b.Test, stripNamespace, printType).Append(','); - sb.NewLineIndentExpr(b.Variable, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(','); - sb.NewLineIndentExpr(b.Body, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(','); - sb.NewLineIndentExpr(b.Filter, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - return sb.Append(')'); - } - - private static StringBuilder ToExpressionString(this IReadOnlyList items, StringBuilder sb, - List paramsExprs, List uniqueExprs, List lts, - int lineIndent, bool stripNamespace, Func printType, int indentSpaces, ObjectToCode notRecognizedToCode) - { - if (items.Count == 0) - return sb.Append("new SwitchCase[0]"); - for (var i = 0; i < items.Count; i++) - items[i].ToExpressionString((i > 0 ? sb.Append(',') : sb).NewLineIndent(lineIndent), - paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - return sb; - } - - private static StringBuilder ToExpressionString(this SwitchCase s, StringBuilder sb, - List paramsExprs, List uniqueExprs, List lts, - int lineIndent, bool stripNamespace, Func printType, int indentSpaces, ObjectToCode notRecognizedToCode) - { - sb.Append("SwitchCase("); - sb.NewLineIndentExpr(s.Body, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(','); - sb.NewLineIndentArgumentExprs(s.TestValues, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - return sb.Append(')'); - } - - private static StringBuilder ToExpressionString(this MemberBinding mb, StringBuilder sb, - List paramsExprs, List uniqueExprs, List lts, - int lineIndent, bool stripNamespace, Func printType, int indentSpaces, ObjectToCode notRecognizedToCode) - { - if (mb is MemberAssignment ma) - { - sb.Append("Bind("); - sb.NewLineIndent(lineIndent).AppendMember(mb.Member, stripNamespace, printType).Append(", "); - sb.NewLineIndentExpr(ma.Expression, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - return sb.Append(")"); - } - - if (mb is MemberMemberBinding mmb) - { - sb.NewLineIndent(lineIndent).Append(NotSupportedExpression).Append(nameof(MemberMemberBinding)).NewLineIndent(lineIndent); - sb.Append("MemberBind("); - sb.NewLineIndent(lineIndent).AppendMember(mb.Member, stripNamespace, printType); - - for (int i = 0; i < mmb.Bindings.Count; i++) - mmb.Bindings[i].ToExpressionString(sb.Append(", ").NewLineIndent(lineIndent), - paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - return sb.Append(")"); - } - - if (mb is MemberListBinding mlb) - { - sb.NewLineIndent(lineIndent).Append(NotSupportedExpression).Append(nameof(MemberListBinding)).NewLineIndent(lineIndent); - sb.Append("ListBind("); - sb.NewLineIndent(lineIndent).AppendMember(mb.Member, stripNamespace, printType); - - for (int i = 0; i < mlb.Initializers.Count; i++) - mlb.Initializers[i].ToExpressionString(sb.Append(", ").NewLineIndent(lineIndent), - paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - - return sb.Append(")"); - } - - return sb; - } - - private static StringBuilder ToExpressionString(this ElementInit ei, StringBuilder sb, - List paramsExprs, List uniqueExprs, List lts, - int lineIndent, bool stripNamespace, Func printType, int indentSpaces, ObjectToCode notRecognizedToCode) - { - sb.Append("ElementInit("); - sb.NewLineIndent(lineIndent).AppendMethod(ei.AddMethod, stripNamespace, printType).Append(", "); - sb.NewLineIndentArgumentExprs(ei.Arguments, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - return sb.Append(")"); - } - - private const string NotSupportedExpression = "// NOT_SUPPORTED_EXPRESSION: "; - - internal static StringBuilder CreateExpressionString(this Expression e, StringBuilder sb, - List paramsExprs, List uniqueExprs, List lts, - int lineIndent = 0, bool stripNamespace = false, Func printType = null, int indentSpaces = 2, ObjectToCode notRecognizedToCode = null) - { - switch (e.NodeType) - { - case ExpressionType.Constant: - { - var x = (ConstantExpression)e; - sb.Append("Constant("); - if (x.Value == null) - { - sb.Append("null"); - if (x.Type != typeof(object)) - sb.Append(", ").AppendTypeOf(x.Type, stripNamespace, printType); - } - else if (x.Value is Type t) - sb.AppendTypeOf(t, stripNamespace, printType); - else - { - sb.Append(x.Value.ToCode(notRecognizedToCode ?? CodePrinter.DefaultNotRecognizedToCode, stripNamespace, printType)); - if (x.Value.GetType() != x.Type) - sb.Append(", ").AppendTypeOf(x.Type, stripNamespace, printType); - } - return sb.Append(')'); - } - case ExpressionType.Parameter: - { - var x = (ParameterExpression)e; - sb.Append("Parameter(").AppendTypeOf(x.Type, stripNamespace, printType); - if (x.IsByRef) - sb.Append(".MakeByRefType()"); - if (x.Name != null) - sb.Append(", \"").Append(x.Name).Append('"'); - return sb.Append(')'); - } - case ExpressionType.New: - { - var x = (NewExpression)e; - var args = x.Arguments; - - if (args.Count == 0 && e.Type.IsValueType) - return sb.Append("New(").AppendTypeOf(e.Type, stripNamespace, printType).Append(')'); - - sb.Append("New( // ").Append(args.Count).Append(" args"); - var ctorIndex = x.Constructor.DeclaringType.GetTypeInfo().DeclaredConstructors.AsArray().GetFirstIndex(x.Constructor, default(RefEq)); - sb.NewLineIndent(lineIndent).AppendTypeOf(x.Type, stripNamespace, printType) - .Append(".GetTypeInfo().DeclaredConstructors.AsArray()[").Append(ctorIndex).Append("],"); - sb.NewLineIndentArgumentExprs(args, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - return sb.Append(')'); - } - case ExpressionType.Call: - { - var mc = (MethodCallExpression)e; - var diffTypes = mc.Type != mc.Method.ReturnType; - sb.Append(diffTypes ? "Convert(Call(" : "Call("); - sb.NewLineIndentExpr(mc.Object, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(", "); - sb.NewLineIndent(lineIndent).AppendMethod(mc.Method, stripNamespace, printType); - if (mc.Arguments.Count > 0) - sb.Append(',').NewLineIndentArgumentExprs(mc.Arguments, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - return diffTypes ? sb.Append("), ").AppendTypeOf(e.Type, stripNamespace, printType).Append(')') : sb.Append(')'); - } - case ExpressionType.MemberAccess: - { - var x = (MemberExpression)e; - if (x.Member is PropertyInfo p) - { - sb.Append("Property("); - sb.NewLineIndentExpr(x.Expression, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(','); - sb.NewLineIndent(lineIndent).AppendProperty(p, stripNamespace, printType); - } - else - { - sb.Append("Field("); - sb.NewLineIndentExpr(x.Expression, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(','); - sb.NewLineIndent(lineIndent).AppendField((FieldInfo)x.Member, stripNamespace, printType); - } - return sb.Append(')'); - } - - case ExpressionType.NewArrayBounds: - case ExpressionType.NewArrayInit: - { - var x = (NewArrayExpression)e; - if (e.NodeType == ExpressionType.NewArrayInit) - { - // todo: @feature multi-dimensional array initializers are not supported yet, they also are not supported by the hoisted expression - if (e.Type.GetArrayRank() > 1) - sb.NewLineIndent(lineIndent).Append(NotSupportedExpression).Append(e.NodeType).NewLineIndent(lineIndent); - sb.Append("NewArrayInit("); - } - else - { - sb.Append("NewArrayBounds("); - } - sb.NewLineIndent(lineIndent).AppendTypeOf(x.Type.GetElementType(), stripNamespace, printType).Append(", "); - sb.NewLineIndentArgumentExprs(x.Expressions, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - return sb.Append(')'); - } - case ExpressionType.MemberInit: - { - var x = (MemberInitExpression)e; - sb.Append("MemberInit((NewExpression)("); - sb.NewLineIndentExpr(x.NewExpression, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode) - .Append(')'); - for (var i = 0; i < x.Bindings.Count; i++) - x.Bindings[i].ToExpressionString(sb.Append(", ").NewLineIndent(lineIndent), - paramsExprs, uniqueExprs, lts, lineIndent + indentSpaces, stripNamespace, printType, indentSpaces, notRecognizedToCode); - return sb.Append(')'); - } - case ExpressionType.Lambda: - { - var x = (LambdaExpression)e; - sb.Append("Lambda<").Append(x.Type.ToCode(stripNamespace, printType)).Append(">("); - sb.NewLineIndentExpr(x.Body, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(','); - sb.NewLineIndentArgumentExprs(x.Parameters, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - return sb.Append(')'); - } - case ExpressionType.Invoke: - { - var x = (InvocationExpression)e; - sb.Append("Invoke("); - sb.NewLineIndentExpr(x.Expression, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(','); - sb.NewLineIndentArgumentExprs(x.Arguments, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - return sb.Append(")"); - } - case ExpressionType.Conditional: - { - var x = (ConditionalExpression)e; - sb.Append("Condition("); - sb.NewLineIndentExpr(x.Test, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(','); - sb.NewLineIndentExpr(x.IfTrue, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(','); - sb.NewLineIndentExpr(x.IfFalse, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(','); - sb.NewLineIndent(lineIndent).AppendTypeOf(x.Type, stripNamespace, printType); - return sb.Append(')'); - } - case ExpressionType.Block: - { - var x = (BlockExpression)e; - sb.Append("Block("); - sb.NewLineIndent(lineIndent).AppendTypeOf(x.Type, stripNamespace, printType).Append(','); - - if (x.Variables.Count == 0) - sb.NewLineIndent(lineIndent).Append("new ParameterExpression[0], "); - else - { - sb.NewLineIndent(lineIndent).Append("new[] {"); - for (var i = 0; i < x.Variables.Count; i++) - x.Variables[i].ToExpressionString( - (i > 0 ? sb.Append(',') : sb).NewLineIndent(lineIndent + indentSpaces), - paramsExprs, uniqueExprs, lts, lineIndent + indentSpaces, stripNamespace, printType, indentSpaces, notRecognizedToCode); - sb.NewLineIndent(lineIndent).Append("},"); - } - - sb.NewLineIndentArgumentExprs(x.Expressions, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - return sb.Append(')'); - } - case ExpressionType.Loop: - { - var x = (LoopExpression)e; - sb.Append("Loop("); - sb.NewLineIndentExpr(x.Body, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - - if (x.BreakLabel != null) - x.BreakLabel.ToExpressionString(sb.Append(',').NewLineIndent(lineIndent), lts, lineIndent, stripNamespace, printType); - - if (x.ContinueLabel != null) - x.ContinueLabel.ToExpressionString(sb.Append(',').NewLineIndent(lineIndent), lts, lineIndent, stripNamespace, printType); - - return sb.Append(')'); - } - case ExpressionType.Index: - { - var x = (IndexExpression)e; - sb.Append(x.Indexer != null ? "MakeIndex(" : "ArrayAccess("); - sb.NewLineIndentExpr(x.Object, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(", "); - - if (x.Indexer != null) - sb.NewLineIndent(lineIndent).AppendProperty(x.Indexer, stripNamespace, printType).Append(", "); - - sb.Append("new Expression[] {"); - for (var i = 0; i < x.Arguments.Count; i++) - (i > 0 ? sb.Append(',') : sb) - .NewLineIndentExpr(x.Arguments[i], paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - return sb.Append("})"); - } - case ExpressionType.Try: - { - var x = (TryExpression)e; - if (x.Fault != null) - { - sb.Append("TryFault("); - sb.NewLineIndentExpr(x.Body, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(','); - sb.NewLineIndentExpr(x.Fault, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - } - else if (x.Finally == null) - { - sb.Append("TryCatch("); - sb.NewLineIndentExpr(x.Body, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(','); - x.Handlers.ToExpressionString(sb, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - } - else if (x.Handlers == null) - { - sb.Append("TryFinally("); - sb.NewLineIndentExpr(x.Body, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(','); - sb.NewLineIndentExpr(x.Finally, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - } - else - { - sb.Append("TryCatchFinally("); - sb.NewLineIndentExpr(x.Body, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(','); - sb.NewLineIndentExpr(x.Finally, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(','); - x.Handlers.ToExpressionString(sb, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - } - - return sb.Append(')'); - } - case ExpressionType.Label: - { - var x = (LabelExpression)e; - sb.Append("Label("); - x.Target.ToExpressionString(sb, lts, lineIndent, stripNamespace, printType); - if (x.DefaultValue != null) - sb.Append(',').NewLineIndentExpr(x.DefaultValue, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - return sb.Append(')'); - } - case ExpressionType.Goto: - { - var x = (GotoExpression)e; - sb.Append("MakeGoto(").AppendEnum(x.Kind, stripNamespace, printType).Append(','); - - sb.NewLineIndent(lineIndent); - x.Target.ToExpressionString(sb, lts, lineIndent, stripNamespace, printType).Append(','); - - sb.NewLineIndentExpr(x.Value, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(','); - sb.NewLineIndent(lineIndent).AppendTypeOf(x.Type, stripNamespace, printType); - return sb.Append(')'); - } - case ExpressionType.Switch: - { - var x = (SwitchExpression)e; - sb.Append("Switch("); - sb.NewLineIndentExpr(x.SwitchValue, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(','); - sb.NewLineIndentExpr(x.DefaultBody, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(','); - sb.NewLineIndent(lineIndent).AppendMethod(x.Comparison, stripNamespace, printType); - ToExpressionString(x.Cases, sb, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - return sb.Append(')'); - } - case ExpressionType.Default: - { - return e.Type == typeof(void) ? sb.Append("Empty()") - : sb.Append("Default(").AppendTypeOf(e.Type, stripNamespace, printType).Append(')'); - } - case ExpressionType.TypeIs: - case ExpressionType.TypeEqual: - { - var x = (TypeBinaryExpression)e; - sb.Append(e.NodeType == ExpressionType.TypeIs ? "TypeIs(" : "TypeEqual("); - sb.NewLineIndentExpr(x.Expression, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(','); - sb.NewLineIndent(lineIndent).AppendTypeOf(x.TypeOperand, stripNamespace, printType); - return sb.Append(')'); - } - case ExpressionType.Coalesce: - { - var x = (BinaryExpression)e; - sb.Append("Coalesce("); - sb.NewLineIndentExpr(x.Left, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(','); - sb.NewLineIndentExpr(x.Right, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - if (x.Conversion != null) - sb.Append(',').NewLineIndentExpr(x.Conversion, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - return sb.Append(')'); - } - case ExpressionType.ListInit: - { - var x = (ListInitExpression)e; - sb.Append("ListInit((NewExpression)("); - sb.NewLineIndentExpr(x.NewExpression, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(')'); - for (var i = 0; i < x.Initializers.Count; i++) - x.Initializers[i].ToExpressionString(sb.Append(", ").NewLineIndent(lineIndent), - paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - return sb.Append(")"); - } - case ExpressionType.Extension: - { - var reduced = e.Reduce(); // proceed with the reduced expression - return reduced.CreateExpressionString(sb, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - } - case ExpressionType.Dynamic: - case ExpressionType.RuntimeVariables: - case ExpressionType.DebugInfo: - case ExpressionType.Quote: - { - return sb.NewLineIndent(lineIndent).Append(NotSupportedExpression).Append(e.NodeType).NewLineIndent(lineIndent); - } - default: - { - var name = Enum.GetName(typeof(ExpressionType), e.NodeType); - if (e is UnaryExpression u) - { - sb.Append(name).Append('('); - // todo: @feature maybe for big expression it makes sense to print the Type in comment here so you don't navigate to the closing parentheses to find it - sb.NewLineIndentExpr(u.Operand, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - - if (e.NodeType == ExpressionType.Convert || - e.NodeType == ExpressionType.ConvertChecked || - e.NodeType == ExpressionType.Unbox || - e.NodeType == ExpressionType.Throw || - e.NodeType == ExpressionType.TypeAs) - sb.Append(',').NewLineIndent(lineIndent).AppendTypeOf(e.Type, stripNamespace, printType); - - if ((e.NodeType == ExpressionType.Convert || e.NodeType == ExpressionType.ConvertChecked) - && u.Method != null) - sb.Append(',').NewLineIndent(lineIndent).AppendMethod(u.Method, stripNamespace, printType); - } - - if (e is BinaryExpression b) - { - sb.Append("MakeBinary(").Append(typeof(ExpressionType).Name).Append('.').Append(name).Append(','); - sb.NewLineIndentExpr(b.Left, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(','); - sb.NewLineIndentExpr(b.Right, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - if (b.IsLiftedToNull || b.Method != null) - { - sb.Append(',').NewLineIndent(lineIndent).Append("liftToNull: ").Append(b.IsLiftedToNull.ToCode()); - sb.Append(',').NewLineIndent(lineIndent).AppendMethod(b.Method, stripNamespace, printType); - if (b.Conversion != null) - sb.Append(',').NewLineIndentExpr(b.Conversion, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - } - - if (b.Conversion != null) - sb.Append(',').NewLineIndentExpr(b.Conversion, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - } - - return sb.Append(')'); - } - } - } - } - - /// Converts the expression into the valid C# code representation - [RequiresUnreferencedCode(Trimming.Message)] - public static class ToCSharpPrinter - { - /// Tries hard to convert the expression into the valid C# code. Avoids parens by default for the root expr. - public static string ToCSharpString(this Expression expr, EnclosedIn enclosedIn = EnclosedIn.AvoidParens) => - expr.ToCSharpString(new StringBuilder(1024), enclosedIn, stripNamespace: true).ToString(); - - /// Tries hard to convert the expression into the valid C# code. Avoids parens by default for the root expr. - public static string ToCSharpString(this Expression expr, ObjectToCode notRecognizedToCode, EnclosedIn enclosedIn = EnclosedIn.AvoidParens) => - expr.ToCSharpString(new StringBuilder(1024), stripNamespace: true, notRecognizedToCode: notRecognizedToCode).ToString(); - - /// Tries hard to convert the expression into the valid C# code - public static StringBuilder ToCSharpString(this Expression e, StringBuilder sb, EnclosedIn enclosedIn = EnclosedIn.ParensByDefault, - int lineIndent = 0, bool stripNamespace = false, Func printType = null, int indentSpaces = 4, ObjectToCode notRecognizedToCode = null) - { - PrintContext ctx = default; - return e.ToCSharpString(sb, enclosedIn, ref ctx, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - } - - /// Indicates the expression container - public enum EnclosedIn - { - /// Prefers the parens by default - ParensByDefault = 0, - /// The test part of the If expression - IfTest, - /// The `if (test)` part - Block, - /// The lambda - LambdaBody, - /// Return expression - Return, - /// Instructs the client code to avoid parenthesis for the generated C# code, e.g. if we have as single argument in a method - AvoidParens, - /// The instance when calling the instance method or accessing the instance member - Instance, - } - - private static StringBuilder NullConstantOrDefaultToCSharpString(Type exprType, StringBuilder sb, EnclosedIn encloseIn, - bool stripNamespace, Func printType) => - exprType == typeof(object) - ? sb.Append("null") - : exprType.IsValueType && !exprType.IsNullable() - ? sb.Append("default(").Append(exprType.ToCode(stripNamespace, printType)).Append(')') - : sb.Append(encloseIn == EnclosedIn.Instance ? "((" : "(") - .Append(exprType.ToCode(stripNamespace, printType)).Append(")null") - .Append(encloseIn == EnclosedIn.Instance ? ")" : ""); - - private static StringBuilder InsertTopFFuncDefinitionOnce(StringBuilder sb) => - sb[0] != 'T' || sb[2] != '_' || sb[3] != '_' || sb[4] != 'f' || sb[5] != '<' - ? sb.Insert(0, "T __f(System.Func f) => f();\n") - : sb; - - internal static StringBuilder ToCSharpString(this Expression e, StringBuilder sb, EnclosedIn enclosedIn, ref PrintContext ctx, - int lineIndent = 0, bool stripNamespace = false, Func printType = null, int indentSpaces = 4, - ObjectToCode notRecognizedToCode = null, bool isReturnByRef = false, bool containerIgnoresResult = false) - { -#if LIGHT_EXPRESSION - if (e.IsCustomToCSharpString) - return e.CustomToCSharpString(sb, enclosedIn, ref ctx, - lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); -#endif - switch (e.NodeType) - { - case ExpressionType.Constant: - { - var x = (ConstantExpression)e; - var val = x.Value; - if (val == null) - return x.Type == null ? sb.Append("null") : NullConstantOrDefaultToCSharpString(x.Type, sb, enclosedIn, stripNamespace, printType); - - if (val is Type t) - return sb.AppendTypeOf(t, stripNamespace, printType); - - var actualType = val.GetType(); - if (actualType != x.Type && (actualType.IsValueType || x.Type != typeof(object))) // add the Type cast, but avoid cast to object for the reference types - sb.Append('(').Append(x.Type.ToCode(stripNamespace, printType)).Append(')'); - - // value output may also add the cast for the primitive values - return sb.Append(val.ToCode(notRecognizedToCode ?? CodePrinter.DefaultNotRecognizedToCode, stripNamespace, printType)); - } - case ExpressionType.Parameter: - { - if (isReturnByRef) - sb.Append("ref "); - return sb.AppendName(e, ((ParameterExpression)e).Name, e.Type.ToCode(stripNamespace, printType), ref ctx); - } - case ExpressionType.New: - { - var x = (NewExpression)e; - lineIndent = sb.GetRealLineIndent(lineIndent); - var argIndent = lineIndent + indentSpaces; - - sb.Append("new ").Append(e.Type.ToCode(stripNamespace, printType)).Append('('); - - var args = x.Arguments; - if (args.Count == 1) - args[0].ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, - lineIndent, // don't increase indent the chain of single arguments inline, e.g. for `new A(new B(\n....a,\n....b))` we still want padding to be 4, not 8 - stripNamespace, printType, indentSpaces, notRecognizedToCode); - else if (args.Count > 1) - for (var i = 0; i < args.Count; i++) - { - (i > 0 ? sb.Append(',') : sb).NewLineIndent(argIndent); - args[i].ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, - argIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - } - return sb.Append(')'); - } - case ExpressionType.Call: - { - var mc = (MethodCallExpression)e; - - // before adding anything about method call to the builder, - // let's measure the current indent to avoid the double indenting the arguments below in some cases - lineIndent = sb.GetRealLineIndent(lineIndent); - var argIndent = lineIndent + indentSpaces; - - var method = mc.Method; - var methodReturnType = method.ReturnType; - if (enclosedIn != EnclosedIn.Instance && methodReturnType.IsByRef) - sb.AppendRefOnce(); - - // output convert only if it is required, e.g. it may happen for custom expressions designed by users - var diffTypes = mc.Type != methodReturnType; - if (diffTypes) sb.Append("((").Append(mc.Type.ToCode(stripNamespace, printType)).Append(')'); - - if (mc.Object != null) - mc.Object.ToCSharpString(sb, EnclosedIn.Instance, ref ctx, - lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - else // for the static method or the static extension method we need to qualify with the class - sb.Append(method.DeclaringType.ToCode(stripNamespace, printType)); - - var name = method.Name; - // check for the special methods, e.g. property access `get_` or `set_` and output them as properties - if (method.IsSpecialName) - { - if (name.StartsWith("get_")) - return sb.Append('.').Append(name.Substring(4)); - if (name.StartsWith("set_")) - { - sb.Append('.').Append(name.Substring(4)).Append(" = "); - mc.Arguments[0].ToCSharpExpression(sb, EnclosedIn.AvoidParens, ref ctx, - false, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - return sb.AppendSemicolonOnce(); - } - } - - sb.Append('.').Append(name); - if (method.IsGenericMethod) - { - sb.Append('<'); - var typeArgs = method.GetGenericArguments(); - for (var i = 0; i < typeArgs.Length; i++) - (i == 0 ? sb : sb.Append(", ")).Append(typeArgs[i].ToCode(stripNamespace, printType)); - sb.Append('>'); - } - - sb.Append('('); - var pars = method.GetParameters(); - var args = mc.Arguments; - if (args.Count == 1) - { - var p = pars[0]; - var a = args[0]; - if (p.ParameterType.IsByRef && !a.IsConstantOrDefault()) - sb.Append(p.IsOut ? "out " : p.IsIn ? "in " : "ref "); - a.ToCSharpExpression(sb, EnclosedIn.AvoidParens, ref ctx, - false, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - } - else if (args.Count > 1) - { - for (var i = 0; i < args.Count; i++) - { - // arguments will start at that minimal indent - (i == 0 ? sb : sb.Append(',')).NewLineIndent(argIndent); - var p = pars[i]; - var a = args[i]; - if (p.ParameterType.IsByRef && !a.IsConstantOrDefault()) - sb.Append(p.IsOut ? "out " : p.IsIn ? "in " : "ref "); - a.ToCSharpExpression(sb, EnclosedIn.AvoidParens, ref ctx, - false, argIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - } - } - // for the different return and expression types wrapping the whole expression including the cast with additional parentheses - return diffTypes ? sb.Append("))") : sb.Append(')'); - } - case ExpressionType.MemberAccess: - { - var x = (MemberExpression)e; - var inst = x.Expression; - if (inst != null) - { - // wrap the `new X().Y` into parens as `(new X()).Y` as it is may be a part of the bigger expression - if (inst.NodeType == ExpressionType.New) - sb.Append('('); - inst.ToCSharpString(sb, EnclosedIn.Instance, ref ctx, - lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - if (inst.NodeType == ExpressionType.New) - sb.Append(')'); - } - else - sb.Append(x.Member.DeclaringType.ToCode(stripNamespace, printType)); - return sb.Append('.').Append(x.Member.GetCSharpName()); - } - case ExpressionType.NewArrayBounds: - case ExpressionType.NewArrayInit: - { - var x = (NewArrayExpression)e; - lineIndent = sb.GetRealLineIndent(lineIndent); - var nextIndent = lineIndent + indentSpaces; - - sb.Append("new ").Append(e.Type.GetElementType().ToCode(stripNamespace, printType)); - sb.Append(e.NodeType == ExpressionType.NewArrayInit ? "[]{" : "["); - - var exprs = x.Expressions; - if (exprs.Count == 1) - exprs[0].ToCSharpString(sb, EnclosedIn.AvoidParens, ref ctx, - lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - else if (exprs.Count > 1) - for (var i = 0; i < exprs.Count; i++) - { - sb = (i > 0 ? sb.Append(',') : sb).NewLineIndent(nextIndent); - exprs[i].ToCSharpString(sb, EnclosedIn.AvoidParens, ref ctx, - nextIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - } - - return sb.Append(e.NodeType == ExpressionType.NewArrayInit ? "}" : "]"); - } - case ExpressionType.MemberInit: - { - var x = (MemberInitExpression)e; - lineIndent = sb.GetRealLineIndent(lineIndent); - - x.NewExpression.ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, - lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - sb.NewLineIndent(lineIndent).Append('{'); - x.Bindings.ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, lineIndent + indentSpaces, stripNamespace, printType, indentSpaces, notRecognizedToCode); - return sb.NewLineIndent(lineIndent).Append('}'); - } - case ExpressionType.ListInit: - { - var x = (ListInitExpression)e; - lineIndent = sb.GetRealLineIndent(lineIndent); - var elemIndent = lineIndent + indentSpaces; - - x.NewExpression.ToCSharpString(sb, EnclosedIn.AvoidParens, ref ctx, - lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - - sb.NewLineIndent(lineIndent).Append('{'); - - var inits = x.Initializers; - for (var i = 0; i < inits.Count; ++i) - { - (i == 0 ? sb : sb.Append(", ")).NewLineIndent(elemIndent); - var elemInit = inits[i]; - var args = elemInit.Arguments; - if (args.Count == 1) - { - args.GetArgument(0).ToCSharpString(sb, EnclosedIn.AvoidParens, ref ctx, - elemIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - } - else - { - sb.Append('{'); - for (var j = 0; j < args.Count; ++j) - { - sb = j == 0 ? sb : sb.Append(", "); - args.GetArgument(j).ToCSharpString(sb, EnclosedIn.AvoidParens, ref ctx, - elemIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - } - sb.Append('}'); - } - } - return sb.NewLineIndent(lineIndent).Append("}"); - } - case ExpressionType.Lambda: - { - var x = (LambdaExpression)e; - lineIndent = sb.GetRealLineIndent(lineIndent); - // The result should be something like this (taken from the #237) - // - // `(DeserializerDlg)((ref ReadOnlySequence input, Word value, out Int64 bytesRead) => {...})` - // - sb.Append('(').Append(e.Type.ToCode(stripNamespace, printType)).Append(")(("); - var lambdaMethod = x.Type.FindDelegateInvokeMethod(); -#if LIGHT_EXPRESSION - var paramExprs = x; -#else - var paramExprs = x.Parameters; -#endif - var count = paramExprs.GetCount(); - if (count > 0) - { - var parInfos = lambdaMethod.GetParameters(); - Debug.Assert(count == parInfos.Length, "Expecting the same amount of parameter expressions and infos"); - for (var i = 0; i < count; i++) - { - if (i > 0) - sb.Append(", "); - if (count > 1) - sb.NewLineIndent(lineIndent + indentSpaces); - - var pe = paramExprs.GetParameter(i); - var pi = parInfos[i]; - if (pe.IsByRef) - sb.Append(pi.IsOut ? "out " : pi.IsIn ? "in " : "ref "); - var typeCode = pe.Type.ToCode(stripNamespace, printType); - sb.Append(typeCode).Append(' '); - sb.AppendName(pe, pe.Name, typeCode, ref ctx); - } - ctx.LambdaPars.Add(new() { Exprs = paramExprs, Infos = parInfos }); - } - - sb.Append(") => //").Append(lambdaMethod.ReturnType.ToCode(stripNamespace, printType)); - var body = x.Body; - var isReturnable = body.IsReturnable(); - var lambdaIgnoresResult = x.ReturnType == typeof(void); - if (isReturnable & !lambdaIgnoresResult) - { - var newLineIndent = lineIndent + indentSpaces; - sb.NewLineIndent(newLineIndent); - body.ToCSharpString(sb, EnclosedIn.LambdaBody, ref ctx, - newLineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode, lambdaMethod.ReturnType.IsByRef); - } - else - { - sb.NewLineIndent(lineIndent).Append('{'); - // Body handles `;` itself - if (body is BlockExpression bb) - bb.BlockToCSharpString(sb, ref ctx, - lineIndent + indentSpaces, stripNamespace, printType, indentSpaces, notRecognizedToCode, - inTheLastBlock: true, containerIgnoresResult: lambdaIgnoresResult); - else - { - sb.NewLineIndent(lineIndent + indentSpaces); - body.ToCSharpString(sb, EnclosedIn.LambdaBody, ref ctx, - lineIndent + indentSpaces, stripNamespace, printType, indentSpaces, notRecognizedToCode, - containerIgnoresResult: lambdaIgnoresResult); - if (isReturnable || lambdaIgnoresResult) - sb.AppendSemicolonOnce(); - } - sb.NewLineIndent(lineIndent).Append('}'); - } - ctx.LambdaPars.TryRemoveLastNoDrop(); - return sb.Append(')'); - } - case ExpressionType.Invoke: - { - var x = (InvocationExpression)e; - - lineIndent = sb.GetRealLineIndent(lineIndent); - var argIndent = lineIndent + indentSpaces; - - // wrap the expression in the possibly excessive parentheses, because usually the expression is the delegate (except if delegate is parameter) - // which should be cast to the proper delegate type, e.g. `(Func)(() => 1)`, so we need an additional `()` to call `.Invoke`. - var encloseInParens = x.Expression.NodeType != ExpressionType.Parameter; - if (encloseInParens) - sb.Append('('); - x.Expression.ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, - lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - if (encloseInParens) - sb.Append(')'); - - // Indicates the lambda invocation more explicitly with the new line, - // Keep Invoke indentation the same as the lambda closing brace indicating their bond - if (x.Expression.NodeType == ExpressionType.Lambda) - sb.NewLineIndent(lineIndent); - sb.Append(".Invoke("); - - for (var i = 0; i < x.Arguments.Count; i++) - { - sb = i > 0 ? sb.Append(',') : sb; - sb.NewLineIndent(argIndent); - x.Arguments[i].ToCSharpString(sb, EnclosedIn.AvoidParens, ref ctx, - argIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - } - return sb.Append(")"); - } - case ExpressionType.Conditional: - { - var x = (ConditionalExpression)e; - lineIndent = sb.GetRealLineIndent(lineIndent); - - if (e.Type == typeof(void)) // otherwise output as ternary expression - { - sb.Append("if ("); - x.Test.ToCSharpString(sb, EnclosedIn.IfTest, ref ctx, - lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - sb.Append(')'); - - x.IfTrue.ToCSharpBlock(sb, ref ctx, - lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - - if (x.IfFalse.NodeType != ExpressionType.Default || x.IfFalse.Type != typeof(void)) - { - sb.NewLineIndent(lineIndent).Append("else"); - x.IfFalse.ToCSharpBlock(sb, ref ctx, - lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - } - } - else - { - var avoidParens = AvoidParens(enclosedIn); - sb = avoidParens ? sb : sb.Append('('); - - x.Test.ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, - lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - - sb.Append(" ? "); - var doNewLine = !x.IfTrue.IsParamOrConstantOrDefault(); - x.IfTrue.ToCSharpExpression(sb, EnclosedIn.AvoidParens, ref ctx, - doNewLine, lineIndent + indentSpaces, stripNamespace, printType, indentSpaces, notRecognizedToCode); - - sb.Append(" : "); - doNewLine = !x.IfFalse.IsParamOrConstantOrDefault(); - x.IfFalse.ToCSharpExpression(sb, EnclosedIn.AvoidParens, ref ctx, - doNewLine, lineIndent + indentSpaces, stripNamespace, printType, indentSpaces, notRecognizedToCode); - - sb = avoidParens ? sb : sb.Append(')'); - } - return sb; - } - case ExpressionType.Block: - { - return ((BlockExpression)e).BlockToCSharpString(sb, ref ctx, - lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - } - case ExpressionType.Loop: - { - var x = (LoopExpression)e; - lineIndent = sb.GetRealLineIndent(lineIndent); - - sb.NewLineIndent(lineIndent).Append("while (true)"); - sb.NewLineIndent(lineIndent).Append("{"); - - if (x.ContinueLabel != null) - { - sb.NewLineIndent(lineIndent); - sb.AppendLabelName(x.ContinueLabel, ref ctx, nameUsage: NameUsage.MarkLabel).Append(":;"); // the label is with the semicolon, because it will invalid code at the end of lambda without it - } - - sb.NewLineIndent(lineIndent + indentSpaces); - x.Body.ToCSharpString(sb, EnclosedIn.AvoidParens, ref ctx, - lineIndent + indentSpaces, stripNamespace, printType, indentSpaces, notRecognizedToCode); - - sb.NewLineIndent(lineIndent).Append("}"); - - // the label is with the semicolon, because it will invalid code at the end of lambda without it - if (x.BreakLabel != null) - sb.NewLineIndent(lineIndent).AppendLabelName(x.BreakLabel, ref ctx, nameUsage: NameUsage.MarkLabel).Append(":;"); - - return sb; - } - case ExpressionType.Index: - { - var x = (IndexExpression)e; - x.Object.ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, - lineIndent + indentSpaces, stripNamespace, printType, indentSpaces, notRecognizedToCode); - - var isStandardIndexer = x.Indexer == null || x.Indexer.Name == "Item"; - if (isStandardIndexer) - sb.Append('['); - else - sb.Append('.').Append(x.Indexer.Name).Append('('); - - for (var i = 0; i < x.Arguments.Count; i++) - x.Arguments[i].ToCSharpString(i > 0 ? sb.Append(", ") : sb, EnclosedIn.AvoidParens, ref ctx, - lineIndent + indentSpaces, stripNamespace, printType, indentSpaces, notRecognizedToCode); - - return sb.Append(isStandardIndexer ? ']' : ')'); - } - case ExpressionType.Try: - { - var x = (TryExpression)e; - lineIndent = sb.GetRealLineIndent(lineIndent); - - var returnsValue = e.Type != typeof(void); - void PrintPart(Expression part, ref PrintContext ctx) - { - var incIndent = lineIndent + indentSpaces; - if (part is BlockExpression pb) - pb.BlockToCSharpString(sb, ref ctx, - incIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode, inTheLastBlock: true); - else - { - sb.NewLineIndent(incIndent); - var isReturnable = returnsValue && part.IsReturnable() && - // todo: @improve right now it is a hack - usually to Assign something means no return - !part.NodeType.IsAssignNodeType(); - if (isReturnable) - sb.Append("return "); - part.ToCSharpString(sb, EnclosedIn.AvoidParens, ref ctx, - incIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - sb.AppendSemicolonOnce(part); - } - } - - sb.AppendNewLineOnce(); - - var isTryFault = x.Fault != null; - if (isTryFault) - { - sb.Append("var fault = 0; // emulating try-fault"); - sb.NewLineIndent(lineIndent); - } - - sb.Append("try"); - sb.NewLineIndent(lineIndent).Append('{'); - PrintPart(x.Body, ref ctx); - sb.NewLineIndent(lineIndent).Append('}'); - if (isTryFault) - sb.NewLineIndent(lineIndent).Append("catch (Exception) when (fault++ != 0) {}"); - - var handlers = x.Handlers; - if (handlers != null && handlers.Count > 0) - { - for (var i = 0; i < handlers.Count; i++) - { - var h = handlers[i]; - sb.NewLineIndent(lineIndent).Append("catch ("); - var exTypeName = h.Test.ToCode(stripNamespace, printType); - sb.Append(exTypeName); - - var hVar = h.Variable; - if (hVar != null) - { - // todo: @wip @feat add around the ex var the "#pragma warning disable CS0168 // unused var" "#pragma warning restore CS0168", see #495, or better check inside the catch block for the usage. We need to completely remove the var, because prefix '_' does not remove the warning. - sb.Append(' ').AppendName(hVar, hVar.Name, hVar.Type.ToCode(stripNamespace, printType), ref ctx); - } - - sb.Append(')'); - if (h.Filter != null) - { - sb.Append("when ("); - sb.NewLineIndent(lineIndent); - h.Filter.ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, - lineIndent + indentSpaces, stripNamespace, printType, indentSpaces, notRecognizedToCode); - sb.NewLineIndent(lineIndent).Append(')'); - } - - sb.NewLineIndent(lineIndent).Append('{'); - PrintPart(h.Body, ref ctx); - sb.NewLineIndent(lineIndent).Append('}'); - } - } - - var faultOrFinally = x.Fault ?? x.Finally; - if (faultOrFinally != null) - { - sb.NewLineIndent(lineIndent).Append("finally"); - sb.NewLineIndent(lineIndent).Append('{'); - if (isTryFault) sb.Append("if (fault != 0) {"); - - PrintPart(faultOrFinally, ref ctx); - - sb.NewLineIndent(lineIndent).Append('}'); - if (isTryFault) sb.Append('}'); - } - return sb; - } - case ExpressionType.Label: - { - // we don't output the default value and relying on the Goto Return `return` instead, otherwise we may change the logic of the code - return sb.NewLineIndent(lineIndent).AppendLabelName(((LabelExpression)e).Target, ref ctx, nameUsage: NameUsage.MarkLabel).Append(":;"); - } - case ExpressionType.Goto: - { - var gt = (GotoExpression)e; - if (gt.Kind == GotoExpressionKind.Return || gt.Value != null) - { - var gtValue = gt.Value; - if (gtValue == null) - return enclosedIn == EnclosedIn.Return ? sb.Append(";") : sb.Append("return;"); - - var isReturnable = gtValue.IsReturnable(); - if (isReturnable & enclosedIn != EnclosedIn.Return) - sb.Append("return "); - - gtValue.ToCSharpString(sb, EnclosedIn.AvoidParens, ref ctx, - lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - - if (isReturnable) - sb.AppendSemicolonOnce(); - return sb; - } - return sb.Append("goto ").AppendLabelName(gt.Target, ref ctx, nameUsage: NameUsage.GotoLabel); - } - case ExpressionType.Switch: - { - var x = (SwitchExpression)e; - if (x.Cases.Count == 0 && x.DefaultBody == null) return sb; // do not output the empty switch - - lineIndent = sb.GetRealLineIndent(lineIndent); - - sb.Append("switch ("); - x.SwitchValue.ToCSharpString(sb, EnclosedIn.AvoidParens, ref ctx, - lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - sb.Append(')'); - sb.NewLineIndent(lineIndent).Append('{'); - - var caseValueIndent = lineIndent + indentSpaces; - var caseBodyIndent = caseValueIndent + indentSpaces; - foreach (var cs in x.Cases) - { - foreach (var tv in cs.TestValues) - { - sb.NewLineIndent(caseValueIndent).Append("case "); - tv.ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, - caseValueIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(':'); - } - - sb.NewLineIndent(caseBodyIndent); - var caseBody = cs.Body; - if (enclosedIn == EnclosedIn.LambdaBody) - { - if (caseBody is BlockExpression bl) - { - bl.BlockToCSharpString(sb, ref ctx, - caseBodyIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode, inTheLastBlock: true); - sb.NewLineIndent(caseBodyIndent).Append("break;"); - } - else - { - var bodyIn = caseBody.Type != typeof(void) ? EnclosedIn.Return : EnclosedIn.AvoidParens; - caseBody.ToCSharpString(bodyIn == EnclosedIn.Return ? sb.Append("return ") : sb, bodyIn, ref ctx, - caseBodyIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).AppendSemicolonOnce(caseBody); - if (bodyIn != EnclosedIn.Return) - sb.NewLineIndent(caseBodyIndent).Append("break;"); - } - } - else - { - caseBody.ToCSharpString(sb, enclosedIn, ref ctx, - caseBodyIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).AppendSemicolonOnce(caseBody); - sb.NewLineIndent(caseBodyIndent).Append("break;"); - } - } - - if (x.DefaultBody != null) - { - var defaultBody = x.DefaultBody; - sb.NewLineIndent(caseValueIndent).Append("default:"); - sb.NewLineIndent(caseBodyIndent); - if (enclosedIn == EnclosedIn.LambdaBody | enclosedIn == EnclosedIn.Return) - { - if (defaultBody is BlockExpression bl) - bl.BlockToCSharpString(sb, ref ctx, - caseBodyIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode, inTheLastBlock: true); - else - { - var bodyIn = enclosedIn == EnclosedIn.Return ? EnclosedIn.Return - : defaultBody.Type != typeof(void) ? EnclosedIn.Return : EnclosedIn.AvoidParens; - defaultBody.ToCSharpString(bodyIn == EnclosedIn.Return ? sb.Append("return ") : sb, bodyIn, ref ctx, - caseBodyIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).AppendSemicolonOnce(defaultBody); - } - } - else - { - defaultBody.ToCSharpString(sb, enclosedIn, ref ctx, - caseBodyIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).AppendSemicolonOnce(defaultBody); - sb.NewLineIndent(caseBodyIndent).Append("break;"); - } - } - - return sb.NewLineIndent(lineIndent).Append("}"); - } - case ExpressionType.Default: - return e.Type == typeof(void) - ? sb // `default(void)` does not make sense in the C# - : NullConstantOrDefaultToCSharpString(e.Type, sb, enclosedIn, stripNamespace, printType); - - case ExpressionType.TypeIs: - case ExpressionType.TypeEqual: - { - var x = (TypeBinaryExpression)e; - sb.Append('('); - // Use C# `is T` even for TypeEqual if the two are equivalent (this syntax is nicer) - // IsSealed returns true for arrays, but arrays are cursed and don't behave like sealed classes (`new string[0] is object[]`, and even `new int[0] is uint[]`) - if (x.NodeType == ExpressionType.TypeIs || (x.TypeOperand.IsSealed && !x.TypeOperand.IsArray)) - { - x.Expression.ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, - lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - sb.Append(" is ").Append(x.TypeOperand.ToCode(stripNamespace, printType)); - } - else - { - x.Expression.ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, - lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - sb.Append(".GetType() == typeof(").Append(x.TypeOperand.ToCode(stripNamespace, printType)).Append(')'); - - } - return sb.Append(')'); - } - case ExpressionType.Coalesce: - { - var x = (BinaryExpression)e; - x.Left.ToCSharpExpression(sb, EnclosedIn.ParensByDefault, ref ctx, - false, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - sb.Append(" ?? "); - x.Right.ToCSharpExpression(sb, EnclosedIn.ParensByDefault, ref ctx, - false, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - return sb; - } - case ExpressionType.Extension: - { - var reduced = e.Reduce(); // proceed with the reduced expression - return reduced.ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, - lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - } - case ExpressionType.Dynamic: - case ExpressionType.RuntimeVariables: - case ExpressionType.DebugInfo: - case ExpressionType.Quote: - { - return sb.NewLineIndent(lineIndent).Append(NotSupportedExpression).Append(e.NodeType).NewLineIndent(lineIndent); - } - default: - { - var avoidParens = AvoidParens(enclosedIn); - - var name = Enum.GetName(typeof(ExpressionType), e.NodeType); - if (e is UnaryExpression u) - { - var op = u.Operand; - switch (e.NodeType) - { - case ExpressionType.ArrayLength: - return op.ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, - lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode) - .Append(".Length"); - - case ExpressionType.Not: // either the bool not or the binary not - return op.ToCSharpString( - e.Type == typeof(bool) ? sb.Append("!(") : sb.Append("~("), enclosedIn, ref ctx, - lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode) - .Append(')'); - - case ExpressionType.Convert: - case ExpressionType.ConvertChecked: - if (e.Type == op.Type || e.Type == typeof(Enum) && op.Type.IsEnum) - return op.ToCSharpExpression(sb, EnclosedIn.AvoidParens, ref ctx, - false, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - - sb = avoidParens ? sb.Append('(') : sb.Append("(("); - sb.Append(e.Type.ToCode(stripNamespace, printType)).Append(')'); - var opParens = op is BinaryExpression || op.NodeType.IsBlockLikeOrConditional() ? EnclosedIn.ParensByDefault : EnclosedIn.AvoidParens; - sb = op.ToCSharpExpression(sb, opParens, ref ctx, - false, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - return avoidParens ? sb : sb.Append(')'); - - case ExpressionType.Decrement: - case ExpressionType.Increment: - if (!avoidParens) sb.Append('('); - sb = op.ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, - lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - sb = e.NodeType == ExpressionType.Decrement ? sb.Append(" - 1") : sb.Append(" + 1"); - if (!avoidParens) sb.Append(')'); - return sb; - - case ExpressionType.Negate: - case ExpressionType.NegateChecked: - if (!avoidParens) sb.Append('('); - op.ToCSharpString(sb.Append('-'), EnclosedIn.ParensByDefault, ref ctx, - lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - if (!avoidParens) sb.Append(')'); - return sb; - - case ExpressionType.PostIncrementAssign: - case ExpressionType.PreIncrementAssign: - case ExpressionType.PostDecrementAssign: - case ExpressionType.PreDecrementAssign: - if (!avoidParens) sb.Append('('); - sb = e.NodeType == ExpressionType.PreIncrementAssign ? sb.Append("++") : e.NodeType == ExpressionType.PreDecrementAssign ? sb.Append("--") : sb; - op.ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, - lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - sb = e.NodeType == ExpressionType.PostIncrementAssign ? sb.Append("++") : e.NodeType == ExpressionType.PostDecrementAssign ? sb.Append("--") : sb; - if (!avoidParens) sb.Append(')'); - return sb; - - case ExpressionType.IsTrue: - return op.ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, - lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append("==true"); - - case ExpressionType.IsFalse: - return op.ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, - lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append("==false"); - - case ExpressionType.TypeAs: - case ExpressionType.TypeIs: - if (!avoidParens) sb.Append('('); - - var valueOp = op.Type.IsValueType && e.NodeType == ExpressionType.TypeAs; // `as` cannot be directly applied to the value type operand, that's why convert to object first. - if (valueOp) sb.Append("(object)("); // see Issue455_TypeAs_should_return_null.Original_case - - op.ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, - lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - - if (valueOp) sb.Append(')'); - - sb = e.NodeType == ExpressionType.TypeAs ? sb.Append(" as ") : sb.Append(" is "); - sb.Append(e.Type.ToCode(stripNamespace, printType)); - - if (!avoidParens) sb.Append(')'); - return sb; - - case ExpressionType.Throw: - return op is null ? sb.Append("throw") : - op.ToCSharpString(sb.Append("throw "), EnclosedIn.ParensByDefault, ref ctx, - lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - - case ExpressionType.Unbox: // output it as the cast - sb = avoidParens ? sb.Append("(") : sb.Append("(("); - sb.Append(e.Type.ToCode(stripNamespace, printType)).Append(')'); - op.ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, - lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - return avoidParens ? sb : sb.Append(')'); - - default: - return sb.Append(e.ToString()); // falling back ro ToString as a closest to C# code output - } - } - - if (e is BinaryExpression b) - { - var nodeType = e.NodeType; - if (nodeType == ExpressionType.ArrayIndex) - { - if (containerIgnoresResult | enclosedIn == EnclosedIn.Block) - sb.Append("_ = "); - var arrInParens = b.Left.NodeType != ExpressionType.Parameter; - if (arrInParens) sb.Append('('); - b.Left.ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, - lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - if (arrInParens) sb.Append(')'); - return b.Right.ToCSharpString(sb.Append("["), EnclosedIn.ParensByDefault, ref ctx, - lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append("]"); - } - - if (nodeType.IsAssignNodeType()) - { - // todo: @perf handle the right part is condition with the blocks for If and/or Else, e.g. see #261 test `Serialize_the_nullable_struct_array` - if (b.Right is BlockExpression rightBlock) // it is valid to assign the block and it is used to my surprise - { - sb.Append("// { The block result will be assigned to `") - .Append(b.Left.ToCSharpString(new StringBuilder(), EnclosedIn.ParensByDefault, ref ctx, - lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode)) - .Append('`'); - rightBlock.BlockToCSharpString(sb, ref ctx, - lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode, false, blockResultAssignment: b); - return sb.NewLineIndent(lineIndent).Append("// } end of block assignment"); - } - - b.Left.ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, - lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - if (nodeType == ExpressionType.PowerAssign) - { - sb.Append(" = System.Math.Pow("); - b.Left.ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, - lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(", "); - return b.Right.ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, - lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(")"); - } - - sb.Append(OperatorToCSharpString(nodeType)); - - if (b.Left is ParameterExpression leftParam && leftParam.IsByRef && !b.Right.IsConstantOrDefault() - && !leftParam.IsOut(ref ctx)) - sb.Append("ref "); - - return b.Right.ToCSharpExpression(sb, EnclosedIn.AvoidParens, ref ctx, - false, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - } - - if (containerIgnoresResult | enclosedIn == EnclosedIn.Block) - sb.Append("_ = "); // apply for non assignments - - sb = sb.AddParenIfNeeded('(', avoidParens); - b.Left.ToCSharpExpression(sb, EnclosedIn.ParensByDefault, ref ctx, - false, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - - if (nodeType == ExpressionType.Equal) - { - if (b.Right is ConstantExpression r && r.Value is bool rb && rb) - return sb.AddParenIfNeeded(')', avoidParens); - sb.Append(" == "); - } - else if (nodeType == ExpressionType.NotEqual) - { - if (b.Right is ConstantExpression r && r.Value is bool rb) - return (rb ? sb.Append(" == false") : sb).AddParenIfNeeded(')', avoidParens); - sb.Append(" != "); - } - else - sb.Append(OperatorToCSharpString(nodeType)); - - b.Right.ToCSharpExpression(sb, EnclosedIn.ParensByDefault, ref ctx, - false, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - return !avoidParens ? sb.Append(')') : sb; - } - - return sb.Append(e.ToString()); // falling back ToString and hoping for the best - } - } - } - - private static bool AvoidParens(EnclosedIn enclosedIn) => - enclosedIn == EnclosedIn.AvoidParens | - enclosedIn == EnclosedIn.LambdaBody | - enclosedIn == EnclosedIn.Block | // statement in a block don't need the parens as well - enclosedIn == EnclosedIn.Return | - enclosedIn == EnclosedIn.IfTest; - - private static StringBuilder AddParenIfNeeded(this StringBuilder sb, char paren, bool avoidParen = false) => - avoidParen ? sb : sb.Append(paren); - - private static StringBuilder ToCSharpBlock(this Expression expr, StringBuilder sb, ref PrintContext ctx, - int lineIndent, bool stripNamespace, Func printType, int indentSpaces, ObjectToCode notRecognizedToCode) - { - sb.NewLineIndent(lineIndent).Append('{'); - if (expr is BlockExpression fb) - fb.BlockToCSharpString(sb, ref ctx, - lineIndent + indentSpaces, stripNamespace, printType, indentSpaces, notRecognizedToCode, inTheLastBlock: false); - else - { - sb.NewLineIndent(lineIndent + indentSpaces); - sb = expr?.ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, - lineIndent + indentSpaces, stripNamespace, printType, indentSpaces, notRecognizedToCode) ?? sb.Append("null"); - sb.AppendSemicolonOnce(expr); - } - return sb.NewLineIndent(lineIndent).Append('}'); - } - - private static StringBuilder ToCSharpExpression(this Expression expr, - StringBuilder sb, EnclosedIn enclosedIn, ref PrintContext ctx, - bool newLineExpr, int lineIndent, bool stripNamespace, Func printType, int indentSpaces, ObjectToCode notRecognizedToCode) - { - if (!expr.NodeType.IsBracedBlockLike()) - { - if (!newLineExpr) - return expr.ToCSharpString(sb, enclosedIn, ref ctx, - lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - - sb.NewLineIndent(lineIndent); - return expr.ToCSharpString(sb, enclosedIn, ref ctx, - lineIndent + indentSpaces, stripNamespace, printType, indentSpaces, notRecognizedToCode); - } - - InsertTopFFuncDefinitionOnce(sb); - sb.NewLineIndent(lineIndent).Append("__f(() => {"); - if (expr is BlockExpression bl) - bl.BlockToCSharpString(sb, ref ctx, - lineIndent + indentSpaces, stripNamespace, printType, indentSpaces, notRecognizedToCode, inTheLastBlock: true); - else - { - sb.NewLineIndent(lineIndent + indentSpaces); - if (expr != null) - expr.ToCSharpString(sb, EnclosedIn.LambdaBody, ref ctx, - lineIndent + indentSpaces, stripNamespace, printType, indentSpaces, notRecognizedToCode); - else - sb.Append("null"); - - } - return sb.NewLineIndent(lineIndent).Append("})"); - } - - internal static StringBuilder AppendSemicolonOnce(this StringBuilder sb, Expression expr = null) => - expr?.NodeType.IsBracedBlockLike() == true ? sb : sb[sb.Length - 1] != ';' ? sb.Append(";") : sb; - - internal static StringBuilder AppendRefOnce(this StringBuilder sb) - { - var len = sb.Length; - return len >= 4 && - sb[len - 4] == 'r' && - sb[len - 3] == 'e' && - sb[len - 2] == 'f' && - sb[len - 1] == ' ' ? sb : sb.Append("ref "); - } - - internal static StringBuilder AppendNewLineOnce(this StringBuilder sb) - { - for (var end = sb.Length - 1; end >= 0; --end) - { - if (sb[end] == '\n') - return sb; // return the unchanged sb when new line is already present - if (sb[end] != ' ') // skip spaces if any - break; - } - return sb.Append(NewLine); - } - - // Returns the number of consecutive spaces from the current position, - // or from the first non-space character to the prev newline. - // e.g. for `\n foo.Bar = ` and for `\n ` indent is 4 - internal static int GetRealLineIndent(this StringBuilder sb, int defaultIndent) - { - var lastSpacePos = -1; - // go back from the last char in the builder - for (var pos = sb.Length - 1; pos >= 0; --pos) - { - var ch = sb[pos]; - if (ch == '\n') - return lastSpacePos == -1 ? defaultIndent : lastSpacePos - pos; - - if (ch != ' ') - lastSpacePos = -1; // reset space position when non-space char is found - else if (lastSpacePos == -1) - lastSpacePos = pos; // set the last space position - } - return defaultIndent; - } - - private const string NotSupportedExpression = "// NOT_SUPPORTED_EXPRESSION: "; - - private static StringBuilder ToCSharpString(this IReadOnlyList bindings, - StringBuilder sb, EnclosedIn enclosedIn, ref PrintContext ctx, - int lineIndent = 0, bool stripNamespace = false, Func printType = null, int indentSpaces = 4, ObjectToCode notRecognizedToCode = null) - { - var count = bindings.Count; - for (var i = 0; i < count; i++) - { - var b = bindings[i]; - sb.NewLineIndent(lineIndent); - sb.Append(b.Member.Name).Append(" = "); - - if (b is MemberAssignment ma) - { - ma.Expression.ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, - lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - } - else if (b is MemberMemberBinding mmb) - { - sb.Append("{"); - mmb.Bindings.ToCSharpString(sb, enclosedIn, ref ctx, - lineIndent + indentSpaces, stripNamespace, printType, indentSpaces, notRecognizedToCode); - sb.NewLineIndent(lineIndent + indentSpaces).Append("}"); - } - else if (b is MemberListBinding mlb) - { - sb.Append("{"); - var elemInits = mlb.Initializers; - var elemCount = elemInits.Count; - for (var e = 0; e < elemCount; e++) - { - var args = elemInits[e].Arguments; - sb.NewLineIndent(lineIndent + indentSpaces); - var manyArgs = args.Count > 1; - if (manyArgs) - sb.Append("("); - - var n = 0; - foreach (var arg in args) - arg.ToCSharpString((++n > 1 ? sb.Append(", ") : sb), EnclosedIn.ParensByDefault, ref ctx, - lineIndent + indentSpaces, stripNamespace, printType, indentSpaces, notRecognizedToCode); - - if (manyArgs) - sb.Append(")"); - - if (e < elemCount - 1) - sb.Append(","); - } - sb.NewLineIndent(lineIndent + indentSpaces).Append("}"); - } - - // don't place comma after the last binding - if (i < count - 1) - sb.Append(","); - } - return sb; - } - - private static StringBuilder BlockToCSharpString(this BlockExpression b, StringBuilder sb, ref PrintContext ctx, - int lineIndent = 0, bool stripNamespace = false, Func printType = null, int indentSpaces = 4, - ObjectToCode notRecognizedToCode = null, bool inTheLastBlock = false, BinaryExpression blockResultAssignment = null, - bool containerIgnoresResult = false) // in case of the container is lambda which is the Action/void delegate and ignores result, we don't need the `return` - it will be invalid c# - { - var vars = b.Variables.AsList(); - var exprs = b.Expressions; - - // handling the special case, AutoMapper like using the tmp variable to reassign the property - if (vars.Count == 1 & exprs.Count == 2 && - exprs[0] is BinaryExpression st0 && st0.NodeType == ExpressionType.Assign && - exprs[1] is BinaryExpression st1 && st1.NodeType == ExpressionType.Assign && - st0.Left == vars[0] && st1.Right == vars[0]) - return Assign(st1.Left, st0.Right).ToCSharpString(sb.NewLineIndent(lineIndent), - EnclosedIn.Block, ref ctx, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode) - .AppendSemicolonOnce(); - - foreach (var v in vars) - { - sb.NewLineIndent(lineIndent); - var vType = v.Type; - var vIsByRef = v.IsByRef; - var vNameSuffix = !vIsByRef ? "" : "__discard_init_by_ref"; - - var vTypeCode = vType.ToCode(stripNamespace, printType); - var vName = new StringBuilder().AppendName(v, v.Name + vNameSuffix, vTypeCode, ref ctx); - sb.Append(vTypeCode).Append(' ').Append(vName).Append(vType.IsValueType && !vType.IsNullable() ? " = default;" : " = null;"); - - if (vIsByRef) - sb.Append(" ref var ").AppendName(v, v.Name, vTypeCode, ref ctx).Append(" = ref ").Append(vName).Append(';'); - } - - // we don't inline a single expression case because it can always go crazy with assignment, e.g. `var a; a = 1 + (a = 2) + a * 2` - for (var i = 0; i < exprs.Count - 1; i++) - { - var expr = exprs[i]; - - // this is basically the return pattern (see #237) so we don't care for the rest of the expressions - // Note (#300) the sentence above is slightly wrong because that may be a goto to this specific label, so we still need to print the label - if (expr is GotoExpression gt && gt.Kind == GotoExpressionKind.Return && - exprs[i + 1] is LabelExpression label && label.Target == gt.Target) - { - sb.NewLineIndent(lineIndent); - if (gt.Value == null) - sb.Append("return;"); - else - gt.Value.ToCSharpString(sb.Append("return "), - EnclosedIn.Return, ref ctx, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode) - .AppendSemicolonOnce(); - return sb; - - // todo: @wip @remove do we need this, let explore what code ouputput will blow up - // sb.NewLineIndent(lineIndent); - - // // In principle the label mark should mark a single place only. So we we found that it used already -> ??? skip it, rename it? - // // see Issue237_Trying_to_implement_For_Foreach_loop_but_getting_an_InvalidProgramException_thrown.Should_Deserialize_Simple - // sb.AppendLabelName(label.Target, ref ctx, nameUsage: NameUsage.MarkLabel).Append(":;"); - - // if (label.DefaultValue == null) - // return sb.AppendLine(); // no return because we may have other expressions after label - // sb.NewLineIndent(lineIndent); - // sb.Append("return "); - // label.DefaultValue.ToCSharpString(sb, EnclosedIn.Return, ref ctx, - // lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - // return sb.AppendSemicolonOnce(); - } - - if (expr is BlockExpression bl) - { - // Unrolling the block on the same vertical line - bl.BlockToCSharpString(sb, ref ctx, - lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode, inTheLastBlock: false); - } - else - { - sb.NewLineIndent(lineIndent); - var nodeType = expr.NodeType; - var returningCondOrCoalesce = expr.Type != typeof(void) - && nodeType == ExpressionType.Conditional | nodeType == ExpressionType.Coalesce; - if (returningCondOrCoalesce) // it requires some assignment target to avoid error or warning - sb.Append("_ = "); - - expr.ToCSharpString(sb, EnclosedIn.Block, ref ctx, - lineIndent + indentSpaces, stripNamespace, printType, indentSpaces, notRecognizedToCode); - - // Preventing the `};` kind of situation and separating the conditional block with empty line - if (nodeType.IsBlockLikeOrConditional()) - { - sb = returningCondOrCoalesce ? sb.AppendSemicolonOnce() : sb; - sb.NewLineIndent(lineIndent); - } - else if (nodeType != ExpressionType.Label & nodeType != ExpressionType.Default) - sb.AppendSemicolonOnce(); - } - } - - var lastExpr = exprs[exprs.Count - 1]; - if (lastExpr.NodeType == ExpressionType.Default && lastExpr.Type == typeof(void)) - return sb; - - if (lastExpr is BlockExpression lastBlock) - return lastBlock.BlockToCSharpString(sb, ref ctx, - lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode, - inTheLastBlock, // the last block is marked so if only it is itself in the last block - blockResultAssignment); - - // todo: @improve the label is already used by the Return GoTo we should skip it output here OR we need to replace the Return Goto `return` with `goto` - if (lastExpr is LabelExpression lastLabel) // keep the last label on the same vertical line - { - lastExpr.ToCSharpString(sb, EnclosedIn.Block, ref ctx, - lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - if (inTheLastBlock) - { - // todo: @improve @hack no return, need to track returns, see Issue320_Bad_label_content_in_ILGenerator_when_creating_through_DynamicModule.Test_instance_call - var labelDefault = lastLabel.DefaultValue; - if (labelDefault != null && // for now just provide return for variable and such, that for soe reason were put in label as default - (labelDefault.NodeType != ExpressionType.Constant & labelDefault.NodeType != ExpressionType.Default)) - { - sb.NewLineIndent(lineIndent); - labelDefault.ToCSharpString(sb.Append("return "), - EnclosedIn.Return, ref ctx, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - } - sb.AppendSemicolonOnce(); // the last label forms the invalid C#, so we need at least ';' at the end - } - return sb; - } - - sb.NewLineIndent(lineIndent); - var enclosedIn = EnclosedIn.Block; - var isBraceLikeBlock = lastExpr.NodeType.IsBracedBlockLike(); - if (blockResultAssignment != null) - { - blockResultAssignment.Left.ToCSharpString(sb, enclosedIn, ref ctx, - lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - if (blockResultAssignment.NodeType != ExpressionType.PowerAssign) - sb.Append(OperatorToCSharpString(blockResultAssignment.NodeType)); - else - { - sb.Append(" = System.Math.Pow("); - blockResultAssignment.Left.ToCSharpString(sb, enclosedIn, ref ctx, - lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(", "); - } - } - else if (inTheLastBlock & !containerIgnoresResult && - b.Type != typeof(void) && lastExpr.Type != typeof(void)) - { - // A Trow may have the non void type, yes, so this check will avoid `return throw Ex` thingy, see #457, case2 - if (!containerIgnoresResult && lastExpr.NodeType != ExpressionType.Throw) - { - enclosedIn = EnclosedIn.Return; - if (!isBraceLikeBlock) sb.Append("return "); // for the braced block like switch, loop, etc. move the return inside the block, see #440 - } - } - - if (isBraceLikeBlock || lastExpr is DefaultExpression d && d.Type == typeof(void)) - { - lastExpr.ToCSharpString(sb, enclosedIn, ref ctx, - lineIndent + indentSpaces, stripNamespace, printType, indentSpaces, notRecognizedToCode); - } - else if (lastExpr.NodeType == ExpressionType.Assign && ((BinaryExpression)lastExpr).Right is BlockExpression) - { - lastExpr.ToCSharpString(sb, enclosedIn, ref ctx, - lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - if (enclosedIn == EnclosedIn.Return) - sb.AppendSemicolonOnce(); - } - else - { - lastExpr.ToCSharpString(sb, enclosedIn, ref ctx, - lineIndent + indentSpaces, stripNamespace, printType, indentSpaces, notRecognizedToCode); - sb = blockResultAssignment?.NodeType == ExpressionType.PowerAssign ? sb.Append(')') : sb; - if (lastExpr.NodeType != ExpressionType.Conditional || lastExpr.Type != typeof(void)) - sb.AppendSemicolonOnce(); - } - return sb; - } - - private static string OperatorToCSharpString(ExpressionType nodeType) => - nodeType switch - { - ExpressionType.And => " & ", - ExpressionType.AndAssign => " &= ", - ExpressionType.AndAlso => " && ", - ExpressionType.Or => " | ", - ExpressionType.OrAssign => " |= ", - ExpressionType.OrElse => " || ", - ExpressionType.GreaterThan => " > ", - ExpressionType.GreaterThanOrEqual => " >= ", - ExpressionType.LessThan => " < ", - ExpressionType.LessThanOrEqual => " <= ", - ExpressionType.Equal => " == ", - ExpressionType.NotEqual => " != ", - ExpressionType.Add => " + ", - ExpressionType.AddChecked => " + ", - ExpressionType.AddAssign => " += ", - ExpressionType.AddAssignChecked => " += ", - ExpressionType.Subtract => " - ", - ExpressionType.SubtractChecked => " - ", - ExpressionType.SubtractAssign => " -= ", - ExpressionType.SubtractAssignChecked => " -= ", - ExpressionType.Assign => " = ", - ExpressionType.ExclusiveOr => " ^ ", - ExpressionType.ExclusiveOrAssign => " ^= ", - ExpressionType.LeftShift => " << ", - ExpressionType.LeftShiftAssign => " <<= ", - ExpressionType.RightShift => " >> ", - ExpressionType.RightShiftAssign => " >>= ", - ExpressionType.Modulo => " % ", - ExpressionType.ModuloAssign => " %= ", - ExpressionType.Multiply => " * ", - ExpressionType.MultiplyChecked => " * ", - ExpressionType.MultiplyAssign => " *= ", - ExpressionType.MultiplyAssignChecked => " *= ", - ExpressionType.Divide => " / ", - ExpressionType.DivideAssign => " /= ", - _ => "???" // todo: @unclear wanna be good - }; - - } - - [RequiresUnreferencedCode(Trimming.Message)] - public static class CodePrinter - { - public static readonly Func PrintTypeStripOuterClasses = (type, name) => - { - if (!type.IsNested) - return name; - var index = name.LastIndexOf('.'); - return index == -1 ? name : name.Substring(index + 1); - }; - - public static StringBuilder AppendTypeOf(this StringBuilder sb, Type type, - bool stripNamespace = false, Func printType = null, bool printGenericTypeArgs = false) - { - if (type == null) - return sb.Append("null"); - sb.Append("typeof(").Append(type.ToCode(stripNamespace, printType, printGenericTypeArgs)).Append(')'); - return type.IsByRef ? sb.Append(".MakeByRefType()") : sb; - } - - public static StringBuilder AppendTypeOfList(this StringBuilder sb, Type[] types, - bool stripNamespace = false, Func printType = null, bool printGenericTypeArgs = false) - { - for (var i = 0; i < types.Length; i++) - (i > 0 ? sb.Append(", ") : sb).AppendTypeOf(types[i], stripNamespace, printType, printGenericTypeArgs); - return sb; - } - - internal static StringBuilder AppendMember(this StringBuilder sb, MemberInfo member, - bool stripNamespace = false, Func printType = null) => - member is FieldInfo f - ? sb.AppendField(f, stripNamespace, printType) - : sb.AppendProperty((PropertyInfo)member, stripNamespace, printType); - - internal static StringBuilder AppendField(this StringBuilder sb, FieldInfo field, - bool stripNamespace = false, Func printType = null) => - sb.AppendTypeOf(field.DeclaringType, stripNamespace, printType) - .Append(".GetTypeInfo().GetDeclaredField(\"").Append(field.Name).Append("\")"); - - internal static StringBuilder AppendProperty(this StringBuilder sb, PropertyInfo property, - bool stripNamespace = false, Func printType = null) => - sb.AppendTypeOf(property.DeclaringType, stripNamespace, printType) - .Append(".GetTypeInfo().GetDeclaredProperty(\"").Append(property.Name).Append("\")"); - - internal static StringBuilder AppendEnum(this StringBuilder sb, TEnum value, - bool stripNamespace = false, Func printType = null) => - sb.Append(typeof(TEnum).ToCode(stripNamespace, printType)).Append('.') - .Append(Enum.GetName(typeof(TEnum), value)); - - private const string _nonPubStatMethods = "BindingFlags.NonPublic|BindingFlags.Static"; - private const string _nonPubInstMethods = "BindingFlags.NonPublic|BindingFlags.Instance"; - - public static StringBuilder AppendMethod(this StringBuilder sb, MethodInfo method, - bool stripNamespace = false, Func printType = null) - { - if (method == null) - return sb.Append("null"); - - sb.AppendTypeOf(method.DeclaringType, stripNamespace, printType); - sb.Append(".GetMethods("); - - if (!method.IsPublic) - sb.Append(method.IsStatic ? _nonPubStatMethods : _nonPubInstMethods); - - var mp = method.GetParameters(); - if (!method.IsGenericMethod) - { - sb.Append(").Single(x => !x.IsGenericMethod && x.Name == \"").Append(method.Name).Append("\" && "); - return mp.Length == 0 - ? sb.Append("x.GetParameters().Length == 0)") - : sb.Append("x.GetParameters().Select(y => y.ParameterType).SequenceEqual(new[] { ") - .AppendTypeOfList(mp.Select(x => x.ParameterType).ToArray(), stripNamespace, printType) - .Append(" }))"); - } - - var tp = method.GetGenericArguments(); - sb.Append(").Where(x => x.IsGenericMethod && x.Name == \"").Append(method.Name).Append("\" && "); - if (mp.Length == 0) - { - sb.Append("x.GetParameters().Length == 0 && x.GetGenericArguments().Length == ").Append(tp.Length); - sb.Append(").Select(x => x.IsGenericMethodDefinition ? x.MakeGenericMethod(").AppendTypeOfList(tp, stripNamespace, printType); - return sb.Append(") : x).Single()"); - } - - sb.Append("x.GetGenericArguments().Length == ").Append(tp.Length); - sb.Append(").Select(x => x.IsGenericMethodDefinition ? x.MakeGenericMethod(").AppendTypeOfList(tp, stripNamespace, printType); - sb.Append(") : x).Single(x => x.GetParameters().Select(y => y.ParameterType).SequenceEqual(new[] { "); - sb.AppendTypeOfList(mp.Select(x => x.ParameterType).ToArray(), stripNamespace, printType); - return sb.Append(" }))"); - } - - /// Name usage - public enum NameUsage - { - /// Whatever - Whatever, - /// Used in goto - GotoLabel, - /// Used in mark - MarkLabel - } - - /// Named with index indeed - public struct NameIndex - { - /// Object to name - public object Obj; - /// Provides an unique suffix for the same named objects - public int Index; - /// Usage context - public NameUsage Usage; - } - - /// Contains the lambda with its paramete infos info. - /// Used to search for parameter expression related props/attrs in the respective Parameterinfo - public struct LambdaPars - { - /// The parameter expressions -#if LIGHT_EXPRESSION - public IParameterProvider Exprs; -#else - public IReadOnlyList Exprs; -#endif - /// The corresponding parameter infos - public ParameterInfo[] Infos; - } - - /// The context propagated through the CSharp to string methods - public struct PrintContext - { - /// Used to generated the unique names for the variables/parameters - public SmallList, NoArrayPool> SameNamed; - /// Use to find additional info about the parameter, like IsOut, which is available in ParameterInfo and not in parameter expression - public SmallList, NoArrayPool> LambdaPars; - } - - internal static bool IsOut(this PE parExpr, ref PrintContext ctx) - { - if (ctx.LambdaPars.Count == 0) - return false; - for (var i = ctx.LambdaPars.Count - 1; i >= 0; --i) - { - ref var it = ref ctx.LambdaPars.GetSurePresentRef(i); - var count = it.Infos.Length; - for (var j = 0; j < count; ++j) - if (it.Exprs.GetParameter(j) == parExpr) - return it.Infos[j].IsOut; - } - return false; - } - - internal static StringBuilder AppendName(this StringBuilder sb, - object parOrTarget, string name, string typeCode, ref PrintContext ctx, - int customIndex = 0, NameUsage nameUsage = NameUsage.Whatever) - { - var nameIndex = 0; - if (customIndex == 0) - { - var found = false; - for (var i = 0; i < ctx.SameNamed.Count; ++i) - { - ref var n = ref ctx.SameNamed.GetSurePresentRef(i); - if (found = ReferenceEquals(n.Obj, parOrTarget)) - { - nameIndex = n.Index; - break; - } - // If param or label target are not the same object but have the same name, say `bool p` and `int p`, - // then we increment assigned index to distinguish p_1 from p_0. - if (n.Obj is ParameterExpression pe1 && parOrTarget is ParameterExpression pe2 && pe1.Name == pe2.Name || - n.Obj is LabelTarget lt1 && parOrTarget is LabelTarget lt2 && lt1.Name == lt2.Name) - ++nameIndex; - } - if (!found) - ctx.SameNamed.Add(new() { Obj = parOrTarget, Index = nameIndex, Usage = nameUsage }); - customIndex = nameIndex; - } - - if (!string.IsNullOrWhiteSpace(name)) - { - if (name[0] != '@' && IsCSharpKeyword(name)) - sb.Append('@'); - sb.Append(name); - return nameIndex == 0 ? sb : sb.Append('_').Append(nameIndex); - } - - var validCsIdentFromTypeName = new StringBuilder(typeCode.Length); - for (var k = 0; k < typeCode.Length; ++k) - { - var ch = typeCode[k]; - var toUnder = ch == '.' | ch == '<' | ch == '>' | ch == ',' | ch == '?' | ch == ' ' | ch == '^' | ch == '['; - var toArray = ch == ']'; - var newChar = toUnder | toArray ? (toUnder ? '_' : 'a') : k == 0 ? Char.ToLowerInvariant(ch) : ch; - validCsIdentFromTypeName.Append(newChar); - } - - return sb.Append(validCsIdentFromTypeName).Append('_').Append(customIndex); - } - - private static bool IsCSharpKeyword(string name) => name switch - { - // Reserved keywords - "abstract" or "as" or "base" or "bool" or "break" or "byte" or "case" or "catch" or "char" or - "checked" or "class" or "const" or "continue" or "decimal" or "default" or "delegate" or "do" or - "double" or "else" or "enum" or "event" or "explicit" or "extern" or "false" or "finally" or - "fixed" or "float" or "for" or "foreach" or "goto" or "if" or "implicit" or "in" or "int" or - "interface" or "internal" or "is" or "lock" or "long" or "namespace" or "new" or "null" or - "object" or "operator" or "out" or "override" or "params" or "private" or "protected" or - "public" or "readonly" or "ref" or "return" or "sbyte" or "sealed" or "short" or "sizeof" or - "stackalloc" or "static" or "string" or "struct" or "switch" or "this" or "throw" or "true" or - "try" or "typeof" or "uint" or "ulong" or "unchecked" or "unsafe" or "ushort" or "using" or - "virtual" or "void" or "volatile" or "while" => true, - - // Contextual keywords (still require @ to be used safely as identifiers in generated code) - "add" or "alias" or "and" or "ascending" or "args" or "async" or "await" or "by" or "descending" or - "dynamic" or "equals" or "file" or "from" or "get" or "global" or "group" or "init" or "into" or - "join" or "let" or "managed" or "nameof" or "nint" or "not" or "notnull" or "nuint" or "on" or - "or" or "orderby" or "partial" or "record" or "remove" or "required" or "scoped" or "select" or - "set" or "unmanaged" or "value" or "var" or "when" or "where" or "with" or "yield" => true, - - _ => false - }; - - internal static StringBuilder AppendLabelName(this StringBuilder sb, LabelTarget target, ref PrintContext ctx, - int customIndex = 0, NameUsage nameUsage = NameUsage.Whatever) => - sb.AppendName(target, target.Name, target.Type.ToCode(stripNamespace: true, printType: null), ref ctx, customIndex, nameUsage); - - /// Returns the standard name (alias) for the well-known primitive type, e.g. Int16 -> short - public static string GetPrimitiveTypeNameAliasOrNull(this Type type) => - Type.GetTypeCode(type) switch - { - TypeCode.Byte => "byte", - TypeCode.SByte => "sbyte", - TypeCode.Int16 => "short", - TypeCode.Int32 => "int", - TypeCode.Int64 => "long", - TypeCode.UInt16 => "ushort", - TypeCode.UInt32 => "uint", - TypeCode.UInt64 => "ulong", - TypeCode.Single => "float", - TypeCode.Double => "double", - TypeCode.Boolean => "bool", - TypeCode.Char => "char", - TypeCode.String => "string", - _ => type == typeof(void) ? "void" : - type == typeof(object) ? "object" : - null - }; - - // todo: @simplify add `addTypeof = false` or use `AppendTypeOf` generally - /// Converts the into the proper C# representation. - public static string ToCode(this Type type, - bool stripNamespace = false, Func printType = null, bool printGenericTypeArgs = false) - { - if (type == null) - return "null"; - - if (type.IsGenericParameter) - return !printGenericTypeArgs ? string.Empty : (printType?.Invoke(type, type.Name) ?? type.Name); - - if (Nullable.GetUnderlyingType(type) is Type nullableElementType && !type.IsGenericTypeDefinition) - { - var result = nullableElementType.ToCode(stripNamespace, printType, printGenericTypeArgs) + "?"; - return printType?.Invoke(type, result) ?? result; - } - - Type arrayType = null; - if (type.IsArray) - { - // store the original type for the later and process its element type further here - arrayType = type; - type = type.GetElementType(); - } - - if (type.IsEnum) - { - var result = !stripNamespace && !string.IsNullOrEmpty(type.Namespace) - ? string.Concat(type.Namespace, ".", type.Name) : type.Name; - return printType?.Invoke(type, result) ?? result; - } - - var buildInTypeString = type.GetPrimitiveTypeNameAliasOrNull(); - if (buildInTypeString != null) - { - if (arrayType != null) - { - var rank = arrayType.GetArrayRank(); - buildInTypeString += rank == 1 ? "[]" : ("[" + new string(',', rank-1) + "]"); - } - return printType?.Invoke(arrayType ?? type, buildInTypeString) ?? buildInTypeString; - } - - var parentCount = 0; - for (var ti = type.GetTypeInfo(); ti.IsNested; ti = ti.DeclaringType.GetTypeInfo()) - ++parentCount; - - Type[] parentTypes = null; - if (parentCount > 0) - { - parentTypes = new Type[parentCount]; - var pt = type.DeclaringType; - for (var i = 0; i < parentTypes.Length; i++, pt = pt.DeclaringType) - parentTypes[i] = pt; - } - - var typeInfo = type.GetTypeInfo(); - Type[] typeArgs = null; - var isTypeClosedGeneric = false; - if (type.IsGenericType) - { - isTypeClosedGeneric = !typeInfo.IsGenericTypeDefinition; - typeArgs = isTypeClosedGeneric ? typeInfo.GenericTypeArguments : typeInfo.GenericTypeParameters; - } - - var typeArgsConsumedByParentsCount = 0; - var s = new StringBuilder(); - if (!stripNamespace && !string.IsNullOrEmpty(type.Namespace)) // for the auto-generated classes Namespace may be empty and in general it may be empty - s.Append(type.Namespace).Append('.'); - - if (parentTypes != null) - { - for (var p = parentTypes.Length - 1; p >= 0; --p) - { - var parentType = parentTypes[p]; - if (!parentType.IsGenericType) - { - s.Append(parentType.Name).Append('.'); - } - else - { - var parentTypeInfo = parentType.GetTypeInfo(); - Type[] parentTypeArgs = null; - if (parentTypeInfo.IsGenericTypeDefinition) - { - parentTypeArgs = parentTypeInfo.GenericTypeParameters; - - // replace the open parent args with the closed child args, - // and close the parent - if (isTypeClosedGeneric) - for (var t = 0; t < parentTypeArgs.Length; ++t) - parentTypeArgs[t] = typeArgs[t]; - - var parentTypeArgCount = parentTypeArgs.Length; - if (typeArgsConsumedByParentsCount > 0) - { - int ownArgCount = parentTypeArgCount - typeArgsConsumedByParentsCount; - if (ownArgCount == 0) - parentTypeArgs = null; - else - { - var ownArgs = new Type[ownArgCount]; - for (var a = 0; a < ownArgs.Length; ++a) - ownArgs[a] = parentTypeArgs[a + typeArgsConsumedByParentsCount]; - parentTypeArgs = ownArgs; - } - } - typeArgsConsumedByParentsCount = parentTypeArgCount; - } - else - { - parentTypeArgs = parentTypeInfo.GenericTypeArguments; - } - - var parentTickIndex = parentType.Name.IndexOf('`'); - s.Append(parentType.Name.Substring(0, parentTickIndex)); - - // The owned parentTypeArgs maybe empty because all args are defined in the parent's parents - if (parentTypeArgs?.Length > 0) - { - s.Append('<'); - for (var t = 0; t < parentTypeArgs.Length; ++t) - (t == 0 ? s : s.Append(", ")).Append(parentTypeArgs[t].ToCode(stripNamespace, printType, printGenericTypeArgs)); - s.Append('>'); - } - s.Append('.'); - } - } - } - var name = type.Name.TrimStart('<', '>').TrimEnd('&'); - - if (typeArgs != null && typeArgsConsumedByParentsCount < typeArgs.Length) - { - var tickIndex = name.IndexOf('`'); - s.Append(name.Substring(0, tickIndex)).Append('<'); - for (var i = 0; i < typeArgs.Length - typeArgsConsumedByParentsCount; ++i) - (i == 0 ? s : s.Append(", ")).Append(typeArgs[i + typeArgsConsumedByParentsCount].ToCode(stripNamespace, printType, printGenericTypeArgs)); - s.Append('>'); - } - else - { - s.Append(name); - } - - if (arrayType != null) - s.Append("[]"); - - return printType?.Invoke(arrayType ?? type, s.ToString()) ?? s.ToString(); - } - - /// Prints valid C# Boolean - public static string ToCode(this bool x) => x ? "true" : "false"; - - /// Prints valid C# String escaping the things - public static string ToCode(this string x) => - x == null ? "null" - : $"\"{x.Replace("\"", "\\\"").Replace("\r", "\\r").Replace("\n", "\\n")}\""; - - private static readonly char[] _enumValueSeparators = new[] { ',', ' ' }; - - /// Prints valid C# Enum literal - public static string ToEnumValueCode(this Type enumType, object x, - bool stripNamespace = false, Func printType = null) - { - var typeStr = enumType.ToCode(stripNamespace, printType); - var valueStr = x.ToString(); - var flags = valueStr.Split(_enumValueSeparators, StringSplitOptions.RemoveEmptyEntries); - if (flags.Length == 1) - { - if (int.TryParse(valueStr, out _)) - return "(" + typeStr + ")" + valueStr; - return typeStr + "." + valueStr; - } - var orTypeDot = "|" + typeStr + "."; - return typeStr + "." + string.Join(orTypeDot, flags); - } - - private static Type[] GetGenericTypeParametersOrArguments(this TypeInfo typeInfo) => - typeInfo.IsGenericTypeDefinition ? typeInfo.GenericTypeParameters : typeInfo.GenericTypeArguments; - - /// Custom handler for output the object in valid C#. - /// Note, the `printGenericTypeArgs` is excluded because it cannot be a open-generic object. - /// This handler is also used to allow user to fully control a Constant expression output - public delegate string ObjectToCode(object x, bool stripNamespace = false, Func printType = null); - - /// Outputs the `default(Type)` for the unknown constant with the comment message - public static readonly ObjectToCode DefaultNotRecognizedToCode = (x, stripNamespace, printType) => - { - var t = x.GetType(); - var isCompGen = t.IsCompilerGenerated(); - var typeCs = x.GetType().ToCode(stripNamespace, printType); - return $"default({typeCs})/*NOTE: Provide the non-default value for the Constant{(isCompGen ? " of compiler-generated type" : "")}!*/"; - }; - - /// Prints many code items as the array initializer. - public static string ToCommaSeparatedCode(this IEnumerable items, ObjectToCode notRecognizedToCode, - bool stripNamespace = false, Func printType = null) - { - var s = new StringBuilder(); - var first = true; - foreach (var item in items) - { - if (!first) - s.Append(", "); - first = false; - s.Append(item.ToCode(notRecognizedToCode, stripNamespace, printType)); - } - return s.ToString(); - } - - /// Prints many code items as array initializer. - public static string ToArrayInitializerCode(this IEnumerable items, Type itemType, ObjectToCode notRecognizedToCode, - bool stripNamespace = false, Func printType = null) - { - var s = new StringBuilder("new "); - // todo: @simplify should we avoid type for the `new Type` because the values also will include the type? - s.Append(itemType.ToCode(stripNamespace, printType)); - s.Append("[]{"); - s.Append(items.ToCommaSeparatedCode(notRecognizedToCode, stripNamespace, printType)); - s.Append('}'); - return s.ToString(); - } - - private static readonly Type[] TypesImplementedByArray = - typeof(object[]).GetInterfaces().Where(t => t.GetTypeInfo().IsGenericType).Select(t => t.GetGenericTypeDefinition()).ToArray(); - - // todo: @simplify convert to using StringBuilder and simplify usage call-sites, or ADD the method - // todo: @simplify add `addTypeof = false` - /// - /// Prints a valid C# for known , - /// otherwise uses passed or falls back to `ToString()`. - /// - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode(Trimming.Message)] - public static string ToCode(this T x, - ObjectToCode notRecognizedToCode = null, bool stripNamespace = false, Func printType = null) - { - if (x == null) - return "null"; - - if (x is bool b) - return b.ToCode(); - - if (x is int i) - return i.ToString(); - - if (x is double d) - return d.ToString(); - - if (x is string s) - return s.ToCode(); - - if (x is char c) - return "'" + c + "'"; - - if (x is Decimal m) - return $"{m}m"; - - if (x is Type t) - return t.ToCode(stripNamespace, printType); - - if (x is Guid guid) - return "Guid.Parse(" + guid.ToString().ToCode() + ")"; - - if (x is DateTime date) - return "DateTime.Parse(" + date.ToString().ToCode() + ")"; - - if (x is TimeSpan time) - return "TimeSpan.Parse(" + time.ToString().ToCode() + ")"; - - var xType = x.GetType(); - var xTypeInfo = xType.GetTypeInfo(); - - // check if item is implemented by array and then use the array initializer only for these types, - // otherwise we may produce the array initializer but it will be incompatible with e.g. `List` - if (xTypeInfo.IsArray || - xTypeInfo.IsGenericType && TypesImplementedByArray.Contains(xType.GetGenericTypeDefinition())) - { - var elemType = xTypeInfo.IsArray - ? xTypeInfo.GetElementType() - : xTypeInfo.GetGenericTypeParametersOrArguments().GetFirst(); - if (elemType != null && elemType != xType) // avoid self recurring types e.g. `class A : IEnumerable` - return ((IEnumerable)x).ToArrayInitializerCode(elemType, notRecognizedToCode, stripNamespace, printType); - } - - // unwrap the Nullable struct - if (xTypeInfo.IsGenericType && xTypeInfo.GetGenericTypeDefinition() == typeof(Nullable<>)) - { - xType = xTypeInfo.GetElementType(); - xTypeInfo = xType.GetTypeInfo(); - } - - if (xTypeInfo.IsEnum) - return x.GetType().ToEnumValueCode(x, stripNamespace, printType); - - if (xTypeInfo.IsPrimitive) // output the primitive casted to the type - return "(" + x.GetType().ToCode(true, null) + ")" + x.ToString(); - - return notRecognizedToCode?.Invoke(x, stripNamespace, printType) ?? x.ToString(); - } - - internal static StringBuilder NewLineIndent(this StringBuilder sb, int lineIndent) - { - var originalLength = sb.Length; - sb.AppendNewLineOnce(); - return originalLength == sb.Length ? sb : sb.Append(' ', lineIndent); - } - - internal static StringBuilder NewLineIndentExpr(this StringBuilder sb, - Expression expr, List paramsExprs, List uniqueExprs, List lts, - int lineIndent, bool stripNamespace, Func printType, int indentSpaces, ObjectToCode notRecognizedToCode) - { - sb.NewLineIndent(lineIndent); - return expr?.ToExpressionString(sb, paramsExprs, uniqueExprs, lts, - lineIndent + indentSpaces, stripNamespace, printType, indentSpaces, notRecognizedToCode) ?? sb.Append("null"); - } - - internal static StringBuilder NewLineIndentArgumentExprs(this StringBuilder sb, IEnumerable exprs, - List paramsExprs, List uniqueExprs, List lts, - int lineIndent, bool stripNamespace, Func printType, int indentSpaces, ObjectToCode notRecognizedToCode) - where T : Expression - { - if (exprs == null || !exprs.Any()) - return sb.Append(" new ").Append(typeof(T).ToCode(true)).Append("[0]"); - var i = 0; - foreach (var e in exprs) - (i++ > 0 ? sb.Append(", ") : sb).NewLineIndentExpr(e, - paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); - return sb; - } - - /// Helper method to find the number of lambdas in the C# code string - public static int CountLambdas(string code) - { - int lambdaCount = 0, lambdaIndex = 0; - while (true) - { - lambdaIndex = code.IndexOf("=>", lambdaIndex + 2); - if (lambdaIndex == -1) - break; - ++lambdaCount; - } - return lambdaCount; - } - } - - internal static class FecHelpers - { - public static int GetFirstIndex(this IReadOnlyList source, T item, TEq eq = default) - where TEq : struct, IEq - { - if (source.Count != 0) - for (var i = 0; i < source.Count; ++i) - if (eq.Equals(source[i], item)) - return i; - return -1; - } - - [MethodImpl((MethodImplOptions)256)] - public static T GetArgument(this IReadOnlyList source, int index) => source[index]; - - [MethodImpl((MethodImplOptions)256)] - public static ParameterExpression GetParameter(this IReadOnlyList source, int index) => source[index]; - -#if LIGHT_EXPRESSION - public static IReadOnlyList ToReadOnlyList(this IParameterProvider source) - { - var count = source.ParameterCount; - var ps = new ParameterExpression[count]; - for (var i = 0; i < count; ++i) - ps[i] = source.GetParameter(i); - return ps; - } - - public static int GetCount(this IParameterProvider p) => p.ParameterCount; -#else - public static IReadOnlyList ToReadOnlyList(this IReadOnlyList source) => source; - - public static int GetCount(this IReadOnlyList p) => p.Count; -#endif - -#if SUPPORTS_ARGUMENT_PROVIDER - public static int GetCount(this IArgumentProvider p) => p.ArgumentCount; -#else - public static int GetCount(this IReadOnlyList p) => p.Count; -#endif - } - - internal static class Trimming - { - public const string Message = "FastExpressionCompiler is not supported in trimming scenarios."; - } -} - -#if !NET5_0_OR_GREATER -namespace System.Diagnostics.CodeAnalysis -{ - [AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)] - internal sealed class UnconditionalSuppressMessageAttribute : Attribute - { - public string Category { get; } - public string CheckId { get; } - public string Justification { get; set; } - public UnconditionalSuppressMessageAttribute(string category, string checkId) - { - Category = category; - CheckId = checkId; - } - } - - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Class, Inherited = false)] - internal sealed class RequiresUnreferencedCodeAttribute : Attribute - { - public string Message { get; } - public RequiresUnreferencedCodeAttribute(string message) => Message = message; - } - - [AttributeUsage( - AttributeTargets.Field | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter | - AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Method | - AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct, - Inherited = false)] - internal sealed class DynamicallyAccessedMembersAttribute : Attribute - { - public DynamicallyAccessedMembersAttribute(DynamicallyAccessedMemberTypes memberTypes) { } - } - - [Flags] - internal enum DynamicallyAccessedMemberTypes - { - None = 0, - PublicParameterlessConstructor = 0x0001, - PublicConstructors = 0x0002 | PublicParameterlessConstructor, - NonPublicConstructors = 0x0004, - PublicMethods = 0x0008, - NonPublicMethods = 0x0010, - PublicFields = 0x0020, - NonPublicFields = 0x0040, - PublicNestedTypes = 0x0080, - NonPublicNestedTypes = 0x0100, - PublicProperties = 0x0200, - NonPublicProperties = 0x0400, - - Interfaces = 0x2000, - All = ~None - } -} -#endif -#if !NET7_0_OR_GREATER -namespace System.Diagnostics.CodeAnalysis -{ - [AttributeUsage(System.AttributeTargets.Method | System.AttributeTargets.Parameter | System.AttributeTargets.Property, AllowMultiple = false, Inherited = false)] - public sealed class UnscopedRefAttribute : Attribute { } -} -#endif +// +/* +The MIT License (MIT) + +Copyright (c) 2016-2026 Maksim Volkau + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +// ReSharper disable CoVariantArrayConversion +#nullable disable + +//#define LIGHT_EXPRESSION + +// todo: @wip disable for the published version! +// #define TESTING // allows to test in Release in addition to the Debug + +#if DEBUG && NET6_0_OR_GREATER +#define DEBUG_INFO_LOCAL_VARIABLE_USAGE +#define DEMIT +#define INTERPRETATION_DIAGNOSTICS +#endif + +#if NET6_0_OR_GREATER +#define SUPPORTS_IL_GENERATOR_REUSE +#endif + +// Enables direct IL stream writes for token-emitting opcodes (Call, Ldfld, Newobj, etc.) +// using UnsafeAccessorType introduced in .NET 10. +// Set ILGeneratorTools.UseILEmitHack = false to fall back to ILGenerator.Emit() for debugging. +#if NET10_0_OR_GREATER +#define SUPPORTS_IL_EMIT_HACK +#endif + +#if LIGHT_EXPRESSION +#define SUPPORTS_ARGUMENT_PROVIDER +#endif + +#if LIGHT_EXPRESSION +namespace FastExpressionCompiler.LightExpression +{ + using static FastExpressionCompiler.LightExpression.Expression; + using PE = FastExpressionCompiler.LightExpression.ParameterExpression; + using FastExpressionCompiler.LightExpression.ImTools; + using FastExpressionCompiler.LightExpression.ILDecoder; + using static FastExpressionCompiler.LightExpression.ImTools.SmallMap; +#else +namespace FastExpressionCompiler +{ + using static System.Linq.Expressions.Expression; + using PE = System.Linq.Expressions.ParameterExpression; + using FastExpressionCompiler.ImTools; + using FastExpressionCompiler.ILDecoder; + using static FastExpressionCompiler.ImTools.SmallMap; +#endif + using System; + using System.Buffers.Binary; + using System.Collections; + using System.Collections.Generic; + using System.Linq; + using System.Linq.Expressions; + using System.Reflection; + using System.Reflection.Emit; + using System.Threading; + using System.Text; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using static System.Environment; + using static CodePrinter; + + /// The flags for the compiler + [Flags] + public enum CompilerFlags : byte + { + /// The default flags: Invocation lambda is inlined, no debug info + Default = 0, + /// Prevents the inlining of the lambda in the Invocation expression to optimize for the multiple same lambda compiled once + NoInvocationLambdaInlining = 1, + /// Adds the Expression, ExpressionString, and CSharpString to the delegate closure for the debugging inspection + EnableDelegateDebugInfo = 1 << 1, + /// When the flag is set then instead of the returning `null` the specific exception is thrown*346 + ThrowOnNotSupportedExpression = 1 << 2, + /// Will try to Interpret arithmetic, logical, comparison expressions for the primitive types, + /// and emit the IL the result only instead of the whole computation. + DisableInterpreter = 1 << 4 + } + + /// FEC Not Supported exception + public sealed class NotSupportedExpressionException : InvalidOperationException + { + /// The reason + public readonly ExpressionCompiler.Result Reason; + /// Constructor + public NotSupportedExpressionException(ExpressionCompiler.Result reason) : base(reason.ToString()) => Reason = reason; + /// Constructor + public NotSupportedExpressionException(ExpressionCompiler.Result reason, string message) : base(reason + ": " + message) => Reason = reason; + } + + /// The interface is implemented by the compiled delegate Target if `CompilerFlags.EnableDelegateDebugInfo` is set. + public interface IDelegateDebugInfo + { + /// The lambda expression object that was compiled to the delegate + LambdaExpression Expression { get; } + + /// Delegate IL op-codes + ILInstruction[] ILInstructions { get; } + + /// Enumerate any nested lambdas in the delegate + [RequiresUnreferencedCode(Trimming.Message)] + IEnumerable EnumerateNestedLambdas(); + } + + /// Compiles expression to delegate ~20 times faster than Expression.Compile. + /// Partial to extend with your things when used as source file. + // ReSharper disable once PartialTypeWithSinglePart + [RequiresUnreferencedCode(Trimming.Message)] + public static partial class ExpressionCompiler + { + #region Expression.CompileFast overloads for Delegate, Func, and Action + + /// Compiles lambda expression to TDelegate type. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. + public static TDelegate CompileFast(this LambdaExpression lambdaExpr, + bool ifFastFailedReturnNull = false, CompilerFlags flags = CompilerFlags.Default) where TDelegate : class => + (TDelegate)(TryCompileBoundToFirstClosureParam( + typeof(TDelegate) == typeof(Delegate) ? lambdaExpr.Type : typeof(TDelegate), lambdaExpr.Body, +#if LIGHT_EXPRESSION + lambdaExpr, +#else + lambdaExpr.Parameters, +#endif + lambdaExpr.ReturnType, flags) ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys())); + + /// Compiles a static method to the passed IL Generator. + /// Could be used as alternative for `CompileToMethod` like this . + /// Check `IssueTests.Issue179_Add_something_like_LambdaExpression_CompileToMethod.cs` for example. + public static bool CompileFastToIL(this LambdaExpression lambdaExpr, ILGenerator il, CompilerFlags flags = CompilerFlags.Default) + { + if ((flags & CompilerFlags.EnableDelegateDebugInfo) != 0) + throw new NotSupportedException("The `CompilerFlags.EnableDelegateDebugInfo` is not supported because the debug info is gathered into the closure object which is not allowed for static lambda to be compiled to method."); + +#if LIGHT_EXPRESSION + var paramExprs = lambdaExpr; +#else + var paramExprs = lambdaExpr.Parameters; +#endif + var bodyExpr = lambdaExpr.Body; + + var closureInfo = new ClosureInfo(ClosureStatus.ShouldBeStaticMethod); + var nestedLambdas = new SmallList(); + if (!TryCollectBoundConstants(ref closureInfo, bodyExpr, paramExprs, null, ref nestedLambdas, flags)) + return false; + + if ((closureInfo.Status & ClosureStatus.HasClosure) != 0) + return false; + + var parent = lambdaExpr.ReturnType == typeof(void) ? ParentFlags.IgnoreResult : ParentFlags.LambdaCall; + if (!EmittingVisitor.TryEmit(bodyExpr, paramExprs, il, ref closureInfo, flags, parent)) + return false; + + il.Demit(OpCodes.Ret); + return true; + } + + /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. + public static Delegate CompileFast(this LambdaExpression lambdaExpr, bool ifFastFailedReturnNull = false, CompilerFlags flags = CompilerFlags.Default) => + (Delegate)TryCompileBoundToFirstClosureParam(lambdaExpr.Type, lambdaExpr.Body, +#if LIGHT_EXPRESSION + lambdaExpr, +#else + lambdaExpr.Parameters, +#endif + lambdaExpr.ReturnType, flags) ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); + + /// Returns the System expression itself or convert the System expression into Light Expression + public static Expression FromSysExpression(this System.Linq.Expressions.Expression lambdaExpr) where TDelegate : System.Delegate => +#if LIGHT_EXPRESSION + lambdaExpr.ToLightExpression(); +#else + lambdaExpr; +#endif + + /// Returns the System expression itself or convert the System expression into Light Expression + public static LambdaExpression FromSysExpression(this System.Linq.Expressions.LambdaExpression lambdaExpr) => +#if LIGHT_EXPRESSION + lambdaExpr.ToLightExpression(); +#else + lambdaExpr; +#endif + + /// Unifies Compile for System.Linq.Expressions and FEC.LightExpression + public static TDelegate CompileSys(this Expression lambdaExpr) where TDelegate : System.Delegate => + lambdaExpr +#if LIGHT_EXPRESSION + .ToLambdaExpression() +#endif + .Compile(); + + /// Unifies Compile for System.Linq.Expressions and FEC.LightExpression + public static Delegate CompileSys(this LambdaExpression lambdaExpr) => + lambdaExpr +#if LIGHT_EXPRESSION + .ToLambdaExpression() +#endif + .Compile(); + + /// Compiles lambda expression to TDelegate type. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. + public static TDelegate CompileFast(this Expression lambdaExpr, bool ifFastFailedReturnNull = false, + CompilerFlags flags = CompilerFlags.Default) where TDelegate : System.Delegate => + ((LambdaExpression)lambdaExpr).CompileFast(ifFastFailedReturnNull, flags); + + /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. + public static Func CompileFast(this Expression> lambdaExpr, bool ifFastFailedReturnNull = false, + CompilerFlags flags = CompilerFlags.Default) => + (Func)TryCompileBoundToFirstClosureParam(typeof(Func), lambdaExpr.Body, +#if LIGHT_EXPRESSION + lambdaExpr, +#else + lambdaExpr.Parameters, +#endif + typeof(R), flags) ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); + + /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. + public static Func CompileFast(this Expression> lambdaExpr, + bool ifFastFailedReturnNull = false, CompilerFlags flags = CompilerFlags.Default) => + (Func)TryCompileBoundToFirstClosureParam(typeof(Func), lambdaExpr.Body, +#if LIGHT_EXPRESSION + lambdaExpr, +#else + lambdaExpr.Parameters, +#endif + typeof(R), flags) ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); + + /// Compiles lambda expression to TDelegate type. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. + public static Func CompileFast(this Expression> lambdaExpr, + bool ifFastFailedReturnNull = false, CompilerFlags flags = CompilerFlags.Default) => + (Func)TryCompileBoundToFirstClosureParam(typeof(Func), lambdaExpr.Body, +#if LIGHT_EXPRESSION + lambdaExpr, +#else + lambdaExpr.Parameters, +#endif + typeof(R), flags) ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); + + /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. + public static Func CompileFast( + this Expression> lambdaExpr, bool ifFastFailedReturnNull = false, CompilerFlags flags = CompilerFlags.Default) => + (Func)TryCompileBoundToFirstClosureParam(typeof(Func), lambdaExpr.Body, +#if LIGHT_EXPRESSION + lambdaExpr, +#else + lambdaExpr.Parameters, +#endif + typeof(R), flags) + ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); + + /// Compiles lambda expression to TDelegate type. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. + public static Func CompileFast( + this Expression> lambdaExpr, bool ifFastFailedReturnNull = false, CompilerFlags flags = CompilerFlags.Default) => + (Func)TryCompileBoundToFirstClosureParam(typeof(Func), lambdaExpr.Body, +#if LIGHT_EXPRESSION + lambdaExpr, +#else + lambdaExpr.Parameters, +#endif + typeof(R), flags) + ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); + + /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. + public static Func CompileFast( + this Expression> lambdaExpr, bool ifFastFailedReturnNull = false, CompilerFlags flags = CompilerFlags.Default) => + (Func)TryCompileBoundToFirstClosureParam(typeof(Func), lambdaExpr.Body, +#if LIGHT_EXPRESSION + lambdaExpr, +#else + lambdaExpr.Parameters, +#endif + typeof(R), flags) + ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); + + /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. + public static Func CompileFast( + this Expression> lambdaExpr, bool ifFastFailedReturnNull = false, CompilerFlags flags = CompilerFlags.Default) => + (Func)TryCompileBoundToFirstClosureParam(typeof(Func), lambdaExpr.Body, +#if LIGHT_EXPRESSION + lambdaExpr, +#else + lambdaExpr.Parameters, +#endif + typeof(R), flags) + ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); + + /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. + public static Action CompileFast(this Expression lambdaExpr, bool ifFastFailedReturnNull = false, CompilerFlags flags = CompilerFlags.Default) => + (Action)TryCompileBoundToFirstClosureParam(typeof(Action), lambdaExpr.Body, +#if LIGHT_EXPRESSION + lambdaExpr, +#else + lambdaExpr.Parameters, +#endif + typeof(void), flags) ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); + + /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. + public static Action CompileFast(this Expression> lambdaExpr, + bool ifFastFailedReturnNull = false, CompilerFlags flags = CompilerFlags.Default) => + (Action)TryCompileBoundToFirstClosureParam(typeof(Action), lambdaExpr.Body, +#if LIGHT_EXPRESSION + lambdaExpr, +#else + lambdaExpr.Parameters, +#endif + typeof(void), flags) ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); + + /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. + public static Action CompileFast(this Expression> lambdaExpr, + bool ifFastFailedReturnNull = false, CompilerFlags flags = CompilerFlags.Default) => + (Action)TryCompileBoundToFirstClosureParam(typeof(Action), lambdaExpr.Body, +#if LIGHT_EXPRESSION + lambdaExpr, +#else + lambdaExpr.Parameters, +#endif + typeof(void), flags) ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); + + /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. + public static Action CompileFast(this Expression> lambdaExpr, + bool ifFastFailedReturnNull = false, CompilerFlags flags = CompilerFlags.Default) => + (Action)TryCompileBoundToFirstClosureParam(typeof(Action), lambdaExpr.Body, +#if LIGHT_EXPRESSION + lambdaExpr, +#else + lambdaExpr.Parameters, +#endif + typeof(void), flags) + ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); + + /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. + public static Action CompileFast( + this Expression> lambdaExpr, bool ifFastFailedReturnNull = false, CompilerFlags flags = CompilerFlags.Default) => + (Action)TryCompileBoundToFirstClosureParam(typeof(Action), lambdaExpr.Body, +#if LIGHT_EXPRESSION + lambdaExpr, +#else + lambdaExpr.Parameters, +#endif + typeof(void), flags) + ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); + + /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. + public static Action CompileFast( + this Expression> lambdaExpr, bool ifFastFailedReturnNull = false, CompilerFlags flags = CompilerFlags.Default) => + (Action)TryCompileBoundToFirstClosureParam(typeof(Action), lambdaExpr.Body, +#if LIGHT_EXPRESSION + lambdaExpr, +#else + lambdaExpr.Parameters, +#endif + typeof(void), flags) + ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); + + /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. + public static Action CompileFast( + this Expression> lambdaExpr, bool ifFastFailedReturnNull = false, CompilerFlags flags = CompilerFlags.Default) => + (Action)TryCompileBoundToFirstClosureParam(typeof(Action), lambdaExpr.Body, +#if LIGHT_EXPRESSION + lambdaExpr, +#else + lambdaExpr.Parameters, +#endif + typeof(void), flags) + ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); + + #endregion + + /// Tries to compile lambda expression to + public static TDelegate TryCompile(this LambdaExpression lambdaExpr, CompilerFlags flags = CompilerFlags.Default) + where TDelegate : class => + (TDelegate)TryCompileBoundToFirstClosureParam(typeof(TDelegate) == typeof(Delegate) ? lambdaExpr.Type : typeof(TDelegate), lambdaExpr.Body, +#if LIGHT_EXPRESSION + lambdaExpr, +#else + lambdaExpr.Parameters, +#endif + lambdaExpr.ReturnType, flags); + + /// Tries to compile lambda expression to + /// with the provided closure object and constant expressions (or lack there of) - + /// Constant expression should be the in order of Fields in closure object! + /// Note 1: Use it on your own risk - FEC won't verify the expression is compile-able with passed closure, it is up to you! + /// Note 2: The expression with NESTED LAMBDA IS NOT SUPPORTED! + /// Note 3: `Label` and `GoTo` are not supported in this case, because they need first round to collect out-of-order labels + public static TDelegate TryCompileWithPreCreatedClosure(this LambdaExpression lambdaExpr, + params ConstantExpression[] closureConstantsExprs) where TDelegate : class => + lambdaExpr.TryCompileWithPreCreatedClosure(closureConstantsExprs, CompilerFlags.Default); + + /// Tries to compile lambda expression to + /// with the provided closure object and constant expressions (or lack there of) + public static TDelegate TryCompileWithPreCreatedClosure(this LambdaExpression lambdaExpr, + ConstantExpression[] closureConstantsExprs, CompilerFlags flags) + where TDelegate : class + { + var closureConstants = new object[closureConstantsExprs.Length]; + for (var i = 0; i < closureConstants.Length; i++) + closureConstants[i] = closureConstantsExprs[i].Value; + + var closureInfo = new ClosureInfo(ClosureStatus.UserProvided | ClosureStatus.HasClosure, closureConstants); + return TryCompileWithPreCreatedClosure(lambdaExpr, ref closureInfo, flags); + } + + internal static TDelegate TryCompileWithPreCreatedClosure( + this LambdaExpression lambdaExpr, ref ClosureInfo closureInfo, CompilerFlags flags) where TDelegate : class + { +#if LIGHT_EXPRESSION + var closurePlusParamTypes = RentPooledOrNewClosureTypeToParamTypes(lambdaExpr); +#else + var closurePlusParamTypes = RentPooledOrNewClosureTypeToParamTypes(lambdaExpr.Parameters); +#endif + var method = new DynamicMethod(string.Empty, lambdaExpr.ReturnType, closurePlusParamTypes, + typeof(ExpressionCompiler), skipVisibility: true); + + var il = method.GetILGenerator(); + EmittingVisitor.EmitLoadConstantsAndNestedLambdasIntoVars(il, ref closureInfo); + + var parent = lambdaExpr.ReturnType == typeof(void) ? ParentFlags.IgnoreResult : ParentFlags.LambdaCall; + if (!EmittingVisitor.TryEmit(lambdaExpr.Body, +#if LIGHT_EXPRESSION + lambdaExpr, +#else + lambdaExpr.Parameters, +#endif + il, ref closureInfo, flags, parent)) + return null; + + il.Demit(OpCodes.Ret); + + var delegateType = typeof(TDelegate) != typeof(Delegate) ? typeof(TDelegate) : lambdaExpr.Type; + var dlg = (TDelegate)(object)method.CreateDelegate(delegateType, new ArrayClosure(closureInfo.Constants.Items)); + FreePooledClosureTypeAndParamTypes(closurePlusParamTypes); + return dlg; + } + + /// Tries to compile expression to "static" delegate, skipping the step of collecting the closure object. + public static TDelegate TryCompileWithoutClosure(this LambdaExpression lambdaExpr, + CompilerFlags flags = CompilerFlags.Default) where TDelegate : class + { + var closureInfo = new ClosureInfo(ClosureStatus.UserProvided); +#if LIGHT_EXPRESSION + var closurePlusParamTypes = RentPooledOrNewClosureTypeToParamTypes(lambdaExpr); +#else + var closurePlusParamTypes = RentPooledOrNewClosureTypeToParamTypes(lambdaExpr.Parameters); +#endif + var method = new DynamicMethod(string.Empty, lambdaExpr.ReturnType, closurePlusParamTypes, typeof(ArrayClosure), + skipVisibility: true); + + var il = method.GetILGenerator(); + if (!EmittingVisitor.TryEmit(lambdaExpr.Body, +#if LIGHT_EXPRESSION + lambdaExpr, +#else + lambdaExpr.Parameters, +#endif + il, ref closureInfo, flags, lambdaExpr.ReturnType == typeof(void) ? ParentFlags.IgnoreResult : ParentFlags.Empty)) + return null; + + il.Demit(OpCodes.Ret); + + var delegateType = typeof(TDelegate) != typeof(Delegate) ? typeof(TDelegate) : lambdaExpr.Type; + var dlg = (TDelegate)(object)method.CreateDelegate(delegateType, EmptyArrayClosure); + FreePooledClosureTypeAndParamTypes(closurePlusParamTypes); + return dlg; + } + + private static Delegate CompileNoArgsNew(NewExpression newExpr, Type delegateType, Type[] closurePlusParamTypes, Type returnType, CompilerFlags flags) + { + var method = new DynamicMethod(string.Empty, returnType, closurePlusParamTypes, typeof(ArrayClosure), true); + var il = DynamicMethodHacks.RentPooledOrNewILGenerator(method, returnType, closurePlusParamTypes, newStreamSize: 16); + il.Demit(OpCodes.Newobj, newExpr.Constructor); + if (returnType == typeof(void)) + il.Demit(OpCodes.Pop); + il.Demit(OpCodes.Ret); + + var closure = (flags & CompilerFlags.EnableDelegateDebugInfo) == 0 + ? EmptyArrayClosure + : new DebugArrayClosure(null, null, Lambda(newExpr, Tools.Empty())); + + var dlg = method.CreateDelegate(delegateType, closure); + DynamicMethodHacks.FreePooledILGenerator(method, il); + + if (closure is DebugArrayClosure diagClosure) + diagClosure.ILInstructions = dlg.Method.ReadAllInstructions(); + + return dlg; + } + +#if LIGHT_EXPRESSION + internal static object TryCompileBoundToFirstClosureParam(Type delegateType, Expression bodyExpr, IParameterProvider paramExprs, + Type returnType, CompilerFlags flags) + { + // There is no Return of the pooled parameter types here, + // because in the rarest case with the unused lambda arguments we may just exhaust the pooled instance + var closureAndParamTypes = RentPooledOrNewClosureTypeToParamTypes(paramExprs); + if (bodyExpr is NoArgsNewClassIntrinsicExpression newExpr) + return CompileNoArgsNew(newExpr, delegateType, closureAndParamTypes, returnType, flags); +#else + internal static object TryCompileBoundToFirstClosureParam(Type delegateType, Expression bodyExpr, IReadOnlyList paramExprs, + Type returnType, CompilerFlags flags) + { + var closureAndParamTypes = RentPooledOrNewClosureTypeToParamTypes(paramExprs); +#endif + // Try to avoid compilation altogether for Func delegates via Interpreter, see #468 + if (closureAndParamTypes.Length == 1 & returnType == typeof(bool) + && bodyExpr.Type.IsPrimitive // todo: @feat #496 nullable is not supported yet + && Interpreter.IsCandidateForInterpretation(bodyExpr) + && Interpreter.TryInterpretBool(out var result, bodyExpr, flags)) + return result ? Interpreter.TrueFunc : Interpreter.FalseFunc; + + Delegate compiledDelegate = null; + + // The method collects the info from the all nested lambdas deep down up-front and de-duplicates the lambdas as well. + var closureInfo = new ClosureInfo(ClosureStatus.ToBeCollected); + var collectResult = TryCollectInfo(ref closureInfo, bodyExpr, paramExprs, null, ref closureInfo.NestedLambdas, flags); + if (collectResult == Result.OK) + { + var constantsAndNestedLambdas = (closureInfo.Status & ClosureStatus.HasClosure) != 0 + ? closureInfo.GetArrayOfConstantsAndNestedLambdas() + : null; + + ArrayClosure closure; + if ((flags & CompilerFlags.EnableDelegateDebugInfo) == 0) + closure = constantsAndNestedLambdas == null ? EmptyArrayClosure : new ArrayClosure(constantsAndNestedLambdas); + else + { + var debugLambdaExpr = Lambda(delegateType, bodyExpr, paramExprs?.ToReadOnlyList() ?? Tools.Empty()); + closure = new DebugArrayClosure(null, constantsAndNestedLambdas, debugLambdaExpr); + } + + // note: @slow this is what System.Compiles does and which makes the compilation 10x slower, but the invocation become faster by a single branch instruction + // var method = new DynamicMethod(string.Empty, returnType, closurePlusParamTypes, true); + // this is FEC way, significantly faster compilation, but +1 branch instruction in the invocation + var dynMethod = new DynamicMethod(string.Empty, returnType, closureAndParamTypes, typeof(ArrayClosure), true); + + var il = DynamicMethodHacks.RentPooledOrNewILGenerator(dynMethod, returnType, closureAndParamTypes); + + if (closure.ConstantsAndNestedLambdas != null) + EmittingVisitor.EmitLoadConstantsAndNestedLambdasIntoVars(il, ref closureInfo); + + var parent = returnType == typeof(void) ? ParentFlags.IgnoreResult : ParentFlags.LambdaCall; + if (returnType.IsByRef) + parent |= ParentFlags.ReturnByRef; + + if (EmittingVisitor.TryEmit(bodyExpr, paramExprs, il, ref closureInfo, flags, parent)) + { + il.Demit(OpCodes.Ret); + compiledDelegate = dynMethod.CreateDelegate(delegateType, closure); + if (closure is DebugArrayClosure diagClosure) + diagClosure.ILInstructions = compiledDelegate.Method.ReadAllInstructions(); + } + + DynamicMethodHacks.FreePooledILGenerator(dynMethod, il); + } + FreePooledClosureTypeAndParamTypes(closureAndParamTypes); + + return compiledDelegate + ?? ((flags & CompilerFlags.ThrowOnNotSupportedExpression) == 0 ? null : NotSupportedCase(collectResult)); + } + + private static readonly Type[] _closureAsASingleParamType = { typeof(ArrayClosure) }; + private static readonly Type[][] _paramTypesPoolWithElem0OfLength1 = new Type[8][]; // todo: @perf @mem could we use this for other Type arrays? + +#if LIGHT_EXPRESSION + internal static Type[] RentPooledOrNewClosureTypeToParamTypes(IParameterProvider paramExprs) + { + var count = paramExprs.ParameterCount; +#else + internal static Type[] RentPooledOrNewClosureTypeToParamTypes(IReadOnlyList paramExprs) + { + var count = paramExprs.Count; +#endif + if (count == 0) + return _closureAsASingleParamType; + + var pooledOrNew = count < 8 ? Interlocked.Exchange(ref _paramTypesPoolWithElem0OfLength1[count], null) ?? new Type[count + 1] : new Type[count + 1]; + pooledOrNew[0] = typeof(ArrayClosure); + for (var i = 0; i < count; i++) + { + var paramExpr = paramExprs.GetParameter(i); // todo: @perf can we avoid calling virtual GetParameter() and maybe use intrinsic with NoByRef? + pooledOrNew[i + 1] = !paramExpr.IsByRef ? paramExpr.Type : paramExpr.Type.MakeByRefType(); + } + + return pooledOrNew; + } + + /// Renting the array of the passed parameter types + [MethodImpl((MethodImplOptions)256)] + public static Type[] RentPooledOrNewParamTypes(Type p0) + { + var pooledOrNew = Interlocked.Exchange(ref _paramTypesPoolWithElem0OfLength1[0], null) ?? new Type[1]; + pooledOrNew[0] = p0; + return pooledOrNew; + } + + /// Renting the array of the passed parameter types + [MethodImpl((MethodImplOptions)256)] + public static Type[] RentPooledOrNewParamTypes(Type p0, Type p1) + { + var pooledOrNew = Interlocked.Exchange(ref _paramTypesPoolWithElem0OfLength1[1], null) ?? new Type[2]; + pooledOrNew[0] = p0; + pooledOrNew[1] = p1; + return pooledOrNew; + } + + /// Renting the array of the passed parameter types + [MethodImpl((MethodImplOptions)256)] + public static Type[] RentPooledOrNewParamTypes(Type p0, Type p1, Type p2) + { + var pooledOrNew = Interlocked.Exchange(ref _paramTypesPoolWithElem0OfLength1[2], null) ?? new Type[3]; + pooledOrNew[0] = p0; + pooledOrNew[1] = p1; + pooledOrNew[2] = p2; + return pooledOrNew; + } + + /// Renting the array of the passed parameter types + [MethodImpl((MethodImplOptions)256)] + public static Type[] RentPooledOrNewParamTypes(Type p0, Type p1, Type p2, Type p3) + { + var pooledOrNew = Interlocked.Exchange(ref _paramTypesPoolWithElem0OfLength1[3], null) ?? new Type[4]; + pooledOrNew[0] = p0; + pooledOrNew[1] = p1; + pooledOrNew[2] = p2; + pooledOrNew[3] = p3; + return pooledOrNew; + } + + /// Renting the array of the passed parameter types + [MethodImpl((MethodImplOptions)256)] + public static Type[] RentPooledOrNewParamTypes(Type p0, Type p1, Type p2, Type p3, Type p4) + { + var pooledOrNew = Interlocked.Exchange(ref _paramTypesPoolWithElem0OfLength1[4], null) ?? new Type[5]; + pooledOrNew[0] = p0; + pooledOrNew[1] = p1; + pooledOrNew[2] = p2; + pooledOrNew[3] = p3; + pooledOrNew[4] = p4; + return pooledOrNew; + } + + /// Freeing to the pool the array of types of closure + plus the passed parameter types + [MethodImpl((MethodImplOptions)256)] + public static void FreePooledClosureTypeAndParamTypes(Type[] closurePlusParamTypes) + { + var paramCountOnly = closurePlusParamTypes.Length - 1; + if (paramCountOnly != 0 & paramCountOnly < 8) + Interlocked.Exchange(ref _paramTypesPoolWithElem0OfLength1[paramCountOnly], closurePlusParamTypes); + } + + /// Freeing to the pool the array of the passed parameter types + [MethodImpl((MethodImplOptions)256)] + public static void FreePooledParamTypes(Type[] paramTypes) + { + var paramCount = paramTypes.Length; + if (paramCount != 0 & paramCount < 8) + Interlocked.Exchange(ref _paramTypesPoolWithElem0OfLength1[paramCount - 1], paramTypes); + } + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + + /// Collects the lambda info for the compilation + public sealed class NestedLambdaInfo + { + /// Compiled lambda + public object Lambda; // todo: @perf can we use the NestedLambdaInfo itself instead of NestedLambdaWithConstantsAndNestedLambdas and avoid duplication in memory? + + /// The nested lambdas and their info + public SmallList NestedLambdas; + + /// The lambda expression + public LambdaExpression LambdaExpression; + + /// Parameters not passed through lambda parameter list But used inside lambda body. + /// The top expression should Not contain not passed parameters. + public SmallList NonPassedParameters; + + /// If the N's bit is set, it means the parameter is mutated (assigned or passed by-ref), + /// where N is the index of the param in `NonPassedParameters` + public ulong NonPassedParamMutatedIndexBits; + + /// Index of the compiled lambda in the parent lambda closure array + public short LambdaVarIndex; + + /// Index of the variable which store the non-passed variables array before passing it to the closure constructor. + /// It used to assign the closed variables from the outside of the nested lambda + public short NonPassedParamsVarIndex; + + public NestedLambdaInfo(LambdaExpression lambdaExpression) => LambdaExpression = lambdaExpression; + + /// Compares 2 lambda expressions for equality + public bool HasTheSameLambdaExpression(LambdaExpression lambda) => // todo: @unclear parameters or is comparing the body is enough? + ReferenceEquals(LambdaExpression, lambda) || + ReferenceEquals(LambdaExpression.Body, lambda.Body) +#if LIGHT_EXPRESSION + && LambdaExpression.ParameterCount == lambda.ParameterCount +#endif + ; + + public override string ToString() => + $"Lambda: {(Lambda is NestedLambdaForNonPassedParams n ? "compiled+closure" : Lambda != null ? "compiled" : "null")}, Expr: {LambdaExpression.ToString()}"; + } + + [Flags] + public enum ClosureStatus : byte + { + ToBeCollected = 1, + UserProvided = 1 << 1, + HasClosure = 1 << 2, + ShouldBeStaticMethod = 1 << 3 + } + + [DebuggerDisplay("Target:{Target}, InlinedLambdaInvokeIndex:{InlinedLambdaInvokeIndex}, ReturnVariableIndexPlusOneAndIsDefined:{ReturnVariableIndexPlusOneAndIsDefined}")] + public struct LabelInfo + { + public object Target; + public short InlinedLambdaInvokeIndex; + public short ReturnVariableIndexPlusOneAndIsDefined; + public Label Label; + public Label ReturnLabel; + } + + /// Track the info required to build a closure object + some context information not directly related to closure. + public struct ClosureInfo + { + /// Tracks that the last emit was an address + public bool LastEmitIsAddress; + + // Tracks the current block nesting count in the stack of blocks in Collect and Emit phase + private ushort _blockCount; + + /// Tracks the use of the variables in the blocks stack per variable, + /// (uint) contains (ushort) BlockIndex in the upper bits and (ushort) VarIndex in the lower bits. + /// to determine if variable is the local variable and in what block it's defined + private SmallMap4, NoArrayPool>, RefEq> _varInBlock; + + /// The map of inlined invocations collected in TryCollect and then used in TryEmit + internal SmallMap4> InlinedLambdaInvocation; + + /// New or Call expressions containing the complex expression, e.g. inlined Lambda Invoke or Try with Finally + internal SmallMap4> ArgsContainingComplexExpression; + + internal bool HasComplexExpression; + + /// The stack for the lambda invocation and the labels bound to them + internal SmallList, NoArrayPool> LambdaInvokeStackLabels; + + /// Tracks of how many gotos, labels referencing the specific target, they may be the same gotos expression, + /// because the gotos may be reused multiple times in the big expression + internal SmallMap4> TargetToGotosAndLabels; + + /// This is required because we have the return from the nested lambda expression, + /// and when inlined in the parent lambda it is no longer the return but just a jump to the label. + internal short CurrentInlinedLambdaInvokeIndex; + + public ClosureStatus Status; + + /// Constant expressions to find an index (by reference) of constant expression from compiled expression. + public SmallList Constants; + + /// Constant usage count and variable index. + /// It is a separate collection from the Constants because we directly convert later into the closure array + public SmallList, NoArrayPool> ConstantUsageThenVarIndex; + + /// Parameters not passed through lambda parameter list But used inside lambda body. + /// The top expression should Not contain not passed parameters. + public SmallList NonPassedParameters; + + /// The nested lambdas and their info + public SmallList NestedLambdas; + + /// Populates the info + public ClosureInfo(ClosureStatus status) + { + Status = status; + Constants = new SmallList(); + LastEmitIsAddress = false; + CurrentInlinedLambdaInvokeIndex = -1; + } + + /// Populates info directly with provided closure object and constants. + public ClosureInfo(ClosureStatus status, object[] constValues) + { + Status = status; + + Constants = new SmallList(constValues ?? Tools.Empty()); + if (constValues != null) + ConstantUsageThenVarIndex.InitCount(constValues.Length); + + LastEmitIsAddress = false; + CurrentInlinedLambdaInvokeIndex = -1; + } + + [MethodImpl((MethodImplOptions)256)] + public bool ContainsConstantsOrNestedLambdas() => Constants.Count != 0 | NestedLambdas.Count != 0; + + public void AddConstantOrIncrementUsageCount(object value) + { + Status |= ClosureStatus.HasClosure; + var constItems = Constants.Items; + var constIndex = Constants.Count - 1; + while (constIndex != -1 && !ReferenceEquals(constItems[constIndex], value)) + --constIndex; + if (constIndex == -1) + { + Constants.Add(value); + ConstantUsageThenVarIndex.Add(1); + } + else + { + ++ConstantUsageThenVarIndex.GetSurePresentRef(constIndex); + } + } + + [RequiresUnreferencedCode(Trimming.Message)] + public void AddLabel(LabelTarget labelTarget, short inlinedLambdaInvokeIndex = -1) + { + // skip null labelTargets, e.g. it may be the case for LoopExpression.Continue + if (labelTarget == null) + return; + GetLabelOrInvokeIndexByTarget(ref LambdaInvokeStackLabels, labelTarget, out var found); + if (!found) + { + ref var label = ref LambdaInvokeStackLabels.AddDefaultAndGetRef(); + label.Target = labelTarget; + label.InlinedLambdaInvokeIndex = inlinedLambdaInvokeIndex; + } + } + + public short AddInlinedLambdaInvoke(InvocationExpression e) + { + var count = LambdaInvokeStackLabels.Count; + for (var i = 0; i < count; ++i) + if (LambdaInvokeStackLabels.GetSurePresentRef(i).Target == e) + return (short)i; + + ref var label = ref LambdaInvokeStackLabels.AddDefaultAndGetRef(); + label.Target = e; + return (short)count; + } + + public object[] GetArrayOfConstantsAndNestedLambdas() + { + var constCount = Constants.Count; + var nestedLambdasCount = NestedLambdas.Count; + if (constCount == 0) + { + if (nestedLambdasCount == 0) + return null; + + var lambdaObjects = new object[nestedLambdasCount]; + for (var i = 0; i < lambdaObjects.Length; i++) + lambdaObjects[i] = NestedLambdas.Items[i].Lambda; + return lambdaObjects; + } + + // if constants `count != 0` + var constItems = Constants.Items; + if (nestedLambdasCount == 0) + return constItems; + + var constPlusLambdaCount = constCount + nestedLambdasCount; + + if (constItems.Length < constPlusLambdaCount) + Array.Resize(ref constItems, constPlusLambdaCount); + + for (var i = 0; i < nestedLambdasCount; ++i) + constItems[constCount + i] = NestedLambdas.Items[i].Lambda; + + return constItems; + } + + /// Local variable index is not known in the collecting phase when we only need to decide if ParameterExpression is an actual parameter or variable + [RequiresUnreferencedCode(Trimming.Message)] + public void PushBlockWithVars(ParameterExpression blockVarExpr) => + PushVarInBlockMap(blockVarExpr, _blockCount++, 0); + + [RequiresUnreferencedCode(Trimming.Message)] + public void PushBlockWithVars(ParameterExpression blockVarExpr, int varIndex) => + PushVarInBlockMap(blockVarExpr, _blockCount++, (ushort)varIndex); + + [RequiresUnreferencedCode(Trimming.Message)] + public void PushBlockWithVars(IReadOnlyList blockVarExprs) + { + for (var i = 0; i < blockVarExprs.Count; i++) + PushVarInBlockMap(blockVarExprs[i], _blockCount, 0); + ++_blockCount; + } + + [RequiresUnreferencedCode(Trimming.Message)] + public void PushBlockAndConstructLocalVars(IReadOnlyList blockVarExprs, ILGenerator il) + { + for (var i = 0; i < blockVarExprs.Count; i++) + { + var varExpr = blockVarExprs[i]; + var varType = varExpr.Type; + if (varExpr.IsByRef && !varType.IsByRef) + varType = varType.MakeByRefType(); + PushVarInBlockMap(varExpr, _blockCount, (ushort)il.GetNextLocalVarIndex(varType)); + } + ++_blockCount; + } + + [MethodImpl((MethodImplOptions)256)] + private void PushVarInBlockMap(ParameterExpression pe, ushort blockIndex, ushort varIndex) + { + ref var blocks = ref _varInBlock.Map.AddOrGetValueRef(pe, out _); + if (blocks.Count == 0 || (blocks.GetLastSurePresentItem() >>> 16) != blockIndex) + blocks.Add((uint)(blockIndex << 16) | varIndex); + } + + public void PopBlock() + { + Debug.Assert(_blockCount > 0); + var varCount = _varInBlock.Map.Count; + for (var i = 0; i < varCount; ++i) + { + ref var varBlocks = ref _varInBlock.Map.GetSurePresentEntryRef(i); + if (varBlocks.Value.Count == _blockCount) + varBlocks.Value.RemoveLastSurePresentItem(); + } + --_blockCount; + } + + [MethodImpl((MethodImplOptions)256)] + public bool IsLocalVar(ParameterExpression varParamExpr) + { + ref var blocks = ref _varInBlock.Map.TryGetValueRef(varParamExpr, out var found); + return found && blocks.Count != 0; + } + + [MethodImpl((MethodImplOptions)256)] + public int GetDefinedLocalVarOrDefault(ParameterExpression varParamExpr) + { + ref var blocks = ref _varInBlock.Map.TryGetValueRef(varParamExpr, out var found); + return found && blocks.Count != 0 // rare case with the block count 0 may occur when we collected the block and vars, but not yet defined the variable for it + ? (int)(blocks.GetLastSurePresentItem() & ushort.MaxValue) + : -1; + } + } + + internal static ref LabelInfo GetLabelOrInvokeIndexByTarget(ref this TLabels labels, object labelTarget, out bool found) + where TLabels : struct, IIndexed + { + var count = labels.Count; + for (var i = 0; i < count; ++i) + { + ref var label = ref labels.GetSurePresentRef(i); + if (label.Target == labelTarget) + { + found = true; + return ref label; + } + } + found = false; + return ref RefTools.GetNullRef(); + } + + [MethodImpl((MethodImplOptions)256)] + private static Label GetOrDefineLabel(ref this LabelInfo label, ILGenerator il) + { + if ((label.ReturnVariableIndexPlusOneAndIsDefined & 1) == 0) + { + label.Label = il.DefineLabel(); + label.ReturnVariableIndexPlusOneAndIsDefined |= 1; + } + return label.Label; + } + + public static readonly ArrayClosure EmptyArrayClosure = new ArrayClosure(null); + + public static FieldInfo ArrayClosureArrayField = + typeof(ArrayClosure).GetField(nameof(ArrayClosure.ConstantsAndNestedLambdas)); + + public static FieldInfo ArrayClosureWithNonPassedParamsField = + typeof(ArrayClosureWithNonPassedParams).GetField(nameof(ArrayClosureWithNonPassedParams.NonPassedParams)); + + private static ConstructorInfo[] _nonPassedParamsArrayClosureCtors = typeof(ArrayClosureWithNonPassedParams).GetConstructors(); + + public static ConstructorInfo ArrayClosureWithNonPassedParamsAndConstantsCtor = _nonPassedParamsArrayClosureCtors[0]; + + public static ConstructorInfo ArrayClosureWithNonPassedParamsCtor = _nonPassedParamsArrayClosureCtors[1]; + + private static ConstructorInfo DebugArrayClosureCtor = typeof(DebugArrayClosure).GetConstructors()[0]; + + public static Result NotSupported_RuntimeVariables { get; private set; } + + public class ArrayClosure + { + // todo: @feature split into two to reduce copying + public readonly object[] ConstantsAndNestedLambdas; + public ArrayClosure(object[] constantsAndNestedLambdas) => ConstantsAndNestedLambdas = constantsAndNestedLambdas; + } + + internal static IEnumerable EnumerateNestedLambdas(object[] constantsAndNestedLambdas) + { + if (constantsAndNestedLambdas != null && constantsAndNestedLambdas.Length != 0) + // todo: @perf how to skip until the nested lambdas fast + foreach (var item in constantsAndNestedLambdas) + { + if (item is IDelegateDebugInfo diagInfo) + yield return diagInfo; + + var dlg = (item is NestedLambdaForNonPassedParams nestedLambda ? nestedLambda.NestedLambda : item) as Delegate; + if (dlg != null && dlg.Target is IDelegateDebugInfo dlgDebugInfo) + yield return dlgDebugInfo; + } + } + + /// Enumerate any nested lambdas in the delegate compiled with CompilerFlags.EnableDelegateDebugInfo flag + public static IEnumerable EnumerateNestedLambdas( + this Delegate fastCompiledDelegateWithDebugInfoFlag) => + fastCompiledDelegateWithDebugInfoFlag.Target is ArrayClosure closure ? EnumerateNestedLambdas(closure.ConstantsAndNestedLambdas) : []; + + [RequiresUnreferencedCode(Trimming.Message)] + public sealed class DebugArrayClosure : ArrayClosureWithNonPassedParams, IDelegateDebugInfo + { + public LambdaExpression Expression { get; internal set; } + + public ILInstruction[] ILInstructions { get; internal set; } + + public DebugArrayClosure(object[] nonPassedParams, object[] constantsAndNestedLambdas, + LambdaExpression expr, ILInstruction[] il = null) + : base(nonPassedParams, constantsAndNestedLambdas) + { + Expression = expr; + ILInstructions = il; + } + + [RequiresUnreferencedCode(Trimming.Message)] + public IEnumerable EnumerateNestedLambdas() => + ExpressionCompiler.EnumerateNestedLambdas(ConstantsAndNestedLambdas); + } + + // todo: @perf better to move the case with no constants to another class OR we can reuse ArrayClosure but now ConstantsAndNestedLambdas will hold NonPassedParams + public class ArrayClosureWithNonPassedParams : ArrayClosure + { + public readonly object[] NonPassedParams; + public ArrayClosureWithNonPassedParams(object[] nonPassedParams, object[] constantsAndNestedLambdas) : base(constantsAndNestedLambdas) => + NonPassedParams = nonPassedParams; + public ArrayClosureWithNonPassedParams(object[] nonPassedParams) : base(null) => + NonPassedParams = nonPassedParams; + } + + // todo: @perf this class is required until we (maybe) move to single constants list per lambda hierarchy + // Those two classes are required only if there are non-passed parameters, + // this class stores the context for creating the ArrayClosureWithNonPassedParams at the point of emitting the nested lambda. + // See the #437 and #353 for the context + public class NestedLambdaForNonPassedParams + { + public static FieldInfo NestedLambdaField = + typeof(NestedLambdaForNonPassedParams).GetField(nameof(NestedLambda)); + public static FieldInfo NonPassedParamsField = + typeof(NestedLambdaForNonPassedParams).GetField(nameof(NonPassedParams)); + +#pragma warning disable CS0649 + public readonly object NestedLambda; + public object[] NonPassedParams; +#pragma warning restore CS0649 + public NestedLambdaForNonPassedParams(object nestedLambda) => NestedLambda = nestedLambda; + } + + public class NestedLambdaForNonPassedParamsWithConstants : NestedLambdaForNonPassedParams + { + public static FieldInfo ConstantsAndNestedLambdasField = + typeof(NestedLambdaForNonPassedParamsWithConstants).GetField(nameof(ConstantsAndNestedLambdas)); + + public readonly object[] ConstantsAndNestedLambdas; + public NestedLambdaForNonPassedParamsWithConstants(object nestedLambda, object[] constantsAndNestedLambdas) + : base(nestedLambda) => ConstantsAndNestedLambdas = constantsAndNestedLambdas; + } + + public sealed class NestedLambdaForNonPassedParamsWithConstantsWithDebugInfo : NestedLambdaForNonPassedParamsWithConstants, IDelegateDebugInfo + { + public LambdaExpression Expression { get; } + public ILInstruction[] ILInstructions { get; internal set; } + public NestedLambdaForNonPassedParamsWithConstantsWithDebugInfo(object nestedLambda, object[] constantsAndNestedLambdas, LambdaExpression expr) + : base(nestedLambda, constantsAndNestedLambdas) => Expression = expr; + + [RequiresUnreferencedCode(Trimming.Message)] + public IEnumerable EnumerateNestedLambdas() => + ExpressionCompiler.EnumerateNestedLambdas(ConstantsAndNestedLambdas); + } + + internal static class CurryClosureFuncs + { + public static readonly MethodInfo[] Methods = typeof(CurryClosureFuncs).GetMethods(); + + // todo: @mem @perf can we avoid closure creation over `f` and `c`? + public static Func Curry(Func f, C c) => + () => f(c); + + public static Func Curry(Func f, C c) => + t1 => f(c, t1); + + public static Func Curry(Func f, C c) => + (t1, t2) => f(c, t1, t2); + + public static Func Curry(Func f, C c) => + (t1, t2, t3) => f(c, t1, t2, t3); + + public static Func Curry(Func f, C c) => + (t1, t2, t3, t4) => f(c, t1, t2, t3, t4); + + public static Func Curry(Func f, + C c) => (t1, t2, t3, t4, t5) => f(c, t1, t2, t3, t4, t5); + + public static Func + Curry(Func f, C c) => + (t1, t2, t3, t4, t5, t6) => f(c, t1, t2, t3, t4, t5, t6); + + public static Func + Curry(Func f, C c) => + (t1, t2, t3, t4, t5, t6, t7) => f(c, t1, t2, t3, t4, t5, t6, t7); + + public static Func + Curry(Func f, C c) => + (t1, t2, t3, t4, t5, t6, t7, t8) => f(c, t1, t2, t3, t4, t5, t6, t7, t8); + + public static Func + Curry(Func f, C c) => + (t1, t2, t3, t4, t5, t6, t7, t8, t9) => f(c, t1, t2, t3, t4, t5, t6, t7, t8, t9); + + public static Func + Curry(Func f, C c) => + (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10) => f(c, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10); + } + + internal static class CurryClosureActions + { + public static readonly MethodInfo[] Methods = typeof(CurryClosureActions).GetMethods(); + + public static Action Curry(Action a, C c) => + () => a(c); + + public static Action Curry(Action f, C c) => + t1 => f(c, t1); + + public static Action Curry(Action f, C c) => + (t1, t2) => f(c, t1, t2); + + public static Action Curry(Action f, C c) => + (t1, t2, t3) => f(c, t1, t2, t3); + + public static Action Curry(Action f, C c) => + (t1, t2, t3, t4) => f(c, t1, t2, t3, t4); + + public static Action Curry(Action f, + C c) => (t1, t2, t3, t4, t5) => f(c, t1, t2, t3, t4, t5); + + public static Action + Curry(Action f, C c) => + (t1, t2, t3, t4, t5, t6) => f(c, t1, t2, t3, t4, t5, t6); + + public static Action + Curry(Action f, C c) => + (t1, t2, t3, t4, t5, t6, t7) => f(c, t1, t2, t3, t4, t5, t6, t7); + + public static Action + Curry(Action f, C c) => + (t1, t2, t3, t4, t5, t6, t7, t8) => f(c, t1, t2, t3, t4, t5, t6, t7, t8); + + public static Action + Curry(Action f, C c) => + (t1, t2, t3, t4, t5, t6, t7, t8, t9) => f(c, t1, t2, t3, t4, t5, t6, t7, t8, t9); + + public static Action + Curry(Action f, C c) => + (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10) => f(c, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10); + } + + #region Collect Bound Constants + + /// Helps to identify constants as the one to be put into the Closure + public static bool IsClosureBoundConstant(object value, Type type) => + value is Delegate || type.IsArray || + !type.IsPrimitive && !type.IsEnum && value is string == false && value is Type == false && value is decimal == false; + + public enum Result + { + OK = 0, + ExpressionIsNull = 1, + ParameterIsNotVariableNorInPassedParameters = 2, + NestedLambdaCompileError = 102, + NotSupported_UnknownExpression = 1000, + /// Multi-dimensional array initializer is not supported + NotSupported_NewArrayInit_MultidimensionalArray = 1001, + /// Quote is not supported + NotSupported_Quote = 1002, + /// Dynamic is not supported + NotSupported_Dynamic = 1003, + /// RuntimeVariables is not supported + NotSupported_RuntimeVariables = 1004, + /// MemberInit MemberBinding is not supported + NotSupported_MemberInit_MemberBinding = 1005, + /// MemberInit ListBinding is not supported + NotSupported_MemberInit_ListBinding = 1006, + /// Goto of the Return kind from the TryCatch is not supported + NotSupported_Try_GotoReturnToTheFollowupLabel = 1007, + /// Not supported assignment target + NotSupported_Assign_Target = 1008, + /// TypeEqual is not supported + NotSupported_TypeEqual = 1009, + /// `when` in catch is not supported yet + NotSupported_ExceptionCatchFilter = 1010 + } + + /// Return value is ignored + [MethodImpl(MethodImplOptions.NoInlining)] + internal static T NotSupportedCase(Result reason) + { + if (reason == Result.OK) + { + Debug.WriteLine($"Not support case found in TryEmit phase because the TryCollect phase is {reason}"); + Debugger.Break(); + } + Debug.WriteLine($"Not supported case is found with the reason: {reason}"); + throw new NotSupportedExpressionException(reason); + } + + /// Wraps the call to `TryCollectInfo` for the compatibility and provide the root place to check the returned error code. + /// Important: The method collects the info from the nested lambdas up-front and de-duplicates the lambdas as well. + [MethodImpl((MethodImplOptions)256)] + public static bool TryCollectBoundConstants(ref ClosureInfo closure, Expression expr, +#if LIGHT_EXPRESSION + IParameterProvider paramExprs, // `paramExprs` are required for nested lambda compilation +#else + IReadOnlyList paramExprs, +#endif + NestedLambdaInfo nestedLambda, ref SmallList rootNestedLambdas, CompilerFlags flags) + { + var r = TryCollectInfo(ref closure, expr, paramExprs, nestedLambda, ref rootNestedLambdas, flags); + return r == Result.OK || (flags & CompilerFlags.ThrowOnNotSupportedExpression) != 0 && NotSupportedCase(r); + } + + /// Collects the information about closure constants, nested lambdas, non-passed parameters, goto labels and variables in blocks. + /// Returns `OK` result if everything is fine and other result for error. + public static Result TryCollectInfo(ref ClosureInfo closure, Expression expr, +#if LIGHT_EXPRESSION + IParameterProvider paramExprs, +#else + IReadOnlyList paramExprs, +#endif + NestedLambdaInfo nestedLambda, ref SmallList rootNestedLambdas, CompilerFlags flags) + { + var r = Result.OK; + while (true) + { + if (expr == null) + return Result.ExpressionIsNull; +#if LIGHT_EXPRESSION + if (expr.IsIntrinsic) + return expr.TryCollectInfo(flags, ref closure, paramExprs, nestedLambda, ref rootNestedLambdas); +#endif + switch (expr.NodeType) + { + case ExpressionType.Constant: +#if LIGHT_EXPRESSION + if (expr == NullConstant | expr == FalseConstant | expr == TrueConstant || expr is IntConstantExpression) + return r; +#endif + var constantExpr = (ConstantExpression)expr; + var value = constantExpr.Value; + if (value != null && IsClosureBoundConstant(value, value.GetType())) + closure.AddConstantOrIncrementUsageCount(value); + return Result.OK; + + case ExpressionType.Parameter: + { +#if LIGHT_EXPRESSION + var paramCount = paramExprs.ParameterCount; +#else + var paramCount = paramExprs.Count; +#endif + // if parameter is used BUT is not in passed parameters and not in local variables, + // it means parameter is provided by outer lambda and should be put in closure for current lambda + var p = paramCount - 1; + var parExpr = (PE)expr; + while (p != -1 && !ReferenceEquals(paramExprs.GetParameter(p), parExpr)) --p; + if (p == -1 && !closure.IsLocalVar(parExpr)) + { + if (nestedLambda == null) // means that we are in the root lambda + return Result.ParameterIsNotVariableNorInPassedParameters; + closure.Status |= ClosureStatus.HasClosure; + _ = nestedLambda.NonPassedParameters.GetIndexOrAdd(parExpr, default(RefEq)); + } + return Result.OK; + } + case ExpressionType.Call: + { + var callExpr = (MethodCallExpression)expr; + var callObjectExpr = callExpr.Object; + +#if SUPPORTS_ARGUMENT_PROVIDER + var callArgs = (IArgumentProvider)callExpr; +#else + var callArgs = callExpr.Arguments; +#endif + var argCount = callArgs.GetCount(); + if (argCount == 0) + { + if (callObjectExpr != null) + { + expr = callObjectExpr; + continue; + } + return Result.OK; + } + + if (callObjectExpr != null && + (r = TryCollectInfo(ref closure, callObjectExpr, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) + return r; + + var hasComplexExpression = false; + for (var i = 0; i < argCount; i++) + { + closure.HasComplexExpression = false; // reset the flag because we want to know the real result after the arg collection + if ((r = TryCollectInfo(ref closure, callArgs.GetArgument(i), paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) + return r; + // if any argument is complex, then thw whole call should be complex, + // because we cannot just store and restore a single argument, it should be done for all arguments + hasComplexExpression |= closure.HasComplexExpression; + } + + // propagate the value up the stack + if (hasComplexExpression) + { + closure.HasComplexExpression = true; + closure.ArgsContainingComplexExpression.Map.AddOrGetValueRef(callExpr, out _); + } + return r; + } + + case ExpressionType.MemberAccess: + var memberExpr = ((MemberExpression)expr).Expression; + if (memberExpr == null) + return r; + expr = memberExpr; + continue; + + case ExpressionType.New: + { + var newExpr = (NewExpression)expr; +#if SUPPORTS_ARGUMENT_PROVIDER + var ctorArgs = (IArgumentProvider)newExpr; +#else + var ctorArgs = newExpr.Arguments; +#endif + var argCount = ctorArgs.GetCount(); + if (argCount == 0) + return r; + + var hasComplexExpression = false; + for (var i = 0; i < argCount; i++) + { + closure.HasComplexExpression = false; + if ((r = TryCollectInfo(ref closure, ctorArgs.GetArgument(i), paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) + return r; + hasComplexExpression |= closure.HasComplexExpression; + } + + // pop the value up the stack + if (hasComplexExpression) + { + closure.HasComplexExpression = true; + closure.ArgsContainingComplexExpression.Map.AddOrGetEntryRef(newExpr, out _); + } + + return r; + } + case ExpressionType.NewArrayBounds: + case ExpressionType.NewArrayInit: + // todo: @feature multi-dimensional array initializers are not supported yet, they also are not supported by the hoisted expression + if (expr.NodeType == ExpressionType.NewArrayInit && expr.Type.GetArrayRank() > 1) + return Result.NotSupported_NewArrayInit_MultidimensionalArray; +#if LIGHT_EXPRESSION + var arrElems = (IArgumentProvider)expr; + var elemCount = arrElems.ArgumentCount; +#else + var arrElems = ((NewArrayExpression)expr).Expressions; + var elemCount = arrElems.Count; +#endif + if (elemCount == 0) + return r; + for (var i = 0; i < elemCount - 1; i++) + if ((r = TryCollectInfo(ref closure, arrElems.GetArgument(i), paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) + return r; + expr = arrElems.GetArgument(elemCount - 1); + continue; + + case ExpressionType.MemberInit: + return TryCollectMemberInitExprConstants( + ref closure, (MemberInitExpression)expr, paramExprs, nestedLambda, ref rootNestedLambdas, flags); + + case ExpressionType.ListInit: + return TryCollectListInitExprConstants( + ref closure, (ListInitExpression)expr, paramExprs, nestedLambda, ref rootNestedLambdas, flags); + + case ExpressionType.Lambda: + // Here we look if the lambda is already stored in the nested lambdas tree (Collected+Compiled), + // or if not found it Collects+Compiles the nested lambda here and adds to the nested lambda tree. + var nestedLambdaExpr = (LambdaExpression)expr; + +#if LIGHT_EXPRESSION // todo: @simplify can we do better? + var nestedParamExprs = (IParameterProvider)nestedLambdaExpr; +#else + var nestedParamExprs = nestedLambdaExpr.Parameters; +#endif + closure.Status |= ClosureStatus.HasClosure; + + // Look for the already collected lambdas starting from the root + if (rootNestedLambdas.Count != 0 && + FindAlreadyCompiledNestedLambdaInfoInLambdas(ref rootNestedLambdas, nestedLambdaExpr, out var compiledNestedLambda)) + { + if (nestedLambda != null) + nestedLambda.NestedLambdas.Add(compiledNestedLambda); + else + rootNestedLambdas.Add(compiledNestedLambda); + + if (compiledNestedLambda.NonPassedParameters.Count != 0 && + !PropagateNonPassedParamsToOuterLambda(ref closure, + nestedLambda, paramExprs, nestedParamExprs, ref compiledNestedLambda.NonPassedParameters)) + return Result.ParameterIsNotVariableNorInPassedParameters; + + return r; + } + + var nestedClosure = new ClosureInfo(ClosureStatus.ToBeCollected); + var newNestedLambda = new NestedLambdaInfo(nestedLambdaExpr); + + if (nestedLambda != null) + nestedLambda.NestedLambdas.Add(newNestedLambda); + else + rootNestedLambdas.Add(newNestedLambda); + + if ((r = TryCollectInfo(ref nestedClosure, nestedLambdaExpr.Body, nestedParamExprs, newNestedLambda, ref rootNestedLambdas, flags)) != Result.OK) + return r; + + if (newNestedLambda.NonPassedParameters.Count != 0 && + !PropagateNonPassedParamsToOuterLambda(ref closure, + nestedLambda, paramExprs, nestedParamExprs, ref newNestedLambda.NonPassedParameters)) + return Result.ParameterIsNotVariableNorInPassedParameters; + + if (!TryCompileNestedLambda(ref nestedClosure, newNestedLambda, flags)) + return Result.NestedLambdaCompileError; + + return r; + + case ExpressionType.Invoke: + { + var invokeExpr = (InvocationExpression)expr; +#if SUPPORTS_ARGUMENT_PROVIDER + var invokeArgs = (IArgumentProvider)invokeExpr; +#else + var invokeArgs = invokeExpr.Arguments; +#endif + var invokeArgCount = invokeArgs.GetCount(); + var invokedExpr = invokeExpr.Expression; + if ((flags & CompilerFlags.NoInvocationLambdaInlining) == 0 && invokedExpr is LambdaExpression lambdaExpr) + { + var oldIndex = closure.CurrentInlinedLambdaInvokeIndex; + closure.CurrentInlinedLambdaInvokeIndex = closure.AddInlinedLambdaInvoke(invokeExpr); + closure.HasComplexExpression = false; // switch off because we have entered the inlined lambda + + ref var inlinedExpr = ref closure.InlinedLambdaInvocation.Map.AddOrGetValueRef(invokeExpr, out var found); + if (!found) + inlinedExpr = CreateInlinedLambdaInvocationExpression(invokeArgs, invokeArgCount, lambdaExpr); + + if ((r = TryCollectInfo(ref closure, inlinedExpr, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) + return r; + + closure.HasComplexExpression = true; + closure.CurrentInlinedLambdaInvokeIndex = oldIndex; + return r; + } + + // No inlining, collect the normal way + if (invokeArgCount == 0) + { + expr = invokedExpr; + continue; + } + + if ((r = TryCollectInfo(ref closure, invokedExpr, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) + return r; + + var lastArgIndex = invokeArgCount - 1; + for (var i = 0; i < lastArgIndex; i++) + if ((r = TryCollectInfo(ref closure, invokeArgs.GetArgument(i), paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) + return r; + expr = invokeArgs.GetArgument(lastArgIndex); + continue; + } + case ExpressionType.Conditional: + var condExpr = (ConditionalExpression)expr; + if ((r = TryCollectInfo(ref closure, condExpr.Test, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK || + (r = TryCollectInfo(ref closure, condExpr.IfFalse, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) + return r; + expr = condExpr.IfTrue; + continue; + + case ExpressionType.Block: + var blockExpr = (BlockExpression)expr; + var blockExprs = blockExpr.Expressions; + var blockExprCount = blockExprs.Count; + if (blockExprCount == 0) + return r; // yeah, this is the real case - the block may not contain any expressions + + var varExprs = blockExpr.Variables; + var varExprCount = varExprs?.Count ?? 0; // todo: @perf optimize for an empty and a single variable + + if (varExprCount == 1 & blockExprCount == 2 && + blockExprs[0] is BinaryExpression st0 && st0.NodeType == ExpressionType.Assign && + blockExprs[1] is BinaryExpression st1 && st1.NodeType == ExpressionType.Assign && + st0.Left == blockExprs[0] && st1.Right == blockExprs[0]) + { + if ((r = TryCollectInfo(ref closure, st0.Right, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) + return r; + expr = st1.Left; + continue; + } + + if (varExprCount == 1) + closure.PushBlockWithVars(varExprs[0]); + else if (varExprCount != 0) + closure.PushBlockWithVars(varExprs); + + for (var i = 0; i < blockExprCount - 1; i++) + if ((r = TryCollectInfo(ref closure, blockExprs[i], paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) + return r; + + expr = blockExprs[blockExprCount - 1]; + if (varExprCount == 0) + continue; // in case of no variables we can collect the last expr without recursion + + if ((r = TryCollectInfo(ref closure, expr, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) + return r; + closure.PopBlock(); + return r; + + case ExpressionType.Loop: + var loopExpr = (LoopExpression)expr; + closure.AddLabel(loopExpr.BreakLabel); + closure.AddLabel(loopExpr.ContinueLabel); + expr = loopExpr.Body; + continue; + + case ExpressionType.Index: + var indexExpr = (IndexExpression)expr; +#if SUPPORTS_ARGUMENT_PROVIDER + var indexArgs = (IArgumentProvider)indexExpr; +#else + var indexArgs = indexExpr.Arguments; +#endif + var indexArgCount = indexArgs.GetCount(); + for (var i = 0; i < indexArgCount; i++) + if ((r = TryCollectInfo(ref closure, indexArgs.GetArgument(i), paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) + return r; + if (indexExpr.Object == null) + return r; + expr = indexExpr.Object; + continue; + + case ExpressionType.Try: + { + closure.HasComplexExpression = false; + r = TryCollectTryExprInfo(ref closure, (TryExpression)expr, paramExprs, nestedLambda, ref rootNestedLambdas, flags); + closure.HasComplexExpression = true; + return r; + } + case ExpressionType.Label: + var labelExpr = (LabelExpression)expr; + closure.AddLabel(labelExpr.Target, closure.CurrentInlinedLambdaInvokeIndex); + if (labelExpr.Target != null) + closure.TargetToGotosAndLabels.Map.AddOrGetValueRef(labelExpr.Target, out _).Item2++; + if (labelExpr.DefaultValue == null) + return r; + expr = labelExpr.DefaultValue; + continue; + + case ExpressionType.Goto: + var gotoExpr = (GotoExpression)expr; + if (gotoExpr.Target != null) + closure.TargetToGotosAndLabels.Map.AddOrGetValueRef(gotoExpr.Target, out _).Item1++; + if (gotoExpr.Value == null) + return r; + expr = gotoExpr.Value; + continue; + + case ExpressionType.Switch: + var switchExpr = ((SwitchExpression)expr); + if ((r = TryCollectInfo(ref closure, switchExpr.SwitchValue, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK || + switchExpr.DefaultBody != null && // todo: @check is the order of collection affects the result? + (r = TryCollectInfo(ref closure, switchExpr.DefaultBody, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) + return r; + + var switchCases = switchExpr.Cases; + if (switchCases.Count != 0) + { + for (var i = 0; i < switchCases.Count - 1; i++) + if ((r = TryCollectInfo(ref closure, switchCases[i].Body, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) + return r; + expr = switchCases[switchCases.Count - 1].Body; + continue; + } + return r; + + case ExpressionType.Extension: + expr = expr.Reduce(); + continue; + + case ExpressionType.Default: + return r; + + case ExpressionType.TypeIs: + case ExpressionType.TypeEqual: + expr = ((TypeBinaryExpression)expr).Expression; + continue; + + case ExpressionType.Quote: // todo: @feature - is not supported yet + return Result.NotSupported_Quote; + case ExpressionType.Dynamic: // todo: @feature - is not supported yet + return Result.NotSupported_Dynamic; + case ExpressionType.RuntimeVariables: // todo: @feature - is not supported yet + return NotSupported_RuntimeVariables; + + case ExpressionType.DebugInfo: // todo: @feature - is not supported yet + return r; // todo: @unclear - just ignoring the info for now + + default: + if (expr is UnaryExpression unaryExpr) + { + if (unaryExpr.Operand is null) + return r; + expr = unaryExpr.Operand; + continue; + } + + if (expr is BinaryExpression binaryExpr) + { + if ((r = TryCollectInfo(ref closure, binaryExpr.Left, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) + return r; + expr = binaryExpr.Right; + continue; + } + + return Result.NotSupported_UnknownExpression; + } + } + } + + private static Expression CreateInlinedLambdaInvocationExpression( +#if SUPPORTS_ARGUMENT_PROVIDER + IArgumentProvider invokeArgs, +#else + IReadOnlyList invokeArgs, +#endif + int invokeArgCount, LambdaExpression lambdaExpr) + { + // Check the actual lambda return type in case it differs from the Body type, + // e.g. often case for the Action lambdas where the Body type is ignored in favor of `void`. + var lambdaReturnType = lambdaExpr.ReturnType; + var lambdaBodyExpr = lambdaExpr.Body; + if (invokeArgCount == 0) + return lambdaReturnType == lambdaBodyExpr.Type ? lambdaBodyExpr : + lambdaReturnType == typeof(void) ? Block(typeof(void), lambdaBodyExpr) : + Convert(lambdaBodyExpr, lambdaReturnType); + + // To inline the lambda we will wrap its body into a block, parameters into the block variables, + // and the invocation arguments into the variable assignments, see #278. +#if LIGHT_EXPRESSION + var lambdaPars = (IParameterProvider)lambdaExpr; +#else + var lambdaPars = lambdaExpr.Parameters; +#endif + SmallList, NoArrayPool> inlinedBlockExprs = default; + SmallList, NoArrayPool> savedVars = default; + SmallList, NoArrayPool> savedVarsBlockExprs = default; + + for (var i = 0; i < invokeArgCount; i++) + { + var lambdaPar = lambdaPars.GetParameter(i); + var invokeArg = invokeArgs.GetArgument(i); + + // The case of reusing the parameters or variables in the different lambdas, + // see the test `NestedLambdaTests.Hmm_I_can_use_the_same_parameter_for_outer_and_nested_lambda` + // and the `Issue401_What_happens_if_inlined_invocation_of_lambda_overrides_the_same_parameter`. + if (lambdaPar == invokeArg) + { + var savedPar = Parameter(lambdaPar.Type, lambdaPar.Name + "_" + lambdaPar.GetHashCode().ToString()); + savedVars.Add(savedPar); + savedVarsBlockExprs.Add(Assign(savedPar, invokeArg)); + inlinedBlockExprs.Add(Assign(lambdaPar, savedPar)); + continue; + } + + inlinedBlockExprs.Add(Assign(lambdaPar, invokeArg)); + } + + inlinedBlockExprs.Add(lambdaBodyExpr); + +#if LIGHT_EXPRESSION + var inlinedBlock = lambdaReturnType == lambdaBodyExpr.Type + ? Block(lambdaPars.ToReadOnlyList(), in inlinedBlockExprs) + : Block(lambdaReturnType, lambdaPars.ToReadOnlyList(), in inlinedBlockExprs); + if (savedVars.Count != 0) + { + savedVarsBlockExprs.Add(inlinedBlock); + inlinedBlock = Block(savedVars.ToArray(), in savedVarsBlockExprs); + } +#else + var inlinedBlock = lambdaReturnType == lambdaBodyExpr.Type + ? Block(lambdaPars, inlinedBlockExprs.ToArray()) + : Block(lambdaReturnType, lambdaPars, inlinedBlockExprs.ToArray()); + if (savedVars.Count != 0) + { + savedVarsBlockExprs.Add(inlinedBlock); + inlinedBlock = Block(savedVars.ToArray(), savedVarsBlockExprs.ToArray()); + } +#endif + return inlinedBlock; + } + +#if LIGHT_EXPRESSION + private static bool PropagateNonPassedParamsToOuterLambda(ref ClosureInfo closure, NestedLambdaInfo lambda, + IParameterProvider paramExprs, IParameterProvider nestedLambdaParamExprs, ref SmallList nestedNonPassedParams) + { + var paramExprCount = paramExprs.ParameterCount; + var nestedLambdaParamExprCount = nestedLambdaParamExprs.ParameterCount; +#else + private static bool PropagateNonPassedParamsToOuterLambda(ref ClosureInfo closure, NestedLambdaInfo lambda, + IReadOnlyList paramExprs, IReadOnlyList nestedLambdaParamExprs, ref SmallList nestedNonPassedParams) + { + var paramExprCount = paramExprs.Count; + var nestedLambdaParamExprCount = nestedLambdaParamExprs.Count; +#endif + // If nested non passed parameter is not matched with any outer passed parameter, + // then we ensure it goes to the outer non passed parameter. + // But having the non-passed parameter in the root expression (nestedLambda == null) is invalid, and results in false. + for (var i = 0; i < nestedNonPassedParams.Count; i++) + { + var nestedNonPassedParam = nestedNonPassedParams.GetSurePresentRef(i); + + var isInNestedLambda = false; + if (nestedLambdaParamExprCount != 0) + for (var p = 0; !isInNestedLambda && p < nestedLambdaParamExprCount; ++p) + isInNestedLambda = ReferenceEquals(nestedLambdaParamExprs.GetParameter(p), nestedNonPassedParam); + + var isInLambda = false; + if (paramExprCount != 0) + for (var p = 0; !isInLambda && p < paramExprCount; ++p) + isInLambda = ReferenceEquals(paramExprs.GetParameter(p), nestedNonPassedParam); + + if (!isInNestedLambda & !isInLambda) + { + if (closure.IsLocalVar(nestedNonPassedParam)) + continue; + if (lambda == null) // means that we at the root level lambda, and non-passed parameter cannot be provided + return false; + _ = lambda.NonPassedParameters.GetIndexOrAdd(nestedNonPassedParam, default(RefEq)); + } + } + + return true; + } + + private static bool FindAlreadyCompiledNestedLambdaInfoInLambdas( + ref SmallList nestedLambdas, LambdaExpression lambdaExpr, out NestedLambdaInfo found) + { + var nestedLambdasCount = nestedLambdas.Count; + for (var i = 0; i < nestedLambdasCount; ++i) + { + var nestedLambda = nestedLambdas.Items[i]; + if (nestedLambda.HasTheSameLambdaExpression(lambdaExpr)) + { + found = nestedLambda; + return true; + } + + if (nestedLambda.NestedLambdas.Count != 0) + return FindAlreadyCompiledNestedLambdaInfoInLambdas(ref nestedLambda.NestedLambdas, lambdaExpr, out found); + } + + found = null; + return false; + } + + private static bool TryCompileNestedLambda(ref ClosureInfo nestedClosureInfo, NestedLambdaInfo nestedLambdaInfo, CompilerFlags flags) + { + // 1. Try to compile nested lambda in place + // 2. Check that parameters used in compiled lambda are passed or closed by outer lambda + // 3. Add the compiled lambda to closure of outer lambda for later invocation + var nestedLambdaExpr = nestedLambdaInfo.LambdaExpression; + var nestedReturnType = nestedLambdaExpr.ReturnType; + var nestedLambdaBody = nestedLambdaExpr.Body; +#if LIGHT_EXPRESSION + var nestedLambdaParamExprs = (IParameterProvider)nestedLambdaExpr; + + if (nestedLambdaBody is NoArgsNewClassIntrinsicExpression newExpr) + { + var paramTypes = RentPooledOrNewClosureTypeToParamTypes(nestedLambdaParamExprs); + nestedLambdaInfo.Lambda = CompileNoArgsNew(newExpr, nestedLambdaExpr.Type, paramTypes, nestedReturnType, flags); + FreePooledClosureTypeAndParamTypes(paramTypes); + return true; + } +#else + var nestedLambdaParamExprs = nestedLambdaExpr.Parameters; +#endif + // copy the nested lambdas and non-passed parameters to closure info to read them in TryEmit + nestedClosureInfo.NestedLambdas = nestedLambdaInfo.NestedLambdas; + nestedClosureInfo.NonPassedParameters = nestedLambdaInfo.NonPassedParameters; + + var constantsAndNestedLambdas = (nestedClosureInfo.Status & ClosureStatus.HasClosure) != 0 + ? nestedClosureInfo.GetArrayOfConstantsAndNestedLambdas() + : null; + + ArrayClosure nestedLambdaClosure = null; + var hasDebugInfo = (flags & CompilerFlags.EnableDelegateDebugInfo) != 0; + var hasNonPassedParameters = nestedLambdaInfo.NonPassedParameters.Count != 0; + if (!hasNonPassedParameters) + nestedLambdaClosure = !hasDebugInfo + ? (constantsAndNestedLambdas == null ? EmptyArrayClosure : new ArrayClosure(constantsAndNestedLambdas)) + : new DebugArrayClosure(null, constantsAndNestedLambdas, nestedLambdaExpr); + + var closurePlusParamTypes = RentPooledOrNewClosureTypeToParamTypes(nestedLambdaParamExprs); + + var method = new DynamicMethod(string.Empty, nestedReturnType, closurePlusParamTypes, typeof(ArrayClosure), true); + var il = DynamicMethodHacks.RentPooledOrNewILGenerator(method, nestedReturnType, closurePlusParamTypes); + + if (constantsAndNestedLambdas != null) + EmittingVisitor.EmitLoadConstantsAndNestedLambdasIntoVars(il, ref nestedClosureInfo); + + var parent = nestedReturnType == typeof(void) ? ParentFlags.IgnoreResult : ParentFlags.LambdaCall; + if (nestedReturnType.IsByRef) + parent |= ParentFlags.ReturnByRef; + + var emitOk = EmittingVisitor.TryEmit(nestedLambdaBody, nestedLambdaParamExprs, il, ref nestedClosureInfo, flags, parent); + if (emitOk) + { + il.Demit(OpCodes.Ret); + + // If we don't have closure then create a static or an open delegate to pass closure later in `TryEmitNestedLambda`, + // constructing the new closure with NonPassedParams and the rest of items stored in NestedLambdaWithConstantsAndNestedLambdas + var nestedLambda = nestedLambdaClosure != null + ? method.CreateDelegate(nestedLambdaExpr.Type, nestedLambdaClosure) + : method.CreateDelegate(Tools.GetFuncOrActionType(closurePlusParamTypes, nestedReturnType), null); + + nestedLambdaInfo.Lambda = !hasNonPassedParameters + ? nestedLambda + : !hasDebugInfo + ? constantsAndNestedLambdas == null + ? new NestedLambdaForNonPassedParams(nestedLambda) + : new NestedLambdaForNonPassedParamsWithConstants(nestedLambda, constantsAndNestedLambdas) + : new NestedLambdaForNonPassedParamsWithConstantsWithDebugInfo(nestedLambda, constantsAndNestedLambdas, nestedLambdaExpr); + + if (hasDebugInfo) + { + var ilInstructions = nestedLambda.Method.ReadAllInstructions(); + if (nestedLambdaClosure is DebugArrayClosure debugInfoClosure) + debugInfoClosure.ILInstructions = ilInstructions; + else + ((NestedLambdaForNonPassedParamsWithConstantsWithDebugInfo)nestedLambdaInfo.Lambda).ILInstructions = ilInstructions; + } + } + DynamicMethodHacks.FreePooledILGenerator(method, il); + FreePooledClosureTypeAndParamTypes(closurePlusParamTypes); + return emitOk; + } + + /// Return IDelegateDebugInfo if the delegate is fast compiled with `CompilerFlags.EnableDelegateDebugInfo` flag + public static IDelegateDebugInfo TryGetDebugInfo(this TDelegate d) + where TDelegate : Delegate => d?.Target as IDelegateDebugInfo; + +#if LIGHT_EXPRESSION + private static Result TryCollectMemberInitExprConstants(ref ClosureInfo closure, MemberInitExpression expr, + IParameterProvider paramExprs, NestedLambdaInfo nestedLambda, ref SmallList rootNestedLambdas, CompilerFlags flags) + { + var newExpr = expr.Expression; + var binds = (IArgumentProvider)expr; + var count = binds.ArgumentCount; +#else + private static Result TryCollectMemberInitExprConstants(ref ClosureInfo closure, MemberInitExpression expr, + IReadOnlyList paramExprs, NestedLambdaInfo nestedLambda, ref SmallList rootNestedLambdas, CompilerFlags flags) + { + var newExpr = expr.NewExpression; + var binds = expr.Bindings; + var count = binds.Count; +#endif + var r = Result.OK; + if ((r = TryCollectInfo(ref closure, newExpr, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) + return r; + + for (var i = 0; i < count; ++i) + { + var b = binds.GetArgument(i); + if (b.BindingType != MemberBindingType.Assignment) + return b.BindingType == MemberBindingType.MemberBinding ? Result.NotSupported_MemberInit_MemberBinding : Result.NotSupported_MemberInit_ListBinding; // todo: @feature MemberMemberBinding and the MemberListBinding is not supported yet. + + if ((r = TryCollectInfo(ref closure, ((MemberAssignment)b).Expression, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) + return r; + } + return r; + } + + private static Result TryCollectListInitExprConstants(ref ClosureInfo closure, ListInitExpression expr, +#if LIGHT_EXPRESSION + IParameterProvider paramExprs, +#else + IReadOnlyList paramExprs, +#endif + NestedLambdaInfo nestedLambda, ref SmallList rootNestedLambdas, CompilerFlags flags) + { + var newExpr = expr.NewExpression; + var inits = expr.Initializers; + var count = inits.Count; + + var r = Result.OK; + if ((r = TryCollectInfo(ref closure, newExpr, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) + return r; + + for (var i = 0; i < count; ++i) + { + var elemInit = inits.GetArgument(i); + var args = elemInit.Arguments; + var argCount = args.Count; + for (var a = 0; a < argCount; ++a) + if ((r = TryCollectInfo(ref closure, args.GetArgument(a), paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) + return r; + } + return r; + } + + private static Result TryCollectTryExprInfo(ref ClosureInfo closure, TryExpression tryExpr, +#if LIGHT_EXPRESSION + IParameterProvider paramExprs, +#else + IReadOnlyList paramExprs, +#endif + NestedLambdaInfo nestedLambda, ref SmallList rootNestedLambdas, CompilerFlags flags) + { + var r = Result.OK; + if ((r = TryCollectInfo(ref closure, tryExpr.Body, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) + return r; + + var catchBlocks = tryExpr.Handlers; + for (var i = 0; i < catchBlocks.Count; i++) + { + var catchBlock = catchBlocks[i]; + var catchExVar = catchBlock.Variable; + if (catchExVar != null) + { + closure.PushBlockWithVars(catchExVar); + if ((r = TryCollectInfo(ref closure, catchExVar, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) + return r; + } + + if (catchBlock.Filter != null && + (r = TryCollectInfo(ref closure, catchBlock.Filter, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) + return r; + + if ((r = TryCollectInfo(ref closure, catchBlock.Body, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) + return r; + + if (catchExVar != null) + closure.PopBlock(); + } + + var faultOrFinally = tryExpr.Fault ?? tryExpr.Finally; + if (faultOrFinally != null && + (r = TryCollectInfo(ref closure, faultOrFinally, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK) + return r; + + return r; + } + + #endregion + + /// The minimal context-aware flags set by parent + [Flags] + public enum ParentFlags + { + /// Default is no flags + Empty = 0, + /// The result of expression is ignored and maybe popped out + IgnoreResult = 1 << 1, + /// Some parent is the call expression + Call = 1 << 2, + /// Any Parent Expression is a MemberExpression + MemberAccess = 1 << 3, + /// Some arithmetic operation + Arithmetic = 1 << 4, + /// Subject + Coalesce = 1 << 5, + /// Expression with instance object (method call or member access or array access) + InstanceAccess = 1 << 6, + /// Subject + DupIt = 1 << 7, + /// Subject + TryCatch = 1 << 8, + /// Combination`of InstanceAccess and Call + InstanceCall = Call | InstanceAccess, + /// Constructor + Ctor = 1 << 9, + /// Constructor call + CtorCall = Call | Ctor, + /// Indexer + IndexAccess = 1 << 10, + /// Invoking the inlined lambda (the default System.Expression behavior) + InlinedLambdaInvoke = 1 << 11, + /// Indicate if the part AT LEAST participates in the assignment on the left side, + /// it may also participate in the right side, e.g. ++x.Bar + AssignmentLeftValue = 1 << 12, + /// Indicates the ONLY right value of assignment, e.g. `p` in `foo.Bar += p` + AssignmentRightValue = 1 << 13, + /// Assigning the ref of the right value to the left, e.g. in `var a = ref b[1]` we are passing this flag for the `ref b[1]` + AssignmentByRef = 1 << 14, + /// Indicates the root lambda call + LambdaCall = 1 << 15, + /// ReturnByRef + ReturnByRef = 1 << 16, + /// The block result + BlockResult = 1 << 17, + } + + [MethodImpl((MethodImplOptions)256)] + public static bool IgnoresResult(this ParentFlags parent) => (parent & ParentFlags.IgnoreResult) != 0; + + [MethodImpl((MethodImplOptions)256)] + internal static bool EmitPopIfIgnoreResult(this ILGenerator il, ParentFlags parent) + { + if ((parent & ParentFlags.IgnoreResult) != 0) + il.Demit(OpCodes.Pop); + return true; + } + + [MethodImpl((MethodImplOptions)256)] + internal static bool TryEmitBoxOf(this ILGenerator il, Type sourceType) + { + if (sourceType.IsValueType) + il.Demit(OpCodes.Box, sourceType); + return true; + } + + [MethodImpl((MethodImplOptions)256)] + internal static bool TryEmitUnboxOf(this ILGenerator il, Type sourceType) + { + if (sourceType.IsValueType) + il.Demit(OpCodes.Unbox_Any, sourceType); + return true; + } + + /// Supports emitting of selected expressions, e.g. lambdaExpr are not supported yet. + /// When emitter find not supported expression it will return false from , so I could fallback + /// to normal and slow Expression.Compile. + [RequiresUnreferencedCode(Trimming.Message)] + public static class EmittingVisitor + { + // todo: @perf use UnsafeAccessAttribute + /// Get a type from handle + public static readonly MethodInfo GetTypeFromHandleMethod = + ((Func)Type.GetTypeFromHandle).Method; + private static readonly MethodInfo _objectEqualsMethod = + ((Func)object.Equals).Method; + + public static bool TryEmit(Expression expr, +#if LIGHT_EXPRESSION + IParameterProvider paramExprs, +#else + IReadOnlyList paramExprs, +#endif + ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent, int byRefIndex = -1) + { + var exprType = expr.Type; + while (true) + { + closure.LastEmitIsAddress = false; +#if LIGHT_EXPRESSION + if (expr.IsIntrinsic) + return expr.TryEmit(setup, ref closure, paramExprs, il, parent, byRefIndex); +#endif + var nodeType = expr.NodeType; + switch (nodeType) + { + case ExpressionType.Parameter: + return (parent & ParentFlags.IgnoreResult) != 0 || + TryEmitParameter((ParameterExpression)expr, paramExprs, il, ref closure, setup, parent, byRefIndex); + + case ExpressionType.TypeAs: + case ExpressionType.IsTrue: + case ExpressionType.IsFalse: + case ExpressionType.Increment: + case ExpressionType.Decrement: + case ExpressionType.Negate: + case ExpressionType.NegateChecked: + case ExpressionType.OnesComplement: + case ExpressionType.UnaryPlus: + case ExpressionType.Unbox: + return TryEmitSimpleUnaryExpression((UnaryExpression)expr, nodeType, paramExprs, il, ref closure, setup, parent); + + case ExpressionType.TypeIs: + case ExpressionType.TypeEqual: + return TryEmitTypeIsOrEqual((TypeBinaryExpression)expr, paramExprs, il, ref closure, setup, parent); + + case ExpressionType.Convert: + case ExpressionType.ConvertChecked: + return TryEmitConvert((UnaryExpression)expr, paramExprs, il, ref closure, setup, parent); + + case ExpressionType.ArrayIndex: + var arrIndexExpr = (BinaryExpression)expr; + return TryEmit(arrIndexExpr.Left, paramExprs, il, ref closure, setup, parent | ParentFlags.IndexAccess) + && TryEmit(arrIndexExpr.Right, paramExprs, il, ref closure, setup, parent | ParentFlags.IndexAccess) // #265 + && TryEmitArrayIndexGet(il, exprType, ref closure, parent); + + case ExpressionType.ArrayLength: + if (!TryEmit(((UnaryExpression)expr).Operand, paramExprs, il, ref closure, setup, parent)) + return false; + if ((parent & ParentFlags.IgnoreResult) == 0) + il.Demit(OpCodes.Ldlen); + return true; + + case ExpressionType.Constant: + return (parent & ParentFlags.IgnoreResult) != 0 || + TryEmitConstant((ConstantExpression)expr, exprType, il, ref closure, byRefIndex); + + case ExpressionType.Call: + return TryEmitMethodCall(expr, paramExprs, il, ref closure, setup, parent, byRefIndex); + + case ExpressionType.MemberAccess: + return TryEmitMemberGet((MemberExpression)expr, paramExprs, il, ref closure, setup, parent, byRefIndex); + + case ExpressionType.New: + return TryEmitNew(expr, paramExprs, il, ref closure, setup, parent); + + case ExpressionType.NewArrayBounds: + return EmitNewArrayBounds((NewArrayExpression)expr, paramExprs, il, ref closure, setup, parent); + + case ExpressionType.NewArrayInit: + return EmitNewArrayInit((NewArrayExpression)expr, paramExprs, il, ref closure, setup, parent); + + case ExpressionType.MemberInit: + return EmitMemberInit((MemberInitExpression)expr, paramExprs, il, ref closure, setup, parent); + + case ExpressionType.ListInit: + return TryEmitListInit((ListInitExpression)expr, paramExprs, il, ref closure, setup, parent); + + case ExpressionType.Lambda: + return TryEmitNestedLambda((LambdaExpression)expr, paramExprs, il, ref closure); + + case ExpressionType.Invoke: + return TryEmitInvoke((InvocationExpression)expr, paramExprs, il, ref closure, setup, parent); + + case ExpressionType.GreaterThan: + case ExpressionType.GreaterThanOrEqual: + case ExpressionType.LessThan: + case ExpressionType.LessThanOrEqual: + case ExpressionType.Equal: + case ExpressionType.NotEqual: + { + if (exprType.IsPrimitive && Interpreter.TryInterpretBool(out var boolResult, expr, setup)) + { + if ((parent & ParentFlags.IgnoreResult) == 0) + il.Demit(boolResult ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); + return true; + } + return TryEmitComparison(((BinaryExpression)expr).Left, ((BinaryExpression)expr).Right, exprType, nodeType, paramExprs, il, + ref closure, setup, parent); + } + case ExpressionType.Add: + case ExpressionType.Subtract: + case ExpressionType.Multiply: + case ExpressionType.Divide: + case ExpressionType.Modulo: + case ExpressionType.And: + case ExpressionType.Or: + case ExpressionType.ExclusiveOr: + case ExpressionType.LeftShift: + case ExpressionType.RightShift: + { + return exprType.IsPrimitive + && TryInterpretAndEmitResult(expr, il, parent, setup) + || TryEmitArithmetic(((BinaryExpression)expr).Left, ((BinaryExpression)expr).Right, nodeType, exprType, paramExprs, il, + ref closure, setup, parent); + } + // todo: @feature #472 add interpretation when those node types are supported + case ExpressionType.AddChecked: + case ExpressionType.SubtractChecked: + case ExpressionType.MultiplyChecked: + case ExpressionType.Power: + return TryEmitArithmetic(((BinaryExpression)expr).Left, ((BinaryExpression)expr).Right, nodeType, exprType, paramExprs, il, + ref closure, setup, parent); + + case ExpressionType.AndAlso: + case ExpressionType.OrElse: + { + if (!exprType.IsPrimitive) // todo: @feat #496 + { + Debug.WriteLine("Unsupported: Nullable in || or && (is invalid C# but valid expression) is not supported yet, see #480: " + expr); + return false; + } + if (Interpreter.TryInterpretBool(out var resultBool, expr, setup)) + { + if ((parent & ParentFlags.IgnoreResult) == 0) + il.Demit(resultBool ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); + return true; + } + return TryEmitLogicalOperator((BinaryExpression)expr, nodeType, paramExprs, il, ref closure, setup, parent); + } + case ExpressionType.Not: + { + if (!exprType.IsPrimitive) // todo: @feat #496 + { + Debug.WriteLine("Unsupported: Nullable in !x (is invalid C# but valid expression) is not supported yet, see #480: " + expr); + return false; + } + + if (Interpreter.TryInterpretBool(out var resultBool, expr, setup)) + { + if ((parent & ParentFlags.IgnoreResult) == 0) + il.Demit(resultBool ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); + return true; + } + return TryEmitNot((UnaryExpression)expr, paramExprs, il, ref closure, setup, parent); + } + case ExpressionType.Coalesce: + return TryEmitCoalesceOperator((BinaryExpression)expr, paramExprs, il, ref closure, setup, parent); + + case ExpressionType.Conditional: + var condExpr = (ConditionalExpression)expr; + var testExpr = condExpr.Test; + if (Interpreter.TryInterpretBool(out var testIsTrue, testExpr, setup)) + { + expr = testIsTrue ? condExpr.IfTrue : condExpr.IfFalse; + continue; // no recursion, just continue with the left or right side of condition + } + return TryEmitConditional(testExpr, condExpr.IfTrue, condExpr.IfFalse, paramExprs, il, ref closure, setup, parent); + + case ExpressionType.PostIncrementAssign: + case ExpressionType.PreIncrementAssign: + return TryEmitArithmeticAndOrAssign(((UnaryExpression)expr).Operand, null, exprType, ExpressionType.Add, + nodeType == ExpressionType.PostIncrementAssign, paramExprs, il, ref closure, setup, parent); + + case ExpressionType.PostDecrementAssign: + case ExpressionType.PreDecrementAssign: + return TryEmitArithmeticAndOrAssign(((UnaryExpression)expr).Operand, null, exprType, ExpressionType.Subtract, + nodeType == ExpressionType.PostDecrementAssign, paramExprs, il, ref closure, setup, parent); + + case ExpressionType.AddAssign: + case ExpressionType.AddAssignChecked: + case ExpressionType.SubtractAssign: + case ExpressionType.SubtractAssignChecked: + case ExpressionType.MultiplyAssign: + case ExpressionType.MultiplyAssignChecked: + case ExpressionType.DivideAssign: + case ExpressionType.ModuloAssign: + case ExpressionType.PowerAssign: + case ExpressionType.AndAssign: + case ExpressionType.OrAssign: + case ExpressionType.ExclusiveOrAssign: + case ExpressionType.LeftShiftAssign: + case ExpressionType.RightShiftAssign: + case ExpressionType.Assign: + var ba = (BinaryExpression)expr; + return TryEmitArithmeticAndOrAssign(ba.Left, ba.Right, exprType, + AssignToArithmeticOrSelf(nodeType), false, paramExprs, il, ref closure, setup, parent); + + case ExpressionType.Block: + { + var blockExpr = (BlockExpression)expr; + var blockVarExprs = blockExpr.Variables; + var blockVarCount = blockVarExprs?.Count ?? 0; + var statementExprs = blockExpr.Expressions; // Trim the expressions after the Throw - #196 + var statementCount = statementExprs.Count; + if (statementCount == 0) + return true; // yeah, it is a valid thing + + if (blockVarCount == 1 & statementCount == 2 && + statementExprs[0] is BinaryExpression st0 && st0.NodeType == ExpressionType.Assign && + statementExprs[1] is BinaryExpression st1 && st1.NodeType == ExpressionType.Assign && + st0.Left == blockVarExprs[0] && st1.Right == blockVarExprs[0]) + return TryEmitArithmeticAndOrAssign(st1.Left, st0.Right, st0.Left.Type, + ExpressionType.Assign, false, paramExprs, il, ref closure, setup, parent); + + if (blockVarCount != 0) + closure.PushBlockAndConstructLocalVars(blockVarExprs, il); + + expr = statementExprs[statementCount - 1]; // The last (result) statement in block will provide the result + + // Try to trim the statements from the end of the Block up to the Throw (if any), + // or to the prior Label as in the #442 case 2 + if (statementCount > 1) + { + var throwIndex = statementCount - 1; + for (; throwIndex != -1; --throwIndex) + { + var se = statementExprs[throwIndex]; + if (se.NodeType == ExpressionType.Label) + { + throwIndex = -1; // stop the search + break; + } + if (se.NodeType == ExpressionType.Throw) + break; + } + + // If we have a Throw and it is not the last one + if (throwIndex != -1 && throwIndex != statementCount - 1) + { + // Change the Throw return type to match the one for the Block, and adjust the statement count + expr = Expression.Throw(((UnaryExpression)statementExprs[throwIndex]).Operand, blockExpr.Type); + statementCount = throwIndex + 1; + } + } + + // handle the all statements in block excluding the last one + if (statementCount > 1) + { + for (var i = 0; i < statementCount - 1; i++) + { + var stExpr = statementExprs[i]; + if (stExpr.NodeType == ExpressionType.Default && stExpr.Type == typeof(void)) + continue; + + // This is basically the return pattern (see #237), so we don't care for the rest of expressions + if (stExpr is GotoExpression gt && gt.Kind == GotoExpressionKind.Return && + statementExprs[i + 1] is LabelExpression label && label.Target == gt.Target) + { + // But we cannot use the return pattern and eliminate the target label if we have more gotos referencing it, see #430 + var (gotos, labels) = closure.TargetToGotosAndLabels.Map.TryGetValueRef(label.Target, out var found); + if (found & gotos <= labels) + { + if ((parent & ParentFlags.TryCatch) != 0) + { + if ((setup & CompilerFlags.ThrowOnNotSupportedExpression) != 0) + throw new NotSupportedExpressionException(Result.NotSupported_Try_GotoReturnToTheFollowupLabel); + return false; // todo: @feature return from the TryCatch with the internal label is not supported, though it is an unlikely case + } + + // we are generating the return value and ensuring here that it is not popped-out + var gtOrLabelValue = gt.Value ?? label.DefaultValue; + if (gtOrLabelValue != null) + { + if (!TryEmit(gtOrLabelValue, paramExprs, il, ref closure, setup, parent & ~ParentFlags.IgnoreResult)) + return false; + + if ((parent & ParentFlags.InlinedLambdaInvoke) != 0) + { + ref var foundLabel = ref closure.LambdaInvokeStackLabels.GetLabelOrInvokeIndexByTarget(gt.Target, out var labelFound); + if (!labelFound || foundLabel.InlinedLambdaInvokeIndex == -1) + return false; + EmitGotoToReturnLabel(ref closure.LambdaInvokeStackLabels.GetSurePresentRef(foundLabel.InlinedLambdaInvokeIndex), il, gtOrLabelValue, OpCodes.Br); + } + else + { + // @hack (related to #237) if `IgnoreResult` set, that means the external/calling code won't planning on returning and + // emitting the double `OpCodes.Ret` (usually for not the last statement in block), so we can safely emit our own `Ret` here. + // And vice-versa, if `IgnoreResult` not set then the external code planning to emit `Ret` (the last block statement), + // so we should avoid it on our side. + if ((parent & ParentFlags.IgnoreResult) != 0) + il.Demit(OpCodes.Ret); + } + } + return true; + } + } + + if (!TryEmit(stExpr, paramExprs, il, ref closure, setup, parent | ParentFlags.IgnoreResult)) + return false; + } + } + + parent |= ParentFlags.BlockResult; + if (blockVarCount == 0) + continue; // OMG! no recursion, continue with the last expression + + if (!TryEmit(expr, paramExprs, il, ref closure, setup, parent)) + return false; + + closure.PopBlock(); + return true; + } + case ExpressionType.Loop: + return TryEmitLoop((LoopExpression)expr, paramExprs, il, ref closure, setup, parent); + + case ExpressionType.Try: + return TryEmitTryCatchFinallyBlock((TryExpression)expr, paramExprs, il, ref closure, setup, parent | ParentFlags.TryCatch); + + case ExpressionType.Throw: + { + var ok = true; + var throwOperand = ((UnaryExpression)expr).Operand; + if (throwOperand != null) + ok = TryEmit(throwOperand, paramExprs, il, ref closure, setup, parent & ~ParentFlags.IgnoreResult); + il.Demit(throwOperand != null ? OpCodes.Throw : OpCodes.Rethrow); + return ok; + } + + case ExpressionType.Default: + if (exprType != typeof(void) && (parent & ParentFlags.IgnoreResult) == 0) + EmitDefault(il, exprType); + return true; + + case ExpressionType.Index: + return TryEmitIndexGet((IndexExpression)expr, paramExprs, il, ref closure, setup, parent); + + case ExpressionType.Goto: + return TryEmitGoto((GotoExpression)expr, paramExprs, il, ref closure, setup, parent); + + case ExpressionType.Label: + return TryEmitLabel((LabelExpression)expr, paramExprs, il, ref closure, setup, parent); + + case ExpressionType.Switch: + return TryEmitSwitch((SwitchExpression)expr, paramExprs, il, ref closure, setup, parent); + + case ExpressionType.Extension: + expr = expr.Reduce(); + continue; + + case ExpressionType.DebugInfo: // todo: @feature - is not supported yet + return true; // todo: @unclear - just ignoring the info for now + + case ExpressionType.Quote: // todo: @feature - is not supported yet + default: + return false; + + } + } + } + +#if LIGHT_EXPRESSION + private static bool TryEmitNew(Expression expr, IParameterProvider paramExprs, ILGenerator il, ref ClosureInfo closure, + CompilerFlags setup, ParentFlags parent) +#else + private static bool TryEmitNew(Expression expr, IReadOnlyList paramExprs, ILGenerator il, ref ClosureInfo closure, + CompilerFlags setup, ParentFlags parent) +#endif + { + var flags = ParentFlags.CtorCall; + var newExpr = (NewExpression)expr; +#if SUPPORTS_ARGUMENT_PROVIDER + var argExprs = (IArgumentProvider)newExpr; +#else + var argExprs = newExpr.Arguments; +#endif + var argCount = argExprs.GetCount(); + var ctor = newExpr.Constructor; + if (argCount != 0) + { + var pars = ctor.GetParameters(); + + // If we have complex arguments then it is better to store them in the variables, then load them before calling ctor. + // Otherwise the stack may be broken by the long emit chain, and the previous argument result on stack + // may be hidden by the next argument interim stack additions, + // see the #488 for the details. + if (argCount == 1) + { + if (!TryEmit(argExprs.GetArgument(0), paramExprs, il, ref closure, setup, flags, pars[0].ParameterType.IsByRef ? 0 : -1)) + return false; + } + else + { + if (!closure.ArgsContainingComplexExpression.Map.ContainsKey(newExpr)) + { + for (var i = 0; i < argCount; ++i) + if (!TryEmit(argExprs.GetArgument(i), paramExprs, il, ref closure, setup, flags, pars[i].ParameterType.IsByRef ? i : -1)) + return false; + } + else + { + SmallList, NoArrayPool> argVars = default; + for (var i = 0; i < argCount; ++i) + { + var argExpr = argExprs.GetArgument(i); + var parType = pars[i].ParameterType; + if (!TryEmit(argExpr, paramExprs, il, ref closure, setup, flags, parType.IsByRef ? i : -1)) + return false; + argVars.Add(EmitStoreLocalVariable(il, parType)); + } + for (var i = 0; i < argCount; ++i) + EmitLoadLocalVariable(il, argVars[i]); + } + } + } + var newType = newExpr.Type; + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + if (ctor != null) + il.Demit(OpCodes.Newobj, ctor); + else if (newType.IsValueType) + { + ctor = newType.GetConstructor( + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, + default, CallingConventions.Any, Tools.Empty(), default); + if (ctor != null) + il.Demit(OpCodes.Newobj, ctor); + else + EmitLoadLocalVariable(il, InitValueTypeVariable(il, newType)); + } + else + return false; + + closure.LastEmitIsAddress = + (parent & ParentFlags.InstanceAccess) != 0 & (parent & ParentFlags.IgnoreResult) == 0 && newType.IsValueType; + if (closure.LastEmitIsAddress) + EmitStoreAndLoadLocalVariableAddress(il, newType); + + if (parent.IgnoresResult()) + il.Demit(OpCodes.Pop); + + return true; + } + +#if LIGHT_EXPRESSION + private static bool TryEmitLoop(LoopExpression loopExpr, IParameterProvider paramExprs, ILGenerator il, ref ClosureInfo closure, + CompilerFlags setup, ParentFlags parent) +#else + private static bool TryEmitLoop(LoopExpression loopExpr, IReadOnlyList paramExprs, ILGenerator il, ref ClosureInfo closure, + CompilerFlags setup, ParentFlags parent) +#endif + { + // Loop expression itself does not leave a value on stack. + // If its body produces a value (e.g. a nested typed break/goto path), we need to ignore it before branching back to the loop head, see #498. + parent |= ParentFlags.IgnoreResult; + + // Mark the start of the loop body: + var loopBodyLabel = il.DefineLabel(); + il.DmarkLabel(loopBodyLabel); + + if (loopExpr.ContinueLabel != null) + { + ref var continueLabelInfo = ref closure.LambdaInvokeStackLabels.GetLabelOrInvokeIndexByTarget(loopExpr.ContinueLabel, out var foundLabel); + if (!foundLabel) + return false; + var continueLabel = continueLabelInfo.GetOrDefineLabel(il); + il.DmarkLabel(continueLabel); + } + + if (!TryEmit(loopExpr.Body, paramExprs, il, ref closure, setup, parent)) + return false; + + // If loop hasn't exited, jump back to start of its body: + il.Demit(OpCodes.Br, loopBodyLabel); + + if (loopExpr.BreakLabel != null) + { + ref var breakLabelInfo = ref closure.LambdaInvokeStackLabels.GetLabelOrInvokeIndexByTarget(loopExpr.BreakLabel, out var foundLabel); + if (!foundLabel) + return false; + var breakLabel = breakLabelInfo.GetOrDefineLabel(il); + il.DmarkLabel(breakLabel); + } + + return true; + } + + // similar code is used by the TryEmitArithmeticAndOrAssign, so don't forget to modify it as well + private static bool TryEmitIndexGet(IndexExpression indexExpr, +#if LIGHT_EXPRESSION + IParameterProvider paramExprs, +#else + IReadOnlyList paramExprs, +#endif + ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent) + { + var p = parent & ~ParentFlags.IgnoreResult | ParentFlags.IndexAccess; + + var objExpr = indexExpr.Object; + if (objExpr != null && + !TryEmit(objExpr, paramExprs, il, ref closure, setup, p | ParentFlags.InstanceAccess)) + return false; + +#if SUPPORTS_ARGUMENT_PROVIDER + var indexArgs = (IArgumentProvider)indexExpr; +#else + var indexArgs = indexExpr.Arguments; +#endif + var indexArgCount = indexArgs.GetCount(); + // Strip InstanceAccess when emitting arguments: they are parameters to the indexer + // getter method, not instances. Without this, a nested IndexExpression like + // `list[i][j]` leaks the outer InstanceAccess into the inner indexer's argument + // emission, causing value-type args (e.g. int) to be loaded by address (ldloca) + // instead of by value (ldloc), producing invalid IL. See #499. + var argParent = p & ~ParentFlags.InstanceAccess; + for (var i = 0; i < indexArgCount; i++) + if (!TryEmit(indexArgs.GetArgument(i), paramExprs, il, ref closure, setup, argParent, -1)) + return false; + + var indexerProp = indexExpr.Indexer; + return indexerProp != null + ? EmitMethodCallOrVirtualCallCheckForNull(il, indexerProp.GetMethod) + : indexArgCount == 1 + ? TryEmitArrayIndexGet(il, indexExpr.Type, ref closure, parent) // one-dimensional array + : EmitMethodCallOrVirtualCallCheckForNull(il, objExpr?.Type.FindMethod("Get")); // multi-dimensional array + } + +#if LIGHT_EXPRESSION + private static bool TryEmitLabel(LabelExpression expr, IParameterProvider paramExprs, ILGenerator il, ref ClosureInfo closure, + CompilerFlags setup, ParentFlags parent) +#else + private static bool TryEmitLabel(LabelExpression expr, IReadOnlyList paramExprs, ILGenerator il, ref ClosureInfo closure, + CompilerFlags setup, ParentFlags parent) +#endif + { + ref var labelInfo = ref closure.LambdaInvokeStackLabels.GetLabelOrInvokeIndexByTarget(expr.Target, out var foundLabel); + if (!foundLabel) + return false; + + var label = labelInfo.GetOrDefineLabel(il); + il.DmarkLabel(label); + + var defaultValue = expr.DefaultValue; + if (defaultValue != null && !TryEmit(defaultValue, paramExprs, il, ref closure, setup, parent)) + return false; + + var returnVariableIndexPlusOne = labelInfo.ReturnVariableIndexPlusOneAndIsDefined >>> 1; + if (returnVariableIndexPlusOne != 0) + { + // if the result value was ignored then we did not IL generate anything for the default value, + // and that in order means we do not have valid thing on stack to store here - so skip the store + if (defaultValue != null & ((parent & ParentFlags.IgnoreResult) == 0)) + EmitStoreLocalVariable(il, returnVariableIndexPlusOne - 1); + + il.DmarkLabel(labelInfo.ReturnLabel); + if (!parent.IgnoresResult()) + EmitLoadLocalVariable(il, returnVariableIndexPlusOne - 1); + } + return foundLabel; + } + + // For TryCatch get the variable for saving the result from the LabelInfo store the return expression result into the that variable. + // Emit OpCodes.Leave or OpCodes.Br to the special label with the result which should be marked after the label to jump over its default value + private static void EmitGotoToReturnLabel(ref LabelInfo labelInfo, ILGenerator il, Expression gotoValue, OpCode returnOpCode) + { + var returnVariableIndexPlusOne = labelInfo.ReturnVariableIndexPlusOneAndIsDefined >>> 1; + if (returnVariableIndexPlusOne == 0) + { + returnVariableIndexPlusOne = il.GetNextLocalVarIndex(gotoValue.Type) + 1; + labelInfo.ReturnVariableIndexPlusOneAndIsDefined = (short)(returnVariableIndexPlusOne << 1); + labelInfo.ReturnLabel = il.DefineLabel(); + } + EmitStoreLocalVariable(il, returnVariableIndexPlusOne - 1); + il.Demit(returnOpCode, labelInfo.ReturnLabel); + } + +#if LIGHT_EXPRESSION + private static bool TryEmitGoto(GotoExpression expr, IParameterProvider paramExprs, ILGenerator il, ref ClosureInfo closure, + CompilerFlags setup, ParentFlags parent) +#else + private static bool TryEmitGoto(GotoExpression expr, IReadOnlyList paramExprs, ILGenerator il, ref ClosureInfo closure, + CompilerFlags setup, ParentFlags parent) +#endif + { + ref var labelInfo = ref closure.LambdaInvokeStackLabels.GetLabelOrInvokeIndexByTarget(expr.Target, out var labelFound); + if (!labelFound) + { + if ((closure.Status & ClosureStatus.ToBeCollected) == 0) + return false; // if no collection cycle then the labels may be not collected + throw new InvalidOperationException($"Cannot jump, no labels found for the target `{expr.Target}`"); + } + var gotoValue = expr.Value; + if (gotoValue != null && + !TryEmit(gotoValue, paramExprs, il, ref closure, setup, parent & ~ParentFlags.IgnoreResult)) + return false; + + switch (expr.Kind) + { + case GotoExpressionKind.Break: + case GotoExpressionKind.Continue: + il.Demit(OpCodes.Br, labelInfo.GetOrDefineLabel(il)); + return true; + + case GotoExpressionKind.Goto: + if (gotoValue != null) + goto case GotoExpressionKind.Return; + + var gotoOpCode = (parent & ParentFlags.TryCatch) != 0 ? OpCodes.Leave : OpCodes.Br; + il.Demit(gotoOpCode, labelInfo.GetOrDefineLabel(il)); + return true; + + case GotoExpressionKind.Return: + if ((parent & ParentFlags.TryCatch) != 0) + { + if (gotoValue != null) + EmitGotoToReturnLabel(ref labelInfo, il, gotoValue, OpCodes.Leave); + else + il.Demit(OpCodes.Leave, labelInfo.GetOrDefineLabel(il)); // if there is no return value just leave to the original label + } + else if ((parent & ParentFlags.InlinedLambdaInvoke) != 0) + { + if (gotoValue != null) + { + var invokeIndex = labelInfo.InlinedLambdaInvokeIndex; + if (invokeIndex == -1) + return false; + EmitGotoToReturnLabel(ref closure.LambdaInvokeStackLabels.GetSurePresentRef(invokeIndex), il, gotoValue, OpCodes.Br); + } + } + else + il.Demit(OpCodes.Ret); + return true; + + default: + return false; + } + } + +#if LIGHT_EXPRESSION + private static bool TryEmitCoalesceOperator(BinaryExpression expr, IParameterProvider paramExprs, ILGenerator il, ref ClosureInfo closure, + CompilerFlags setup, ParentFlags parent) +#else + private static bool TryEmitCoalesceOperator(BinaryExpression expr, IReadOnlyList paramExprs, ILGenerator il, ref ClosureInfo closure, + CompilerFlags setup, ParentFlags parent) +#endif + { + var labelFalse = il.DefineLabel(); // todo: @perf define only if needed + var labelDone = il.DefineLabel(); + + var left = expr.Left; + var right = expr.Right; + + // we won't OpCodes.Pop inside the Coalesce as it may leave the Il in invalid state - instead we will pop at the end here (#284) + var flags = (parent & ~ParentFlags.IgnoreResult) | ParentFlags.Coalesce; + + if (!TryEmit(left, paramExprs, il, ref closure, setup, flags)) + return false; + + var exprType = expr.Type; + var leftType = left.Type; + if (leftType.IsValueType) + { + Debug.Assert(leftType.IsNullable(), "Expecting Nullable, it is the only ValueType comparable to null"); + + var varIndex = EmitStoreAndLoadLocalVariableAddress(il, leftType); + EmitMethodCall(il, leftType.GetNullableHasValueGetterMethod()); + + il.Demit(OpCodes.Brfalse, labelFalse); + + if (exprType == Nullable.GetUnderlyingType(leftType)) + { + // if the target expression type is of underlying nullable, and the left operand is not null, + // then extract its underlying value + EmitLoadLocalVariableAddress(il, varIndex); + il.DemitLdfld(leftType.GetNullableValueUnsafeAkaGetValueOrDefaultMethod()); + } + else + EmitLoadLocalVariable(il, varIndex); // loading the value (not address) to return it + + il.Demit(OpCodes.Br, labelDone); + il.DmarkLabel(labelFalse); + if (!TryEmit(right, paramExprs, il, ref closure, setup, flags)) + return false; + + il.DmarkLabel(labelDone); + } + else + { + il.Demit(OpCodes.Dup); // duplicate left, if it's not null, after the branch this value will be on the top of the stack + il.Demit(OpCodes.Brtrue, labelFalse); // automates the chain of the Ldnull, Ceq, Brfalse + il.Demit(OpCodes.Pop); // left is null, pop its value from the stack + + if (!TryEmit(right, paramExprs, il, ref closure, setup, flags)) + return false; + + if (right.Type != exprType) + il.TryEmitBoxOf(right.Type); + + if (left.Type == exprType) + il.DmarkLabel(labelFalse); + else + { + il.Demit(OpCodes.Br, labelDone); + il.DmarkLabel(labelFalse); // todo: @bug? should we insert the boxing for the Nullable value type before the Castclass + il.Demit(OpCodes.Castclass, exprType); + il.DmarkLabel(labelDone); + } + } + return il.EmitPopIfIgnoreResult(parent); + } + + /// Emit default op code for the type + public static void EmitDefault(ILGenerator il, Type type) + { + if (type.IsClass) + { + il.Demit(OpCodes.Ldnull); + return; + } + switch (Type.GetTypeCode(type)) + { + case TypeCode.Boolean: + case TypeCode.Char: + case TypeCode.Byte: + case TypeCode.SByte: + case TypeCode.Int16: + case TypeCode.UInt16: + case TypeCode.Int32: + case TypeCode.UInt32: + il.Demit(OpCodes.Ldc_I4_0); + break; + case TypeCode.Int64: + case TypeCode.UInt64: + il.Demit(OpCodes.Ldc_I4_0); + il.Demit(OpCodes.Conv_I8); + break; + case TypeCode.Single: + il.Demit(OpCodes.Ldc_R4, default(float)); + break; + case TypeCode.Double: + il.Demit(OpCodes.Ldc_R8, default(double)); + break; + default: + EmitLoadLocalVariable(il, InitValueTypeVariable(il, type)); + break; + } + } + +#if LIGHT_EXPRESSION + private static bool TryEmitTryCatchFinallyBlock(TryExpression tryExpr, IParameterProvider paramExprs, ILGenerator il, ref ClosureInfo closure, + CompilerFlags setup, ParentFlags parent) +#else + private static bool TryEmitTryCatchFinallyBlock(TryExpression tryExpr, IReadOnlyList paramExprs, ILGenerator il, ref ClosureInfo closure, + CompilerFlags setup, ParentFlags parent) +#endif + { +#if DEMIT + Debug.WriteLine("try {"); +#endif + il.BeginExceptionBlock(); + + if (!TryEmit(tryExpr.Body, paramExprs, il, ref closure, setup, parent)) + return false; + + var exprType = tryExpr.Type; + var returnsResult = exprType != typeof(void) && !parent.IgnoresResult(); + var resultVarIndex = -1; + + if (returnsResult) + EmitStoreLocalVariable(il, resultVarIndex = il.GetNextLocalVarIndex(exprType)); + + var catchBlocks = tryExpr.Handlers; + for (var i = 0; i < catchBlocks.Count; i++) + { + var catchBlock = catchBlocks[i]; + if (catchBlock.Filter != null) + { + if ((setup & CompilerFlags.ThrowOnNotSupportedExpression) != 0) + throw new NotSupportedExpressionException(Result.NotSupported_ExceptionCatchFilter); + return false; + } + + il.BeginCatchBlock(catchBlock.Test); + + // at the beginning of catch the Exception value is on the stack, + // we will store into local variable. + var exVarExpr = catchBlock.Variable; +#if DEMIT + Debug.WriteLine($"}} catch {{"); +#endif + if (exVarExpr != null) + { + // first, check if the exception variable was used before and supposed to be reused in the new catch + // (this is decided by creator of expression) + var exVarIndex = closure.GetDefinedLocalVarOrDefault(exVarExpr); + if (exVarIndex == -1) + exVarIndex = il.GetNextLocalVarIndex(exVarExpr.Type); + closure.PushBlockWithVars(exVarExpr, exVarIndex); + EmitStoreLocalVariable(il, exVarIndex); + } + + if (!TryEmit(catchBlock.Body, paramExprs, il, ref closure, setup, parent)) + return false; + + if (exVarExpr != null) + closure.PopBlock(); + + if (returnsResult) + EmitStoreLocalVariable(il, resultVarIndex); + } + + if (tryExpr.Fault != null) + { + var faultExpr = tryExpr.Fault; +#if DEMIT + Debug.WriteLine("} fault {" + faultExpr); +#endif + il.BeginFaultBlock(); + // it is important to ignore result for the fault block, because it should not return anything + if (!TryEmit(faultExpr, paramExprs, il, ref closure, setup, parent | ParentFlags.IgnoreResult)) + return false; + } + else if (tryExpr.Finally != null) + { + var finallyExpr = tryExpr.Finally; +#if DEMIT + Debug.WriteLine("} finally {" + finallyExpr); +#endif + il.BeginFinallyBlock(); + // it is important to ignore result for the finally block, because it should not return anything + if (!TryEmit(finallyExpr, paramExprs, il, ref closure, setup, parent | ParentFlags.IgnoreResult)) + return false; + } + + il.EndExceptionBlock(); + +#if DEMIT + Debug.WriteLine("}"); +#endif + + if (returnsResult) + EmitLoadLocalVariable(il, resultVarIndex); + + return true; + } + + public static bool TryEmitParameter(ParameterExpression paramExpr, +#if LIGHT_EXPRESSION + IParameterProvider paramExprs, +#else + IReadOnlyList paramExprs, +#endif + ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent, int byRefIndex = -1) + { + var paramExprCount = paramExprs.GetCount(); + var paramType = paramExpr.Type; + var isValueType = paramType.IsValueType; + var isParamOrVarByRef = paramExpr.IsByRef && !paramType.IsNullable(); // for the nullable part check the #461 cases with nullable + var isPassedRef = byRefIndex != -1; + + // Parameter may represent a variable, so first look if this is the case, + // and the variable is defined in the current block + var varIndex = closure.GetDefinedLocalVarOrDefault(paramExpr); + if (varIndex != -1) + { + // todo: @perf analyze if the variable is actually may be mutated in the nested lambda by being assigned or passed by ref + // Check if the variable is passed to the nested closure (#437), so it should be loaded from the nested closure NonPassedParams array + var nestedLambdasCount = closure.NestedLambdas.Count; + if (nestedLambdasCount != 0) + { + var nestedLambdas = closure.NestedLambdas.Items; + for (var nestedLambdaIndex = 0; nestedLambdaIndex < nestedLambdasCount; ++nestedLambdaIndex) + { + var lambdaInfo = nestedLambdas[nestedLambdaIndex]; + var nonPassedParamCount = lambdaInfo.NonPassedParameters.Count; + if (nonPassedParamCount != 0) + { + var varIndexInNonPassedParams = lambdaInfo.NonPassedParameters.TryGetIndex(paramExpr, default(RefEq)); + if (varIndexInNonPassedParams != -1 && + (lambdaInfo.NonPassedParamMutatedIndexBits & (1UL << varIndexInNonPassedParams)) != 0) + { + // Load the nested lambda item from the closure constants and nested lambdas array + var closureArrayItemIndex = closure.Constants.Count + nestedLambdaIndex; + EmitLoadClosureArrayItem(il, closureArrayItemIndex); + + // Check if the NonPassedArray field is being set (not null), + // otherwise the nested lambda is not yet emitted, and it is not expected for the variable to be set inside it + il.DemitLdfld(NestedLambdaForNonPassedParams.NonPassedParamsField); + + // Load the variable from the NonPassedParams array + EmitLoadConstantInt(il, varIndexInNonPassedParams); + il.Demit(OpCodes.Ldelem_Ref); + il.TryEmitUnboxOf(paramType); + return true; + } + } + } + } + + var valueTypeMemberButNotIndexAccess = isValueType & + // means the parameter is the instance for the called method or the instance for the member access, see #274, #283 + (parent & (ParentFlags.MemberAccess | ParentFlags.InstanceAccess)) != 0 & + ( + // but the variable is not used as an index for the method call or the member access + // `a[i].Foo()` -> false, see #281 + // `a[i].Bar` -> false, see #265 + // `a[i]` -> true, see #413 + (parent & ParentFlags.IndexAccess) == 0 | + (parent & (ParentFlags.Call | ParentFlags.MemberAccess)) == 0 + ); + + closure.LastEmitIsAddress = !isParamOrVarByRef & (isPassedRef | valueTypeMemberButNotIndexAccess); + + if (closure.LastEmitIsAddress) + EmitLoadLocalVariableAddress(il, varIndex); + else if (!isParamOrVarByRef) + EmitLoadLocalVariable(il, varIndex); + else + { + if (isParamOrVarByRef & isValueType & + (parent & ParentFlags.BlockResult) != 0 & + (parent & (ParentFlags.MemberAccess | ParentFlags.Call | ParentFlags.InstanceAccess | ParentFlags.Arithmetic)) == 0) + { + EmitLoadIndirectlyByRef(il, paramType); + return true; + } + + var byAddress = isParamOrVarByRef & isPassedRef & isValueType; + if (byAddress) + EmitStoreAndLoadLocalVariableAddress(il, varIndex); + else if ((parent & ParentFlags.InstanceCall) == ParentFlags.InstanceCall) + EmitLoadLocalVariable(il, varIndex); + else + EmitStoreAndLoadLocalVariable(il, varIndex); + + // Assume that the var is the last on the stack and just duplicating it, see #346 `Get_array_element_ref_and_increment_it` + // Do only when accessing the variable directly, and not the member and the array element by index + if (byAddress) + il.Demit(OpCodes.Dup); + else if ((parent & ParentFlags.InstanceAccess) == 0) + EmitLoadLocalVariable(il, varIndex); + + if (isValueType) + { + if ((parent & (ParentFlags.Arithmetic | ParentFlags.AssignmentRightValue)) != 0 & + (parent & (ParentFlags.MemberAccess | ParentFlags.InstanceAccess)) == 0) + EmitLoadIndirectlyByRef(il, paramType); + } + else + { + if ((parent & (ParentFlags.Coalesce | ParentFlags.MemberAccess | ParentFlags.IndexAccess | ParentFlags.AssignmentRightValue)) != 0) + il.Demit(OpCodes.Ldind_Ref); + } + } + return true; + } + + // If not variable then look if the parameter is passed to the current lambda + var paramIndex = paramExprCount - 1; + while (paramIndex != -1 && !ReferenceEquals(paramExprs.GetParameter(paramIndex), paramExpr)) --paramIndex; + if (paramIndex != -1) + { + if ((closure.Status & ClosureStatus.ShouldBeStaticMethod) == 0) + ++paramIndex; // shift parameter index by one, because the first one will be closure + + // means the parameter is the instance for what method is called or the instance for the member access, see #274, #283 + var valueTypeMemberButNotIndexAccess = isValueType & + // means the parameter is the instance for what method is called or the instance for the member access, see #274, #283 + (parent & (ParentFlags.MemberAccess | ParentFlags.InstanceAccess)) != 0 & + // see #352 for the arithmetic + (parent & ParentFlags.Arithmetic) == 0 & + ( + // but the variable is not used as an index for the method call or the member access + // `a[i].Foo()` -> false, see #281 + // `a[i].Bar` -> false, see #265 + // `a[i]` -> true, see #413 + (parent & ParentFlags.IndexAccess) == 0 | + (parent & (ParentFlags.Call | ParentFlags.MemberAccess)) == 0 + ); + + closure.LastEmitIsAddress = !isParamOrVarByRef & (isPassedRef | valueTypeMemberButNotIndexAccess); + + if (closure.LastEmitIsAddress) + EmitLoadArgAddress(il, paramIndex); + else + EmitLoadArg(il, paramIndex); + + // todo: @simplify as it is complex overall and EmitLoadIndirectlyByRef does the Ldind_Ref too + if (isParamOrVarByRef) + { + // todo: @wip requires a cleanup + if (isValueType) + { + // #248 - skip the cases with `ref param.Field` were we are actually want to load the `Field` address not the `param` + // this means the parameter is the argument to the method call and not the instance in the method call or member access + if (!isPassedRef & ( + ((parent & ParentFlags.Call) != 0 & (parent & ParentFlags.InstanceAccess) == 0) | + ((parent & ParentFlags.LambdaCall) != 0 & (parent & ParentFlags.ReturnByRef) == 0) + ) || + (parent & (ParentFlags.Arithmetic | ParentFlags.AssignmentRightValue)) != 0 & + (parent & (ParentFlags.MemberAccess | ParentFlags.InstanceAccess | ParentFlags.AssignmentLeftValue)) == 0) + EmitLoadIndirectlyByRef(il, paramType); + } + else + { + if (!isPassedRef & ( + (parent & (ParentFlags.Call | ParentFlags.LambdaCall)) != 0) || + (parent & (ParentFlags.Coalesce | ParentFlags.MemberAccess | ParentFlags.IndexAccess | ParentFlags.AssignmentRightValue)) != 0) + il.Demit(OpCodes.Ldind_Ref); + } + } + return true; + } + + if (isParamOrVarByRef) + { + EmitLoadLocalVariableAddress(il, byRefIndex); // todo: @bug? `closure.LastEmitIsAddress = true;` should we do it too as in above code with the variable + return true; + } + + // the only possibility that we are here is because we are in the nested lambda, + // and it uses the parameter or variable from the outer lambda + var nonPassedParamIndex = closure.NonPassedParameters.TryGetIndex(paramExpr, default(RefEq)); + if (nonPassedParamIndex == -1) + return false; + + // Load non-passed argument from Closure - closure object is always a first argument + il.Demit(OpCodes.Ldarg_0); + il.DemitLdfld(ArrayClosureWithNonPassedParamsField); + EmitLoadConstantInt(il, nonPassedParamIndex); + il.Demit(OpCodes.Ldelem_Ref); + return il.TryEmitUnboxOf(paramType); + } + +#if LIGHT_EXPRESSION + public static bool TryEmitNonByRefNonValueTypeParameter(ParameterExpression paramExpr, IParameterProvider paramExprs, ILGenerator il, ref ClosureInfo closure) + { + var paramExprCount = paramExprs.ParameterCount; +#else + public static bool TryEmitNonByRefNonValueTypeParameter(ParameterExpression paramExpr, IReadOnlyList paramExprs, ILGenerator il, ref ClosureInfo closure) + { + var paramExprCount = paramExprs.Count; +#endif + // if parameter is passed through, then just load it on stack + var paramType = paramExpr.Type; + var paramIndex = paramExprCount - 1; + while (paramIndex != -1 && !ReferenceEquals(paramExprs.GetParameter(paramIndex), paramExpr)) + --paramIndex; + if (paramIndex != -1) + { + if ((closure.Status & ClosureStatus.ShouldBeStaticMethod) == 0) + ++paramIndex; // shift parameter index by one, because the first one will be closure + if (closure.LastEmitIsAddress) + EmitLoadArgAddress(il, paramIndex); + else + EmitLoadArg(il, paramIndex); + return true; + } + + // the only possibility that we are here is because we are in the nested lambda, + // and it uses the parameter or variable from the outer lambda + var nonPassedParamIndex = closure.NonPassedParameters.TryGetIndex(paramExpr, default(RefEq)); + if (nonPassedParamIndex == -1) + return false; + + // Load non-passed argument from Closure - closure object is always a first argument + il.Demit(OpCodes.Ldarg_0); + il.DemitLdfld(ArrayClosureWithNonPassedParamsField); + EmitLoadConstantInt(il, nonPassedParamIndex); + il.Demit(OpCodes.Ldelem_Ref); + return true; + } + + private static bool TryEmitSimpleUnaryExpression(UnaryExpression expr, ExpressionType nodeType, +#if LIGHT_EXPRESSION + IParameterProvider paramExprs, +#else + IReadOnlyList paramExprs, +#endif + ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent) + { + var exprType = expr.Type; + + if (!TryEmit(expr.Operand, paramExprs, il, ref closure, setup, parent)) + return false; + + if (nodeType == ExpressionType.TypeAs) + { + il.TryEmitBoxOf(expr.Operand.Type); + il.Demit(OpCodes.Isinst, exprType); + if (exprType.IsValueType) + il.Demit(OpCodes.Unbox_Any, exprType); + } + else if (nodeType == ExpressionType.IsFalse) + { + var falseLabel = il.DefineLabel(); + var continueLabel = il.DefineLabel(); + il.Demit(OpCodes.Brfalse, falseLabel); + il.Demit(OpCodes.Ldc_I4_0); + il.Demit(OpCodes.Br, continueLabel); + il.DmarkLabel(falseLabel); + il.Demit(OpCodes.Ldc_I4_1); + il.DmarkLabel(continueLabel); + } + else if (nodeType == ExpressionType.Increment) + { + if (exprType.IsPrimitive) + { + if (!TryEmitNumberOne(il, exprType)) + return false; + il.Demit(OpCodes.Add); + } + else if (!EmitMethodCallCheckForNull(il, exprType.GetMethod("op_Increment"))) + return false; + } + else if (nodeType == ExpressionType.Decrement) + { + if (exprType.IsPrimitive) + { + if (!TryEmitNumberOne(il, exprType)) + return false; + il.Demit(OpCodes.Sub); + } + else if (!EmitMethodCallCheckForNull(il, exprType.GetMethod("op_Decrement"))) + return false; + } + else if (nodeType == ExpressionType.Negate | nodeType == ExpressionType.NegateChecked) + { + if (exprType.IsPrimitive) + il.Demit(OpCodes.Neg); + else if (!EmitMethodCallCheckForNull(il, exprType.GetMethod("op_UnaryNegation"))) + return false; + } + else if (nodeType == ExpressionType.OnesComplement) + il.Demit(OpCodes.Not); + else if (nodeType == ExpressionType.Unbox) + il.Demit(OpCodes.Unbox_Any, exprType); + // else if (nodeType == ExpressionType.IsTrue) { } + // else if (nodeType == ExpressionType.UnaryPlus) { } + + return il.EmitPopIfIgnoreResult(parent); + } + + private static bool TryEmitTypeIsOrEqual(TypeBinaryExpression expr, +#if LIGHT_EXPRESSION + IParameterProvider paramExprs, +#else + IReadOnlyList paramExprs, +#endif + ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent) + { + if (!TryEmit(expr.Expression, paramExprs, il, ref closure, setup, parent)) + return false; + if ((parent & ParentFlags.IgnoreResult) != 0) + return true; + if (expr.NodeType == ExpressionType.TypeIs) + { + il.Demit(OpCodes.Isinst, expr.TypeOperand); + il.Demit(OpCodes.Ldnull); + il.Demit(OpCodes.Cgt_Un); + return true; + } + if ((setup & CompilerFlags.ThrowOnNotSupportedExpression) != 0) + throw new NotSupportedExpressionException(Result.NotSupported_TypeEqual); + return false; + } + + private static bool TryEmitNot(UnaryExpression expr, +#if LIGHT_EXPRESSION + IParameterProvider paramExprs, +#else + IReadOnlyList paramExprs, +#endif + ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent) + { + var op = expr.Operand; + if (op.NodeType == ExpressionType.Equal) + return TryEmitComparison(((BinaryExpression)op).Left, ((BinaryExpression)op).Right, + expr.Type, ExpressionType.NotEqual, paramExprs, il, ref closure, setup, parent); + + if (!TryEmit(op, paramExprs, il, ref closure, setup, parent)) + return false; + + if ((parent & ParentFlags.IgnoreResult) != 0) + il.Demit(OpCodes.Pop); + else if (expr.Type == typeof(bool)) + EmitEqualToZeroOrNull(il); + else + il.Demit(OpCodes.Not); + return true; + } + + private static bool TryEmitConvert(UnaryExpression expr, +#if LIGHT_EXPRESSION + IParameterProvider paramExprs, +#else + IReadOnlyList paramExprs, +#endif + ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent) + { + var opExpr = expr.Operand; + var sourceType = opExpr.Type; + var targetType = expr.Type; + var underlyingNullableSourceType = Nullable.GetUnderlyingType(sourceType); + var underlyingNullableTargetType = Nullable.GetUnderlyingType(targetType); + + var convertMethod = expr.Method; + if (convertMethod == null) + { + // Try fast the special cases which does not require searching for the conversion operators in principle: + if (parent.IgnoresResult() && (sourceType == targetType || targetType.IsAssignableFrom(sourceType))) + return TryEmit(opExpr, paramExprs, il, ref closure, setup, parent & ~ParentFlags.InstanceAccess); + + // Emit the operand before going to the fast checks below + if (!TryEmit(opExpr, paramExprs, il, ref closure, setup, parent & ~ParentFlags.IgnoreResult & ~ParentFlags.InstanceAccess)) + return false; + + if (sourceType == targetType) + return il.EmitPopIfIgnoreResult(parent); + + if (targetType == underlyingNullableSourceType) + { + if (!closure.LastEmitIsAddress) + EmitStoreAndLoadLocalVariableAddress(il, sourceType); + EmitMethodCall(il, sourceType.GetNullableValueGetterMethod()); + return il.EmitPopIfIgnoreResult(parent); + } + + if (sourceType == underlyingNullableTargetType) + { + il.Demit(OpCodes.Newobj, targetType.GetNullableConstructor()); + return il.EmitPopIfIgnoreResult(parent); + } + + if (targetType == typeof(object) && sourceType.IsValueType) + { + il.Demit(OpCodes.Box, sourceType); + return il.EmitPopIfIgnoreResult(parent); + } + + if (sourceType == typeof(object) && targetType.IsValueType || + sourceType == typeof(Enum) && targetType.IsEnum // a special case, see the AutoMapper test `StringToEnumConverter.Should_work` + ) + { + il.Demit(OpCodes.Unbox_Any, targetType); + return il.EmitPopIfIgnoreResult(parent); + } + + // At least just check the assignability of the source to the target type, + // check only after the checks above for the ValueType or object Type, + // because their require additional boxing/unboxing operations + if (targetType.IsAssignableFrom(sourceType)) + { + if (sourceType.IsValueType && !targetType.IsValueType) + il.Demit(OpCodes.Box, sourceType); + il.Demit(OpCodes.Castclass, targetType); + return il.EmitPopIfIgnoreResult(parent); + } + } + + // Check implicit / explicit conversion operators on source and then on the target type, + // check their underlying nullable types, because Nullable does not contain conversion ops, + // also check inside that it is not primitive type, nor string or enum because those do no contain the conversion operators either. + // see #73, #451 for examples + Type methodReturnType = null; + Type methodParamType = null; + if (convertMethod != null) + { + methodReturnType = convertMethod.ReturnType; + + var mParams = convertMethod.GetParameters(); + Debug.Assert(mParams.Length == 1, $"Expected for the conversion operator to have a single param, but found {mParams.Length}"); + methodParamType = mParams[0].ParameterType; + + // todo: @wip check if we need to add the ParentFlags.Call here? + if (!TryEmit(opExpr, paramExprs, il, ref closure, setup, parent & ~ParentFlags.IgnoreResult & ~ParentFlags.InstanceAccess)) + return false; + } + else + { + // Lookup for the conversion method first in sourceType then in the targetType + Type underlyingOrNullableSourceType = null; + for (var lookupCount = 0; lookupCount < 2 & convertMethod == null; ++lookupCount) + { + var convOwnerType = lookupCount == 0 + ? underlyingNullableSourceType ?? sourceType + : underlyingNullableTargetType ?? targetType; + + if (convOwnerType == typeof(string) || convOwnerType.IsEnum || convOwnerType.IsPrimitive) + continue; + + var staticMethods = convOwnerType.GetMethods(BindingFlags.Static | BindingFlags.Public); + foreach (var m in staticMethods) + { + if (!m.IsSpecialName) + continue; + + // Method return type should be convertible to target type, + // and therefore it does not check for the method return type of Nullable + // because it cannot be coalesced to targetType without loss of information + methodReturnType = m.ReturnType; + if (methodReturnType != targetType && methodReturnType != underlyingNullableTargetType || + m.Name != "op_Implicit" && m.Name != "op_Explicit") + continue; + + var operatorParams = m.GetParameters(); + Debug.Assert(operatorParams.Length == 1, $"Expected for the conversion operator to have a single param, but found {operatorParams.Length}"); + + methodParamType = operatorParams[0].ParameterType; + if (methodParamType == sourceType) + { + convertMethod = m; + break; + } + + // This next check is only valid if the source type is the ValueType, so it can be either a Nullable struct or it can be wrapped in the Nullable + if (sourceType.IsValueType) + { + // Check for all variants of the source type which maybe either underlying nullable or nullable of the source type + // Calculate it once because less work is better. + underlyingOrNullableSourceType ??= underlyingNullableSourceType ?? sourceType.GetNullable(); + if (methodParamType == underlyingOrNullableSourceType) + { + convertMethod = m; + break; + } + } + } + } + } + + if (convertMethod != null) + { + Debug.Assert(methodParamType != null & methodReturnType != null, + "Expecting that actual source and return type are set for the found conversion operator method"); + + // For the both nullable source and target types, + // first check the source value for null and return null without calling the conversion method, otherwise call the conversion method + if (methodParamType == underlyingNullableSourceType & underlyingNullableTargetType != null) + { + var sourceVarIndex = EmitStoreAndLoadLocalVariableAddress(il, sourceType); + EmitMethodCall(il, sourceType.GetNullableHasValueGetterMethod()); + + var labelSourceHasValue = il.DefineLabel(); + il.Demit(OpCodes.Brtrue_S, labelSourceHasValue); // Jump to this label when the source has a value + + // Otherwise, emit and load the default nullable target without value, e.g. `default(Nullable)` + // then... the conversion is completed, so jumping to the done label + EmitLoadLocalVariable(il, InitValueTypeVariable(il, targetType)); + var labelConversionDone = il.DefineLabel(); + il.Demit(OpCodes.Br_S, labelConversionDone); + + // If the nullable source has the value, do the conversion + il.DmarkLabel(labelSourceHasValue); + EmitLoadLocalVariableAddress(il, sourceVarIndex); + il.DemitLdfld(sourceType.GetNullableValueUnsafeAkaGetValueOrDefaultMethod()); + + EmitMethodCallOrVirtualCall(il, convertMethod); + + // Wrap the conversion result into the target type only if the conversion method return the underlying target type, + // otherwise we done + if (methodReturnType == underlyingNullableTargetType) + il.Demit(OpCodes.Newobj, targetType.GetNullableConstructor()); + + il.DmarkLabel(labelConversionDone); + return il.EmitPopIfIgnoreResult(parent); + } + + if (methodParamType == underlyingNullableSourceType) + { + EmitStoreAndLoadLocalVariableAddress(il, sourceType); + EmitMethodCall(il, sourceType.GetNullableValueGetterMethod()); + } + else if (methodParamType != sourceType) // This is an unlikely case of Target(Source? source) + { + Debug.Assert(Nullable.GetUnderlyingType(methodParamType) == sourceType, "Expecting that the parameter type is the Nullable"); + il.Demit(OpCodes.Newobj, methodParamType.GetNullableConstructor()); + } + + EmitMethodCallOrVirtualCall(il, convertMethod); + + if (methodReturnType == underlyingNullableTargetType) + il.Demit(OpCodes.Newobj, targetType.GetNullableConstructor()); + + return il.EmitPopIfIgnoreResult(parent); + } + + if (underlyingNullableSourceType != null & underlyingNullableTargetType != null) + { + var sourceVarIndex = EmitStoreAndLoadLocalVariableAddress(il, sourceType); + EmitMethodCall(il, sourceType.GetNullableHasValueGetterMethod()); + + var labelSourceHasValue = il.DefineLabel(); + il.Demit(OpCodes.Brtrue_S, labelSourceHasValue); // Jump here when the source has a value + + // Otherwise, emit and load the default nullable target without value, e.g. `default(Nullable)` + // and... the conversion is completed, so jumping to the done label + EmitLoadLocalVariable(il, InitValueTypeVariable(il, targetType)); + var labelConversionDone = il.DefineLabel(); + il.Demit(OpCodes.Br_S, labelConversionDone); + + // If the nullable source has the value, do the conversion + il.DmarkLabel(labelSourceHasValue); + EmitLoadLocalVariableAddress(il, sourceVarIndex); + il.DemitLdfld(sourceType.GetNullableValueUnsafeAkaGetValueOrDefaultMethod()); + + if (!TryEmitPrimitiveValueConvert(underlyingNullableSourceType, underlyingNullableTargetType, il, expr.NodeType == ExpressionType.ConvertChecked)) + return false; + + il.Demit(OpCodes.Newobj, targetType.GetNullableConstructor()); + + // We done here. + il.DmarkLabel(labelConversionDone); + return il.EmitPopIfIgnoreResult(parent); + } + + if (underlyingNullableTargetType != null) // sourceType is NOT nullable here (checked above) + { + if (!underlyingNullableTargetType.IsEnum && + !TryEmitPrimitiveValueConvert(sourceType, underlyingNullableTargetType, il, isChecked: false)) + return false; + il.Demit(OpCodes.Newobj, targetType.GetNullableConstructor()); + } + else // targetType is NOT nullable here (checked above) + { + if (targetType.IsEnum) + { + targetType = Enum.GetUnderlyingType(targetType); + if (targetType == sourceType) + return il.EmitPopIfIgnoreResult(parent); + } + + // fixes #159 + if (underlyingNullableSourceType != null) + { + EmitStoreAndLoadLocalVariableAddress(il, sourceType); + EmitMethodCall(il, sourceType.GetNullableValueGetterMethod()); + } + + // Cast as the last resort and let's it fail if unlucky + if (!TryEmitPrimitiveValueConvert(underlyingNullableSourceType ?? sourceType, targetType, il, expr.NodeType == ExpressionType.ConvertChecked)) + { + il.TryEmitBoxOf(sourceType); + il.Demit(OpCodes.Castclass, targetType); + } + } + + return il.EmitPopIfIgnoreResult(parent); + } + + private static bool TryEmitPrimitiveValueConvert(Type sourceType, Type targetType, ILGenerator il, bool isChecked) + { + switch (Type.GetTypeCode(targetType)) + { + case TypeCode.SByte: + il.Demit(isChecked ? OpCodes.Conv_Ovf_I1 : OpCodes.Conv_I1); + break; + case TypeCode.Byte: + il.Demit(isChecked ? OpCodes.Conv_Ovf_U1 : OpCodes.Conv_U1); + break; + case TypeCode.Int16: + il.Demit(isChecked ? OpCodes.Conv_Ovf_I2 : OpCodes.Conv_I2); + break; + case TypeCode.Int32: + il.Demit(isChecked ? OpCodes.Conv_Ovf_I4 : OpCodes.Conv_I4); + break; + case TypeCode.Int64: + il.Demit(isChecked ? OpCodes.Conv_Ovf_I8 : OpCodes.Conv_I8); + break; + case TypeCode.Double: + if (sourceType.IsUnsigned()) + il.Demit(OpCodes.Conv_R_Un); + il.Demit(OpCodes.Conv_R8); + break; + case TypeCode.Single: + if (sourceType.IsUnsigned()) + il.Demit(OpCodes.Conv_R_Un); + il.Demit(OpCodes.Conv_R4); + break; + case TypeCode.UInt16: + case TypeCode.Char: + il.Demit(isChecked ? OpCodes.Conv_Ovf_U2 : OpCodes.Conv_U2); + break; + case TypeCode.UInt32: + il.Demit(isChecked ? OpCodes.Conv_Ovf_U4 : OpCodes.Conv_U4); + break; + case TypeCode.UInt64: + il.Demit(isChecked ? OpCodes.Conv_Ovf_U8 : OpCodes.Conv_U8); // should we consider if sourceType.IsUnsigned == false and using the OpCodes.Conv_I8 (seems like the System.Compile does it) + break; + default: + // todo: @feature for net7+ add Half, Int128, UInt128 + return false; + } + return true; + } + + public static bool TryEmitConstant(ConstantExpression expr, Type exprType, ILGenerator il, ref ClosureInfo closure, int byRefIndex = -1) + { + var ok = false; +#if LIGHT_EXPRESSION + if (expr == NullConstant) + { + il.Demit(OpCodes.Ldnull); + ok = true; + } + else if (expr == FalseConstant | expr == ZeroConstant) + { + il.Demit(OpCodes.Ldc_I4_0); + ok = true; + } + else if (expr == TrueConstant | expr == OneConstant) + { + il.Demit(OpCodes.Ldc_I4_1); + ok = true; + } + else if (expr is IntConstantExpression n) + { + EmitLoadConstantInt(il, (int)n.Value); + ok = true; + } +#endif + if (!ok) + { + var constValue = expr.Value; + if (constValue != null) + ok = TryEmitConstant(closure.ContainsConstantsOrNestedLambdas(), exprType, constValue.GetType(), constValue, il, ref closure, byRefIndex); + else if (exprType.IsValueType) + // null for a value type and yep, this is a proper way to emit the Nullable null + ok = EmitLoadLocalVariable(il, InitValueTypeVariable(il, exprType)); + else + { + il.Demit(OpCodes.Ldnull); + ok = true; + } + } + + if (ok & byRefIndex != -1) + EmitStoreAndLoadLocalVariableAddress(il, exprType); + return ok; + } + + [MethodImpl((MethodImplOptions)256)] + public static bool TryEmitNotNullConstant(bool considerClosure, object constValue, ILGenerator il, ref ClosureInfo closure, int byRefIndex = -1) + { + Debug.Assert(constValue != null, "Expecting that the constant value is not null here"); + var constType = constValue.GetType(); + var ok = TryEmitConstant(considerClosure, null, constType, constValue, il, ref closure, byRefIndex); + if (ok & byRefIndex != -1) + EmitStoreAndLoadLocalVariableAddress(il, constType); + return ok; + } + + public static bool TryEmitConstant(bool considerClosure, Type exprType, Type constType, object constValue, ILGenerator il, ref ClosureInfo closure, + int byRefIndex = -1, FieldInfo refField = null) + { + if (exprType == null) + exprType = constType; +#if LIGHT_EXPRESSION + if (considerClosure && (refField != null || IsClosureBoundConstant(constValue, constType))) +#else + if (considerClosure && IsClosureBoundConstant(constValue, constType)) +#endif + { + var constIndex = closure.Constants.TryGetIndex(constValue, default(RefEq)); + if (constIndex == -1) + return false; // todo: @check should we throw an exception instead? + + var varIndex = closure.ConstantUsageThenVarIndex[constIndex] - 1; + if (varIndex > 0) + EmitLoadLocalVariable(il, varIndex); + else + { + EmitLoadClosureArrayItem(il, constIndex); +#if LIGHT_EXPRESSION + // Handle the loading of the ref field if the constant usage is only once, + // for the multiple usages the field was loaded and saved into variable in `EmitLoadConstantsAndNestedLambdasIntoVars` + if (refField != null) + { + il.DemitLdfld(refField); + if (refField.FieldType != typeof(object)) + return true; // for typed constant we done, + // but the object ref field requires the normal constant treatment with unboxing of the ValueType or the cast + exprType = constType = ((ConstantExpression)constValue).Value.GetType(); + } +#endif + if (constType.IsValueType) + il.Demit(OpCodes.Unbox_Any, constType); +#if NETFRAMEWORK + else + // The cast is probably required only for the Full CLR, + // e.g. `Test_283_Case6_MappingSchemaTests_CultureInfo_VerificationException`. + // .NET Core does not seem to care about verifiability and it's faster without the explicit cast. + il.Demit(OpCodes.Castclass, constType); +#endif + } + } + else + { + if (constValue is string s) + { + il.Demit(s, OpCodes.Ldstr); + return true; + } + if (constValue is Type t) + { + il.Demit(OpCodes.Ldtoken, t); + return EmitMethodCall(il, GetTypeFromHandleMethod); + } + if (!TryEmitPrimitiveOrEnumOrDecimalConstant(il, constValue, constType)) + return false; + } + + if (exprType != constType && constType.IsValueType) + { + if (exprType.IsNullable()) + il.Demit(OpCodes.Newobj, exprType.GetNullableConstructor()); + else if (exprType == typeof(object)) + il.Demit(OpCodes.Box, constType); // using normal type for Enum instead of underlying type + } + return true; + } + + /// Emit the IL for the value of the primitive type. + public static bool TryEmitPrimitiveOrEnumOrDecimalConstant(ILGenerator il, object constValue, Type constType) + { + if (constType.IsEnum) + constType = Enum.GetUnderlyingType(constType); + + switch (Type.GetTypeCode(constType)) + { + case TypeCode.Boolean: + il.Demit((bool)constValue ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); // todo: @perf check for LightExpression + break; + case TypeCode.Char: + EmitLoadConstantInt(il, (char)constValue); + break; + case TypeCode.SByte: + EmitLoadConstantInt(il, (sbyte)constValue); + break; + case TypeCode.Byte: + EmitLoadConstantInt(il, (byte)constValue); + break; + case TypeCode.Int16: + EmitLoadConstantInt(il, (short)constValue); + break; + case TypeCode.UInt16: + EmitLoadConstantInt(il, (ushort)constValue); + break; + case TypeCode.Int32: + EmitLoadConstantInt(il, (int)constValue); + break; + case TypeCode.UInt32: + unchecked + { + EmitLoadConstantInt(il, (int)(uint)constValue); + } + break; + case TypeCode.Int64: + il.Demit(OpCodes.Ldc_I8, (long)constValue); + break; + case TypeCode.UInt64: + unchecked + { + il.Demit(OpCodes.Ldc_I8, (long)(ulong)constValue); + } + break; + case TypeCode.Double: + il.Demit(OpCodes.Ldc_R8, (double)constValue); + break; + case TypeCode.Single: + il.Demit(OpCodes.Ldc_R4, (float)constValue); + break; + case TypeCode.Decimal: + EmitDecimalConstant((decimal)constValue, il); + break; + // todo: @feature for net7 add Half, Int128, UInt128 + default: + if (constType == typeof(IntPtr)) + { + il.Demit(OpCodes.Ldc_I8, ((IntPtr)constValue).ToInt64()); + break; + } + else if (constType == typeof(UIntPtr)) + { + unchecked + { + il.Demit(OpCodes.Ldc_I8, (long)((UIntPtr)constValue).ToUInt64()); + } + break; + } + return false; + } + return true; + } + + // todo: @perf optimize using Type.TypeCode + internal static bool TryEmitNumberOne(ILGenerator il, Type type) + { + if (type == typeof(int) || type == typeof(char) || type == typeof(short) || + type == typeof(byte) || type == typeof(ushort) || type == typeof(sbyte) || + type == typeof(uint)) + il.Demit(OpCodes.Ldc_I4_1); + else if (type == typeof(long) || type == typeof(ulong) || + type == typeof(IntPtr) || type == typeof(UIntPtr)) + il.Demit(OpCodes.Ldc_I8, (long)1); + else if (type == typeof(float)) + il.Demit(OpCodes.Ldc_R4, 1f); + else if (type == typeof(double)) + il.Demit(OpCodes.Ldc_R8, 1d); + else + return false; + return true; + } + + [MethodImpl((MethodImplOptions)256)] + private static void EmitLoadClosureArrayItem(ILGenerator il, int i) + { + il.Demit(OpCodes.Ldloc_0);// SHOULD BE always at 0 location; load array field variable on the stack + EmitLoadConstantInt(il, i); + il.Demit(OpCodes.Ldelem_Ref); + } + + internal static void EmitLoadConstantsAndNestedLambdasIntoVars(ILGenerator il, ref ClosureInfo closure) + { + // todo: @perf load the field to `var` only if the constants are more than 1 + // Load constants array field from Closure and store it into the variable + il.Demit(OpCodes.Ldarg_0); + il.DemitLdfld(ArrayClosureArrayField); + EmitStoreLocalVariable(il, il.GetNextLocalVarIndex(typeof(object[]))); // always does Stloc_0, because it is done at start of the lambda emit + + // important that the constant will contain the nested lambdas as well in the same array after the actual constants, + // so the Count indicates where the constants end + var constItems = closure.Constants.Items; + var constCount = closure.Constants.Count; + + short varIndex; + for (var i = 0; i < constCount; i++) + { + ref var constUsage = ref closure.ConstantUsageThenVarIndex.GetSurePresentRef(i); + if (constUsage > 1) // todo: @perf should we proceed to do this or simplify and remove the usages for the closure info? + { + EmitLoadClosureArrayItem(il, i); + var constValue = constItems[i]; + var constType = constValue.GetType(); + if (constType.IsValueType) + il.Demit(OpCodes.Unbox_Any, constType); + + varIndex = (short)il.GetNextLocalVarIndex(constType); + constUsage = (short)(varIndex + 1); // to distinguish from the default 1 + EmitStoreLocalVariable(il, varIndex); + } + } + + var nestedLambdasCount = closure.NestedLambdas.Count; + if (nestedLambdasCount != 0) + { + var nestedLambdas = closure.NestedLambdas.Items; + for (var i = 0; i < nestedLambdasCount; i++) + { + var lambdaInfo = nestedLambdas[i]; + EmitLoadClosureArrayItem(il, constCount + i); + lambdaInfo.LambdaVarIndex = varIndex = (short)il.GetNextLocalVarIndex(lambdaInfo.Lambda.GetType()); + EmitStoreLocalVariable(il, varIndex); + } + } + } + + private static ConstructorInfo _decimalIntCtor, _decimalLongCtor; + private static FieldInfo _decimalZero, _decimalOne; + + private static void EmitDecimalConstant(decimal value, ILGenerator il) + { + if (value == 0 | value == 1) + { + // emit Decimal.Zero or Decimal.One instead of new Decimal(0) or new Decimal(1) + var field = value == 0 ? + _decimalZero ?? (_decimalZero = typeof(Decimal).GetField(nameof(Decimal.Zero))) : + _decimalOne ?? (_decimalOne = typeof(Decimal).GetField(nameof(Decimal.One))); + il.DemitLdsfld(field); + return; + } + + //check if decimal has decimal places, if not use shorter IL code (constructor from int or long) + if (value % 1 == 0) + { + if (value >= int.MinValue && value <= int.MaxValue) + { + EmitLoadConstantInt(il, decimal.ToInt32(value)); + il.Demit(OpCodes.Newobj, _decimalIntCtor ?? (_decimalIntCtor = typeof(decimal).FindSingleParamConstructor(typeof(int)))); + return; + } + + if (value >= long.MinValue && value <= long.MaxValue) + { + il.Demit(OpCodes.Ldc_I8, decimal.ToInt64(value)); + il.Demit(OpCodes.Newobj, _decimalLongCtor ?? (_decimalLongCtor = typeof(decimal).FindSingleParamConstructor(typeof(long)))); + return; + } + } + + if (value == decimal.MinValue) + { + il.DemitLdsfld(typeof(decimal).GetField(nameof(decimal.MinValue))); + return; + } + + if (value == decimal.MaxValue) + { + il.DemitLdsfld(typeof(decimal).GetField(nameof(decimal.MaxValue))); + return; + } + + var parts = decimal.GetBits(value); + var sign = (parts[3] & 0x80000000) != 0; + var scale = (byte)((parts[3] >> 16) & 0x7F); + + EmitLoadConstantInt(il, parts[0]); + EmitLoadConstantInt(il, parts[1]); + EmitLoadConstantInt(il, parts[2]); + + il.Demit(sign ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); + EmitLoadConstantInt(il, scale); + il.Demit(OpCodes.Conv_U1); + il.Demit(OpCodes.Newobj, _decimalCtor.Value); + } + + private static readonly Lazy _decimalCtor = new Lazy(() => + { + foreach (var ctor in typeof(decimal).GetTypeInfo().DeclaredConstructors) + if (ctor.GetParameters().Length == 5) + return ctor; + return null; + }); + + [MethodImpl((MethodImplOptions)256)] + private static int InitValueTypeVariable(ILGenerator il, Type exprType, int valueVarIndex) + { + Debug.Assert(valueVarIndex != -1); + EmitLoadLocalVariableAddress(il, valueVarIndex); + il.Demit(OpCodes.Initobj, exprType); + return valueVarIndex; + } + + // todo: @perf merge with EmitLoadLocalVariable + private static int InitValueTypeVariable(ILGenerator il, Type exprType) => + InitValueTypeVariable(il, exprType, il.GetNextLocalVarIndex(exprType)); + +#if LIGHT_EXPRESSION + private static bool EmitNewArrayBounds(NewArrayExpression expr, IParameterProvider paramExprs, + ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent) + { + var bounds = (IArgumentProvider)expr; + var boundCount = bounds.ArgumentCount; +#else + private static bool EmitNewArrayBounds(NewArrayExpression expr, IReadOnlyList paramExprs, + ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent) + { + var bounds = expr.Expressions; + var boundCount = bounds.Count; +#endif + if (boundCount == 1) + { + if (!TryEmit(bounds.GetArgument(0), paramExprs, il, ref closure, setup, parent)) + return false; + var elemType = expr.Type.GetElementType(); + if (elemType == null) + return false; + il.Demit(OpCodes.Newarr, elemType); + } + else + { + for (var i = 0; i < boundCount; i++) + if (!TryEmit(bounds.GetArgument(i), paramExprs, il, ref closure, setup, parent)) + return false; + il.Demit(OpCodes.Newobj, expr.Type.GetTypeInfo().DeclaredConstructors.GetFirst()); + } + return true; + } + +#if LIGHT_EXPRESSION + private static bool EmitNewArrayInit(NewArrayExpression expr, IParameterProvider paramExprs, + ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent) + { +#else + private static bool EmitNewArrayInit(NewArrayExpression expr, IReadOnlyList paramExprs, + ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent) + { +#endif + var arrayType = expr.Type; + if (arrayType.GetArrayRank() > 1) + return false; // todo: @feature multi-dimensional array initializers are not supported yet, they also are not supported by the hoisted expression + + var elemType = arrayType.GetElementType(); + if (elemType == null) + return false; + +#if LIGHT_EXPRESSION + var elems = (IArgumentProvider)expr; + var elemCount = elems.ArgumentCount; +#else + var elems = expr.Expressions; + var elemCount = elems.Count; +#endif + EmitLoadConstantInt(il, elemCount); // emit the length of the array calculated from the number of initializer elements + il.Demit(OpCodes.Newarr, elemType); + + var isElemOfValueType = elemType.IsValueType; + for (var i = 0; i < elemCount; i++) + { + il.Demit(OpCodes.Dup); + EmitLoadConstantInt(il, i); + if (isElemOfValueType) // loading element address for later copying of value into it. + { + il.Demit(OpCodes.Ldelema, elemType); + if (!TryEmit(elems.GetArgument(i), paramExprs, il, ref closure, setup, parent)) + return false; + il.Demit(OpCodes.Stobj, elemType); // store element of value type by array element address + } + else + { + if (!TryEmit(elems.GetArgument(i), paramExprs, il, ref closure, setup, parent)) + return false; + il.Demit(OpCodes.Stelem_Ref); + } + } + return true; + } + +#if LIGHT_EXPRESSION + private static bool EmitMemberInit(MemberInitExpression expr, IParameterProvider paramExprs, ILGenerator il, ref ClosureInfo closure, + CompilerFlags setup, ParentFlags parent) +#else + private static bool EmitMemberInit(MemberInitExpression expr, IReadOnlyList paramExprs, ILGenerator il, ref ClosureInfo closure, + CompilerFlags setup, ParentFlags parent) +#endif + { + var valueVarIndex = -1; + var exprType = expr.Type; + if (exprType.IsValueType) + valueVarIndex = il.GetNextLocalVarIndex(exprType); + + var newExpr = expr.NewExpression; +#if LIGHT_EXPRESSION + if (newExpr == null) + { + if (!TryEmit(expr.Expression, paramExprs, il, ref closure, setup, parent)) + return false; + if (valueVarIndex != -1) + EmitStoreLocalVariable(il, valueVarIndex); + } + else +#endif + { +#if SUPPORTS_ARGUMENT_PROVIDER + var argExprs = (IArgumentProvider)newExpr; +#else + var argExprs = newExpr.Arguments; +#endif + var argCount = argExprs.GetCount(); + if (argCount > 0) + { + var args = newExpr.Constructor.GetParameters(); + for (var i = 0; i < argCount; i++) + if (!TryEmit(argExprs.GetArgument(i), paramExprs, il, ref closure, setup, parent, + args[i].ParameterType.IsByRef ? i : -1)) + return false; + } + + if (newExpr.Constructor != null) + { + il.Demit(OpCodes.Newobj, newExpr.Constructor); + if (valueVarIndex != -1) + EmitStoreLocalVariable(il, valueVarIndex); + } + else if (valueVarIndex != -1) + InitValueTypeVariable(il, exprType, valueVarIndex); + else + return false; // null constructor and not a value type, better to fallback + } + +#if LIGHT_EXPRESSION + var bindings = (IArgumentProvider)expr; + var bindCount = bindings.ArgumentCount; +#else + var bindings = expr.Bindings; + var bindCount = bindings.Count; +#endif + for (var i = 0; i < bindCount; i++) + { + var binding = bindings.GetArgument(i); + if (binding.BindingType != MemberBindingType.Assignment) // todo: @feature is not supported yet + return false; + + if (valueVarIndex != -1) // load local value address, to set its members + EmitLoadLocalVariableAddress(il, valueVarIndex); + else + il.Demit(OpCodes.Dup); // duplicate member owner on stack + + if (!TryEmit(((MemberAssignment)binding).Expression, paramExprs, il, ref closure, setup, parent) || + !EmitMemberSet(il, binding.Member)) + return false; + } + + if (valueVarIndex != -1) + EmitLoadLocalVariable(il, valueVarIndex); + return true; + } + + [MethodImpl((MethodImplOptions)256)] + private static bool TryEmitPropertySet(ILGenerator il, PropertyInfo prop) + { + var method = prop.SetMethod; + return method != null && EmitMethodCallOrVirtualCall(il, method); + } + + [MethodImpl((MethodImplOptions)256)] + private static bool EmitFieldSet(ILGenerator il, FieldInfo field) + { + il.Demit(field.IsStatic ? OpCodes.Stsfld : OpCodes.Stfld, field); + return true; + } + + [MethodImpl((MethodImplOptions)256)] + private static bool EmitMemberSet(ILGenerator il, MemberInfo member) => + member is PropertyInfo pr ? TryEmitPropertySet(il, pr) : + member is FieldInfo field ? EmitFieldSet(il, field) : + false; + +#if LIGHT_EXPRESSION + private static bool TryEmitListInit(ListInitExpression expr, IParameterProvider paramExprs, ILGenerator il, ref ClosureInfo closure, + CompilerFlags setup, ParentFlags parent) +#else + private static bool TryEmitListInit(ListInitExpression expr, IReadOnlyList paramExprs, ILGenerator il, ref ClosureInfo closure, + CompilerFlags setup, ParentFlags parent) +#endif + { + var valueVarIndex = -1; + var exprType = expr.Type; + if (exprType.IsValueType) + valueVarIndex = il.GetNextLocalVarIndex(exprType); + + var newExpr = expr.NewExpression; +#if SUPPORTS_ARGUMENT_PROVIDER + var argExprs = (IArgumentProvider)newExpr; +#else + var argExprs = newExpr.Arguments; +#endif + var argCount = argExprs.GetCount(); + if (argCount > 0) + { + var args = newExpr.Constructor.GetParameters(); + for (var i = 0; i < argCount; i++) + if (!TryEmit(argExprs.GetArgument(i), paramExprs, il, ref closure, setup, parent, + args[i].ParameterType.IsByRef ? i : -1)) + return false; + } + + if (newExpr.Constructor != null) + { + il.Demit(OpCodes.Newobj, newExpr.Constructor); + if (valueVarIndex != -1) + EmitStoreLocalVariable(il, valueVarIndex); + } + else if (valueVarIndex != -1) + InitValueTypeVariable(il, exprType, valueVarIndex); + else + return false; // null constructor and not a value type, better to fallback + + var inits = expr.Initializers; + var initCount = inits.Count; + var ok = true; + + // see the TryEmitMethodCall for the reason of the callFlags + var callFlags = (parent + & ~(ParentFlags.IgnoreResult | ParentFlags.MemberAccess | ParentFlags.InstanceAccess | + ParentFlags.LambdaCall | ParentFlags.ReturnByRef)) + | ParentFlags.Call; + for (var i = 0; i < initCount; ++i) + { + if (valueVarIndex != -1) // load local value address, to set its members + EmitLoadLocalVariableAddress(il, valueVarIndex); + else + il.Demit(OpCodes.Dup); // duplicate member owner on stack + + var elemInit = inits.GetArgument(i); + var method = elemInit.AddMethod; + var methodParams = method.GetParameters(); +#if LIGHT_EXPRESSION + var addArgs = (IArgumentProvider)elemInit; + var addArgCount = elemInit.ArgumentCount; +#else + var addArgs = elemInit.Arguments; + var addArgCount = addArgs.Count; +#endif + for (var a = 0; ok && a < addArgCount; ++a) + ok = TryEmit(addArgs.GetArgument(a), paramExprs, il, ref closure, setup, callFlags, methodParams[a].ParameterType.IsByRef ? a : -1); + + if (!exprType.IsValueType) + ok = EmitMethodCallOrVirtualCall(il, method); + else if (!method.IsVirtual // #251 - no need for constrained or virtual call because it is already by-ref + || method.DeclaringType == exprType) + ok = EmitMethodCall(il, method); + else + { + il.Demit(OpCodes.Constrained, exprType); // todo: @clarify it is a value type so... can we de-virtualize the call? + ok = EmitVirtualMethodCall(il, method); + } + } + + if (valueVarIndex != -1) + EmitLoadLocalVariable(il, valueVarIndex); + return ok; + } + + // the `right = null` argument indicates the increment/decrement operation + private static bool TryEmitArithmeticAndOrAssign( + Expression left, Expression right, Type exprType, ExpressionType nodeType, bool isPost, +#if LIGHT_EXPRESSION + IParameterProvider paramExprs, +#else + IReadOnlyList paramExprs, +#endif + ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent) + { + // we need the result variable and the time of Post/Pre when to store it only if the result is not ignored, otherwise don't bother + var resultVar = -1; + if (!parent.IgnoresResult()) + resultVar = il.GetNextLocalVarIndex(exprType); + + switch (left.NodeType) + { + case ExpressionType.Parameter: + if (!isPost) + return TryEmitAssignToParameterOrVariable((ParameterExpression)left, right, + nodeType, isPost, exprType, paramExprs, il, ref closure, setup, parent, resultVar); + + // todo: @better split for now between the Increment/Decrement and the rest + var p = (ParameterExpression)left; +#if LIGHT_EXPRESSION + var paramExprCount = paramExprs.ParameterCount; +#else + var paramExprCount = paramExprs.Count; +#endif + var paramIndex = -1; + var localVarIndex = closure.GetDefinedLocalVarOrDefault(p); + if (localVarIndex != -1) + EmitLoadLocalVariable(il, localVarIndex); + else + { + paramIndex = paramExprCount - 1; + while (paramIndex != -1 && !ReferenceEquals(paramExprs.GetParameter(paramIndex), p)) --paramIndex; + if (paramIndex == -1) + return false; + if ((closure.Status & ClosureStatus.ShouldBeStaticMethod) == 0) + ++paramIndex; + EmitLoadArg(il, paramIndex); + if (p.IsByRef) + EmitLoadIndirectlyByRef(il, p.Type); + } + + if (resultVar != -1 & isPost) + EmitStoreAndLoadLocalVariable(il, resultVar); // for the post increment/decrement save the non-incremented value for the later further use + + EmitIncOrDec(il, nodeType == ExpressionType.Add); + + if (resultVar != -1 & !isPost) + EmitStoreAndLoadLocalVariable(il, resultVar); + + if (localVarIndex != -1) + EmitStoreLocalVariable(il, localVarIndex); // store incremented value into the local value; + else if (p.IsByRef) + { + var incrementedVar = il.GetNextLocalVarIndex(exprType); + EmitStoreLocalVariable(il, incrementedVar); + EmitLoadArg(il, paramIndex); + EmitLoadLocalVariable(il, incrementedVar); + EmitStoreIndirectlyByRef(il, exprType); + } + else + il.Demit(OpCodes.Starg_S, paramIndex); + break; + + case ExpressionType.ArrayIndex: + throw new InvalidOperationException("ArrayIndex is not supported for the left part of the assignment operation. Use Index instead."); + + case ExpressionType.MemberAccess: + case ExpressionType.Index: + var leftMemberExpr = left as MemberExpression; + var orLeftIndexExpr = left as IndexExpression; + + // return early for not supported types of left value to avoid multiple checks below + if (leftMemberExpr == null & orLeftIndexExpr == null) + return false; + + var indexArgCount = -1; +#if SUPPORTS_ARGUMENT_PROVIDER + IArgumentProvider indexArgs = null; +#else + IReadOnlyList indexArgs = null; +#endif + if (orLeftIndexExpr != null) + { +#if SUPPORTS_ARGUMENT_PROVIDER + indexArgs = (IArgumentProvider)orLeftIndexExpr; +#else + indexArgs = orLeftIndexExpr.Arguments; +#endif + indexArgCount = indexArgs.GetCount(); + if (indexArgCount > 4) + return false; // todo: @feature more than 4 index arguments are not supported, and probably not need to be supported + } + + var objExpr = leftMemberExpr != null ? leftMemberExpr.Expression : orLeftIndexExpr.Object; + + // Remove the InstanceCall because we need to operate on the (nullable) field value and not on `ref` to return the value. + // We may avoid it in case of not returning the value or PreIncrement/PreDecrement, but let's do less checks and branching. + var baseFlags = parent & + ~(ParentFlags.IgnoreResult | ParentFlags.InstanceCall | + ParentFlags.LambdaCall | ParentFlags.ReturnByRef); + var rightOnlyFlags = baseFlags | ParentFlags.AssignmentRightValue; + + + var memberOrIndexFlags = leftMemberExpr != null ? ParentFlags.MemberAccess : ParentFlags.IndexAccess; + var leftArLeastFlags = baseFlags | ParentFlags.AssignmentLeftValue | memberOrIndexFlags; + + var leftIsByAddress = false; + if (nodeType == ExpressionType.Assign) + { + Debug.Assert(right != null); + var rightType = right.Type; + + // if the right part is the block or alike, it is better from the complexity perspective + // to emit it first and then restore the assignment target from var and assign the value + var rightVar = -1; + if (right.NodeType.IsBlockLikeOrConditional() || + right.NodeType == ExpressionType.Invoke) + { + if (!TryEmit(right, paramExprs, il, ref closure, setup, rightOnlyFlags)) + return false; + if (closure.LastEmitIsAddress) + EmitLoadIndirectlyByRef(il, rightType); + rightVar = resultVar != -1 ? resultVar : il.GetNextLocalVarIndex(rightType); + EmitStoreLocalVariable(il, rightVar); + } + + // Emit the left-value instance and index(es) (for the index access) + if (leftMemberExpr != null) + { + if (objExpr != null && + !TryEmit(objExpr, paramExprs, il, ref closure, setup, leftArLeastFlags | ParentFlags.InstanceAccess)) + return false; + } + else + { + Debug.Assert(orLeftIndexExpr != null); + if (objExpr != null) + { + var isIndexerAMethodCall = indexArgCount > 1 | orLeftIndexExpr.Indexer != null; + var objFlags = isIndexerAMethodCall ? ParentFlags.InstanceCall : ParentFlags.InstanceAccess | ParentFlags.IndexAccess; + if (!TryEmit(objExpr, paramExprs, il, ref closure, setup, objFlags | ParentFlags.AssignmentLeftValue)) + return false; + } + for (var i = 0; i < indexArgCount; i++) + if (!TryEmit(indexArgs.GetArgument(i), paramExprs, il, ref closure, setup, baseFlags, -1)) + return false; + } + + // Load already emitted or emit the right-value normally after the left to be assigned + if (rightVar != -1) + { + EmitLoadLocalVariable(il, rightVar); + } + else + { + if (!TryEmit(right, paramExprs, il, ref closure, setup, rightOnlyFlags)) + return false; + if (resultVar != -1) + EmitStoreAndLoadLocalVariable(il, resultVar); + } + + if (leftMemberExpr != null) + { + if (!EmitMemberSet(il, leftMemberExpr.Member)) + return false; + } + else // if (leftIndexExpr != null) + { + var ok = orLeftIndexExpr.Indexer != null + ? EmitMethodCallOrVirtualCallCheckForNull(il, orLeftIndexExpr.Indexer.SetMethod) + : indexArgCount == 1 + ? TryEmitArrayIndexSet(il, orLeftIndexExpr.Type) // one-dimensional array + : EmitMethodCallOrVirtualCallCheckForNull(il, objExpr?.Type.FindMethod("Set")); // multi-dimensional array + if (!ok) + return false; + } + + if (resultVar != -1) + EmitLoadLocalVariable(il, resultVar); + return true; + } + + // Here we are at Arithmetic + Assign: + // 1. First loading the left part as a part of right assignment, + // 2. Loading the right value + // 3. Do arithmetic operation + // 4. Storing the result in the local variable + // 5. Loading the left value for assignment + // 6. Loading the stored arithmetic result + // 7. Assign the result + var leftOrRightNullableAreNullLabel = default(Label); + var leftType = left.Type; + if (leftMemberExpr != null) + { + if (!TryEmitMemberGet(leftMemberExpr, paramExprs, il, ref closure, setup, + leftArLeastFlags | ParentFlags.Arithmetic | ParentFlags.DupIt)) + return false; + } + else // if (leftIndexExpr != null) + { + // determine is the index essentially the method call to get/set value + var isIndexerAMethodCall = indexArgCount > 1 | orLeftIndexExpr.Indexer != null; + var objVar = -1; + var objVarByAddress = false; + if (objExpr != null) + { + var objFlags = isIndexerAMethodCall ? ParentFlags.InstanceCall : ParentFlags.InstanceAccess | ParentFlags.IndexAccess; + if (!TryEmit(objExpr, paramExprs, il, ref closure, setup, objFlags | ParentFlags.Arithmetic)) + return false; + + // required for calling the method on the value type parameter + var objType = objExpr.Type; + objVarByAddress = !closure.LastEmitIsAddress && objType.IsValueType && // todo: @better avoid ad-hocking with parameter here + (objExpr.NodeType != ExpressionType.Parameter || !((ParameterExpression)objExpr).IsByRef); + if (objVarByAddress) + objVar = EmitStoreAndLoadLocalVariableAddress(il, objType); + else + { + if (objExpr is ParameterExpression pe && pe.IsByRef) + objType = objType.MakeByRefType(); + objVar = EmitStoreAndLoadLocalVariable(il, objType); + } + } + + int indexArgVar0 = -1, indexArgVar1 = -1, indexArgVar2 = -1, indexArgVar3 = -1; // using stackalloc array? + for (var i = 0; i < indexArgCount; i++) + { + var indexArg = indexArgs.GetArgument(i); + if (!TryEmit(indexArg, paramExprs, il, ref closure, setup, baseFlags)) + return false; + var indexArgVar = EmitStoreAndLoadLocalVariable(il, indexArg.Type); + if (i == 0) indexArgVar0 = indexArgVar; + else if (i == 1) indexArgVar1 = indexArgVar; + else if (i == 2) indexArgVar2 = indexArgVar; + else if (i == 3) indexArgVar3 = indexArgVar; + } + + // repeat the load of the obj and index variables for the assignment here to avoid store and load of the right value + if (objExpr != null) + { + if (!objVarByAddress) + EmitLoadLocalVariable(il, objVar); + else + EmitLoadLocalVariableAddress(il, objVar); + } + + EmitLoadLocalVariable(il, indexArgVar0); // there is always at least one index argument + if (indexArgVar1 != -1) + { + EmitLoadLocalVariable(il, indexArgVar1); + if (indexArgVar2 != -1) + EmitLoadLocalVariable(il, indexArgVar2); + if (indexArgVar3 != -1) + EmitLoadLocalVariable(il, indexArgVar3); + } + + var ok = !isIndexerAMethodCall + ? TryEmitArrayIndexGet(il, orLeftIndexExpr.Type, ref closure, baseFlags) // one-dimensional array + : orLeftIndexExpr.Indexer != null + ? EmitMethodCallOrVirtualCallCheckForNull(il, orLeftIndexExpr.Indexer.GetMethod) + : EmitMethodCallOrVirtualCallCheckForNull(il, objExpr?.Type.FindMethod("Get")); // multi-dimensional array + if (!ok) + return false; + } + + if (leftIsByAddress = closure.LastEmitIsAddress) + EmitLoadIndirectlyByRef(il, leftType); // if the field is loaded by ref, it need to be loaded from the ref in order to do arithmetic operation on it + + var leftIsNullable = leftType.IsNullable(); + if (!leftIsNullable) + { + if (right == null) // optimization for the common increment/decrement case, indicated by using the null for the right argument + { + if (resultVar != -1 & isPost) + EmitStoreAndLoadLocalVariable(il, resultVar); // for the post increment/decrement save the non-incremented value before doing any operation on it + EmitIncOrDec(il, nodeType == ExpressionType.Add); + if (resultVar != -1 & !isPost) + EmitStoreAndLoadLocalVariable(il, resultVar); + } + else + { + var rightType = right.Type; + if (!TryEmit(right, paramExprs, il, ref closure, setup, rightOnlyFlags)) + return false; + + var rightIsNullable = rightType.IsNullable(); + if (rightIsNullable) // todo: @perf @clarify is it even possible to have left non-nullable and right nullable + { + var rightVar = EmitStoreAndLoadLocalVariableAddress(il, rightType); + il.Demit(OpCodes.Call, leftType.GetNullableHasValueGetterMethod()); + + il.Demit(OpCodes.Brfalse, leftOrRightNullableAreNullLabel = il.DefineLabel()); + + EmitLoadLocalVariableAddress(il, rightVar); + il.DemitLdfld(rightType.GetNullableValueUnsafeAkaGetValueOrDefaultMethod()); // unwrap right operand + } + + if (!TryEmitArithmeticOperation(leftType, rightType, nodeType, exprType, il)) + return false; + if (resultVar != -1) + EmitStoreAndLoadLocalVariable(il, resultVar); + } + } + else // if `leftIsNullable == true` + { + // Reuse the result variable for the field, + // so it may be returned as the original value of field if nullable is `null` and we jump to the return + var leftNullableVar = resultVar != -1 ? resultVar : il.GetNextLocalVarIndex(leftType); + EmitStoreLocalVariable(il, leftNullableVar); + + if (right == null) + { + EmitLoadLocalVariableAddress(il, leftNullableVar); + il.Demit(OpCodes.Call, leftType.GetNullableHasValueGetterMethod()); + + il.Demit(OpCodes.Brfalse, leftOrRightNullableAreNullLabel = il.DefineLabel()); + + EmitLoadLocalVariableAddress(il, leftNullableVar); + il.DemitLdfld(leftType.GetNullableValueUnsafeAkaGetValueOrDefaultMethod()); + + EmitIncOrDec(il, nodeType == ExpressionType.Add); + } + else + { + // emit the right expression immediately after the left and then just process their results + var rightType = right.Type; + if (!TryEmit(right, paramExprs, il, ref closure, setup, rightOnlyFlags)) + return false; + if (closure.LastEmitIsAddress) + EmitLoadIndirectlyByRef(il, rightType); + + var rightVar = EmitStoreLocalVariable(il, rightType); + + var rightIsNullable = rightType.IsNullable(); + if (!rightIsNullable) // todo: @perf @clarify if it is possible to have left nullable and right non-nullable + { + EmitLoadLocalVariableAddress(il, leftNullableVar); + il.Demit(OpCodes.Call, leftType.GetNullableHasValueGetterMethod()); + + il.Demit(OpCodes.Brfalse, leftOrRightNullableAreNullLabel = il.DefineLabel()); + + EmitLoadLocalVariableAddress(il, leftNullableVar); + il.DemitLdfld(leftType.GetNullableValueUnsafeAkaGetValueOrDefaultMethod()); + + EmitLoadLocalVariable(il, rightVar); + } + else + { + EmitLoadLocalVariableAddress(il, leftNullableVar); + il.Demit(OpCodes.Call, leftType.GetNullableHasValueGetterMethod()); + + EmitLoadLocalVariableAddress(il, rightVar); + il.Demit(OpCodes.Call, rightType.GetNullableHasValueGetterMethod()); + il.Demit(OpCodes.And); + il.Demit(OpCodes.Brfalse, leftOrRightNullableAreNullLabel = il.DefineLabel()); + + EmitLoadLocalVariableAddress(il, leftNullableVar); + il.DemitLdfld(leftType.GetNullableValueUnsafeAkaGetValueOrDefaultMethod()); // unwrap left operand + + EmitLoadLocalVariableAddress(il, rightVar); + il.DemitLdfld(rightType.GetNullableValueUnsafeAkaGetValueOrDefaultMethod()); // unwrap right operand + } + + if (!TryEmitArithmeticOperation(leftType, rightType, nodeType, exprType, il)) + return false; + } + + il.Demit(OpCodes.Newobj, leftType.GetNullableConstructor()); // wrap the result back into the nullable + if (resultVar != -1 & !isPost) + EmitStoreAndLoadLocalVariable(il, resultVar); + } + + if (leftIsByAddress) + EmitStoreIndirectlyByRef(il, leftType); + else if (leftMemberExpr != null) + { + if (!EmitMemberSet(il, leftMemberExpr.Member)) + return false; + } + else // if (leftIndexExpr != null) + { + var ok = orLeftIndexExpr.Indexer != null + ? EmitMethodCallOrVirtualCallCheckForNull(il, orLeftIndexExpr.Indexer.SetMethod) + : indexArgCount == 1 + ? TryEmitArrayIndexSet(il, orLeftIndexExpr.Type) // one-dimensional array + : EmitMethodCallOrVirtualCallCheckForNull(il, objExpr?.Type.FindMethod("Set")); // multi-dimensional array + if (!ok) + return false; + } + + if (leftIsNullable) + { + // todo: @perf @simplify avoid the Dup and the Pop for this case via storing and loading local var same as in `TryEmitArithmetic` + if (leftIsByAddress | objExpr != null) + { + var skipPopLeftDuppedInstance = il.DefineLabel(); + il.Demit(OpCodes.Br_S, skipPopLeftDuppedInstance); + il.DmarkLabel(leftOrRightNullableAreNullLabel); // jump here if nullables are null after checking them with `!HasValue` + + il.Demit(OpCodes.Pop); // pop the dupped instance address or the field address, or the array instance address + + if (orLeftIndexExpr != null) + { + il.Demit(OpCodes.Pop); // pop the first index argument which is always present + if (indexArgCount > 1) + { + il.Demit(OpCodes.Pop); + if (indexArgCount > 2) + { + il.Demit(OpCodes.Pop); + if (indexArgCount > 3) + il.Demit(OpCodes.Pop); // pop the 4th last supported index argument + } + } + } + + il.DmarkLabel(skipPopLeftDuppedInstance); + } + else + { + il.DmarkLabel(leftOrRightNullableAreNullLabel); // jump here if nullables are null after checking them with `!HasValue` + } + } + break; + default: + return false; + } + + if (resultVar != -1) + EmitLoadLocalVariable(il, resultVar); + return true; + } + + private static ExpressionType AssignToArithmeticOrSelf(ExpressionType nodeType) => nodeType switch + { + ExpressionType.AddAssign => ExpressionType.Add, + ExpressionType.AddAssignChecked => ExpressionType.AddChecked, + ExpressionType.SubtractAssign => ExpressionType.Subtract, + ExpressionType.SubtractAssignChecked => ExpressionType.SubtractChecked, + ExpressionType.MultiplyAssign => ExpressionType.Multiply, + ExpressionType.MultiplyAssignChecked => ExpressionType.MultiplyChecked, + ExpressionType.DivideAssign => ExpressionType.Divide, + ExpressionType.ModuloAssign => ExpressionType.Modulo, + ExpressionType.PowerAssign => ExpressionType.Power, + ExpressionType.AndAssign => ExpressionType.And, + ExpressionType.OrAssign => ExpressionType.Or, + ExpressionType.ExclusiveOrAssign => ExpressionType.ExclusiveOr, + ExpressionType.LeftShiftAssign => ExpressionType.LeftShift, + ExpressionType.RightShiftAssign => ExpressionType.RightShift, + _ => nodeType + }; + + // the null `right` means the increment/decrement operation + private static bool TryEmitAssignToParameterOrVariable( + ParameterExpression left, Expression right, ExpressionType nodeType, bool isPost, Type exprType, +#if LIGHT_EXPRESSION + IParameterProvider paramExprs, +#else + IReadOnlyList paramExprs, +#endif + ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent, int resultVar = -1) + { +#if LIGHT_EXPRESSION + var paramExprCount = paramExprs.ParameterCount; +#else + var paramExprCount = paramExprs.Count; +#endif + var ok = false; + var flags = parent & ~ParentFlags.IgnoreResult; + + // First look if the left value is the local variable (in the current block) then store the right value in it. + var leftLocalVar = closure.GetDefinedLocalVarOrDefault(left); + if (leftLocalVar != -1) + { + if (resultVar != -1 & isPost) + { + EmitLoadLocalVariable(il, leftLocalVar); + EmitStoreLocalVariable(il, resultVar); // for the post increment/decrement save the non-incremented value for the later further use + } + + var isLeftByRef = left.IsByRef; + if (nodeType == ExpressionType.Assign) + { + var varFlags = flags | ParentFlags.AssignmentRightValue; + if (isLeftByRef) + varFlags |= ParentFlags.AssignmentByRef; + ok = TryEmit(right, paramExprs, il, ref closure, setup, varFlags); + if (resultVar != -1 & !isPost) + EmitStoreAndLoadLocalVariable(il, resultVar); + if (!isLeftByRef) + EmitStoreLocalVariable(il, leftLocalVar); + } + else + { + ok = TryEmitArithmetic(left, right, nodeType, exprType, paramExprs, il, ref closure, setup, flags | ParentFlags.AssignmentLeftValue); + if (resultVar != -1 & !isPost) + EmitStoreAndLoadLocalVariable(il, resultVar); + if (isLeftByRef) + EmitStoreIndirectlyByRef(il, exprType); + else + EmitStoreLocalVariable(il, leftLocalVar); + } + + // assigning the new value into the already closed variable - it enables the recursive nested lambda calls, see #353 + var nestedLambdasCount = closure.NestedLambdas.Count; + for (var i = 0; i < nestedLambdasCount; ++i) + EmitStoreAssignedLeftVarIntoClosureArray(il, closure.NestedLambdas.Items[i], left, leftLocalVar); + + if (resultVar != -1) + EmitLoadLocalVariable(il, resultVar); + return ok; + } + + // If not the variable, then look if it is the passed parameter - yes it is bad but you can assign to the parameter + var paramIndex = paramExprCount - 1; + while (paramIndex != -1 && !ReferenceEquals(paramExprs.GetParameter(paramIndex), left)) --paramIndex; + if (paramIndex != -1) + { + if ((closure.Status & ClosureStatus.ShouldBeStaticMethod) == 0) + ++paramIndex; // shift parameter index by one, because the first one will be closure + + var isLeftByRef = left.IsByRef; + if (isLeftByRef) + EmitLoadArg(il, paramIndex); + + if (resultVar != -1 & isPost) + EmitStoreAndLoadLocalVariable(il, resultVar); // for the post increment/decrement save the non-incremented value for the later further use + + ok = nodeType == ExpressionType.Assign + ? TryEmit(right, paramExprs, il, ref closure, setup, flags) + : TryEmitArithmetic(left, right, nodeType, exprType, paramExprs, il, ref closure, setup, flags); + + if (resultVar != -1 & !isPost) + EmitStoreAndLoadLocalVariable(il, resultVar); + + if (isLeftByRef) + EmitStoreIndirectlyByRef(il, left.Type); + else + il.Demit(OpCodes.Starg_S, paramIndex); + + if (resultVar != -1) + EmitLoadLocalVariable(il, resultVar); + return ok; + } + + // check that it is a captured parameter by closure + var nonPassedParamIndex = closure.NonPassedParameters.TryGetIndex(left, default(RefEq)); + if (nonPassedParamIndex == -1) + return false; + + if (nodeType == ExpressionType.Assign) + { + if (!TryEmit(right, paramExprs, il, ref closure, setup, flags)) + return false; + if (right is ParameterExpression rp && rp.IsByRef) + EmitLoadIndirectlyByRef(il, rp.Type); + + var rightVar = resultVar != -1 ? resultVar : il.GetNextLocalVarIndex(exprType); + EmitStoreLocalVariable(il, rightVar); + + // load array field and param item index + il.Demit(OpCodes.Ldarg_0); // load closure as it is always an argument zero + il.DemitLdfld(ArrayClosureWithNonPassedParamsField); + EmitLoadConstantInt(il, nonPassedParamIndex); + + EmitLoadLocalVariable(il, rightVar); + + il.TryEmitBoxOf(exprType); + il.Demit(OpCodes.Stelem_Ref); // put the variable into array + + // assigning the new value into the already closed variable - it enables the recursive nested lambda calls, see #353 + var nestedLambdasCount = closure.NestedLambdas.Count; + for (var i = 0; i < nestedLambdasCount; ++i) + EmitStoreAssignedLeftVarIntoClosureArray(il, closure.NestedLambdas.Items[i], left, rightVar); + + if (resultVar != -1) + EmitLoadLocalVariable(il, rightVar); + return true; + } + + if (resultVar != -1 & isPost) + { + il.Demit(OpCodes.Ldarg_0); // load closure as it is always an argument zero + il.DemitLdfld(ArrayClosureWithNonPassedParamsField); + EmitLoadConstantInt(il, nonPassedParamIndex); + il.Demit(OpCodes.Ldelem_Ref); // load the variable from array + if (exprType.IsValueType) + il.Demit(OpCodes.Unbox_Any, exprType); +#if NETFRAMEWORK + else + il.Demit(OpCodes.Castclass, exprType); +#endif + EmitStoreLocalVariable(il, resultVar); + } + + // todo: @perf optimize for the increment/decrement case + if (!TryEmitArithmetic(left, right, nodeType, exprType, paramExprs, il, ref closure, setup, flags)) + return false; + + var arithmeticResultVar = resultVar != -1 ? resultVar : il.GetNextLocalVarIndex(exprType); + EmitStoreLocalVariable(il, arithmeticResultVar); + + il.Demit(OpCodes.Ldarg_0); // load closure as it is always an argument zero + il.DemitLdfld(ArrayClosureWithNonPassedParamsField); + EmitLoadConstantInt(il, nonPassedParamIndex); + + EmitLoadLocalVariable(il, arithmeticResultVar); + + il.TryEmitBoxOf(exprType); + il.Demit(OpCodes.Stelem_Ref); // put the variable into array + + if (resultVar != -1 & !isPost) + EmitLoadLocalVariable(il, arithmeticResultVar); + return true; + } + + private static void EmitStoreAssignedLeftVarIntoClosureArray(ILGenerator il, NestedLambdaInfo nestedLambdaInfo, ParameterExpression assignedLeftVar, int assignedLeftVarIndex) + { + if (nestedLambdaInfo.NonPassedParamsVarIndex == 0) + return; + var nonPassedParIndex = nestedLambdaInfo.NonPassedParameters.TryGetIndex(assignedLeftVar, default(RefEq)); + if (nonPassedParIndex == -1) + return; + EmitLoadLocalVariable(il, nestedLambdaInfo.NonPassedParamsVarIndex); + EmitLoadConstantInt(il, nonPassedParIndex); + EmitLoadLocalVariable(il, assignedLeftVarIndex); + il.TryEmitBoxOf(assignedLeftVar.Type); + il.Demit(OpCodes.Stelem_Ref); // put the variable into non-passed parameters (variables) array + } + + private static void EmitLoadIndirectlyByRef(ILGenerator il, Type type) + { + if (type.IsEnum) + type = Enum.GetUnderlyingType(type); + switch (Type.GetTypeCode(type)) + { + case TypeCode.Boolean: il.Demit(OpCodes.Ldind_U1); break; + case TypeCode.Char: il.Demit(OpCodes.Ldind_U1); break; + case TypeCode.Byte: il.Demit(OpCodes.Ldind_U1); break; + case TypeCode.SByte: il.Demit(OpCodes.Ldind_I1); break; + case TypeCode.Int16: il.Demit(OpCodes.Ldind_I2); break; + case TypeCode.Int32: il.Demit(OpCodes.Ldind_I4); break; + case TypeCode.Int64: il.Demit(OpCodes.Ldind_I8); break; + case TypeCode.Double: il.Demit(OpCodes.Ldind_R8); break; + case TypeCode.Single: il.Demit(OpCodes.Ldind_R4); break; + case TypeCode.UInt16: il.Demit(OpCodes.Ldind_U2); break; + case TypeCode.UInt32: il.Demit(OpCodes.Ldind_U4); break; + case TypeCode.UInt64: il.Demit(OpCodes.Ldobj, type); break; + case TypeCode.String: il.Demit(OpCodes.Ldind_Ref); break; + default: + if (type == typeof(IntPtr) | type == typeof(UIntPtr)) + il.Demit(OpCodes.Ldind_I); + else if (type.IsValueType) + il.Demit(OpCodes.Ldobj, type); + else + il.Demit(OpCodes.Ldind_Ref); + break; + } + } + + private static void EmitStoreIndirectlyByRef(ILGenerator il, Type type) + { + if (type.IsEnum) + type = Enum.GetUnderlyingType(type); + switch (Type.GetTypeCode(type)) + { + case TypeCode.Boolean: il.Demit(OpCodes.Stind_I1); break; + case TypeCode.Char: il.Demit(OpCodes.Stind_I1); break; + case TypeCode.Byte: il.Demit(OpCodes.Stind_I1); break; + case TypeCode.SByte: il.Demit(OpCodes.Stind_I1); break; + case TypeCode.Int16: il.Demit(OpCodes.Stind_I2); break; + case TypeCode.Int32: il.Demit(OpCodes.Stind_I4); break; + case TypeCode.Int64: il.Demit(OpCodes.Stind_I8); break; + case TypeCode.Double: il.Demit(OpCodes.Stind_R8); break; + case TypeCode.Single: il.Demit(OpCodes.Stind_R4); break; + case TypeCode.String: il.Demit(OpCodes.Stind_Ref); break; + case TypeCode.UInt16: il.Demit(OpCodes.Stind_I2); break; + case TypeCode.UInt32: il.Demit(OpCodes.Stind_I4); break; + case TypeCode.UInt64: il.Demit(OpCodes.Stind_I8); break; + default: + if (type == typeof(IntPtr) || type == typeof(UIntPtr)) + il.Demit(OpCodes.Stind_I); + else if (type.IsValueType) + il.Demit(OpCodes.Stobj, type); + else + il.Demit(OpCodes.Stind_Ref); + break; + } + } + + private static bool TryEmitArrayIndexGet(ILGenerator il, Type type, ref ClosureInfo closure, ParentFlags parent) + { + if (!type.IsValueType) + { + il.Demit(OpCodes.Ldelem_Ref); + return true; + } + + // access the value type by address when it is used later for the member access, as instance in the method call, assigned to the left; + if ((parent & (ParentFlags.MemberAccess | ParentFlags.InstanceAccess | ParentFlags.AssignmentByRef)) != 0) + { + il.Demit(OpCodes.Ldelema, type); + closure.LastEmitIsAddress = true; + return true; + } + // todo: @perf @simplify convert to switch on TypeCode + if (type == typeof(Int32)) + il.Demit(OpCodes.Ldelem_I4); + else if (type == typeof(Int64)) + il.Demit(OpCodes.Ldelem_I8); + else if (type == typeof(Int16)) + il.Demit(OpCodes.Ldelem_I2); + else if (type == typeof(SByte)) + il.Demit(OpCodes.Ldelem_I1); + else if (type == typeof(Single)) + il.Demit(OpCodes.Ldelem_R4); + else if (type == typeof(Double)) + il.Demit(OpCodes.Ldelem_R8); + else if (type == typeof(IntPtr)) + il.Demit(OpCodes.Ldelem_I); + else if (type == typeof(UIntPtr)) + il.Demit(OpCodes.Ldelem_I); + else if (type == typeof(Byte)) + il.Demit(OpCodes.Ldelem_U1); + else if (type == typeof(UInt16)) + il.Demit(OpCodes.Ldelem_U2); + else if (type == typeof(UInt32)) + il.Demit(OpCodes.Ldelem_U4); + else + il.Demit(OpCodes.Ldelem, type); + return true; + } + + private static bool TryEmitArrayIndexSet(ILGenerator il, Type elementType) + { + if (!elementType.IsValueType) + { + il.Demit(OpCodes.Stelem_Ref); + return true; + } + + if (elementType == typeof(Int32)) + il.Demit(OpCodes.Stelem_I4); + else if (elementType == typeof(Int64)) + il.Demit(OpCodes.Stelem_I8); + else if (elementType == typeof(Int16)) + il.Demit(OpCodes.Stelem_I2); + else if (elementType == typeof(SByte)) + il.Demit(OpCodes.Stelem_I1); + else if (elementType == typeof(Single)) + il.Demit(OpCodes.Stelem_R4); + else if (elementType == typeof(Double)) + il.Demit(OpCodes.Stelem_R8); + else if (elementType == typeof(IntPtr)) + il.Demit(OpCodes.Stelem_I); + else if (elementType == typeof(UIntPtr)) + il.Demit(OpCodes.Stelem_I); + else + il.Demit(OpCodes.Stelem, elementType); + return true; + } + + private static bool TryEmitMethodCall(Expression expr, +#if LIGHT_EXPRESSION + IParameterProvider paramExprs, +#else + IReadOnlyList paramExprs, +#endif + ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent, int byRefIndex = -1) + { + var flags = ParentFlags.Call; + var callExpr = (MethodCallExpression)expr; + var objExpr = callExpr.Object; + var method = callExpr.Method; + var methodParams = method.GetParameters(); // todo: @perf @mem find how to avoid the call, look at `NewNoByRefArgs` expressions as example + + var objIsValueType = false; + var loadObjByAddress = false; + if (objExpr != null) + { + if (!TryEmit(objExpr, paramExprs, il, ref closure, setup, flags | ParentFlags.InstanceAccess)) + return false; + objIsValueType = objExpr.Type.IsValueType; + loadObjByAddress = objIsValueType && objExpr.NodeType != ExpressionType.Parameter && !closure.LastEmitIsAddress; + } + + var parCount = methodParams.Length; + if (parCount == 0) + { + if (loadObjByAddress) + EmitStoreAndLoadLocalVariableAddress(il, objExpr.Type); + } + else + { +#if SUPPORTS_ARGUMENT_PROVIDER + var callArgs = (IArgumentProvider)callExpr; +#else + var callArgs = callExpr.Arguments; +#endif + if (!closure.ArgsContainingComplexExpression.Map.ContainsKey(callExpr)) + { + if (loadObjByAddress) + EmitStoreAndLoadLocalVariableAddress(il, objExpr.Type); + + for (var i = 0; i < parCount; i++) + { + var argExpr = callArgs.GetArgument(i); + var parType = methodParams[i].ParameterType; + if (!TryEmit(argExpr, paramExprs, il, ref closure, setup, flags, parType.IsByRef ? i : -1)) + return false; + } + } + else + { + // don't forget to store the object into the variable first, before emitting the arguments + var objVar = objExpr == null ? -1 : EmitStoreLocalVariable(il, objExpr.Type); + + SmallList, NoArrayPool> argVars = default; + for (var i = 0; i < methodParams.Length; i++) + { + var argExpr = callArgs.GetArgument(i); + var parType = methodParams[i].ParameterType; + if (!TryEmit(argExpr, paramExprs, il, ref closure, setup, flags, parType.IsByRef ? i : -1)) + return false; + argVars.Add(EmitStoreLocalVariable(il, parType)); + } + + // restore the object and the args from the variables in the proper order to emit the call + if (objExpr != null) + { + if (loadObjByAddress) + EmitLoadLocalVariableAddress(il, objVar); + else + EmitLoadLocalVariable(il, objVar); + } + + for (var i = 0; i < methodParams.Length; i++) + EmitLoadLocalVariable(il, argVars[i]); + } + } + + var ok = true; + if (!objIsValueType) + ok = EmitMethodCallOrVirtualCall(il, method); + else if (method.DeclaringType != typeof(Enum) && + (!method.IsVirtual || + method.DeclaringType == objExpr.Type || + objExpr is ParameterExpression pe && pe.IsByRef)) + ok = EmitMethodCall(il, method); + else + { + il.Demit(OpCodes.Constrained, objExpr.Type); + ok = EmitVirtualMethodCall(il, method); + } + + if (byRefIndex != -1) + EmitStoreAndLoadLocalVariableAddress(il, method.ReturnType); + + if (parent.IgnoresResult() && method.ReturnType != typeof(void)) + il.Demit(OpCodes.Pop); + + closure.LastEmitIsAddress = false; + return ok; + } + + public static bool TryEmitMemberGet(MemberExpression expr, +#if LIGHT_EXPRESSION + IParameterProvider paramExprs, +#else + IReadOnlyList paramExprs, +#endif + ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent, int byRefIndex = -1) + { + var objExpr = expr.Expression; + if (expr.Member is PropertyInfo prop) + { + if (objExpr != null) + { + var p = (parent | ParentFlags.InstanceCall) + // removing ParentFlags.MemberAccess here because we are calling the method instead of accessing the field + & ~(ParentFlags.IgnoreResult | ParentFlags.MemberAccess | ParentFlags.DupIt | + ParentFlags.LambdaCall | ParentFlags.ReturnByRef); + + if (!TryEmit(objExpr, paramExprs, il, ref closure, setup, p)) + return false; + + if ((parent & ParentFlags.DupIt) != 0) // just duplicate the whatever is emitted for object + il.Demit(OpCodes.Dup); + else + // Value type special treatment to load address of value instance in order to call a method. + // For the parameters, we will skip the address loading because the `LastEmitIsAddress == true` for `Ldarga`, + // so the condition here will be skipped + if (!closure.LastEmitIsAddress && objExpr.Type.IsValueType && !(objExpr is PE pe && pe.IsByRef)) + EmitStoreAndLoadLocalVariableAddress(il, objExpr.Type); + } + + closure.LastEmitIsAddress = false; + return EmitMethodCallOrVirtualCall(il, prop.GetMethod); + } + + if (expr.Member is FieldInfo field) + { + if (objExpr != null) + { + var p = (parent | ParentFlags.InstanceAccess | ParentFlags.MemberAccess) + & ~ParentFlags.IgnoreResult & ~ParentFlags.DupIt; + + if (!TryEmit(objExpr, paramExprs, il, ref closure, setup, p)) + return false; + + if (parent.IgnoresResult()) + { + il.Demit(OpCodes.Pop); // pop the obj value - it is emitted only for the side effects + return true; + } + + // #248 indicates that expression is argument passed by ref to Call + var isByAddress = byRefIndex != -1; + + // we are assigning to the field of ValueType so we need its address `val.Bar += 1`, #352 + if ((parent & ParentFlags.AssignmentLeftValue) != 0 && objExpr.Type.IsValueType) + isByAddress = true; + else + // if the field is not used as an index, #302 + // or if the field is not accessed from the just constructed object `new Widget().DodgyValue`, #333 + if (((parent & ParentFlags.InstanceAccess) != 0 & + (parent & ParentFlags.IndexAccess) == 0) && field.FieldType.IsValueType) + isByAddress = true; + + // we don't need to duplicate the instance if we are working with the field address to save to it directly, + // so the field address should be Dupped instead for loading and then storing by-ref for assignment (see below). + // (don't forget to Pop if the assignment should be skipped for nullable with `null` value) + if ((parent & ParentFlags.DupIt) != 0 & + (!isByAddress | (parent & ParentFlags.AssignmentLeftValue) == 0)) + il.Demit(OpCodes.Dup); + + closure.LastEmitIsAddress = isByAddress; + if (!isByAddress) + { + if (objExpr.Type.IsEnum) + EmitStoreAndLoadLocalVariableAddress(il, objExpr.Type); + il.DemitLdfld(field); + } + else + { + il.DemitLdflda(field); + if ((parent & ParentFlags.AssignmentLeftValue) != 0) + il.Demit(OpCodes.Dup); + } + } + else if (field.IsLiteral) + { + if (parent.IgnoresResult()) + return true; // do nothing + var fieldValue = field.GetValue(null); + if (fieldValue != null) + return TryEmitConstant(false, null, field.FieldType, fieldValue, il, ref closure); + il.Demit(OpCodes.Ldnull); + } + else + { + if (parent.IgnoresResult()) + return true; // do nothing + il.DemitLdsfld(field); + } + return true; + } + return false; + } + + // ReSharper disable once FunctionComplexityOverflow +#if LIGHT_EXPRESSION + private static bool TryEmitNestedLambda(LambdaExpression lambdaExpr, IParameterProvider outerParamExprs, ILGenerator il, ref ClosureInfo closure) + { + var outerParamExprCount = outerParamExprs.ParameterCount; +#else + private static bool TryEmitNestedLambda(LambdaExpression lambdaExpr, IReadOnlyList outerParamExprs, ILGenerator il, ref ClosureInfo closure) + { + var outerParamExprCount = outerParamExprs.Count; +#endif + // First, find in closed compiled lambdas the one corresponding to the current lambda expression. + // Situation with not found lambda is not possible/exceptional, + // it means that we somehow skipped the lambda expression while collecting closure info. + var outerNestedLambdasCount = closure.NestedLambdas.Count; + var outerNestedLambdas = closure.NestedLambdas.Items; + var i = outerNestedLambdasCount - 1; + while (i != -1 && !outerNestedLambdas[i].HasTheSameLambdaExpression(lambdaExpr)) --i; + if (i == -1) + return false; + + ref var nestedLambdaInfo = ref outerNestedLambdas[i]; + EmitLoadLocalVariable(il, nestedLambdaInfo.LambdaVarIndex); + + // If lambda does not use any outer parameters to be set in closure, then we're done + var nonPassedParams = nestedLambdaInfo.NonPassedParameters; + if (nonPassedParams.Count == 0) + return true; + + //------------------------------------------------------------------- + // For the lambda with non-passed parameters (or variables) in closure + + // Emit the NonPassedParams array for the non-passed parameters and variables + var nonPassedParamsCount = nonPassedParams.Count; + EmitLoadConstantInt(il, nonPassedParamsCount); // load the length of array + il.Demit(OpCodes.Newarr, typeof(object)); + var nonPassedParamsVarIndex = EmitStoreAndLoadLocalVariable(il, typeof(object[])); + nestedLambdaInfo.NonPassedParamsVarIndex = (short)nonPassedParamsVarIndex; + + // Store the NonPassedParams back into the NestedLambda wrapper for the #437. + // Also, it is needed to be able to assign the closed variable after the closure is passed to the lambda, + // e.g. `var x = 1; var f = () => x + 1; x = 2; f();` expects 3, not 2 + il.DemitStfld(NestedLambdaForNonPassedParams.NonPassedParamsField); + + // Populate the NonPassedParams array + for (var nestedParamIndex = 0; nestedParamIndex < nonPassedParamsCount; ++nestedParamIndex) + { + // todo: @wip move this code to where the assignment and by-ref parameter passing + Debug.Assert(nestedParamIndex < 64, "Assume that we don't have more than 64 mutated non-passed parameters"); + if (nonPassedParamsCount < 64) + nestedLambdaInfo.NonPassedParamMutatedIndexBits |= 1UL << nestedParamIndex; + + // Load the array and index where to store the item + EmitLoadLocalVariable(il, nonPassedParamsVarIndex); + EmitLoadConstantInt(il, nestedParamIndex); + + var nestedParam = nonPassedParams.GetSurePresentRef(nestedParamIndex); + var outerParamIndex = outerParamExprCount - 1; + while (outerParamIndex != -1 && !ReferenceEquals(outerParamExprs.GetParameter(outerParamIndex), nestedParam)) + --outerParamIndex; + if (outerParamIndex != -1) // load parameter from input outer params + { + // Add `+1` to index because the `0` index is for the closure argument + EmitLoadArg(il, outerParamIndex + 1); + il.TryEmitBoxOf(nestedParam.Type); + } + else // load parameter from outer closure or from the local variables + { + var outerLocalVarIndex = closure.GetDefinedLocalVarOrDefault(nestedParam); + if (outerLocalVarIndex != -1) // it's a local variable + { + EmitLoadLocalVariable(il, outerLocalVarIndex); + il.TryEmitBoxOf(nestedParam.Type); + } + else // it's a parameter from the outer closure + { + var outerNonPassedParamIndex = closure.NonPassedParameters.TryGetIndex(nestedParam, default(RefEq)); + if (outerNonPassedParamIndex == -1) + return false; // impossible, return error code 2 the same as in TryCollectInfo + + // Load the parameter from outer closure `Items` array + il.Demit(OpCodes.Ldarg_0); // closure is always a first argument + il.DemitLdfld(ArrayClosureWithNonPassedParamsField); + EmitLoadConstantInt(il, outerNonPassedParamIndex); + il.Demit(OpCodes.Ldelem_Ref); + } + } + + // Store the item into nested lambda array + il.Demit(OpCodes.Stelem_Ref); + } + + // Load the actual lambda delegate on stack + EmitLoadLocalVariable(il, nestedLambdaInfo.LambdaVarIndex); + il.DemitLdfld(NestedLambdaForNonPassedParams.NestedLambdaField); + + // Load the nonPassedParams as a first argument of closure + EmitLoadLocalVariable(il, nonPassedParamsVarIndex); + + // Load the constants as a second argument and call the closure constructor + var lambda = nestedLambdaInfo.Lambda; + if (lambda is NestedLambdaForNonPassedParamsWithConstants) + { + EmitLoadLocalVariable(il, nestedLambdaInfo.LambdaVarIndex); + il.DemitLdfld(NestedLambdaForNonPassedParamsWithConstants.ConstantsAndNestedLambdasField); + il.Demit(OpCodes.Newobj, ArrayClosureWithNonPassedParamsAndConstantsCtor); + } + else + il.Demit(OpCodes.Newobj, ArrayClosureWithNonPassedParamsCtor); + + // Call the `Curry` method with the nested lambda and closure to produce a closed lambda with the expected signature + var lambdaType = (lambda is NestedLambdaForNonPassedParams lp ? lp.NestedLambda : lambda).GetType(); + var lambdaTypeArgs = lambdaType.GetGenericArguments(); + var nestedLambdaExpr = nestedLambdaInfo.LambdaExpression; + var closureMethod = nestedLambdaExpr.ReturnType == typeof(void) + ? CurryClosureActions.Methods[lambdaTypeArgs.Length - 1].MakeGenericMethod(lambdaTypeArgs) + : CurryClosureFuncs.Methods[lambdaTypeArgs.Length - 2].MakeGenericMethod(lambdaTypeArgs); + + var ok = EmitMethodCall(il, closureMethod); + + // Convert to the original possibly custom delegate type, see #308 + if (closureMethod.ReturnType != nestedLambdaExpr.Type) + { + il.Demit(OpCodes.Ldftn, closureMethod.ReturnType.FindDelegateInvokeMethod()); + il.Demit(OpCodes.Newobj, nestedLambdaExpr.Type.GetConstructors()[0]); + } + + return ok; + } + +#if LIGHT_EXPRESSION + private static bool TryEmitInvoke(InvocationExpression expr, IParameterProvider paramExprs, ILGenerator il, ref ClosureInfo closure, + CompilerFlags setup, ParentFlags parent) + { + var paramCount = paramExprs.ParameterCount; +#else + private static bool TryEmitInvoke(InvocationExpression expr, IReadOnlyList paramExprs, ILGenerator il, ref ClosureInfo closure, + CompilerFlags setup, ParentFlags parent) + { + var paramCount = paramExprs.Count; +#endif +#if SUPPORTS_ARGUMENT_PROVIDER + var argExprs = (IArgumentProvider)expr; +#else + var argExprs = expr.Arguments; +#endif + var argCount = argExprs.GetCount(); + var invokedExpr = expr.Expression; + if ((setup & CompilerFlags.NoInvocationLambdaInlining) == 0 && invokedExpr is LambdaExpression lambdaExpr) + { + parent |= ParentFlags.InlinedLambdaInvoke; + + ref var inlinedExpr = ref closure.InlinedLambdaInvocation.Map.AddOrGetValueRef(expr, out var found); + Debug.Assert(found, "The invocation expression should be collected in TryCollectInfo but it is not"); + if (!found) + return false; + + if (!TryEmit(inlinedExpr, paramExprs, il, ref closure, setup, parent)) + return false; + + if ((parent & ParentFlags.IgnoreResult) == 0 && inlinedExpr.Type != typeof(void)) + { + // find if the variable with the result is exist in the label infos + ref var label = ref closure.LambdaInvokeStackLabels.GetLabelOrInvokeIndexByTarget(expr, out var labelFound); + if (labelFound) + { + var returnVariableIndexPlusOne = label.ReturnVariableIndexPlusOneAndIsDefined >>> 1; + if (returnVariableIndexPlusOne != 0) + { + il.DmarkLabel(label.ReturnLabel); + EmitLoadLocalVariable(il, returnVariableIndexPlusOne - 1); + } + } + } + return true; + } + + if (!TryEmit(invokedExpr, paramExprs, il, ref closure, setup, parent & ~ParentFlags.IgnoreResult)) // removing the IgnoreResult temporary because we need "full" lambda emit and we will re-apply the IgnoreResult later at the end of the method + return false; + + //if (lambda is ConstantExpression lambdaConst) // todo: @perf opportunity to optimize + // delegateInvokeMethod = ((Delegate)lambdaConst.Value).GetMethodInfo(); + //else + var delegateInvokeMethod = invokedExpr.Type.FindDelegateInvokeMethod(); // todo: @perf bad thingy + if (argCount != 0) + { + var useResult = parent & ~ParentFlags.IgnoreResult & ~ParentFlags.InstanceAccess; + var args = delegateInvokeMethod.GetParameters(); // todo: @perf avoid this if possible + for (var i = 0; i < args.Length; ++i) + { + var argExpr = argExprs.GetArgument(i); + if (!TryEmit(argExpr, paramExprs, il, ref closure, setup, useResult, args[i].ParameterType.IsByRef ? i : -1)) + return false; + } + } + + EmitMethodCall(il, delegateInvokeMethod); + if ((parent & ParentFlags.IgnoreResult) != 0 && delegateInvokeMethod.ReturnType != typeof(void)) + il.Demit(OpCodes.Pop); + + return true; + } + + private static Label[][] _labelPool = null; + + private struct TestValueAndMultiTestCaseIndex + { + public long Value; + public int CaseBodyIdx; + public int MultiTestValCaseBodyIdxPlusOne; // 0 means not multi-test case, otherwise index+1 + } + + private static long ConvertValueObjectToLong(object valObj) + { + Debug.Assert(valObj != null); + var type = valObj.GetType(); + type = type.IsEnum ? Enum.GetUnderlyingType(type) : type; + return Type.GetTypeCode(type) switch + { + TypeCode.Char => (long)(char)valObj, + TypeCode.SByte => (long)(sbyte)valObj, + TypeCode.Byte => (long)(byte)valObj, + TypeCode.Int16 => (long)(short)valObj, + TypeCode.UInt16 => (long)(ushort)valObj, + TypeCode.Int32 => (long)(int)valObj, + TypeCode.UInt32 => (long)(uint)valObj, + TypeCode.Int64 => (long)valObj, + TypeCode.UInt64 => (long)(ulong)valObj, + _ => 0 // unreachable + }; + } + +#if LIGHT_EXPRESSION + private static bool TryEmitSwitch(SwitchExpression expr, IParameterProvider paramExprs, ILGenerator il, ref ClosureInfo closure, + CompilerFlags setup, ParentFlags parent) +#else + private static bool TryEmitSwitch(SwitchExpression expr, IReadOnlyList paramExprs, ILGenerator il, ref ClosureInfo closure, + CompilerFlags setup, ParentFlags parent) +#endif + { + var switchValueExpr = expr.SwitchValue; + var customEqualMethod = expr.Comparison; + var cases = expr.Cases; + var caseCount = cases.Count; + var defaultBody = expr.DefaultBody; + + // Optimization for the single case + if (caseCount == 1 & defaultBody != null) + { + var cs0 = cases[0]; + if (cs0.TestValues.Count == 1) + { + Expression testExpr; + if (customEqualMethod == null) + { + // todo: @perf avoid creation of the additional expression + testExpr = Equal(switchValueExpr, cs0.TestValues[0]); + if (Interpreter.TryInterpretBool(out var testResult, testExpr, setup)) + return TryEmit(testResult ? cs0.Body : defaultBody, paramExprs, il, ref closure, setup, parent); + } + else + testExpr = Call(customEqualMethod, switchValueExpr, cs0.TestValues[0]); + + return TryEmitConditional(testExpr, cs0.Body, defaultBody, paramExprs, il, ref closure, setup, parent); + } + } + + var switchValueType = switchValueExpr.Type; + var switchValueParent = parent & ~ParentFlags.IgnoreResult & ~ParentFlags.InstanceAccess; + + // Check the pre-requisite conditions and emit OpCodes.Switch if possible, see #398 + const int minSwitchTableSize = 3; + Debug.Assert(minSwitchTableSize >= 3); // The later code will assume at least so few of the cases count + if (customEqualMethod == null && caseCount >= minSwitchTableSize && switchValueType.IsIntegerOrUnderlyingInteger()) + { + var multipleTestValuesLabelsId = 0; +#if TESTING || DEBUG + SmallList, NoArrayPool> switchValues = default; +#else + SmallList, NoArrayPool> switchValues = default; +#endif + for (var caseIndex = 0; caseIndex < caseCount; ++caseIndex) + { + var testValues = cases[caseIndex].TestValues; + var testValueCount = testValues.Count; + var id = testValueCount > 1 ? ++multipleTestValuesLabelsId : 0; + + for (var v = 0; v < testValueCount; ++v) + { + var testValExpr = testValues[v]; + if (testValExpr is not ConstantExpression && testValExpr is not DefaultExpression) + { + Debug.Assert(false, $"Not supported non-constant,non-default switch case value expression: `{testValExpr}`"); + return false; + } + var testValueLong = testValExpr is ConstantExpression constExpr ? ConvertValueObjectToLong(constExpr.Value) : 0L; + + // Adding a free slot for the new or for the shifted max value + ref var freeValRef = ref switchValues.AddDefaultAndGetRef(); // the default value is (0,0) + var lastIndex = switchValues.Count - 1; + while (true) + { + --lastIndex; + if (lastIndex == -1) + { + freeValRef.Value = testValueLong; + freeValRef.CaseBodyIdx = caseIndex; + freeValRef.MultiTestValCaseBodyIdxPlusOne = id; + break; + } + + ref var valRef = ref switchValues.GetSurePresentRef(lastIndex); + Debug.Assert(valRef.Value != testValueLong, "Duplicate test value in switch case"); + + // Shift current value further to get space for the smaller new value + if (testValueLong < valRef.Value) + { + freeValRef = valRef; // shift the value by copying it into the free slot + freeValRef = ref valRef; // set the free slot reference to the current value + } + else + { + lastIndex = 0; // stop searching + } + } + } + } + + // Let's analyze the switch values for the starting value and the gaps. + // We require no more than nonContValueCountMax non-continuous values and no larger gap than the valueGapMin for the rest, + // to consider the values for the switch table. Because otherwise the table will be filled with gaps and + // will become too large for the final optimization goal. + // Note that we cannot check the gaps earlier when collecting the values because the gaps may be filled at the end. + const int valueGapMin = 4; + const int nonContValueCountMax = 3; + var contValueCount = 0; + var valuesConditionsMet = true; + var actualSwitchTableSize = 1; // start from the 1 and then add the gaps + + var firstTestValueIdx = 0; + var switchValuesCount = switchValues.Count; + var lastTestValueIdx = switchValuesCount - 1; + + // Check for the 1 or 2 outliers with the big gap only if there are 1/2 + minSwitchTableSize, otherwise the table is too small and just count the outlier toward the nonContValueCountMax gaps. + if (switchValuesCount >= 1 + minSwitchTableSize) + { + if (switchValues.GetSurePresentRef(1).Value - switchValues.GetSurePresentRef(0).Value > valueGapMin) + firstTestValueIdx = 1; + + if (firstTestValueIdx == 0 || switchValuesCount >= 2 + minSwitchTableSize) { + if (switchValues.GetSurePresentRef(lastTestValueIdx).Value - switchValues.GetSurePresentRef(lastTestValueIdx - 1).Value > valueGapMin) + --lastTestValueIdx; + } + } + Debug.Assert(lastTestValueIdx - firstTestValueIdx + 1 >= minSwitchTableSize, "Postcondition: switch table between outliers should be of enough size"); + var prevSwitchValue = switchValues.GetSurePresentRef(firstTestValueIdx).Value; + for (var i = firstTestValueIdx + 1; i <= lastTestValueIdx; ++i) + { + var currValue = switchValues.GetSurePresentRef(i).Value; + var valueGap = currValue - prevSwitchValue; + Debug.Assert(valueGap > 0, $"The values should be in increased order and non-equal but found {currValue} <= {prevSwitchValue}"); + + if (valueGap > valueGapMin || + valueGap > 1 && ++contValueCount > nonContValueCountMax) + { + valuesConditionsMet = false; + break; + } + + prevSwitchValue = currValue; + actualSwitchTableSize += (int)valueGap; + } + + if (valuesConditionsMet) + { + Debug.Assert(actualSwitchTableSize >= lastTestValueIdx - firstTestValueIdx + 1, // take outliers into account + $"The switch table size should be at least as large as the case count, but found {actualSwitchTableSize} < {lastTestValueIdx - firstTestValueIdx + 1}"); + + var endOfSwitchLabel = il.DefineLabel(); + var defaultBodyLabel = defaultBody == null ? endOfSwitchLabel : il.DefineLabel(); + +#if TESTING || DEBUG + SmallList, NoArrayPool> sameCaseLabelIndexes = default; +#else + SmallList, NoArrayPool> sameCaseLabelIndexes = default; +#endif + sameCaseLabelIndexes.InitCount(multipleTestValuesLabelsId); // may stay empty if id is 0 + +#if TESTING || DEBUG + // Test the pool at work + _labelPool ??= new Label[8][]; +#endif + // Let define and collect the labels that we will be using for switch table and optional outliers + ProvidedArrayPool> labelPool = default; + labelPool.Init(_labelPool, 8); + var switchTableLabels = labelPool.RentExactOrNew(actualSwitchTableSize); + + // Define outliers labels + Label minOutlierLabel = default, maxOutlierLabel = default; + int minOutlierMultiTestCaseBodyIdx = -1, maxOutlierMultiTestCaseBodyIdx = -1; + int minOutlierCaseBodyIdx = -1, maxOutlierCaseBodyIdx = -1; + if (firstTestValueIdx == 1) + { + ref var minOutlier = ref switchValues.GetSurePresentRef(0); + minOutlierCaseBodyIdx = minOutlier.CaseBodyIdx; + minOutlierMultiTestCaseBodyIdx = minOutlier.MultiTestValCaseBodyIdxPlusOne - 1; + minOutlierLabel = il.DefineLabel(); // define the label that later can be shared with others in switch table or with max outlier + } + if (lastTestValueIdx == switchValuesCount - 2) + { + ref var maxOutlier = ref switchValues.GetSurePresentRef(switchValuesCount - 1); + maxOutlierCaseBodyIdx = maxOutlier.CaseBodyIdx; + maxOutlierMultiTestCaseBodyIdx = maxOutlier.MultiTestValCaseBodyIdxPlusOne - 1; + maxOutlierLabel = maxOutlierMultiTestCaseBodyIdx != -1 && maxOutlierMultiTestCaseBodyIdx == minOutlierMultiTestCaseBodyIdx + ? minOutlierLabel : il.DefineLabel(); // Check if the max outlier share the label with some and if this some is the min outlier + } + + // Let's start with the continuous values from the start + var firstTestValue = switchValues.GetSurePresentRef(firstTestValueIdx).Value; + prevSwitchValue = firstTestValue - 1; // init the prev value to by 1 before the first for calc purposes + var switchTableIndex = 0; + for (var v = firstTestValueIdx; v <= lastTestValueIdx; ++v) + { + var currSwitchVal = switchValues.GetSurePresentRef(v); + var currSwitchValue = currSwitchVal.Value; + var valueGap = currSwitchValue - prevSwitchValue; + if (valueGap > 1) + { + var endOfGap = switchTableIndex + valueGap - 1; + while (switchTableIndex < endOfGap) + switchTableLabels[switchTableIndex++] = defaultBodyLabel; // the label will be same as endOfSwitchLabel if no default body present + } + + var caseIndex = currSwitchVal.MultiTestValCaseBodyIdxPlusOne - 1; + if (caseIndex != -1) + { + Debug.Assert(caseIndex < sameCaseLabelIndexes.Count, "Invalid MultiTestValCaseBodyIdxPlusOne in switch case values"); + ref var labelIndexPlusOneRef = ref sameCaseLabelIndexes.GetSurePresentRef(caseIndex); + if (labelIndexPlusOneRef > 0) + { + switchTableLabels[switchTableIndex] = switchTableLabels[labelIndexPlusOneRef - 1]; + } + else if (caseIndex == minOutlierMultiTestCaseBodyIdx | caseIndex == maxOutlierMultiTestCaseBodyIdx) + { + switchTableLabels[switchTableIndex] = caseIndex == minOutlierMultiTestCaseBodyIdx ? minOutlierLabel : maxOutlierLabel; + labelIndexPlusOneRef = switchTableIndex + 1; + } + else + { + switchTableLabels[switchTableIndex] = il.DefineLabel(); + labelIndexPlusOneRef = switchTableIndex + 1; + } + } + else + switchTableLabels[switchTableIndex] = il.DefineLabel(); + + prevSwitchValue = currSwitchVal.Value; + ++switchTableIndex; + } + + // Emit the switch value here only after we sure about proceeding with switch table + if (!TryEmit(switchValueExpr, paramExprs, il, ref closure, setup, switchValueParent, -1)) + return false; + + // Emit the min outlier before the switch table + var switchValVar = -1; + if (firstTestValueIdx == 1) + { + // store and use the switch value over in case of the outlier, otherwise just use whatever is on stack from the prev TryEmit + EmitStoreAndLoadLocalVariable(il, switchValVar = il.GetNextLocalVarIndex(switchValueType)); + EmitLoadConstantLong(il, switchValues.GetSurePresentRef(0).Value); // Load the min outlier + il.Demit(OpCodes.Beq, minOutlierLabel); + EmitLoadLocalVariable(il, switchValVar); // Load the switch var on stack for the next operation + } + else if (lastTestValueIdx == switchValuesCount - 2) // if the max outlier is defined, store and load the switch value + { + EmitStoreAndLoadLocalVariable(il, switchValVar = il.GetNextLocalVarIndex(switchValueType)); + } + + // Before emitting switch we need to normalize the switch value to start from zero + if (firstTestValue != 0) + { + EmitLoadConstantLong(il, firstTestValue); + il.Demit(firstTestValue > 0 ? OpCodes.Sub : OpCodes.Add); + } + + // Emit the switch instruction + il.DemitSwitch(switchTableLabels); + + // Emit the max outlier after the switch table + if (lastTestValueIdx == switchValuesCount - 2) + { + Debug.Assert(switchValVar != -1); + EmitLoadLocalVariable(il, switchValVar); + EmitLoadConstantLong(il, switchValues.GetSurePresentRef(switchValuesCount - 1).Value); // Load the max outlier + il.Demit(OpCodes.Beq, maxOutlierLabel); // If switch value matches the outlier break to its label + } + + // For the values OUTSIDE of switch table range, immediately branch to the default or to the end of the switch + il.Demit(OpCodes.Br, defaultBodyLabel); // the label is the same as endOfSwitchLabel if no default body present + + for (var i = 0; i < caseCount; ++i) + { + var cs = cases[i]; + if (i == minOutlierCaseBodyIdx | i == maxOutlierCaseBodyIdx) + il.DmarkLabel(i == minOutlierCaseBodyIdx ? minOutlierLabel : maxOutlierLabel); + else + { + // First test value is enough to find the corresponding label in switch table to mark the case body + // because all test values share the same case body and the same label, so the first test value will do. + var testValExpr = cs.TestValues[0]; + var testValue = testValExpr is ConstantExpression constExpr ? ConvertValueObjectToLong(constExpr.Value) : 0L; + var labelIndex = (int)(testValue - firstTestValue); + il.DmarkLabel(switchTableLabels[labelIndex]); + } + + if (!TryEmit(cs.Body, paramExprs, il, ref closure, setup, parent)) + return false; + il.Demit(OpCodes.Br, endOfSwitchLabel); + } + + labelPool.ReuseIfPossible(switchTableLabels); + labelPool.MergeInto(ref _labelPool); + + if (defaultBody != null) + { + il.DmarkLabel(defaultBodyLabel); + if (!TryEmit(defaultBody, paramExprs, il, ref closure, setup, parent)) + return false; + } + + il.DmarkLabel(endOfSwitchLabel); + return true; + } + } + + var switchValueIsNullable = switchValueType.IsNullable(); + Type switchNullableUnderlyingValueType = null; + MethodInfo switchNullableHasValueMethod = null; + FieldInfo switchNullableUnsafeValueField = null; + if (switchValueIsNullable) + { + switchNullableUnderlyingValueType = Nullable.GetUnderlyingType(switchValueType); + switchNullableHasValueMethod = switchValueType.GetNullableHasValueGetterMethod(); + switchNullableUnsafeValueField = switchValueType.GetNullableValueUnsafeAkaGetValueOrDefaultMethod(); + } + + var checkType = switchNullableUnderlyingValueType ?? switchValueType; + var equalityMethod = customEqualMethod != null + ? customEqualMethod + : !checkType.IsPrimitive && !checkType.IsEnum + ? FindBinaryOperandMethod("op_Equality", switchValueType, switchValueType, switchValueType, typeof(bool)) + ?? _objectEqualsMethod + : null; + + var isEqualityMethodForUnderlyingNullable = false; + int param0ByRefIndex = -1, param1ByRefIndex = -1; + if (equalityMethod != null) + { + switchValueParent |= ParentFlags.Call; + var paramInfos = equalityMethod.GetParameters(); + Debug.Assert(paramInfos.Length == 2); + var paramType = paramInfos[0].ParameterType; + isEqualityMethodForUnderlyingNullable = paramType == switchNullableUnderlyingValueType; + if (paramType.IsByRef) + param0ByRefIndex = 0; + if (paramInfos[1].ParameterType.IsByRef) + param1ByRefIndex = 1; + } + + // Emit the switch value once and store it in the local variable for comparison in cases below + if (!TryEmit(switchValueExpr, paramExprs, il, ref closure, setup, switchValueParent, param0ByRefIndex)) + return false; + + if (caseCount == 0) // see #440 + { + il.Demit(OpCodes.Pop); // remove the switch value result + return defaultBody == null || TryEmit(defaultBody, paramExprs, il, ref closure, setup, parent); + } + + var switchValueVar = EmitStoreLocalVariable(il, switchValueType); + + var switchEndLabel = il.DefineLabel(); + +#if TESTING || DEBUG + // Check the heap/pool part of the labels in debug mode + SmallList, ProvidedArrayPool>> caseLabels = default; +#else + SmallList, ProvidedArrayPool>> caseLabels = default; +#endif + caseLabels.Pool.Init(_labelPool, 8); + + for (var caseIndex = 0; caseIndex < caseCount; ++caseIndex) + { + var cs = cases[caseIndex]; + var caseBodyLabel = il.DefineLabel(); + caseLabels.Add(caseBodyLabel); + + foreach (var caseTestValue in cs.TestValues) + { + if (!switchValueIsNullable) + { + EmitLoadLocalVariable(il, switchValueVar); + if (!TryEmit(caseTestValue, paramExprs, il, ref closure, setup, switchValueParent, param1ByRefIndex)) + return false; + if (equalityMethod == null) + { + il.Demit(OpCodes.Beq, caseBodyLabel); + continue; + } + + if (!EmitMethodCall(il, equalityMethod)) + return false; + il.Demit(OpCodes.Brtrue, caseBodyLabel); + continue; + } + + if (equalityMethod != null & !isEqualityMethodForUnderlyingNullable) + { + EmitLoadLocalVariable(il, switchValueVar); + if (!TryEmit(caseTestValue, paramExprs, il, ref closure, setup, switchValueParent, param1ByRefIndex) || + !EmitMethodCall(il, equalityMethod)) + return false; + il.Demit(OpCodes.Brtrue, caseBodyLabel); + continue; + } + + if (equalityMethod == null) + { + // short-circuit the comparison with the null, if the switch value has value == false the let's do a Brfalse + if (caseTestValue is ConstantExpression r && r.Value == null) + { + EmitLoadLocalVariableAddress(il, switchValueVar); + EmitMethodCall(il, switchNullableHasValueMethod); + il.Demit(OpCodes.Brfalse, caseBodyLabel); + continue; + } + } + + // Compare the switch value with the case value via Ceq or comparison method and then compare the HasValue of both + EmitLoadLocalVariableAddress(il, switchValueVar); + il.DemitLdfld(switchNullableUnsafeValueField); + if (!TryEmit(caseTestValue, paramExprs, il, ref closure, setup, switchValueParent, param1ByRefIndex)) + return false; + var caseValueVar = EmitStoreAndLoadLocalVariableAddress(il, switchValueType); + il.DemitLdfld(switchNullableUnsafeValueField); + if (equalityMethod == null) + il.Demit(OpCodes.Ceq); + else if (!EmitMethodCall(il, equalityMethod)) + return false; + + EmitLoadLocalVariableAddress(il, switchValueVar); + EmitMethodCall(il, switchNullableHasValueMethod); + EmitLoadLocalVariableAddress(il, caseValueVar); + EmitMethodCall(il, switchNullableHasValueMethod); + il.Demit(OpCodes.Ceq); + + il.Demit(OpCodes.And); // both the Nullable values and HashValue results need to be true + il.Demit(OpCodes.Brtrue, caseBodyLabel); + } + } + + if (defaultBody != null && !TryEmit(defaultBody, paramExprs, il, ref closure, setup, parent)) + return false; + il.Demit(OpCodes.Br, switchEndLabel); + + for (var caseIndex = 0; caseIndex < caseCount; ++caseIndex) + { + il.DmarkLabel(caseLabels[caseIndex]); + var cs = cases[caseIndex]; + if (!TryEmit(cs.Body, paramExprs, il, ref closure, setup, parent)) + return false; + + il.Demit(OpCodes.Br, switchEndLabel); + } + + caseLabels.FreePooled(); + caseLabels.Pool.MergeInto(ref _labelPool); + + il.DmarkLabel(switchEndLabel); + return true; + } + + // todo: @perf cache found method, because for some cases there many methods to search from, e.g. 157 methods in BigInteger + private static MethodInfo FindBinaryOperandMethod( + string methodName, Type sourceType, Type leftOpType, Type rightOpType, Type resultType) + { + var methods = sourceType.GetMethods(); + for (var i = 0; i < methods.Length; i++) + { + var m = methods[i]; + if (m.IsSpecialName && m.IsStatic && m.Name == methodName && m.ReturnType == resultType) + { + var ps = m.GetParameters(); + if (ps.Length == 2 && ps[0].ParameterType == leftOpType && ps[1].ParameterType == rightOpType) + return m; + } + } + return null; + } + + private static bool TryEmitComparison( + Expression left, Expression right, Type exprType, ExpressionType nodeType, +#if LIGHT_EXPRESSION + IParameterProvider paramExprs, +#else + IReadOnlyList paramExprs, +#endif + ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent) + { + var leftType = left.Type; + var leftIsNullable = leftType.IsNullable(); + var rightType = right.Type; + + // If one operand is `null` then the equality comparison can be simplified to `ldnull, ceq` + var rightIsNull = IsNullContainingExpression(right); + var comparingToRightNull = rightIsNull & rightType.IsClass; + + // Coalesce the right object type to the more specific left type + if (comparingToRightNull & rightType == typeof(object)) + rightType = leftType; + + var leftIsNull = IsNullContainingExpression(left); + var comparingToLeftNull = leftIsNull & leftType.IsClass; + if (!comparingToRightNull && comparingToLeftNull & leftType == typeof(object)) + leftType = rightType; + + var operandParent = parent & ~ParentFlags.IgnoreResult & ~ParentFlags.InstanceAccess; + + // short-circuit the comparison with null on the right + var isEqualityOp = nodeType == ExpressionType.Equal | nodeType == ExpressionType.NotEqual; + if (isEqualityOp) + { + if (leftIsNullable & rightIsNull) + { + if (!TryEmit(left, paramExprs, il, ref closure, setup, operandParent)) + return false; + + // See #341 `Nullable_decimal_parameter_with_decimal_constant_comparison_cases` + if (!closure.LastEmitIsAddress && !(left is ParameterExpression p && p.IsByRef)) // Nullable type does not track IsByRef for some reason, so we check the param explicitly, see #461 `Case_equal_nullable_and_object_null` + EmitStoreAndLoadLocalVariableAddress(il, leftType); + + EmitMethodCall(il, leftType.GetNullableHasValueGetterMethod()); + if (nodeType == ExpressionType.Equal) + EmitEqualToZeroOrNull(il); + return il.EmitPopIfIgnoreResult(parent); + } + + if (leftIsNull && rightType.IsNullable()) + { + if (!TryEmit(right, paramExprs, il, ref closure, setup, operandParent)) + return false; + + if (!closure.LastEmitIsAddress && !(right is ParameterExpression p && p.IsByRef)) + EmitStoreAndLoadLocalVariableAddress(il, rightType); + + EmitMethodCall(il, rightType.GetNullableHasValueGetterMethod()); + if (nodeType == ExpressionType.Equal) + EmitEqualToZeroOrNull(il); + return il.EmitPopIfIgnoreResult(parent); + } + } + + var lVarIndex = -1; + var rightIsComplexExpression = false; + // just load the `null` later when done with the right operand, without need for go to nested TryEmit call + // and store, load the left result for the complex expressions, see `IsComplexExpression` and #422 + if (!leftIsNull) + { + if (!TryEmit(left, paramExprs, il, ref closure, setup, operandParent)) + return false; + + // save the left result to restore it later after the complex expression, see #422 + if (rightIsComplexExpression = right.IsComplexExpression()) + lVarIndex = EmitStoreLocalVariable(il, leftType); + else if (leftIsNullable) + { + lVarIndex = EmitStoreAndLoadLocalVariableAddress(il, leftType); + il.DemitLdfld(leftType.GetNullableValueUnsafeAkaGetValueOrDefaultMethod()); + leftType = Nullable.GetUnderlyingType(leftType); + } + } + + if (rightIsNull) + il.Demit(OpCodes.Ldnull); + else if (!TryEmit(right, paramExprs, il, ref closure, setup, operandParent)) + return false; + + if (comparingToLeftNull | comparingToRightNull || + (leftType != rightType && leftType.IsClass && rightType.IsClass && + (leftType == typeof(object) | rightType == typeof(object)))) + { + // If the operation is not Equal or NotEqual then comparison with null is not possible + if (!isEqualityOp) + return false; + + if (leftIsNull) + il.Demit(OpCodes.Ldnull); + else if (rightIsComplexExpression) + EmitLoadLocalVariable(il, lVarIndex); // the order of comparison does not matter, because equality ops are commutative + + il.Demit(OpCodes.Ceq); + if (nodeType == ExpressionType.NotEqual) + EmitEqualToZeroOrNull(il); + + return il.EmitPopIfIgnoreResult(parent); + } + + var rVarIndex = -1; + if (rightIsComplexExpression) + { + rVarIndex = EmitStoreLocalVariable(il, rightType); + if (!leftIsNullable) + EmitLoadLocalVariable(il, lVarIndex); + else + { + EmitLoadLocalVariableAddress(il, lVarIndex); + il.DemitLdfld(leftType.GetNullableValueUnsafeAkaGetValueOrDefaultMethod()); + leftType = Nullable.GetUnderlyingType(leftType); + } + + if (!rightType.IsNullable()) + EmitLoadLocalVariable(il, rVarIndex); + else + { + EmitLoadLocalVariableAddress(il, rVarIndex); + il.DemitLdfld(rightType.GetNullableValueUnsafeAkaGetValueOrDefaultMethod()); + rightType = Nullable.GetUnderlyingType(rightType); + } + } + else if (leftIsNull) + { + // here we're handling only non-nullable right, the nullable right with null left is handled above + rVarIndex = EmitStoreLocalVariable(il, rightType); + il.Demit(OpCodes.Ldnull); + EmitLoadLocalVariable(il, rVarIndex); + } + else if (rightType.IsNullable()) + { + rVarIndex = EmitStoreAndLoadLocalVariableAddress(il, rightType); + il.DemitLdfld(rightType.GetNullableValueUnsafeAkaGetValueOrDefaultMethod()); + rightType = Nullable.GetUnderlyingType(rightType); + } + + if (!leftType.IsPrimitive && !leftType.IsEnum) + { + var methodName + = nodeType == ExpressionType.Equal ? "op_Equality" + : nodeType == ExpressionType.NotEqual ? "op_Inequality" + : nodeType == ExpressionType.GreaterThan ? "op_GreaterThan" + : nodeType == ExpressionType.GreaterThanOrEqual ? "op_GreaterThanOrEqual" + : nodeType == ExpressionType.LessThan ? "op_LessThan" + : nodeType == ExpressionType.LessThanOrEqual ? "op_LessThanOrEqual" + : null; + if (methodName == null) + return false; + + var method = FindBinaryOperandMethod(methodName, leftType, leftType, rightType, typeof(bool)); + if (method == null & leftType != rightType) + method = FindBinaryOperandMethod(methodName, rightType, leftType, rightType, typeof(bool)); + if (method != null) + { + var ok = EmitMethodCall(il, method); + if (leftIsNullable) + goto nullableCheck; + return ok; + } + + if (!isEqualityOp) + return false; // todo: @unclear what is the alternative? + + EmitMethodCall(il, _objectEqualsMethod); + if (nodeType == ExpressionType.NotEqual) // invert result for not equal + EmitEqualToZeroOrNull(il); + + if (leftIsNullable) + goto nullableCheck; + + return il.EmitPopIfIgnoreResult(parent); + } + + // handle primitives comparison + switch (nodeType) + { + case ExpressionType.Equal: + il.Demit(OpCodes.Ceq); + break; + case ExpressionType.NotEqual: + il.Demit(OpCodes.Ceq); + EmitEqualToZeroOrNull(il); + break; + case ExpressionType.LessThan: + il.Demit(OpCodes.Clt); + break; + case ExpressionType.GreaterThan: + il.Demit(OpCodes.Cgt); + break; + case ExpressionType.GreaterThanOrEqual: + // simplifying by using the LessThen (Clt) and comparing with negative outcome (Ceq 0) + if (leftType.IsUnsigned() && rightType.IsUnsigned() || + (leftType.IsFloatingPoint() || rightType.IsFloatingPoint())) + il.Demit(OpCodes.Clt_Un); + else + il.Demit(OpCodes.Clt); + EmitEqualToZeroOrNull(il); + break; + case ExpressionType.LessThanOrEqual: + // simplifying by using the GreaterThen (Cgt) and comparing with negative outcome (Ceq 0) + if (leftType.IsUnsigned() && rightType.IsUnsigned() || + (leftType.IsFloatingPoint() || rightType.IsFloatingPoint())) + il.Demit(OpCodes.Cgt_Un); + else + il.Demit(OpCodes.Cgt); + EmitEqualToZeroOrNull(il); + break; + + default: + return false; + } + + nullableCheck: + if (leftIsNullable) + { + var leftNullableHasValueGetterMethod = left.Type.GetNullableHasValueGetterMethod(); // asking from the left.Type because leftType now is set to the underlying type + + EmitLoadLocalVariableAddress(il, lVarIndex); + EmitMethodCall(il, leftNullableHasValueGetterMethod); + + var isLiftedToNull = exprType == typeof(bool?); + var leftHasValueVar = -1; + if (isLiftedToNull) + EmitStoreAndLoadLocalVariable(il, leftHasValueVar = il.GetNextLocalVarIndex(typeof(bool))); + + // ReSharper disable once AssignNullToNotNullAttribute + EmitLoadLocalVariableAddress(il, rVarIndex); + EmitMethodCall(il, leftNullableHasValueGetterMethod); + + var rightHasValueVar = -1; + if (isLiftedToNull) + EmitStoreAndLoadLocalVariable(il, rightHasValueVar = il.GetNextLocalVarIndex(typeof(bool))); + + switch (nodeType) + { + case ExpressionType.Equal: + il.Demit(OpCodes.Ceq); // compare both HasValue calls + il.Demit(OpCodes.And); // both results need to be true + break; + + case ExpressionType.NotEqual: + il.Demit(OpCodes.Ceq); + EmitEqualToZeroOrNull(il); + il.Demit(OpCodes.Or); + break; + + case ExpressionType.LessThan: + case ExpressionType.GreaterThan: + case ExpressionType.LessThanOrEqual: + case ExpressionType.GreaterThanOrEqual: + // left.HasValue `and` right.HasValue + il.Demit(OpCodes.And); + // `and` the prev result of comparison operation + il.Demit(OpCodes.And); + break; + + default: + return false; + } + + if (isLiftedToNull) + { + var resultLabel = il.DefineLabel(); + var isNullLabel = il.DefineLabel(); + EmitLoadLocalVariable(il, leftHasValueVar); + il.Demit(OpCodes.Brfalse, isNullLabel); + EmitLoadLocalVariable(il, rightHasValueVar); + il.Demit(OpCodes.Brtrue, resultLabel); + il.DmarkLabel(isNullLabel); + il.Demit(OpCodes.Pop); + il.Demit(OpCodes.Ldnull); + il.DmarkLabel(resultLabel); + } + } + + return il.EmitPopIfIgnoreResult(parent); + } + + private static bool TryEmitArithmetic(Expression left, Expression right, ExpressionType nodeType, Type exprType, +#if LIGHT_EXPRESSION + IParameterProvider paramExprs, +#else + IReadOnlyList paramExprs, +#endif + ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent) + { + var flags = (parent + & ~(ParentFlags.IgnoreResult | ParentFlags.InstanceCall | + ParentFlags.LambdaCall | ParentFlags.ReturnByRef)) + | ParentFlags.Arithmetic; + + var noNullableValueLabel = default(Label); + var leftType = left.Type; + var leftIsNullable = leftType.IsNullable(); + var leftVar = -1; + var leftValueVar = -1; + if (leftIsNullable) + { + noNullableValueLabel = il.DefineLabel(); + if (!TryEmit(left, paramExprs, il, ref closure, setup, flags | ParentFlags.InstanceCall)) + return false; + + leftVar = EmitStoreAndLoadLocalVariableAddress(il, leftType); + EmitMethodCall(il, leftType.GetNullableHasValueGetterMethod()); + il.Demit(OpCodes.Brfalse, noNullableValueLabel); + + EmitLoadLocalVariableAddress(il, leftVar); + il.DemitLdfld(leftType.GetNullableValueUnsafeAkaGetValueOrDefaultMethod()); + leftValueVar = EmitStoreLocalVariable(il, Nullable.GetUnderlyingType(leftType)); + } + else if (!TryEmit(left, paramExprs, il, ref closure, setup, flags)) + return false; + + var rightIsNullable = false; + if (right == null) // indicates the increment/decrement operation + { + EmitIncOrDec(il, nodeType == ExpressionType.Add); + } + else + { + // Stores the left value for later to restore it after the complex right emit, + // it prevents the problems in cases of right being a block, try-catch, etc. + // see `Using_try_finally_as_arithmetic_operand_use_void_block_in_finally` + var rightType = right.Type; + if (leftValueVar == -1 && right.IsComplexExpression()) + leftValueVar = EmitStoreLocalVariable(il, leftType); + + var rightVar = -1; + var rightValueVar = -1; + rightIsNullable = rightType.IsNullable(); + if (rightIsNullable) + { + if (!TryEmit(right, paramExprs, il, ref closure, setup, flags | ParentFlags.InstanceCall)) + return false; + + rightVar = EmitStoreAndLoadLocalVariableAddress(il, rightType); + + EmitMethodCall(il, rightType.GetNullableHasValueGetterMethod()); + il.Demit(OpCodes.Brfalse, noNullableValueLabel); + + EmitLoadLocalVariableAddress(il, rightVar); + il.DemitLdfld(rightType.GetNullableValueUnsafeAkaGetValueOrDefaultMethod()); + rightValueVar = EmitStoreLocalVariable(il, Nullable.GetUnderlyingType(rightType)); + } + else if (!TryEmit(right, paramExprs, il, ref closure, setup, flags)) + return false; + + // Means that it was complex right and the result of the left operation was stored + // and should be restored now, so the left and right go in order before the arithmetic operation + if (leftValueVar != -1) + { + if (rightValueVar == -1) + rightValueVar = EmitStoreLocalVariable(il, rightType); + EmitLoadLocalVariable(il, leftValueVar); + EmitLoadLocalVariable(il, rightValueVar); + } + + if (!TryEmitArithmeticOperation(leftType, rightType, nodeType, exprType, il)) + return false; + } + + if (leftIsNullable | rightIsNullable) + { + var valueLabel = il.DefineLabel(); + il.Demit(OpCodes.Br, valueLabel); + + il.DmarkLabel(noNullableValueLabel); + + if (exprType.IsNullable()) + { + EmitLoadLocalVariable(il, InitValueTypeVariable(il, exprType)); + var endLabel = il.DefineLabel(); + il.Demit(OpCodes.Br_S, endLabel); + il.DmarkLabel(valueLabel); + il.Demit(OpCodes.Newobj, exprType.GetNullableConstructor()); + il.DmarkLabel(endLabel); + } + else + { + il.Demit(OpCodes.Ldc_I4_0); + il.DmarkLabel(valueLabel); + } + } + + il.EmitPopIfIgnoreResult(parent); + return true; + } + + private static MethodInfo _stringStringConcatMethod, _stringObjectConcatMethod; + private static MethodInfo GetStringConcatMethod(Type paraType) + { + var methods = typeof(string).GetMethods(); + for (var i = 0; i < methods.Length; i++) + { + var m = methods[i]; + if (m.IsStatic && m.Name == "Concat" && + m.GetParameters().Length == 2 && m.GetParameters()[0].ParameterType == paraType) + return m; + } + return null; + } + + private static bool TryEmitArithmeticOperation(Type leftType, Type rightType, ExpressionType arithmeticNodeType, Type exprType, ILGenerator il) + { + if (!exprType.IsPrimitive) + { + if (exprType.IsNullable()) + exprType = Nullable.GetUnderlyingType(exprType); + + if (!exprType.IsPrimitive) + { + var opMethodName = arithmeticNodeType.GetArithmeticBinaryOperatorMethodName(); + if (opMethodName == null) + return false; // todo: @feature should return specific error + + MethodInfo method = null; + if (exprType != typeof(string)) + { + // Note, that the result operation Type may be different from the operand Type, + // e.g. `TimeSpan op_Subtraction(DateTime, DateTime)`, that mean we should look + // for the specific method in the operand types, then in the result (expr) type. + method = FindBinaryOperandMethod(opMethodName, leftType, leftType, rightType, exprType); + if (method == null & leftType != rightType) + method = FindBinaryOperandMethod(opMethodName, rightType, leftType, rightType, exprType); + if (method == null & leftType != exprType & rightType != exprType) + method = FindBinaryOperandMethod(opMethodName, exprType, leftType, rightType, exprType); + // todo: @feature should return specific error + return method != null && EmitMethodCall(il, method); + } + + method = leftType != rightType | leftType != typeof(string) + ? _stringObjectConcatMethod ?? (_stringObjectConcatMethod = GetStringConcatMethod(typeof(object))) + : _stringStringConcatMethod ?? (_stringStringConcatMethod = GetStringConcatMethod(typeof(string))); + + return method != null && EmitMethodCallOrVirtualCall(il, method); + } + } + + var opCode = arithmeticNodeType switch + { + ExpressionType.Add => OpCodes.Add, + ExpressionType.AddChecked => exprType.IsUnsigned() ? OpCodes.Add_Ovf_Un : OpCodes.Add_Ovf, + ExpressionType.Subtract => OpCodes.Sub, + ExpressionType.SubtractChecked => exprType.IsUnsigned() ? OpCodes.Sub_Ovf_Un : OpCodes.Sub_Ovf, + ExpressionType.Multiply => OpCodes.Mul, + ExpressionType.MultiplyChecked => exprType.IsUnsigned() ? OpCodes.Mul_Ovf_Un : OpCodes.Mul_Ovf, + ExpressionType.Divide => OpCodes.Div, + ExpressionType.Modulo => OpCodes.Rem, + ExpressionType.And => OpCodes.And, + ExpressionType.Or => OpCodes.Or, + ExpressionType.ExclusiveOr => OpCodes.Xor, + ExpressionType.LeftShift => OpCodes.Shl, + ExpressionType.RightShift => exprType.IsUnsigned() ? OpCodes.Shr_Un : OpCodes.Shr, + ExpressionType.Power => OpCodes.Call, + _ => throw new NotSupportedException("Unsupported arithmetic operation: " + arithmeticNodeType) + }; + + if (opCode.Equals(OpCodes.Call)) + il.Demit(OpCodes.Call, typeof(Math).FindMethod("Pow")); + else + il.Demit(opCode); + return true; + } + + private static bool TryEmitLogicalOperator(BinaryExpression expr, ExpressionType nodeType, +#if LIGHT_EXPRESSION + IParameterProvider paramExprs, +#else + IReadOnlyList paramExprs, +#endif + ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent) + { + if (!TryEmit(expr.Left, paramExprs, il, ref closure, setup, parent)) + return false; + + var labelSkipRight = il.DefineLabel(); + il.Demit(nodeType == ExpressionType.AndAlso ? OpCodes.Brfalse : OpCodes.Brtrue, labelSkipRight); + + if (!TryEmit(expr.Right, paramExprs, il, ref closure, setup, parent)) + return false; + + var labelDone = il.DefineLabel(); + il.Demit(OpCodes.Br, labelDone); + + il.DmarkLabel(labelSkipRight); // label the second branch + il.Demit(nodeType == ExpressionType.AndAlso ? OpCodes.Ldc_I4_0 : OpCodes.Ldc_I4_1); + il.DmarkLabel(labelDone); + + return true; + } + + private static bool IsNullContainingExpression(Expression expr) => + expr is DefaultExpression ld && (ld.Type.IsClass || ld.Type.IsNullable()) || + expr is ConstantExpression lc && lc.Value == null; + + private static bool TryEmitConditional( + Expression testExpr, Expression ifTrueExpr, Expression ifFalseExpr, + // Type type, // todo: @wip what about the type, what if it is a void? +#if LIGHT_EXPRESSION + IParameterProvider paramExprs, +#else + IReadOnlyList paramExprs, +#endif + ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent) + { + testExpr = Tools.TryReduceConditionalTest(testExpr); + var testNodeType = testExpr.NodeType; + + // Detect a simplistic case when we can use `Brtrue` or `Brfalse`. + // We are checking the negative result to go into the `IfFalse` branch, + // because for `IfTrue` we don't need to jump and just need to proceed emitting the `IfTrue` expression + // + // The cases: + // `x == true` => `Brfalse` + // `x != true` => `Brtrue` + // `x == false` => `Brtrue` + // `x != false` => `Brfalse` + // `x == null` => `Brtrue` + // `x != null` => `Brfalse` + // `x == 0` => `Brtrue` + // `x != 0` => `Brfalse` + + var useBrFalseOrTrue = -1; // 0 - is comparison with Zero (0, null, false), 1 - is comparison with (true) + Type nullOfValueType = null; + if (testExpr is BinaryExpression tb && + (testNodeType == ExpressionType.Equal | testNodeType == ExpressionType.NotEqual)) + { + var testLeftExpr = tb.Left; + var testRightExpr = tb.Right; + + Expression oppositeTestExpr = null; + var sideConstExpr = testRightExpr as ConstantExpression ?? testLeftExpr as ConstantExpression; + if (sideConstExpr != null) + { + oppositeTestExpr = sideConstExpr == testLeftExpr ? testRightExpr : testLeftExpr; + var sideConstVal = sideConstExpr.Value; + if (sideConstVal == null) // todo: @perf we need to optimize for the Default as well + { + useBrFalseOrTrue = 0; + if (oppositeTestExpr.Type.IsNullable()) + nullOfValueType = oppositeTestExpr.Type; + } + else if (sideConstVal is bool boolConst) + useBrFalseOrTrue = boolConst ? 1 : 0; + else if (sideConstVal is int intConst && intConst == 0) + useBrFalseOrTrue = 0; // Brtrue does not work for `1`, you need to use Beq, or similar + else if (sideConstVal is byte bytConst && bytConst == 0) + useBrFalseOrTrue = 0; + } + else + { + var sideDefaultExpr = testRightExpr as DefaultExpression ?? testLeftExpr as DefaultExpression; + if (sideDefaultExpr != null) + { + oppositeTestExpr = sideDefaultExpr == testLeftExpr ? testRightExpr : testLeftExpr; + var testSideType = sideDefaultExpr.Type; + // except decimal, because its 0 is Decimal.Zero a struct and is not working with Brtrue/Brfalse + if (testSideType.IsPrimitiveWithZeroDefaultExceptDecimal()) + useBrFalseOrTrue = 0; + else if (testSideType.IsClass || testSideType.IsNullable()) + { + useBrFalseOrTrue = 0; + if (oppositeTestExpr.Type.IsNullable()) + nullOfValueType = oppositeTestExpr.Type; + } + } + } + + if (useBrFalseOrTrue != -1 && + !TryEmit(oppositeTestExpr, paramExprs, il, ref closure, setup, parent & ~ParentFlags.IgnoreResult)) + return false; + } + + if (useBrFalseOrTrue == -1 && + !TryEmit(testExpr, paramExprs, il, ref closure, setup, parent & ~ParentFlags.IgnoreResult)) + return false; + + if (nullOfValueType != null) + { + if (!closure.LastEmitIsAddress) + EmitStoreAndLoadLocalVariableAddress(il, nullOfValueType); + EmitMethodCall(il, nullOfValueType.GetNullableHasValueGetterMethod()); + } + + var labelIfFalse = il.DefineLabel(); + + // todo: @perf try to recognize the patterns like `b == 1` and replace Ceq, Brtrue with Beq, and respectively Ceq, Brfalse with Bne_Un + // for this we need to inline here the logic from the TryEmitComparison. + if ((testNodeType == ExpressionType.Equal & useBrFalseOrTrue == 0) || + (testNodeType == ExpressionType.NotEqual & useBrFalseOrTrue == 1)) + il.Demit(OpCodes.Brtrue, labelIfFalse); + else + il.Demit(OpCodes.Brfalse, labelIfFalse); + + if (!TryEmit(ifTrueExpr, paramExprs, il, ref closure, setup, parent)) + return false; + + if (ifFalseExpr.NodeType == ExpressionType.Default && ifFalseExpr.Type == typeof(void)) + il.DmarkLabel(labelIfFalse); + else + { + var labelDone = il.DefineLabel(); + il.Demit(OpCodes.Br, labelDone); + il.DmarkLabel(labelIfFalse); + if (!TryEmit(ifFalseExpr, paramExprs, il, ref closure, setup, parent)) + return false; + il.DmarkLabel(labelDone); + } + return true; + } + + [MethodImpl((MethodImplOptions)256)] + public static void EmitEqualToZeroOrNull(ILGenerator il) + { + il.Demit(OpCodes.Ldc_I4_0); // OpCodes.Not does not work here because it is a bitwise operation + il.Demit(OpCodes.Ceq); + } + + /// Get the advantage of the optimized specialized EmitCall method + [MethodImpl((MethodImplOptions)256)] + public static bool EmitMethodCallOrVirtualCall(ILGenerator il, MethodInfo method) + { + il.Demit(method.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, method); + // todo: @feature EmitCall is specifically for the varags method and not for normal C# conventions methods, + // for those you need to call Emit(OpCodes.Call|Callvirt, methodInfo). + // So for now the varargs methods are not supported yet. + return (method.CallingConvention & CallingConventions.VarArgs) == 0; + } + + [MethodImpl((MethodImplOptions)256)] + public static bool EmitVirtualMethodCall(ILGenerator il, MethodInfo method) + { + il.Demit(OpCodes.Callvirt, method); + // todo: @feature EmitCall is specifically for the varags method and not for normal C# conventions methods, + // for those you need to call Emit(OpCodes.Call|Callvirt, methodInfo). + // So for now the varargs methods are not supported yet. + return (method.CallingConvention & CallingConventions.VarArgs) == 0; + } + + [MethodImpl((MethodImplOptions)256)] + public static bool EmitMethodCall(ILGenerator il, MethodInfo method) + { + il.Demit(OpCodes.Call, method); + // todo: @feature EmitCall is specifically for the varags method and not for normal C# conventions methods, + // for those you need to call Emit(OpCodes.Call|Callvirt, methodInfo). + // So for now the varargs methods are not supported yet. + return (method.CallingConvention & CallingConventions.VarArgs) == 0; + } + + /// Same as EmitMethodCall which checks the method for null first, and returns false if it is null. + [MethodImpl((MethodImplOptions)256)] + public static bool EmitMethodCallCheckForNull(ILGenerator il, MethodInfo method) => + method != null && EmitMethodCall(il, method); + + /// Same as EmitMethodCallOrVirtualCall which checks the method for null first, and returns false if it is null. + [MethodImpl((MethodImplOptions)256)] + public static bool EmitMethodCallOrVirtualCallCheckForNull(ILGenerator il, MethodInfo method) => + method != null && EmitMethodCallOrVirtualCall(il, method); + + /// Efficiently emit the int constant + [MethodImpl((MethodImplOptions)256)] + public static void EmitLoadConstantInt(ILGenerator il, int i) + { + switch (i) + { + case -1: il.Demit(OpCodes.Ldc_I4_M1); break; + case 0: il.Demit(OpCodes.Ldc_I4_0); break; + case 1: il.Demit(OpCodes.Ldc_I4_1); break; + case 2: il.Demit(OpCodes.Ldc_I4_2); break; + case 3: il.Demit(OpCodes.Ldc_I4_3); break; + case 4: il.Demit(OpCodes.Ldc_I4_4); break; + case 5: il.Demit(OpCodes.Ldc_I4_5); break; + case 6: il.Demit(OpCodes.Ldc_I4_6); break; + case 7: il.Demit(OpCodes.Ldc_I4_7); break; + case 8: il.Demit(OpCodes.Ldc_I4_8); break; + default: + if (i > -129 && i < 128) + il.Demit(OpCodes.Ldc_I4_S, (sbyte)i); + else + il.Demit(OpCodes.Ldc_I4, i); + break; + } + } + + /// Efficiently emit the long constant based on its value as int or long + [MethodImpl((MethodImplOptions)256)] + public static void EmitLoadConstantLong(ILGenerator il, long i) + { + if (i >= int.MinValue && i <= int.MaxValue) + EmitLoadConstantInt(il, (int)i); + else + il.Demit(OpCodes.Ldc_I8, i); + } + + [MethodImpl((MethodImplOptions)256)] + private static void EmitLoadLocalVariableAddress(ILGenerator il, int location) + { + if ((uint)location <= byte.MaxValue) + il.Demit(OpCodes.Ldloca_S, (byte)location); + else + il.Demit(OpCodes.Ldloca, (short)location); + } + + /// Load local variable on stack + [MethodImpl((MethodImplOptions)256)] + public static bool EmitLoadLocalVariable(ILGenerator il, int location) + { + if (location == 0) + il.Demit(OpCodes.Ldloc_0); + else if (location == 1) + il.Demit(OpCodes.Ldloc_1); + else if (location == 2) + il.Demit(OpCodes.Ldloc_2); + else if (location == 3) + il.Demit(OpCodes.Ldloc_3); + else if ((uint)location <= byte.MaxValue) + il.Demit(OpCodes.Ldloc_S, (byte)location); + else + il.Demit(OpCodes.Ldloc, (short)location); + return true; + } + + [MethodImpl((MethodImplOptions)256)] + private static bool EmitIncOrDec(ILGenerator il, bool isInc = false) + { + il.Demit(OpCodes.Ldc_I4_1); + il.Demit(isInc ? OpCodes.Add : OpCodes.Sub); + return true; + } + + /// Store the variable location on stack + [MethodImpl((MethodImplOptions)256)] + public static void EmitStoreLocalVariable(ILGenerator il, int location) + { + if (location == 0) + il.Demit(OpCodes.Stloc_0); + else if (location == 1) + il.Demit(OpCodes.Stloc_1); + else if (location == 2) + il.Demit(OpCodes.Stloc_2); + else if (location == 3) + il.Demit(OpCodes.Stloc_3); + else if ((uint)location <= byte.MaxValue) + il.Demit(OpCodes.Stloc_S, (byte)location); + else + il.Demit(OpCodes.Stloc, (short)location); + } + + /// Get and store the variable of the type on stack + [MethodImpl((MethodImplOptions)256)] + public static int EmitStoreLocalVariable(ILGenerator il, Type type) + { + var location = il.GetNextLocalVarIndex(type); + EmitStoreLocalVariable(il, location); + return location; + } + + /// Stores and loads the variable + [MethodImpl((MethodImplOptions)256)] + public static void EmitStoreAndLoadLocalVariable(ILGenerator il, int location) + { + if (location == 0) + { + il.Demit(OpCodes.Stloc_0); + il.Demit(OpCodes.Ldloc_0); + } + else if (location == 1) + { + il.Demit(OpCodes.Stloc_1); + il.Demit(OpCodes.Ldloc_1); + } + else if (location == 2) + { + il.Demit(OpCodes.Stloc_2); + il.Demit(OpCodes.Ldloc_2); + } + else if (location == 3) + { + il.Demit(OpCodes.Stloc_3); + il.Demit(OpCodes.Ldloc_3); + } + else if ((uint)location <= byte.MaxValue) + { + il.Demit(OpCodes.Stloc_S, (byte)location); + il.Demit(OpCodes.Ldloc_S, (byte)location); + } + else + { + il.Demit(OpCodes.Stloc, (short)location); + il.Demit(OpCodes.Ldloc, (short)location); + } + } + + /// Stores and loads the variable, and returns it + public static int EmitStoreAndLoadLocalVariable(ILGenerator il, Type t) + { + var location = il.GetNextLocalVarIndex(t); + EmitStoreAndLoadLocalVariable(il, location); + return location; + } + + [MethodImpl((MethodImplOptions)256)] + private static void EmitStoreAndLoadLocalVariableAddress(ILGenerator il, int location) + { + // #if DEBUG + // var ilLengthField = typeof(ILGenerator).GetField("m_length", BindingFlags.Instance | BindingFlags.NonPublic); + // var ilStreamField = typeof(ILGenerator).GetField("m_ILStream", BindingFlags.Instance | BindingFlags.NonPublic); + // var ilLength = (int)ilLengthField.GetValue(il); + // var ilStream = (byte[])ilStreamField.GetValue(il); + + // var ilMaxMidStackField = typeof(ILGenerator).GetField("m_maxMidStack", BindingFlags.Instance | BindingFlags.NonPublic); + // var ilMaxMidStackCurField = typeof(ILGenerator).GetField("m_maxMidStackCur", BindingFlags.Instance | BindingFlags.NonPublic); + // var ilMaxMidStack = (int)ilMaxMidStackField.GetValue(il); + // var ilMaxMidStackCur = (int)ilMaxMidStackCurField.GetValue(il); + // #endif + if (location == 0) + { + // todo: @perf + // the internal code for this is + // + // EnsureCapacity(3); + // InternalEmit(opcode); + // EnsureCapacity(4); + // InternalEmit(opcode); + // m_ILStream[m_length++] = (byte)arg; + // + // which translates to -> + // + // if (m_length + 7 >= m_ILStream.Length) + // IncreaseCapacity(7); + // // No stack change here cause 1st op decrease stack by 1 and second increase by 1 + // m_ILStream[m_length++] = (byte)OpCodes.Stloc_0.Value; + // m_ILStream[m_length++] = (byte)OpCodes.Ldloca_S.Value; + // m_ILStream[m_length++] = (byte)0; // we may no need it + // + il.Demit(OpCodes.Stloc_0); + il.Demit(OpCodes.Ldloca_S, (byte)0); + } + else if (location == 1) + { + // todo: @perf we may introduce the EmitOne, EmitBatchNonStackModified(OpCode store, OpCode load, byte value), etc. method overloads + // + // if (ilLength + 7 < ilStream.Length) + // { + // ilStream[ilLength++] = (byte)OpCodes.Stloc_1.Value; + // if (ilMaxMidStackCur + 1 > ilMaxMidStack) + // ilMaxMidStackField.SetValue(il, ilMaxMidStackCur + 1); + // ilStream[ilLength++] = (byte)OpCodes.Ldloca_S.Value; + // ilStream[ilLength++] = (byte)1; + // ilLengthField.SetValue(il, ilLength); + // } + // else + // { + il.Demit(OpCodes.Stloc_1); + il.Demit(OpCodes.Ldloca_S, (byte)1); + // } + } + else if (location == 2) + { + il.Demit(OpCodes.Stloc_2); + il.Demit(OpCodes.Ldloca_S, (byte)2); + } + else if (location == 3) + { + il.Demit(OpCodes.Stloc_3); + il.Demit(OpCodes.Ldloca_S, (byte)3); + } + else if ((uint)location <= byte.MaxValue) + { + il.Demit(OpCodes.Stloc_S, (byte)location); + il.Demit(OpCodes.Ldloca_S, (byte)location); + } + else + { + il.Demit(OpCodes.Stloc, (short)location); + il.Demit(OpCodes.Ldloca, (short)location); + } + } + + private static int EmitStoreAndLoadLocalVariableAddress(ILGenerator il, Type type) + { + var location = il.GetNextLocalVarIndex(type); + EmitStoreAndLoadLocalVariableAddress(il, location); + return location; + } + + [MethodImpl((MethodImplOptions)256)] + private static void EmitLoadArg(ILGenerator il, int paramIndex) + { + if (paramIndex == 0) + il.Demit(OpCodes.Ldarg_0); + else if (paramIndex == 1) + il.Demit(OpCodes.Ldarg_1); + else if (paramIndex == 2) + il.Demit(OpCodes.Ldarg_2); + else if (paramIndex == 3) + il.Demit(OpCodes.Ldarg_3); + else if ((uint)paramIndex <= byte.MaxValue) + il.Demit(OpCodes.Ldarg_S, (byte)paramIndex); + else + il.Demit(OpCodes.Ldarg, (short)paramIndex); + } + + [MethodImpl((MethodImplOptions)256)] + private static void EmitLoadArgAddress(ILGenerator il, int paramIndex) + { + if ((uint)paramIndex <= byte.MaxValue) + il.Demit(OpCodes.Ldarga_S, (byte)paramIndex); + else + il.Demit(OpCodes.Ldarga, (short)paramIndex); + } + + /// Tries to interpret and emit the result IL + /// In case of exception return false, to allow FEC emit normally and throw in the invocation phase + public static bool TryInterpretAndEmitResult(Expression expr, ILGenerator il, ParentFlags parent, CompilerFlags flags) + { + var type = expr.Type; + Debug.Assert(type.IsPrimitive); + if ((flags & CompilerFlags.DisableInterpreter) != 0) + return false; + + var typeCode = Type.GetTypeCode(type); + try + { + switch (typeCode) + { + case TypeCode.Boolean: + var resultBool = false; + if (!Interpreter.TryInterpretBool(ref resultBool, expr, expr.NodeType)) + return false; + il.Demit(resultBool ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); + break; + + case TypeCode.Int32: + int resultInt = 0; + if (!Interpreter.TryInterpretInt(ref resultInt, expr, expr.NodeType)) + return false; + if ((parent & ParentFlags.IgnoreResult) == 0) + EmitLoadConstantInt(il, resultInt); + break; + case TypeCode.Decimal: + decimal resultDec = default; + if (!Interpreter.TryInterpretDecimal(ref resultDec, expr, expr.NodeType)) + return false; + if ((parent & ParentFlags.IgnoreResult) == 0) + EmitDecimalConstant(resultDec, il); + break; + default: + Interpreter.PValue resultVal = default; + if (!Interpreter.TryInterpretPrimitiveValue(ref resultVal, expr, typeCode, expr.NodeType)) + return false; + if ((parent & ParentFlags.IgnoreResult) == 0) + switch (typeCode) + { + case TypeCode.Char: + EmitLoadConstantInt(il, resultVal.CharValue); + break; + case TypeCode.SByte: + EmitLoadConstantInt(il, resultVal.SByteValue); + break; + case TypeCode.Byte: + EmitLoadConstantInt(il, resultVal.ByteValue); + break; + case TypeCode.Int16: + EmitLoadConstantInt(il, resultVal.Int16Value); + break; + case TypeCode.UInt16: + EmitLoadConstantInt(il, resultVal.UInt16Value); + break; + case TypeCode.Int32: + EmitLoadConstantInt(il, resultVal.Int32Value); + break; + case TypeCode.UInt32: + unchecked + { + EmitLoadConstantInt(il, (int)resultVal.UInt32Value); + } + break; + case TypeCode.Int64: + il.Demit(OpCodes.Ldc_I8, resultVal.Int64Value); + break; + case TypeCode.UInt64: + unchecked + { + il.Demit(OpCodes.Ldc_I8, (long)resultVal.UInt64Value); + } + break; + case TypeCode.Single: + il.Demit(OpCodes.Ldc_R4, resultVal.SingleValue); + break; + case TypeCode.Double: + il.Demit(OpCodes.Ldc_R8, resultVal.DoubleValue); + break; + default: Interpreter.UnreachableCase(typeCode); break; + } + break; + } + return true; + } + catch + { + // ignore exception and return the false and rethrow the exception in the invocation time + return false; + } + } + } + + /// Interpreter + public static class Interpreter + { + /// Always returns true + public static readonly Func TrueFunc = static () => true; + /// Always returns false + public static readonly Func FalseFunc = static () => false; + + /// Return value should be ignored + [MethodImpl(MethodImplOptions.NoInlining)] + internal static void UnreachableCase(T @case, + [CallerMemberName] string caller = "", [CallerLineNumber] int line = -1) + { +#if INTERPRETATION_DIAGNOSTICS + Console.WriteLine($"Unreachable switch case detected `{@case}` at `{caller}`:{line}"); + Debugger.Break(); +#endif + throw new InvalidCastException($"Unreachable switch case detected `{@case}` at `{caller}`:{line}"); + } + + /// Return value should be ignored + [MethodImpl(MethodImplOptions.NoInlining)] + private static R UnreachableCase(T @case, R result, + [CallerMemberName] string caller = "", [CallerLineNumber] int line = -1) + { +#if INTERPRETATION_DIAGNOSTICS + Console.WriteLine($"Unreachable switch case detected `{@case}` at `{caller}`:{line}"); + Debugger.Break(); +#endif + throw new InvalidCastException($"Unreachable switch case detected `{@case}` at `{caller}`:{line}"); + } + + /// Operation accepting IComparable inputs and producing bool output + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsComparison(ExpressionType nodeType) => + nodeType == ExpressionType.Equal | + nodeType == ExpressionType.NotEqual | + nodeType == ExpressionType.GreaterThan | + nodeType == ExpressionType.GreaterThanOrEqual | + nodeType == ExpressionType.LessThan | + nodeType == ExpressionType.LessThanOrEqual; + + /// Operation accepting the same primitive type inputs (or of the coalescing types) and producing the "same" primitive type output + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsArithmeticBinary(ExpressionType nodeType) => + nodeType == ExpressionType.Add | + nodeType == ExpressionType.Subtract | + nodeType == ExpressionType.Multiply | + nodeType == ExpressionType.Divide | + nodeType == ExpressionType.Modulo | + nodeType == ExpressionType.Power | + nodeType == ExpressionType.LeftShift | + nodeType == ExpressionType.RightShift | + nodeType == ExpressionType.And | + nodeType == ExpressionType.Or | + nodeType == ExpressionType.ExclusiveOr; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void NegatePrimitiveValue(ref PValue value, TypeCode code) + { + switch (code) + { + case TypeCode.Char: value.CharValue = (char)-value.CharValue; break; + case TypeCode.SByte: value.SByteValue = (sbyte)-value.SByteValue; break; + case TypeCode.Byte: value.Int16Value = (short)-value.ByteValue; break; + case TypeCode.Int16: value.Int16Value = (short)-value.Int16Value; break; + case TypeCode.UInt16: value.Int32Value = (int)-value.UInt16Value; break; + case TypeCode.Int32: value.Int32Value = -value.Int32Value; break; + // Negate can not be applied to the UInt32 + case TypeCode.UInt32: UnreachableCase(code); break; + case TypeCode.Single: value.SingleValue = -value.SingleValue; break; + case TypeCode.Double: value.DoubleValue = -value.DoubleValue; break; + default: UnreachableCase(code); break; + } + } + + internal static void ConvertPrimitiveValueFromTo(ref PValue value, TypeCode fromCode, TypeCode toCode) + { + switch (toCode) + { + case TypeCode.SByte: + switch (fromCode) + { + case TypeCode.Char: break; + case TypeCode.SByte: break; + case TypeCode.Byte: value.SByteValue = (sbyte)value.ByteValue; break; + case TypeCode.Int16: value.SByteValue = (sbyte)value.Int16Value; break; + case TypeCode.UInt16: value.SByteValue = (sbyte)value.UInt16Value; break; + case TypeCode.Int32: value.SByteValue = (sbyte)value.Int32Value; break; + case TypeCode.UInt32: value.SByteValue = (sbyte)value.UInt32Value; break; + case TypeCode.Int64: value.SByteValue = (sbyte)value.Int64Value; break; + case TypeCode.UInt64: value.SByteValue = (sbyte)value.UInt64Value; break; + case TypeCode.Single: value.SByteValue = (sbyte)value.SingleValue; break; + case TypeCode.Double: value.SByteValue = (sbyte)value.DoubleValue; break; + default: UnreachableCase(fromCode); break; + } + break; + case TypeCode.Byte: + switch (fromCode) + { + case TypeCode.Char: value.ByteValue = (byte)value.CharValue; break; + case TypeCode.SByte: value.ByteValue = (byte)value.SByteValue; break; + case TypeCode.Byte: break; + case TypeCode.Int16: value.ByteValue = (byte)value.Int16Value; break; + case TypeCode.UInt16: value.ByteValue = (byte)value.UInt16Value; break; + case TypeCode.Int32: value.ByteValue = (byte)value.Int32Value; break; + case TypeCode.UInt32: value.ByteValue = (byte)value.UInt32Value; break; + case TypeCode.Int64: value.ByteValue = (byte)value.Int64Value; break; + case TypeCode.UInt64: value.ByteValue = (byte)value.UInt64Value; break; + case TypeCode.Single: value.ByteValue = (byte)value.SingleValue; break; + case TypeCode.Double: value.ByteValue = (byte)value.DoubleValue; break; + default: UnreachableCase(fromCode); break; + } + break; + case TypeCode.Int16: + switch (fromCode) + { + case TypeCode.Char: value.Int16Value = (short)value.CharValue; break; + case TypeCode.SByte: value.Int16Value = (short)value.SByteValue; break; + case TypeCode.Byte: value.Int16Value = (short)value.ByteValue; break; + case TypeCode.Int16: break; + case TypeCode.UInt16: value.Int16Value = (short)value.UInt16Value; break; + case TypeCode.Int32: value.Int16Value = (short)value.Int32Value; break; + case TypeCode.UInt32: value.Int16Value = (short)value.UInt32Value; break; + case TypeCode.Int64: value.Int16Value = (short)value.Int64Value; break; + case TypeCode.UInt64: value.Int16Value = (short)value.UInt64Value; break; + case TypeCode.Single: value.Int16Value = (short)value.SingleValue; break; + case TypeCode.Double: value.Int16Value = (short)value.DoubleValue; break; + default: UnreachableCase(fromCode); break; + } + break; + case TypeCode.UInt16: + switch (fromCode) + { + case TypeCode.Char: value.UInt16Value = (ushort)value.CharValue; break; + case TypeCode.SByte: value.UInt16Value = (ushort)value.SByteValue; break; + case TypeCode.Byte: value.UInt16Value = (ushort)value.ByteValue; break; + case TypeCode.Int16: value.UInt16Value = (ushort)value.Int16Value; break; + case TypeCode.UInt16: break; + case TypeCode.Int32: value.UInt16Value = (ushort)value.Int32Value; break; + case TypeCode.UInt32: value.UInt16Value = (ushort)value.UInt32Value; break; + case TypeCode.Int64: value.UInt16Value = (ushort)value.Int64Value; break; + case TypeCode.UInt64: value.UInt16Value = (ushort)value.UInt64Value; break; + case TypeCode.Single: value.UInt16Value = (ushort)value.SingleValue; break; + case TypeCode.Double: value.UInt16Value = (ushort)value.DoubleValue; break; + default: UnreachableCase(fromCode); break; + } + break; + case TypeCode.Int32: + switch (fromCode) + { + case TypeCode.Char: value.Int32Value = (int)value.CharValue; break; + case TypeCode.SByte: value.Int32Value = (int)value.SByteValue; break; + case TypeCode.Byte: value.Int32Value = (int)value.ByteValue; break; + case TypeCode.Int16: value.Int32Value = (int)value.Int16Value; break; + case TypeCode.UInt16: value.Int32Value = (int)value.UInt16Value; break; + case TypeCode.Int32: break; + case TypeCode.UInt32: value.Int32Value = (int)value.UInt32Value; break; + case TypeCode.Int64: value.Int32Value = (int)value.Int64Value; break; + case TypeCode.UInt64: value.Int32Value = (int)value.UInt64Value; break; + case TypeCode.Single: value.Int32Value = (int)value.SingleValue; break; + case TypeCode.Double: value.Int32Value = (int)value.DoubleValue; break; + default: UnreachableCase(fromCode); break; + } + break; + case TypeCode.UInt32: + switch (fromCode) + { + case TypeCode.Char: value.UInt32Value = (uint)value.CharValue; break; + case TypeCode.SByte: value.UInt32Value = (uint)value.SByteValue; break; + case TypeCode.Byte: value.UInt32Value = (uint)value.ByteValue; break; + case TypeCode.Int16: value.UInt32Value = (uint)value.Int16Value; break; + case TypeCode.UInt16: value.UInt32Value = (uint)value.UInt16Value; break; + case TypeCode.Int32: value.UInt32Value = (uint)value.Int32Value; break; + case TypeCode.UInt32: break; + case TypeCode.Int64: value.UInt32Value = (uint)value.Int64Value; break; + case TypeCode.UInt64: value.UInt32Value = (uint)value.UInt64Value; break; + case TypeCode.Single: value.UInt32Value = (uint)value.SingleValue; break; + case TypeCode.Double: value.UInt32Value = (uint)value.DoubleValue; break; + default: UnreachableCase(fromCode); break; + } + break; + case TypeCode.Int64: + switch (fromCode) + { + case TypeCode.Char: value.Int64Value = (long)value.CharValue; break; + case TypeCode.SByte: value.Int64Value = (long)value.SByteValue; break; + case TypeCode.Byte: value.Int64Value = (long)value.ByteValue; break; + case TypeCode.Int16: value.Int64Value = (long)value.Int16Value; break; + case TypeCode.UInt16: value.Int64Value = (long)value.UInt16Value; break; + case TypeCode.Int32: value.Int64Value = (long)value.Int32Value; break; + case TypeCode.UInt32: value.Int64Value = (long)value.UInt32Value; break; + case TypeCode.Int64: break; + case TypeCode.UInt64: value.Int64Value = (long)value.UInt64Value; break; + case TypeCode.Single: value.Int64Value = (long)value.SingleValue; break; + case TypeCode.Double: value.Int64Value = (long)value.DoubleValue; break; + default: UnreachableCase(fromCode); break; + } + break; + case TypeCode.UInt64: + switch (fromCode) + { + case TypeCode.Char: value.UInt64Value = (ulong)value.CharValue; break; + case TypeCode.SByte: value.UInt64Value = (ulong)value.SByteValue; break; + case TypeCode.Byte: value.UInt64Value = (ulong)value.ByteValue; break; + case TypeCode.Int16: value.UInt64Value = (ulong)value.Int16Value; break; + case TypeCode.UInt16: value.UInt64Value = (ulong)value.UInt16Value; break; + case TypeCode.Int32: value.UInt64Value = (ulong)value.Int32Value; break; + case TypeCode.UInt32: value.UInt64Value = (ulong)value.UInt32Value; break; + case TypeCode.Int64: value.UInt64Value = (ulong)value.Int64Value; break; + case TypeCode.UInt64: break; + case TypeCode.Single: value.UInt64Value = (ulong)value.SingleValue; break; + case TypeCode.Double: value.UInt64Value = (ulong)value.DoubleValue; break; + default: UnreachableCase(fromCode); break; + } + break; + case TypeCode.Single: + switch (fromCode) + { + case TypeCode.Char: value.SingleValue = (float)value.CharValue; break; + case TypeCode.SByte: value.SingleValue = (float)value.SByteValue; break; + case TypeCode.Byte: value.SingleValue = (float)value.ByteValue; break; + case TypeCode.Int16: value.SingleValue = (float)value.Int16Value; break; + case TypeCode.UInt16: value.SingleValue = (float)value.UInt16Value; break; + case TypeCode.Int32: value.SingleValue = (float)value.Int32Value; break; + case TypeCode.UInt32: value.SingleValue = (float)value.UInt32Value; break; + case TypeCode.Int64: value.SingleValue = (float)value.Int64Value; break; + case TypeCode.UInt64: value.SingleValue = (float)value.UInt64Value; break; + case TypeCode.Single: break; + case TypeCode.Double: value.SingleValue = (float)value.DoubleValue; break; + default: UnreachableCase(fromCode); break; + } + break; + case TypeCode.Double: + switch (fromCode) + { + case TypeCode.Char: value.DoubleValue = (double)value.CharValue; break; + case TypeCode.SByte: value.DoubleValue = (double)value.SByteValue; break; + case TypeCode.Byte: value.DoubleValue = (double)value.ByteValue; break; + case TypeCode.Int16: value.DoubleValue = (double)value.Int16Value; break; + case TypeCode.UInt16: value.DoubleValue = (double)value.UInt16Value; break; + case TypeCode.Int32: value.DoubleValue = (double)value.Int32Value; break; + case TypeCode.UInt32: value.DoubleValue = (double)value.UInt32Value; break; + case TypeCode.Int64: value.DoubleValue = (double)value.Int64Value; break; + case TypeCode.UInt64: value.DoubleValue = (double)value.UInt64Value; break; + case TypeCode.Single: value.DoubleValue = (double)value.SingleValue; break; + case TypeCode.Double: break; + default: UnreachableCase(fromCode); break; + } + break; + } + } + + [DebuggerDisplay("{Code}")] + [StructLayout(LayoutKind.Explicit)] + internal struct PValue + { + [FieldOffset(0)] + public char CharValue; + [FieldOffset(0)] + public sbyte SByteValue; + [FieldOffset(0)] + public byte ByteValue; + [FieldOffset(0)] + public short Int16Value; + [FieldOffset(0)] + public ushort UInt16Value; + [FieldOffset(0)] + public int Int32Value; + [FieldOffset(0)] + public uint UInt32Value; + [FieldOffset(0)] + public long Int64Value; + [FieldOffset(0)] + public ulong UInt64Value; + [FieldOffset(0)] + public float SingleValue; + [FieldOffset(0)] + public double DoubleValue; + public PValue(char value) => CharValue = value; + public PValue(sbyte value) => SByteValue = value; + public PValue(byte value) => ByteValue = value; + public PValue(short value) => Int16Value = value; + public PValue(ushort value) => UInt16Value = value; + public PValue(int value) => Int32Value = value; + public PValue(uint value) => UInt32Value = value; + public PValue(long value) => Int64Value = value; + public PValue(ulong value) => UInt64Value = value; + public PValue(float value) => SingleValue = value; + public PValue(double value) => DoubleValue = value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool TryUnboxToPrimitiveValue(ref PValue value, object boxedValue, TypeCode code) + { + switch (code) + { + case TypeCode.Char: value.CharValue = (char)boxedValue; break; + case TypeCode.SByte: value.SByteValue = (sbyte)boxedValue; break; + case TypeCode.Byte: value.ByteValue = (byte)boxedValue; break; + case TypeCode.Int16: value.Int16Value = (short)boxedValue; break; + case TypeCode.UInt16: value.UInt16Value = (ushort)boxedValue; break; + case TypeCode.Int32: value.Int32Value = (int)boxedValue; break; + case TypeCode.UInt32: value.UInt32Value = (uint)boxedValue; break; + case TypeCode.Int64: value.Int64Value = (long)boxedValue; break; + case TypeCode.UInt64: value.UInt64Value = (ulong)boxedValue; break; + case TypeCode.Single: value.SingleValue = (float)boxedValue; break; + case TypeCode.Double: value.DoubleValue = (double)boxedValue; break; + default: return false; + } + return true; + } + + // todo: @perf think how to avoid this boxing thing altogether, maybe do not expose it at all to force client to handle the union values + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static object BoxPrimitiveValue(ref PValue value, TypeCode code) => code switch + { + TypeCode.Char => value.CharValue, + TypeCode.SByte => value.SByteValue, + TypeCode.Byte => value.ByteValue, + TypeCode.Int16 => value.Int16Value, + TypeCode.UInt16 => value.UInt16Value, + TypeCode.Int32 => value.Int32Value, + TypeCode.UInt32 => value.UInt32Value, + TypeCode.Int64 => value.Int64Value, + TypeCode.UInt64 => value.UInt64Value, + TypeCode.Single => value.SingleValue, + TypeCode.Double => value.DoubleValue, + _ => UnreachableCase(code, (object)null) + }; + + internal static bool ComparePrimitiveValues(ref PValue left, ref PValue right, TypeCode code, ExpressionType nodeType) + { + switch (nodeType) + { + case ExpressionType.GreaterThan: + return code switch + { + TypeCode.Char => left.CharValue > right.CharValue, + TypeCode.SByte => left.SByteValue > right.SByteValue, + TypeCode.Byte => left.ByteValue > right.ByteValue, + TypeCode.Int16 => left.Int16Value > right.Int16Value, + TypeCode.UInt16 => left.UInt16Value > right.UInt16Value, + TypeCode.Int32 => left.Int32Value > right.Int32Value, + TypeCode.UInt32 => left.UInt32Value > right.UInt32Value, + TypeCode.Int64 => left.Int64Value > right.Int64Value, + TypeCode.UInt64 => left.UInt64Value > right.UInt64Value, + TypeCode.Single => left.SingleValue > right.SingleValue, + TypeCode.Double => left.DoubleValue > right.DoubleValue, + _ => UnreachableCase(code, false) + }; + case ExpressionType.GreaterThanOrEqual: + return code switch + { + TypeCode.Char => left.CharValue >= right.CharValue, + TypeCode.SByte => left.SByteValue >= right.SByteValue, + TypeCode.Byte => left.ByteValue >= right.ByteValue, + TypeCode.Int16 => left.Int16Value >= right.Int16Value, + TypeCode.UInt16 => left.UInt16Value >= right.UInt16Value, + TypeCode.Int32 => left.Int32Value >= right.Int32Value, + TypeCode.UInt32 => left.UInt32Value >= right.UInt32Value, + TypeCode.Int64 => left.Int64Value >= right.Int64Value, + TypeCode.UInt64 => left.UInt64Value >= right.UInt64Value, + TypeCode.Single => left.SingleValue >= right.SingleValue, + TypeCode.Double => left.DoubleValue >= right.DoubleValue, + _ => UnreachableCase(code, false) + }; + case ExpressionType.LessThan: + return code switch + { + TypeCode.Char => left.CharValue < right.CharValue, + TypeCode.SByte => left.SByteValue < right.SByteValue, + TypeCode.Byte => left.ByteValue < right.ByteValue, + TypeCode.Int16 => left.Int16Value < right.Int16Value, + TypeCode.UInt16 => left.UInt16Value < right.UInt16Value, + TypeCode.Int32 => left.Int32Value < right.Int32Value, + TypeCode.UInt32 => left.UInt32Value < right.UInt32Value, + TypeCode.Int64 => left.Int64Value < right.Int64Value, + TypeCode.UInt64 => left.UInt64Value < right.UInt64Value, + TypeCode.Single => left.SingleValue < right.SingleValue, + TypeCode.Double => left.DoubleValue < right.DoubleValue, + _ => UnreachableCase(code, false) + }; + case ExpressionType.LessThanOrEqual: + return code switch + { + TypeCode.Char => left.CharValue <= right.CharValue, + TypeCode.SByte => left.SByteValue <= right.SByteValue, + TypeCode.Byte => left.ByteValue <= right.ByteValue, + TypeCode.Int16 => left.Int16Value <= right.Int16Value, + TypeCode.UInt16 => left.UInt16Value <= right.UInt16Value, + TypeCode.Int32 => left.Int32Value <= right.Int32Value, + TypeCode.UInt32 => left.UInt32Value <= right.UInt32Value, + TypeCode.Int64 => left.Int64Value <= right.Int64Value, + TypeCode.UInt64 => left.UInt64Value <= right.UInt64Value, + TypeCode.Single => left.SingleValue <= right.SingleValue, + TypeCode.Double => left.DoubleValue <= right.DoubleValue, + _ => UnreachableCase(code, false) + }; + default: return UnreachableCase(nodeType, false); + } + } + + /// Puts the result to the `left` para meter + internal static void DoArithmeticForPrimitiveValues(ref PValue left, ref PValue right, TypeCode code, ExpressionType nodeType) + { + switch (nodeType) + { + case ExpressionType.Add: + switch (code) + { + case TypeCode.Char: left.CharValue += right.CharValue; break; + // System Expression does not define the Add for sbyte and byte, but let's keep it here because it is allowed in C# and LightExpression + case TypeCode.SByte: left.SByteValue += right.SByteValue; break; + case TypeCode.Byte: left.ByteValue += right.ByteValue; break; + // the rest + case TypeCode.Int16: left.Int16Value += right.Int16Value; break; + case TypeCode.UInt16: left.UInt16Value += right.UInt16Value; break; + case TypeCode.Int32: left.Int32Value += right.Int32Value; break; + case TypeCode.UInt32: left.UInt32Value += right.UInt32Value; break; + case TypeCode.Int64: left.Int64Value += right.Int64Value; break; + case TypeCode.UInt64: left.UInt64Value += right.UInt64Value; break; + case TypeCode.Single: left.SingleValue += right.SingleValue; break; + case TypeCode.Double: left.DoubleValue += right.DoubleValue; break; + default: UnreachableCase(code); break; + } + break; + case ExpressionType.Subtract: + switch (code) + { + case TypeCode.Char: left.CharValue -= right.CharValue; break; + case TypeCode.SByte: left.SByteValue -= right.SByteValue; break; + case TypeCode.Byte: left.ByteValue -= right.ByteValue; break; + case TypeCode.Int16: left.Int16Value -= right.Int16Value; break; + case TypeCode.UInt16: left.UInt16Value -= right.UInt16Value; break; + case TypeCode.Int32: left.Int32Value -= right.Int32Value; break; + case TypeCode.UInt32: left.UInt32Value -= right.UInt32Value; break; + case TypeCode.Int64: left.Int64Value -= right.Int64Value; break; + case TypeCode.UInt64: left.UInt64Value -= right.UInt64Value; break; + case TypeCode.Single: left.SingleValue -= right.SingleValue; break; + case TypeCode.Double: left.DoubleValue -= right.DoubleValue; break; + default: UnreachableCase(code); break; + } + break; + case ExpressionType.Multiply: + switch (code) + { + case TypeCode.Char: left.CharValue *= right.CharValue; break; + case TypeCode.SByte: left.SByteValue *= right.SByteValue; break; + case TypeCode.Byte: left.ByteValue *= right.ByteValue; break; + case TypeCode.Int16: left.Int16Value *= right.Int16Value; break; + case TypeCode.UInt16: left.UInt16Value *= right.UInt16Value; break; + case TypeCode.Int32: left.Int32Value *= right.Int32Value; break; + case TypeCode.UInt32: left.UInt32Value *= right.UInt32Value; break; + case TypeCode.Int64: left.Int64Value *= right.Int64Value; break; + case TypeCode.UInt64: left.UInt64Value *= right.UInt64Value; break; + case TypeCode.Single: left.SingleValue *= right.SingleValue; break; + case TypeCode.Double: left.DoubleValue *= right.DoubleValue; break; + default: UnreachableCase(code); break; + } + break; + case ExpressionType.Divide: + switch (code) + { + case TypeCode.Char: left.CharValue /= right.CharValue; break; + case TypeCode.SByte: left.SByteValue /= right.SByteValue; break; + case TypeCode.Byte: left.ByteValue /= right.ByteValue; break; + case TypeCode.Int16: left.Int16Value /= right.Int16Value; break; + case TypeCode.UInt16: left.UInt16Value /= right.UInt16Value; break; + case TypeCode.Int32: left.Int32Value /= right.Int32Value; break; + case TypeCode.UInt32: left.UInt32Value /= right.UInt32Value; break; + case TypeCode.Int64: left.Int64Value /= right.Int64Value; break; + case TypeCode.UInt64: left.UInt64Value /= right.UInt64Value; break; + case TypeCode.Single: left.SingleValue /= right.SingleValue; break; + case TypeCode.Double: left.DoubleValue /= right.DoubleValue; break; + default: UnreachableCase(code); break; + } + break; + case ExpressionType.Modulo: + switch (code) + { + case TypeCode.Char: left.CharValue %= right.CharValue; break; + case TypeCode.SByte: left.SByteValue %= right.SByteValue; break; + case TypeCode.Byte: left.ByteValue %= right.ByteValue; break; + case TypeCode.Int16: left.Int16Value %= right.Int16Value; break; + case TypeCode.UInt16: left.UInt16Value %= right.UInt16Value; break; + case TypeCode.Int32: left.Int32Value %= right.Int32Value; break; + case TypeCode.UInt32: left.UInt32Value %= right.UInt32Value; break; + case TypeCode.Int64: left.Int64Value %= right.Int64Value; break; + case TypeCode.UInt64: left.UInt64Value %= right.UInt64Value; break; + case TypeCode.Single: left.SingleValue %= right.SingleValue; break; + case TypeCode.Double: left.DoubleValue %= right.DoubleValue; break; + default: UnreachableCase(code); break; + } + break; + case ExpressionType.And: + switch (code) + { + case TypeCode.Char: left.CharValue &= right.CharValue; break; + case TypeCode.SByte: left.SByteValue &= right.SByteValue; break; + case TypeCode.Byte: left.ByteValue &= right.ByteValue; break; + case TypeCode.Int16: left.Int16Value &= right.Int16Value; break; + case TypeCode.UInt16: left.UInt16Value &= right.UInt16Value; break; + case TypeCode.Int32: left.Int32Value &= right.Int32Value; break; + case TypeCode.UInt32: left.UInt32Value &= right.UInt32Value; break; + case TypeCode.Int64: left.Int64Value &= right.Int64Value; break; + case TypeCode.UInt64: left.UInt64Value &= right.UInt64Value; break; + default: UnreachableCase(code); break; + } + break; + case ExpressionType.Or: + switch (code) + { + case TypeCode.Char: left.CharValue |= right.CharValue; break; + case TypeCode.SByte: left.SByteValue |= right.SByteValue; break; + case TypeCode.Byte: left.ByteValue |= right.ByteValue; break; + case TypeCode.Int16: left.Int16Value |= right.Int16Value; break; + case TypeCode.UInt16: left.UInt16Value |= right.UInt16Value; break; + case TypeCode.Int32: left.Int32Value |= right.Int32Value; break; + case TypeCode.UInt32: left.UInt32Value |= right.UInt32Value; break; + case TypeCode.Int64: left.Int64Value |= right.Int64Value; break; + case TypeCode.UInt64: left.UInt64Value |= right.UInt64Value; break; + default: UnreachableCase(code); break; + } + break; + case ExpressionType.ExclusiveOr: + switch (code) + { + case TypeCode.Char: left.CharValue ^= right.CharValue; break; + case TypeCode.SByte: left.SByteValue ^= right.SByteValue; break; + case TypeCode.Byte: left.ByteValue ^= right.ByteValue; break; + case TypeCode.Int16: left.Int16Value ^= right.Int16Value; break; + case TypeCode.UInt16: left.UInt16Value ^= right.UInt16Value; break; + case TypeCode.Int32: left.Int32Value ^= right.Int32Value; break; + case TypeCode.UInt32: left.UInt32Value ^= right.UInt32Value; break; + case TypeCode.Int64: left.Int64Value ^= right.Int64Value; break; + case TypeCode.UInt64: left.UInt64Value ^= right.UInt64Value; break; + default: UnreachableCase(code); break; + } + break; + case ExpressionType.LeftShift: + switch (code) + { + case TypeCode.Char: left.CharValue <<= right.CharValue; break; + case TypeCode.SByte: left.SByteValue <<= right.SByteValue; break; + case TypeCode.Byte: left.ByteValue <<= right.ByteValue; break; + case TypeCode.Int16: left.Int16Value <<= right.Int16Value; break; + case TypeCode.UInt16: left.UInt16Value <<= right.UInt16Value; break; + case TypeCode.Int32: left.Int32Value <<= right.Int32Value; break; + case TypeCode.UInt32: left.UInt32Value <<= (int)right.UInt32Value; break; + case TypeCode.Int64: left.Int64Value <<= (int)right.Int64Value; break; + case TypeCode.UInt64: left.UInt64Value <<= (int)right.UInt64Value; break; + default: UnreachableCase(code); break; + } + break; + case ExpressionType.RightShift: + switch (code) + { + case TypeCode.Char: left.CharValue >>= right.CharValue; break; + case TypeCode.SByte: left.SByteValue >>= right.SByteValue; break; + case TypeCode.Byte: left.ByteValue >>= right.ByteValue; break; + case TypeCode.Int16: left.Int16Value >>= right.Int16Value; break; + case TypeCode.UInt16: left.UInt16Value >>= right.UInt16Value; break; + case TypeCode.Int32: left.Int32Value >>= right.Int32Value; break; + case TypeCode.UInt32: left.UInt32Value >>= (int)right.UInt32Value; break; + case TypeCode.Int64: left.Int64Value >>= (int)right.Int64Value; break; + case TypeCode.UInt64: left.UInt64Value >>= (int)right.UInt64Value; break; + default: UnreachableCase(code); break; + } + break; + default: UnreachableCase(nodeType); break; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool TrySetPrimitiveValueToDefault(ref PValue value, TypeCode code) + { + switch (code) + { + case TypeCode.Char: value.CharValue = default; break; + case TypeCode.SByte: value.SByteValue = default; break; + case TypeCode.Byte: value.ByteValue = default; break; + case TypeCode.Int16: value.Int16Value = default; break; + case TypeCode.UInt16: value.UInt16Value = default; break; + case TypeCode.Int32: value.Int32Value = default; break; + case TypeCode.UInt32: value.UInt32Value = default; break; + case TypeCode.Int64: value.Int64Value = default; break; + case TypeCode.UInt64: value.UInt64Value = default; break; + case TypeCode.Single: value.SingleValue = default; break; + case TypeCode.Double: value.DoubleValue = default; break; + default: return false; + } + return true; + } + + /// Fast, mostly negative check to skip or proceed with interpretation. + /// Depending on the context you may avoid calling it because you know the interpreted expression beforehand, + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsCandidateForInterpretation(Expression expr) + { + var nodeType = expr.NodeType; + return + nodeType == ExpressionType.Constant | + nodeType == ExpressionType.Default | + nodeType == ExpressionType.Convert | + nodeType == ExpressionType.Not | + nodeType == ExpressionType.Negate | + expr is BinaryExpression; + // todo: @wip include conditional? + } + +#if INTERPRETATION_DIAGNOSTICS + + [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Used in diagnostics only")] + [UnconditionalSuppressMessage("Trimming", "IL2075:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Used in diagnostics only")] + private static void CollectCallingTestName() + { + var stackTrace = new StackTrace(); + var frames = stackTrace.GetFrames(); + + // Skip this method and its immediate caller, and start from the outer callers + var found = false; + for (int i = 3; i < frames.Length; ++i) + { + var frame = frames[i]; + var method = frame.GetMethod(); + var type = method?.DeclaringType; + if (type == null) + continue; + + var ifaces = type.GetInterfaces(); + if (ifaces.Length == 0) + continue; + + foreach (var iface in ifaces) + if (iface.Name.Contains("Test")) + { + found = true; + break; + } + + if (found) + { + found = true; + Console.WriteLine($"//Interpretation in: {type.Name}.{method.Name}"); + break; // collect the first found thing in stack trace + } + } + + if (!found) + { + var methodTrace = string.Join("; ", frames.Skip(3).Select(f => f.GetMethod().Name).ToArray()); + Console.WriteLine($"//Interpretation in: not found in stack trace: {methodTrace}"); + } + } +#endif + + /// Wraps `TryInterpretPrimitive` in the try catch block. + /// In case of exception FEC will emit the whole computation to throw exception in the invocation phase + public static bool TryInterpretBool(out bool result, Expression expr, CompilerFlags flags) + { + var exprType = expr.Type; + Debug.Assert(exprType.IsPrimitive, // todo: @feat nullables are not supported yet // || Nullable.GetUnderlyingType(exprType)?.IsPrimitive == true, + "Can only reduce the boolean for the expressions of primitive types but found " + expr.Type); + result = false; + if ((flags & CompilerFlags.DisableInterpreter) != 0) + return false; + try + { + var ok = TryInterpretBool(ref result, expr, expr.NodeType); +#if INTERPRETATION_DIAGNOSTICS + if (ok) CollectCallingTestName(); +#endif + return ok; + } + catch + { + // ignore exception and return the false and rethrow the exception in the invocation time + return false; + } + } + + // todo: @perf try split to `TryInterpretBinary` overload to streamline the calls for TryEmitConditional and similar + /// Tries to interpret the expression of the Primitive type of Constant, Convert, Logical, Comparison, Arithmetic. + internal static bool TryInterpretBool(ref bool resultBool, Expression expr, ExpressionType nodeType) + { + // what operations are supported, to get the boolean result? + // yes: not, logical, comparison, default + // not: negate, arithmetic, convert to bool + if (nodeType == ExpressionType.Not) + { + var operandExpr = ((UnaryExpression)expr).Operand; + if (operandExpr.NodeType == ExpressionType.Conditional) + operandExpr = Tools.TryReduceConditional((ConditionalExpression)operandExpr); + if (operandExpr is ConstantExpression co) + resultBool = (bool)co.Value; + else if (!TryInterpretBool(ref resultBool, operandExpr, operandExpr.NodeType)) + return false; + + resultBool = !resultBool; + return true; + } + + if (nodeType == ExpressionType.AndAlso | + nodeType == ExpressionType.OrElse) + { + var binaryExpr = (BinaryExpression)expr; + + // Interpreting the left part as the first candidate for the result + var left = binaryExpr.Left; + if (left.NodeType == ExpressionType.Conditional) + left = Tools.TryReduceConditional((ConditionalExpression)left); + if (left is ConstantExpression lc) + resultBool = (bool)lc.Value; + else if (!TryInterpretBool(ref resultBool, left, left.NodeType)) + return false; + + // Short circuit the interpretation, because this is an actual logic of these logical operations + if (resultBool & nodeType == ExpressionType.OrElse || + !resultBool & nodeType == ExpressionType.AndAlso) + return true; + + // If the first part is not enough to decide of the expression result, go right + var right = binaryExpr.Right; + if (right.NodeType == ExpressionType.Conditional) + right = Tools.TryReduceConditional((ConditionalExpression)right); + if (right is ConstantExpression rc) + { + resultBool = (bool)rc.Value; + return true; + } + return TryInterpretBool(ref resultBool, right, right.NodeType); + } + + if (nodeType == ExpressionType.Equal | + nodeType == ExpressionType.NotEqual) + { + var binaryExpr = (BinaryExpression)expr; + var right = binaryExpr.Right; + var left = binaryExpr.Left; + var leftCode = Type.GetTypeCode(left.Type); + if (leftCode == TypeCode.Boolean) + { + var leftBool = false; + if (left is ConstantExpression lc) + leftBool = (bool)lc.Value; + else if (!TryInterpretBool(ref leftBool, left, left.NodeType)) + return false; + + var rightBool = false; + if (right is ConstantExpression rc) + rightBool = (bool)rc.Value; + else if (!TryInterpretBool(ref rightBool, right, right.NodeType)) + return false; + + resultBool = nodeType == ExpressionType.Equal ? leftBool == rightBool : leftBool != rightBool; + return true; + } + if (leftCode == TypeCode.Int32) + { + var leftInt = 0; + if (left is ConstantExpression lc) + leftInt = (int)lc.Value; + else if (!TryInterpretInt(ref leftInt, left, left.NodeType)) + return false; + + var rightInt = 0; + if (right is ConstantExpression rc) + rightInt = (int)rc.Value; + else if (!TryInterpretInt(ref rightInt, right, right.NodeType)) + return false; + + resultBool = nodeType == ExpressionType.Equal ? leftInt == rightInt : leftInt != rightInt; + return true; + } + if (leftCode == TypeCode.Decimal) + { + decimal decimalLeft = default; + if (left is ConstantExpression lc) + decimalLeft = (decimal)lc.Value; + else if (!TryInterpretDecimal(ref decimalLeft, left, left.NodeType)) + return false; + + decimal rightDec = default; + if (right is ConstantExpression rc) + rightDec = (decimal)rc.Value; + else if (!TryInterpretDecimal(ref rightDec, right, right.NodeType)) + return false; + + resultBool = nodeType == ExpressionType.Equal ? decimalLeft == rightDec : decimalLeft != rightDec; + return true; + } + // not a bool, int, or decimal + { + PValue leftVal = default; + if (left is ConstantExpression lc && !TryUnboxToPrimitiveValue(ref leftVal, lc.Value, leftCode) || + !TryInterpretPrimitiveValue(ref leftVal, left, leftCode, left.NodeType)) + return false; + + PValue rightVal = default; + if (right is ConstantExpression rc && !TryUnboxToPrimitiveValue(ref rightVal, rc.Value, leftCode) || + !TryInterpretPrimitiveValue(ref rightVal, right, leftCode, right.NodeType)) + return false; + + resultBool = leftCode switch + { + TypeCode.Char => leftVal.CharValue == rightVal.CharValue, + TypeCode.SByte => leftVal.SByteValue == rightVal.SByteValue, + TypeCode.Byte => leftVal.ByteValue == rightVal.ByteValue, + TypeCode.Int16 => leftVal.Int16Value == rightVal.Int16Value, + TypeCode.UInt16 => leftVal.UInt16Value == rightVal.UInt16Value, + TypeCode.Int32 => leftVal.Int32Value == rightVal.Int32Value, + TypeCode.UInt32 => leftVal.UInt32Value == rightVal.UInt32Value, + TypeCode.Int64 => leftVal.Int64Value == rightVal.Int64Value, + TypeCode.UInt64 => leftVal.UInt64Value == rightVal.UInt64Value, + TypeCode.Single => leftVal.SingleValue == rightVal.SingleValue, + TypeCode.Double => leftVal.DoubleValue == rightVal.DoubleValue, + _ => UnreachableCase(leftCode, false), + }; + resultBool = nodeType == ExpressionType.Equal ? resultBool : !resultBool; + return true; + } + } + + if (nodeType == ExpressionType.GreaterThan | + nodeType == ExpressionType.GreaterThanOrEqual | + nodeType == ExpressionType.LessThan | + nodeType == ExpressionType.LessThanOrEqual) + { + var binaryExpr = (BinaryExpression)expr; + var left = binaryExpr.Left; + var right = binaryExpr.Right; + var leftCode = Type.GetTypeCode(left.Type); + Debug.Assert(leftCode != TypeCode.Boolean, "Boolean values are not comparable by less or greater"); + + if (leftCode == TypeCode.Int32) + { + int intLeft = 0; + if (left is ConstantExpression lc) + intLeft = (int)lc.Value; + else if (!TryInterpretInt(ref intLeft, left, left.NodeType)) + return false; + + int rightInt = 0; + if (right is ConstantExpression rc) + rightInt = (int)rc.Value; + else if (!TryInterpretInt(ref rightInt, right, right.NodeType)) + return false; + + resultBool = nodeType switch + { + ExpressionType.GreaterThan => intLeft > rightInt, + ExpressionType.GreaterThanOrEqual => intLeft >= rightInt, + ExpressionType.LessThan => intLeft < rightInt, + ExpressionType.LessThanOrEqual => intLeft <= rightInt, + _ => UnreachableCase(nodeType, false), + }; + return true; + } + if (leftCode == TypeCode.Decimal) + { + decimal leftDec = default; + if (left is ConstantExpression lc) + leftDec = (decimal)lc.Value; + else if (!TryInterpretDecimal(ref leftDec, left, left.NodeType)) + return false; + + decimal rightDec = default; + if (right is ConstantExpression rc) + rightDec = (decimal)rc.Value; + else if (!TryInterpretDecimal(ref rightDec, right, right.NodeType)) + return false; + + resultBool = nodeType switch + { + ExpressionType.GreaterThan => leftDec > rightDec, + ExpressionType.GreaterThanOrEqual => leftDec >= rightDec, + ExpressionType.LessThan => leftDec < rightDec, + ExpressionType.LessThanOrEqual => leftDec <= rightDec, + _ => UnreachableCase(nodeType, false), + }; + return true; + } + // not a bool, int, or decimal + { + PValue leftVal = default; + if (left is ConstantExpression lc && !TryUnboxToPrimitiveValue(ref leftVal, lc.Value, leftCode) || + !TryInterpretPrimitiveValue(ref leftVal, left, leftCode, left.NodeType)) + return false; + + PValue rightVal = default; + if (right is ConstantExpression rc && !TryUnboxToPrimitiveValue(ref rightVal, rc.Value, leftCode) || + !TryInterpretPrimitiveValue(ref rightVal, right, leftCode, right.NodeType)) + return false; + + resultBool = ComparePrimitiveValues(ref leftVal, ref rightVal, leftCode, nodeType); + return true; + } + } + + if (expr is ConstantExpression constExpr) + { + resultBool = (bool)constExpr.Value; + return true; + } + + if (nodeType == ExpressionType.Default) + { + resultBool = false; + return true; + } + + return false; + } + + /// Tries to interpret the expression of the Primitive type of Constant, Convert, Logical, Comparison, Arithmetic. + internal static bool TryInterpretDecimal(ref decimal resultDec, Expression expr, ExpressionType nodeType) + { + // What operations are supported, to get the decimal result: + // yes: arithmetic, negate, default, convert + // no: not, logical, comparison + + if (IsArithmeticBinary(nodeType)) + { + var binaryExpr = (BinaryExpression)expr; + var left = binaryExpr.Left; + if (left is ConstantExpression lc) + resultDec = (decimal)lc.Value; + else if (!TryInterpretDecimal(ref resultDec, left, left.NodeType)) + return false; + + decimal rightDec = default; + var right = binaryExpr.Right; + if (right is ConstantExpression rc) + rightDec = (decimal)rc.Value; + else if (!TryInterpretDecimal(ref rightDec, right, right.NodeType)) + return false; + + switch (nodeType) + { + case ExpressionType.Add: resultDec += rightDec; break; + case ExpressionType.Subtract: resultDec -= rightDec; break; + case ExpressionType.Multiply: resultDec *= rightDec; break; + case ExpressionType.Divide: resultDec /= rightDec; break; + case ExpressionType.Modulo: resultDec %= rightDec; break; + default: UnreachableCase(nodeType); break; + } + return true; + } + + if (nodeType == ExpressionType.Negate) + { + var operandExpr = ((UnaryExpression)expr).Operand; + if (operandExpr is ConstantExpression co) + resultDec = (decimal)co.Value; + else if (!TryInterpretDecimal(ref resultDec, operandExpr, operandExpr.NodeType)) + return false; + + resultDec = -resultDec; + return true; + } + + if (expr is ConstantExpression constExpr) + { + resultDec = (decimal)constExpr.Value; + return true; + } + + if (nodeType == ExpressionType.Default) + { + resultDec = default; + return true; + } + + if (nodeType == ExpressionType.Convert) + { + var operandExpr = ((UnaryExpression)expr).Operand; + var operandCode = Type.GetTypeCode(operandExpr.Type); + Debug.Assert(operandCode != TypeCode.Boolean, + "Operand may be a decimal but cannot be bool, because there is no conversation from bool to the types in UValue"); + + PValue operandVal = default; + if (operandExpr is ConstantExpression co && !TryUnboxToPrimitiveValue(ref operandVal, co.Value, operandCode) || + !TryInterpretPrimitiveValue(ref operandVal, operandExpr, operandCode, operandExpr.NodeType)) + return false; + + resultDec = operandCode switch + { + TypeCode.Char => operandVal.CharValue, + TypeCode.SByte => operandVal.SByteValue, + TypeCode.Byte => operandVal.ByteValue, + TypeCode.Int16 => operandVal.Int16Value, + TypeCode.UInt16 => operandVal.UInt16Value, + TypeCode.Int32 => operandVal.Int32Value, + TypeCode.UInt32 => operandVal.UInt32Value, + TypeCode.Int64 => operandVal.Int64Value, + TypeCode.UInt64 => operandVal.UInt64Value, + TypeCode.Single => (decimal)operandVal.SingleValue, + TypeCode.Double => (decimal)operandVal.DoubleValue, + _ => UnreachableCase(operandCode, default(decimal)), + }; + return true; + } + + return false; + } + + /// Tries to interpret the expression of the Primitive type of Constant, Convert, Logical, Comparison, Arithmetic. + /// Returns `false` if it failed to do so. + internal static bool TryInterpretInt(ref int resultInt, Expression expr, ExpressionType nodeType) + { + // What is supported for the int result + // yes: arithmetic, convert, default + // no: not, logical, comparison + if (IsArithmeticBinary(nodeType)) + { + var binaryExpr = (BinaryExpression)expr; + var left = binaryExpr.Left; + if (left is ConstantExpression lc) + resultInt = (int)lc.Value; + else if (!TryInterpretInt(ref resultInt, left, left.NodeType)) + return false; + + int rightVal = 0; + var right = binaryExpr.Right; + if (right is ConstantExpression rc) + rightVal = (int)rc.Value; + else if (!TryInterpretInt(ref rightVal, right, right.NodeType)) + return false; + + resultInt = nodeType switch + { + ExpressionType.Add => resultInt + rightVal, + ExpressionType.Subtract => resultInt - rightVal, + ExpressionType.Multiply => resultInt * rightVal, + ExpressionType.Divide => resultInt / rightVal, + ExpressionType.Modulo => resultInt % rightVal, + ExpressionType.LeftShift => resultInt << rightVal, + ExpressionType.RightShift => resultInt >> rightVal, + ExpressionType.And => resultInt & rightVal, + ExpressionType.Or => resultInt | rightVal, + ExpressionType.ExclusiveOr => resultInt ^ rightVal, + ExpressionType.Power => (int)Math.Pow(resultInt, rightVal), + _ => UnreachableCase(nodeType, 0), + }; + return true; + } + + if (nodeType == ExpressionType.Negate) + { + var operandExpr = ((UnaryExpression)expr).Operand; + if (operandExpr is ConstantExpression co) + resultInt = (int)co.Value; + else if (!TryInterpretInt(ref resultInt, operandExpr, operandExpr.NodeType)) + return false; + + resultInt = -resultInt; + return true; + } + + if (expr is ConstantExpression constExpr) + { + resultInt = (int)constExpr.Value; + return true; + } + + if (nodeType == ExpressionType.Default) + { + resultInt = 0; + return true; + } + + if (nodeType == ExpressionType.Convert) + { + var operandExpr = ((UnaryExpression)expr).Operand; + var operandCode = Type.GetTypeCode(operandExpr.Type); + Debug.Assert(operandCode != TypeCode.Boolean, + "Operand may be a decimal but cannot be bool, because there is no conversation from bool to the types in PValue"); + + if (operandCode != TypeCode.Decimal) + { + PValue operandVal = default; + if (operandExpr is ConstantExpression co && !TryUnboxToPrimitiveValue(ref operandVal, co.Value, operandCode) || + !TryInterpretPrimitiveValue(ref operandVal, operandExpr, operandCode, operandExpr.NodeType)) + return false; + + resultInt = operandCode switch + { + TypeCode.Char => operandVal.CharValue, + TypeCode.SByte => operandVal.SByteValue, + TypeCode.Byte => operandVal.ByteValue, + TypeCode.Int16 => operandVal.Int16Value, + TypeCode.UInt16 => operandVal.UInt16Value, + TypeCode.Int32 => operandVal.Int32Value, + TypeCode.UInt32 => (int)operandVal.UInt32Value, + TypeCode.Int64 => (int)operandVal.Int64Value, + TypeCode.UInt64 => (int)operandVal.UInt64Value, + TypeCode.Single => (int)operandVal.SingleValue, + TypeCode.Double => (int)operandVal.DoubleValue, + _ => UnreachableCase(operandCode, 0), + }; + return true; + } + // then for the decimal + { + decimal resultDec = default; + if (operandExpr is ConstantExpression co) + resultDec = (decimal)co.Value; + else if (!TryInterpretDecimal(ref resultDec, operandExpr, operandExpr.NodeType)) + return false; + + resultInt = (int)resultDec; + return true; + } + } + return false; + } + + /// Tries to interpret the expression of the Primitive type of Constant, Convert, Logical, Comparison, Arithmetic. + /// Returns `false` if it is failed to do so. + internal static bool TryInterpretPrimitiveValue(ref PValue result, Expression expr, TypeCode exprCode, ExpressionType nodeType) + { + // What is supported for the non-boolean, non-decimal result + // yes: arithmetic, convert, default + // no: not, logical, comparison + if (IsArithmeticBinary(nodeType)) + { + var binaryExpr = (BinaryExpression)expr; + var left = binaryExpr.Left; + var leftCode = Type.GetTypeCode(left.Type); + if (left is ConstantExpression lc && !TryUnboxToPrimitiveValue(ref result, lc.Value, leftCode) || + !TryInterpretPrimitiveValue(ref result, left, leftCode, left.NodeType)) + return false; + + PValue rightVal = default; + var right = binaryExpr.Right; + // Using the leftCode to interpret the right part of the binary expression, + // because for supported operations left and right types are the same + if (right is ConstantExpression rc && !TryUnboxToPrimitiveValue(ref rightVal, rc.Value, leftCode) || + !TryInterpretPrimitiveValue(ref rightVal, right, leftCode, right.NodeType)) + return false; + + DoArithmeticForPrimitiveValues(ref result, ref rightVal, leftCode, nodeType); + return true; + } + + if (nodeType == ExpressionType.Negate) + { + var operandExpr = ((UnaryExpression)expr).Operand; + var operandCode = Type.GetTypeCode(operandExpr.Type); + if (operandExpr is ConstantExpression co && !TryUnboxToPrimitiveValue(ref result, co.Value, operandCode) || + !TryInterpretPrimitiveValue(ref result, operandExpr, operandCode, operandExpr.NodeType)) + return false; + + NegatePrimitiveValue(ref result, operandCode); + return true; + } + + if (expr is ConstantExpression constExpr) + return TryUnboxToPrimitiveValue(ref result, constExpr.Value, exprCode); + + if (nodeType == ExpressionType.Default) + return TrySetPrimitiveValueToDefault(ref result, exprCode); + + if (nodeType == ExpressionType.Convert) + { + var operandExpr = ((UnaryExpression)expr).Operand; + var operandCode = Type.GetTypeCode(operandExpr.Type); + Debug.Assert(operandCode != TypeCode.Boolean, + "Operand may be a decimal but cannot be bool, because there is no conversation from bool to the types in PValue"); + + if (operandCode != TypeCode.Decimal) + { + if (operandExpr is ConstantExpression co && !TryUnboxToPrimitiveValue(ref result, co.Value, operandCode) || + !TryInterpretPrimitiveValue(ref result, operandExpr, operandCode, operandExpr.NodeType)) + return false; + + if (exprCode != operandCode) + ConvertPrimitiveValueFromTo(ref result, operandCode, exprCode); + return true; + } + // then for the decimal + { + decimal operandDec = default; + if (operandExpr is ConstantExpression co) + operandDec = (decimal)co.Value; + else if (!TryInterpretDecimal(ref operandDec, operandExpr, operandExpr.NodeType)) + return false; + + switch (exprCode) + { + case TypeCode.Char: result.CharValue = (char)operandDec; break; + case TypeCode.SByte: result.SByteValue = (sbyte)operandDec; break; + case TypeCode.Byte: result.ByteValue = (byte)operandDec; break; + case TypeCode.Int16: result.Int16Value = (short)operandDec; break; + case TypeCode.UInt16: result.UInt16Value = (ushort)operandDec; break; + case TypeCode.Int32: result.Int32Value = (int)operandDec; break; + case TypeCode.UInt32: result.UInt32Value = (uint)operandDec; break; + case TypeCode.Int64: result.Int64Value = (long)operandDec; break; + case TypeCode.UInt64: result.UInt64Value = (ulong)operandDec; break; + case TypeCode.Single: result.SingleValue = (float)operandDec; break; + case TypeCode.Double: result.DoubleValue = (double)operandDec; break; + default: + // todo: @feature #472 support conversion to nullable, put nullable marker into PValue or something + return false; + } + return true; + } + } + return false; + } + } + } + + /// + /// Helpers targeting the performance. Extensions method names may be a bit funny (non standard), + /// in order to prevent conflicts with YOUR helpers with standard names + /// + public static class Tools + { + public static Expression AsExpr(this object obj) => obj as Expression ?? Constant(obj); + public static Expression[] AsExprs(this object[] obj) + { + var exprs = new Expression[obj.Length]; + for (var i = 0; i < obj.Length; i++) + exprs[i] = obj[i].AsExpr(); + return exprs; + } + + /// Returns true if class is compiler generated. Checking for CompilerGeneratedAttribute + /// is not enough, because this attribute is not applied for classes generated from "async/await". + [MethodImpl((MethodImplOptions)256)] + public static bool IsCompilerGenerated(this Type type) => + type.Name[0] == '<'; // consider the types with obstruct names like `<>blah` as compiler-generated + + [MethodImpl((MethodImplOptions)256)] + internal static bool IsUnsigned(this Type type) => + type == typeof(byte) || + type == typeof(ushort) || + type == typeof(uint) || + type == typeof(ulong); + + [MethodImpl((MethodImplOptions)256)] + internal static bool IsFloatingPoint(this Type type) => + type == typeof(float) || + type == typeof(double); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool IsInteger(this Type type) => Type.GetTypeCode(type) switch + { + TypeCode.Char => true, + TypeCode.SByte => true, + TypeCode.Byte => true, + TypeCode.Int16 => true, + TypeCode.UInt16 => true, + TypeCode.Int32 => true, + TypeCode.UInt32 => true, + TypeCode.Int64 => true, + TypeCode.UInt64 => true, + _ => false + }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool IsIntegerOrUnderlyingInteger(this Type type) => + type.IsPrimitive && type.IsInteger() || + type.IsEnum && Enum.GetUnderlyingType(type).IsInteger(); + + internal static bool IsPrimitiveWithZeroDefaultExceptDecimal(this Type type) + { + switch (Type.GetTypeCode(type)) + { + case TypeCode.Boolean: + case TypeCode.Char: + case TypeCode.SByte: + 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: + return true; + default: + return false; + } + } + + [RequiresUnreferencedCode(Trimming.Message)] + [MethodImpl((MethodImplOptions)256)] + public static bool IsNullable(this Type type) => + (type.IsValueType & type.IsGenericType) && type.GetGenericTypeDefinition() == typeof(Nullable<>); + + [RequiresUnreferencedCode(Trimming.Message)] + [MethodImpl((MethodImplOptions)256)] + public static Type GetUnderlyingNullableTypeUnsafe(this Type type) => type.GetGenericArguments()[0]; + + [RequiresUnreferencedCode(Trimming.Message)] + [MethodImpl((MethodImplOptions)256)] + public static Type GetNonNullableOrSelf(this Type type) => type.IsNullable() ? type.GetGenericArguments()[0] : type; + + [RequiresUnreferencedCode(Trimming.Message)] + [MethodImpl((MethodImplOptions)256)] + public static Type GetNullable(this Type type) => typeof(Nullable<>).MakeGenericType(type); + + public static string GetArithmeticBinaryOperatorMethodName(this ExpressionType nodeType) => + nodeType switch + { + ExpressionType.Add => "op_Addition", + ExpressionType.AddChecked => "op_Addition", + ExpressionType.Subtract => "op_Subtraction", + ExpressionType.SubtractChecked => "op_Subtraction", + ExpressionType.Multiply => "op_Multiply", + ExpressionType.MultiplyChecked => "op_Multiply", + ExpressionType.Divide => "op_Division", + ExpressionType.Modulo => "op_Modulus", + _ => null + }; + + internal static bool IsAssignNodeType(this ExpressionType nodeType) => nodeType switch + { + ExpressionType.Assign => true, + ExpressionType.PowerAssign => true, + ExpressionType.AndAssign => true, + ExpressionType.OrAssign => true, + ExpressionType.AddAssign => true, + ExpressionType.ExclusiveOrAssign => true, + ExpressionType.AddAssignChecked => true, + ExpressionType.SubtractAssign => true, + ExpressionType.SubtractAssignChecked => true, + ExpressionType.MultiplyAssign => true, + ExpressionType.MultiplyAssignChecked => true, + ExpressionType.DivideAssign => true, + ExpressionType.LeftShiftAssign => true, + ExpressionType.RightShiftAssign => true, + ExpressionType.ModuloAssign => true, + _ => false + }; + + [MethodImpl((MethodImplOptions)256)] + internal static bool IsBracedBlockLike(this ExpressionType nodeType) => + nodeType == ExpressionType.Try | + nodeType == ExpressionType.Switch | + nodeType == ExpressionType.Block | + nodeType == ExpressionType.Loop; + + + [MethodImpl((MethodImplOptions)256)] + internal static bool IsBlockLikeOrConditional(this ExpressionType nodeType) => + nodeType == ExpressionType.Conditional | nodeType == ExpressionType.Coalesce || + IsBracedBlockLike(nodeType); + + [MethodImpl((MethodImplOptions)256)] + internal static bool IsReturnable(this Expression expr) + { + var nodeType = expr.NodeType; + return expr.Type != typeof(void) && + nodeType != ExpressionType.Goto & nodeType != ExpressionType.Label & nodeType != ExpressionType.Throw && + !IsBracedBlockLike(nodeType); + } + + internal static Expression StripConvertRecursively(this Expression expr) => + expr is UnaryExpression convert && convert.NodeType == ExpressionType.Convert + ? StripConvertRecursively(convert.Operand) + : expr; + + internal static bool IsComplexExpression(this Expression expr) + { + expr = expr.StripConvertRecursively(); + return expr.NodeType == ExpressionType.Invoke + || expr.NodeType.IsBlockLikeOrConditional(); + } + + internal static bool IsConstantOrDefault(this Expression expr) + { + var nodeType = StripConvertRecursively(expr).NodeType; + return nodeType == ExpressionType.Constant + | nodeType == ExpressionType.Default; + } + + internal static bool IsParamOrConstantOrDefault(this Expression expr) + { + var nodeType = StripConvertRecursively(expr).NodeType; + return nodeType == ExpressionType.Parameter + | nodeType == ExpressionType.Constant + | nodeType == ExpressionType.Default; + } + + internal static string GetCSharpName(this MemberInfo m) + { + var name = m.Name; + if (m is FieldInfo fi && m.DeclaringType.IsValueType) + { + // btw, `fi.IsSpecialName` returns `false` :/ + if (name[0] == '<') // a backing field for the properties in struct, e.g. k__BackingField + { + var end = name.IndexOf('>'); + if (end > 1) + name = name.Substring(1, end - 1); + } + } + return name; + } + + [RequiresUnreferencedCode(Trimming.Message)] + internal static MethodInfo FindMethod(this Type type, string methodName) + { + var methods = type.GetMethods(); + for (var i = 0; i < methods.Length; i++) + if (methods[i].Name == methodName) + return methods[i]; + return type.BaseType?.FindMethod(methodName); + } + + internal static MethodInfo DelegateTargetGetterMethod = + typeof(Delegate).GetProperty(nameof(Delegate.Target)).GetMethod; + + [MethodImpl((MethodImplOptions)256)] + internal static MethodInfo FindDelegateInvokeMethod( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] this Type type) => + type.GetMethod("Invoke"); + + [RequiresUnreferencedCode(Trimming.Message)] + internal static class NullableReflected where T : struct + { + public static readonly Type NullableType = typeof(T?); + public static readonly MethodInfo ValueGetterMethod = + NullableType.GetProperty("Value").GetMethod; + public static readonly MethodInfo HasValueGetterMethod = + NullableType.GetProperty("HasValue").GetMethod; + public static readonly FieldInfo ValueField = + NullableType.GetField("value", BindingFlags.Instance | BindingFlags.NonPublic); + public static readonly ConstructorInfo Constructor = + NullableType.GetConstructors()[0]; + } + + [RequiresUnreferencedCode(Trimming.Message)] + [MethodImpl((MethodImplOptions)256)] + internal static MethodInfo GetNullableValueGetterMethod(this Type type) => + type == typeof(int?) ? NullableReflected.ValueGetterMethod : + type == typeof(double?) ? NullableReflected.ValueGetterMethod : + type.GetProperty("Value").GetMethod; + + [RequiresUnreferencedCode(Trimming.Message)] + [MethodImpl((MethodImplOptions)256)] + internal static MethodInfo GetNullableHasValueGetterMethod(this Type type) => + type == typeof(int?) ? NullableReflected.HasValueGetterMethod : + type == typeof(double?) ? NullableReflected.HasValueGetterMethod : + type.GetProperty("HasValue").GetMethod; + + [RequiresUnreferencedCode(Trimming.Message)] + [MethodImpl((MethodImplOptions)256)] + internal static FieldInfo GetNullableValueUnsafeAkaGetValueOrDefaultMethod(this Type type) => + type == typeof(int?) ? NullableReflected.ValueField : + type == typeof(double?) ? NullableReflected.ValueField : + type.GetField("value", BindingFlags.Instance | BindingFlags.NonPublic); + + [RequiresUnreferencedCode(Trimming.Message)] + [MethodImpl((MethodImplOptions)256)] + internal static ConstructorInfo GetNullableConstructor(this Type type) => + type == typeof(int?) ? NullableReflected.Constructor : + type == typeof(double?) ? NullableReflected.Constructor : + type.GetConstructors()[0]; + + /// Finds the implicit or explicit conversion operator inType from the sourceType to targetType, + /// otherwise returns null + [RequiresUnreferencedCode(Trimming.Message)] + public static MethodInfo FindConvertOperator(this Type inType, Type sourceType, Type targetType) + { + Debug.Assert(!inType.IsNullable(), "Should not be called for the nullable type"); + Debug.Assert(!inType.IsPrimitive, "Should not be called for the primitive type"); + Debug.Assert(!inType.IsEnum, "Should not be called for the enum type"); + + // note: remember that if inType.IsPrimitive it does contain the explicit or implicit conversion operators at all + if (sourceType == typeof(object) | targetType == typeof(object)) + return null; + + // conversion operators should be declared as static and public + var methods = inType.GetMethods(BindingFlags.Static | BindingFlags.Public); + foreach (var m in methods) + if (m.IsSpecialName && m.ReturnType == targetType) + { + var n = m.Name; + if ((n == "op_Implicit" || n == "op_Explicit") && + m.GetParameters()[0].ParameterType == sourceType) + return m; + } + + return null; + } + + [RequiresUnreferencedCode(Trimming.Message)] + internal static ConstructorInfo FindSingleParamConstructor(this Type type, Type paramType) + { + var ctors = type.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + for (var i = 0; i < ctors.Length; i++) + { + var ctor = ctors[i]; + var parameters = ctor.GetParameters(); + if (parameters.Length == 1 && parameters[0].ParameterType == paramType) + return ctor; + } + + return null; + } + + public static T[] AsArray(this IEnumerable xs) + { + if (xs is T[] array) + return array; + return xs == null ? null : xs.ToArray(); + } + + internal static IList AsList(this IEnumerable source) => + source == null ? Empty() : source as IList ?? source.ToList(); + + internal static bool TryGetIndex(this IList items, out int index, T item, int count, + TEq eq = default) where TEq : struct, IEq + { + for (var i = 0; (uint)i < count; ++i) + if (eq.Equals(items[i], item)) + { + index = i; + return true; + } + index = -1; + return false; + } + + private static class EmptyArray + { + public static readonly T[] Value = new T[0]; + } + + public static T[] Empty() => EmptyArray.Value; + + public static Type[] GetParamTypes(IReadOnlyList paramExprs) + { + if (paramExprs == null) + return Empty(); + + var count = paramExprs.Count; + if (count == 0) + return Empty(); + + if (count == 1) + return new[] { paramExprs[0].IsByRef ? paramExprs[0].Type.MakeByRefType() : paramExprs[0].Type }; + + var paramTypes = new Type[count]; + for (var i = 0; i < paramTypes.Length; i++) + { + var parameterExpr = paramExprs[i]; + paramTypes[i] = parameterExpr.IsByRef ? parameterExpr.Type.MakeByRefType() : parameterExpr.Type; + } + + return paramTypes; + } + + public static Type GetFuncOrActionType(Type returnType) => + returnType == typeof(void) ? typeof(Action) : typeof(Func<>).MakeGenericType(returnType); + + public static Type GetFuncOrActionType(Type p, Type returnType) => + returnType == typeof(void) ? typeof(Action<>).MakeGenericType(p) : typeof(Func<,>).MakeGenericType(p, returnType); + + public static Type GetFuncOrActionType(Type p0, Type p1, Type returnType) => + returnType == typeof(void) ? typeof(Action<,>).MakeGenericType(p0, p1) : typeof(Func<,,>).MakeGenericType(p0, p1, returnType); + + public static Type GetFuncOrActionType(Type p0, Type p1, Type p2, Type returnType) => + returnType == typeof(void) ? typeof(Action<,,>).MakeGenericType(p0, p1, p2) : typeof(Func<,,,>).MakeGenericType(p0, p1, p2, returnType); + + public static Type GetFuncOrActionType(Type p0, Type p1, Type p2, Type p3, Type returnType) => + returnType == typeof(void) ? typeof(Action<,,,>).MakeGenericType(p0, p1, p2, p3) : typeof(Func<,,,,>).MakeGenericType(p0, p1, p2, p3, returnType); + + public static Type GetFuncOrActionType(Type p0, Type p1, Type p2, Type p3, Type p4, Type returnType) => + returnType == typeof(void) ? typeof(Action<,,,,>).MakeGenericType(p0, p1, p2, p3, p4) : typeof(Func<,,,,,>).MakeGenericType(p0, p1, p2, p3, p4, returnType); + + public static Type GetFuncOrActionType(Type p0, Type p1, Type p2, Type p3, Type p4, Type p5, Type returnType) => + returnType == typeof(void) ? typeof(Action<,,,,,>).MakeGenericType(p0, p1, p2, p3, p4, p5) : typeof(Func<,,,,,,>).MakeGenericType(p0, p1, p2, p3, p4, p5, returnType); + + [RequiresUnreferencedCode(Trimming.Message)] + public static Type GetFuncOrActionType(Type[] paramTypes, Type returnType) + { + if (returnType == typeof(void)) + { + if (paramTypes.Length == 0) + return typeof(Action); + + return GetAction(paramTypes.Length).MakeGenericType(paramTypes); + } + + Type funcType = GetFunc(paramTypes.Length); + Type[] typeParams = new Type[paramTypes.Length + 1]; // todo: @perf could we Rent the array? + Array.Copy(paramTypes, typeParams, paramTypes.Length); + typeParams[paramTypes.Length] = returnType; + return funcType.MakeGenericType(typeParams); + + static Type GetAction(int length) + { + return length switch + { + 1 => typeof(Action<>), + 2 => typeof(Action<,>), + 3 => typeof(Action<,,>), + 4 => typeof(Action<,,,>), + 5 => typeof(Action<,,,,>), + 6 => typeof(Action<,,,,,>), + 7 => typeof(Action<,,,,,,>), + 8 => typeof(Action<,,,,,,,>), + 9 => typeof(Action<,,,,,,,,>), + 10 => typeof(Action<,,,,,,,,,>), + 11 => typeof(Action<,,,,,,,,,,>), + 12 => typeof(Action<,,,,,,,,,,,>), + 13 => typeof(Action<,,,,,,,,,,,,>), + 14 => typeof(Action<,,,,,,,,,,,,,>), + 15 => typeof(Action<,,,,,,,,,,,,,,>), + 16 => typeof(Action<,,,,,,,,,,,,,,,>), + _ => throw new NotSupportedException($"Action with so many ({length}) parameters is not supported!") + }; + } + + static Type GetFunc(int length) + { + return length switch + { + 0 => typeof(Func<>), + 1 => typeof(Func<,>), + 2 => typeof(Func<,,>), + 3 => typeof(Func<,,,>), + 4 => typeof(Func<,,,,>), + 5 => typeof(Func<,,,,,>), + 6 => typeof(Func<,,,,,,>), + 7 => typeof(Func<,,,,,,,>), + 8 => typeof(Func<,,,,,,,,>), + 9 => typeof(Func<,,,,,,,,,>), + 10 => typeof(Func<,,,,,,,,,,>), + 11 => typeof(Func<,,,,,,,,,,,>), + 12 => typeof(Func<,,,,,,,,,,,,>), + 13 => typeof(Func<,,,,,,,,,,,,,>), + 14 => typeof(Func<,,,,,,,,,,,,,,>), + 15 => typeof(Func<,,,,,,,,,,,,,,,>), + 16 => typeof(Func<,,,,,,,,,,,,,,,,>), + _ => throw new NotSupportedException($"Func with so many ({length}) parameters is not supported!") + }; + } + } + + public static T GetFirst(this IEnumerable source) + { + // This is pretty much Linq.FirstOrDefault except it does not need to check + // if source is IPartition (but should it?) + + if (source is IList list) + return list.Count == 0 ? default : list[0]; + var items = source.GetEnumerator(); + return items.MoveNext() ? items.Current : default; + } + + public static T GetFirst(this T[] source) => source.Length == 0 ? default : source[0]; + + public static Expression TryReduceConditionalTest(Expression testExpr) + { + // removing Not by turning Equal -> NotEqual, NotEqual -> Equal + if (testExpr.NodeType == ExpressionType.Not) + { + // simplify the not `==` -> `!=`, `!=` -> `==` + var op = TryReduceConditionalTest(((UnaryExpression)testExpr).Operand); + var nodeType = op.NodeType; + if (nodeType == ExpressionType.Equal) // ensures that it is a BinaryExpression + { + var binOp = (BinaryExpression)op; + return NotEqual(binOp.Left, binOp.Right); + } + else if (nodeType == ExpressionType.NotEqual) // ensures that it is a BinaryExpression + { + var binOp = (BinaryExpression)op; + return Equal(binOp.Left, binOp.Right); + } + } + else if (testExpr is BinaryExpression b) + { + var nodeType = b.NodeType; + if (nodeType == ExpressionType.OrElse | nodeType == ExpressionType.Or) + { + if (b.Left is ConstantExpression lc && lc.Value is bool lcb) + return lcb ? lc : TryReduceConditionalTest(b.Right); + + if (b.Right is ConstantExpression rc && rc.Value is bool rcb && !rcb) + return TryReduceConditionalTest(b.Left); + } + else if (nodeType == ExpressionType.AndAlso | nodeType == ExpressionType.And) + { + if (b.Left is ConstantExpression lc && lc.Value is bool lcb) + return !lcb ? lc : TryReduceConditionalTest(b.Right); + + if (b.Right is ConstantExpression rc && rc.Value is bool rcb && rcb) + return TryReduceConditionalTest(b.Left); + } + } + + return testExpr; + } + + // Does a low-hanging fruit reductions for now @date-20260227 + public static Expression TryReduceConditional(ConditionalExpression condExpr) + { + var testExpr = TryReduceConditionalTest(condExpr.Test); + if (testExpr is BinaryExpression bi && (bi.NodeType == ExpressionType.Equal || bi.NodeType == ExpressionType.NotEqual)) + { + if (bi.Left is ConstantExpression lc && bi.Right is ConstantExpression rc) + { +#if INTERPRETATION_DIAGNOSTICS + Console.WriteLine("//Reduced Conditional in Interpretation: " + condExpr); +#endif + var equals = Equals(lc.Value, rc.Value); + return bi.NodeType == ExpressionType.Equal + ? (equals ? condExpr.IfTrue : condExpr.IfFalse) + : (equals ? condExpr.IfFalse: condExpr.IfTrue); + } + } + + return testExpr is ConstantExpression constExpr && constExpr.Value is bool testBool + ? (testBool ? condExpr.IfTrue : condExpr.IfFalse) + : condExpr; + } + } + + [RequiresUnreferencedCode(Trimming.Message)] + public static class ILGeneratorTools + { + /// Configuration option to disable the pooling + public static bool DisableILGeneratorPooling; + /// Configuration option to disable the ILGenerator Emit debug output + public static bool DisableDemit; +#if SUPPORTS_IL_EMIT_HACK + /// + /// When true, uses direct IL stream writes for token-emitting opcodes + /// (Call, Callvirt, Newobj, Ldfld, Stfld, etc.) bypassing ILGenerator.Emit() overhead. + /// Set to false to fall back to standard ILGenerator.Emit() (e.g. for benchmarking or debugging). + /// Starts as false and is enabled by DynamicMethodHacks static initializer once the required + /// internals (m_scope, etc.) have been resolved successfully. + /// + public static bool UseILEmitHack; +#endif + +#if DEMIT + [MethodImpl((MethodImplOptions)256)] + public static void Demit(this ILGenerator il, OpCode opcode, [CallerMemberName] string emitterName = "", [CallerLineNumber] int emitterLine = 0) + { + il.Emit(opcode); + if (DisableDemit) return; + Debug.WriteLine($"{opcode} -- {emitterName}:{emitterLine}"); + } + + [MethodImpl((MethodImplOptions)256)] + public static void Demit(this ILGenerator il, OpCode opcode, Type type, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) + { + il.Emit(opcode, type); + if (DisableDemit) return; + Debug.WriteLine($"{opcode} {type.ToCode(stripNamespace: true)} -- {emitterName}:{emitterLine}"); + } + + [MethodImpl((MethodImplOptions)256)] + public static void Demit(this ILGenerator il, OpCode opcode, FieldInfo value, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) + { + il.Emit(opcode, value); + if (DisableDemit) return; + + var declType = value.DeclaringType?.ToCode(stripNamespace: true) ?? ""; + var fieldType = value.FieldType.ToCode(stripNamespace: true); + Debug.WriteLine($"{opcode} {fieldType} {declType}.{value.Name} -- {emitterName}:{emitterLine}"); + } + + [MethodImpl((MethodImplOptions)256)] + public static void Demit(this ILGenerator il, OpCode opcode, MethodInfo value, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) + { + il.Emit(opcode, value); + if (DisableDemit) return; + + var declType = value.DeclaringType?.ToCode(stripNamespace: true) ?? ""; + var retType = value.ReturnType.ToCode(stripNamespace: true); + var signature = value.ToString(); + var paramStart = signature.IndexOf('('); + var paramsInParens = paramStart == -1 ? "()" : signature.Substring(paramStart); + Debug.WriteLine($"{opcode} {retType} {declType}.{value.Name}{paramsInParens} -- {emitterName}:{emitterLine}"); + } + + [MethodImpl((MethodImplOptions)256)] + public static void Demit(this ILGenerator il, OpCode opcode, ConstructorInfo value, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) + { + il.Emit(opcode, value); + if (DisableDemit) return; + + var declType = value.DeclaringType?.ToCode(stripNamespace: true) ?? ""; + var signature = value.ToString(); + var paramStart = signature.IndexOf('('); + var paramsInParens = paramStart == -1 ? "()" : signature.Substring(paramStart); + Debug.WriteLine($"{opcode} {declType}{paramsInParens} -- {emitterName}:{emitterLine}"); + } + + [MethodImpl((MethodImplOptions)256)] + public static void Demit(this ILGenerator il, OpCode opcode, Label value, + [CallerArgumentExpression("value")] string valueName = null, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) + { + il.Emit(opcode, value); + if (DisableDemit) return; + Debug.WriteLine($"{opcode} {valueName ?? value.ToString()} -- {emitterName}:{emitterLine}"); + } + + [MethodImpl((MethodImplOptions)256)] + public static void DemitSwitch(this ILGenerator il, Label[] gotoLabels, + [CallerArgumentExpression("gotoLabels")] string valueName = null, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) + { + il.Emit(OpCodes.Switch, gotoLabels); + if (DisableDemit) return; + // Use GetHashCode to identify the labels, because it returns Id. Id is not used directly because Id is exposed since .NET 9+ only + Debug.WriteLine($"{OpCodes.Switch} {valueName}=[{string.Join(",", gotoLabels.Select(l => l.GetHashCode())).ToString()}] -- {emitterName}:{emitterLine}"); + } + + [MethodImpl((MethodImplOptions)256)] + public static void DmarkLabel(this ILGenerator il, Label value, + [CallerArgumentExpression("value")] string valueName = null, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) + { + il.MarkLabel(value); + if (DisableDemit) return; + Debug.WriteLine($"MarkLabel: {valueName ?? value.ToString()} -- {emitterName}:{emitterLine}"); + } + + [MethodImpl((MethodImplOptions)256)] + public static void Demit(this ILGenerator il, OpCode opcode, byte value, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) + { + il.Emit(opcode, value); + if (DisableDemit) return; + Debug.WriteLine($"{opcode} {value} -- {emitterName}:{emitterLine}"); + } + + [MethodImpl((MethodImplOptions)256)] + public static void Demit(this ILGenerator il, OpCode opcode, sbyte value, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) + { + il.Emit(opcode, value); + if (DisableDemit) return; + Debug.WriteLine($"{opcode} {value} -- {emitterName}:{emitterLine}"); + } + + [MethodImpl((MethodImplOptions)256)] + public static void Demit(this ILGenerator il, OpCode opcode, short value, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) + { + il.Emit(opcode, value); + if (DisableDemit) return; + Debug.WriteLine($"{opcode} {value} -- {emitterName}:{emitterLine}"); + } + + [MethodImpl((MethodImplOptions)256)] + public static void Demit(this ILGenerator il, OpCode opcode, int value, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) + { + il.Emit(opcode, value); + if (DisableDemit) return; + Debug.WriteLine($"{opcode} {value} -- {emitterName}:{emitterLine}"); + } + + [MethodImpl((MethodImplOptions)256)] + public static void Demit(this ILGenerator il, OpCode opcode, long value, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) + { + il.Emit(opcode, value); + if (DisableDemit) return; + Debug.WriteLine($"{opcode} {value} -- {emitterName}:{emitterLine}"); + } + + [MethodImpl((MethodImplOptions)256)] + public static void Demit(this ILGenerator il, OpCode opcode, float value, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) + { + il.Emit(opcode, value); + if (DisableDemit) return; + Debug.WriteLine($"{opcode} {value} -- {emitterName}:{emitterLine}"); + } + + [MethodImpl((MethodImplOptions)256)] + public static void Demit(this ILGenerator il, OpCode opcode, double value, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) + { + il.Emit(opcode, value); + if (DisableDemit) return; + Debug.WriteLine($"{opcode} {value} -- {emitterName}:{emitterLine}"); + } + + [MethodImpl((MethodImplOptions)256)] + public static void Demit(this ILGenerator il, string value, OpCode opcode, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) + { + il.Emit(opcode, value); + if (DisableDemit) return; + Debug.WriteLine($"{opcode} {value} -- {emitterName}:{emitterLine}"); + } + + [MethodImpl((MethodImplOptions)256)] + public static void DemitLdfld(this ILGenerator il, FieldInfo field, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) + { + il.Emit(OpCodes.Ldfld, field); + if (DisableDemit) return; + Debug.WriteLine($"ldfld {field.FieldType.ToCode(stripNamespace: true)} {field.DeclaringType?.ToCode(stripNamespace: true)}.{field.Name} -- {emitterName}:{emitterLine}"); + } + + [MethodImpl((MethodImplOptions)256)] + public static void DemitStfld(this ILGenerator il, FieldInfo field, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) + { + il.Emit(OpCodes.Stfld, field); + if (DisableDemit) return; + Debug.WriteLine($"stfld {field.FieldType.ToCode(stripNamespace: true)} {field.DeclaringType?.ToCode(stripNamespace: true)}.{field.Name} -- {emitterName}:{emitterLine}"); + } + + [MethodImpl((MethodImplOptions)256)] + public static void DemitLdsfld(this ILGenerator il, FieldInfo field, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) + { + il.Emit(OpCodes.Ldsfld, field); + if (DisableDemit) return; + Debug.WriteLine($"ldsfld {field.FieldType.ToCode(stripNamespace: true)} {field.DeclaringType?.ToCode(stripNamespace: true)}.{field.Name} -- {emitterName}:{emitterLine}"); + } + + [MethodImpl((MethodImplOptions)256)] + public static void DemitLdflda(this ILGenerator il, FieldInfo field, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) + { + il.Emit(OpCodes.Ldflda, field); + if (DisableDemit) return; + Debug.WriteLine($"ldflda {field.FieldType.ToCode(stripNamespace: true)} {field.DeclaringType?.ToCode(stripNamespace: true)}.{field.Name} -- {emitterName}:{emitterLine}"); + } + +#else // no DEMIT! :-) + + [MethodImpl((MethodImplOptions)256)] + public static void Demit(this ILGenerator il, OpCode opcode) => il.Emit(opcode); + + [MethodImpl((MethodImplOptions)256)] + public static void Demit(this ILGenerator il, OpCode opcode, Type value) => il.Emit(opcode, value); + + [MethodImpl((MethodImplOptions)256)] + public static void Demit(this ILGenerator il, OpCode opcode, FieldInfo value) + { +#if SUPPORTS_IL_EMIT_HACK + if (UseILEmitHack) { DynamicMethodHacks.HackEmitField(il, opcode, value); return; } +#endif + il.Emit(opcode, value); + } + + [MethodImpl((MethodImplOptions)256)] + public static void Demit(this ILGenerator il, OpCode opcode, MethodInfo value) + { +#if SUPPORTS_IL_EMIT_HACK + if (UseILEmitHack) { DynamicMethodHacks.HackEmitMethod(il, opcode, value); return; } +#endif + il.Emit(opcode, value); + } + + [MethodImpl((MethodImplOptions)256)] + public static void Demit(this ILGenerator il, OpCode opcode, ConstructorInfo value) + { +#if SUPPORTS_IL_EMIT_HACK + if (UseILEmitHack) { DynamicMethodHacks.HackEmitCtor(il, opcode, value); return; } +#endif + il.Emit(opcode, value); + } + + [MethodImpl((MethodImplOptions)256)] + public static void Demit(this ILGenerator il, OpCode opcode, Label value) => il.Emit(opcode, value); + + [MethodImpl((MethodImplOptions)256)] + public static void DemitSwitch(this ILGenerator il, Label[] gotoLabels) => il.Emit(OpCodes.Switch, gotoLabels); + + [MethodImpl((MethodImplOptions)256)] + public static void DmarkLabel(this ILGenerator il, Label value) => il.MarkLabel(value); + + [MethodImpl((MethodImplOptions)256)] + public static void Demit(this ILGenerator il, OpCode opcode, byte value) => il.Emit(opcode, value); + + [MethodImpl((MethodImplOptions)256)] + public static void Demit(this ILGenerator il, OpCode opcode, sbyte value) => il.Emit(opcode, value); + + [MethodImpl((MethodImplOptions)256)] + public static void Demit(this ILGenerator il, OpCode opcode, short value) => il.Emit(opcode, value); + + [MethodImpl((MethodImplOptions)256)] + public static void Demit(this ILGenerator il, OpCode opcode, int value) => il.Emit(opcode, value); + + [MethodImpl((MethodImplOptions)256)] + public static void Demit(this ILGenerator il, OpCode opcode, long value) => il.Emit(opcode, value); + + [MethodImpl((MethodImplOptions)256)] + public static void Demit(this ILGenerator il, OpCode opcode, float value) => il.Emit(opcode, value); + + [MethodImpl((MethodImplOptions)256)] + public static void Demit(this ILGenerator il, OpCode opcode, double value) => il.Emit(opcode, value); + + [MethodImpl((MethodImplOptions)256)] + public static void Demit(this ILGenerator il, string value, OpCode opcode) => il.Emit(opcode, value); + + // Specialized field-access helpers that hardcode the opcode and stack change, + // avoiding the runtime opcode switch in HackEmitField when the call site already knows the operation. + + [MethodImpl((MethodImplOptions)256)] + public static void DemitLdfld(this ILGenerator il, FieldInfo field) + { +#if SUPPORTS_IL_EMIT_HACK + if (UseILEmitHack && CanUseHackEmit(field.DeclaringType, il)) + { DynamicMethodHacks.HackEmitFieldToken(il, OpCodes.Ldfld, field.FieldHandle, 0); return; } +#endif + il.Emit(OpCodes.Ldfld, field); + } + + [MethodImpl((MethodImplOptions)256)] + public static void DemitStfld(this ILGenerator il, FieldInfo field) + { +#if SUPPORTS_IL_EMIT_HACK + if (UseILEmitHack && CanUseHackEmit(field.DeclaringType, il)) + { DynamicMethodHacks.HackEmitFieldToken(il, OpCodes.Stfld, field.FieldHandle, -2); return; } +#endif + il.Emit(OpCodes.Stfld, field); + } + + [MethodImpl((MethodImplOptions)256)] + public static void DemitLdsfld(this ILGenerator il, FieldInfo field) + { +#if SUPPORTS_IL_EMIT_HACK + if (UseILEmitHack && CanUseHackEmit(field.DeclaringType, il)) + { DynamicMethodHacks.HackEmitFieldToken(il, OpCodes.Ldsfld, field.FieldHandle, 1); return; } +#endif + il.Emit(OpCodes.Ldsfld, field); + } + + [MethodImpl((MethodImplOptions)256)] + public static void DemitLdflda(this ILGenerator il, FieldInfo field) + { +#if SUPPORTS_IL_EMIT_HACK + if (UseILEmitHack && CanUseHackEmit(field.DeclaringType, il)) + { DynamicMethodHacks.HackEmitFieldToken(il, OpCodes.Ldflda, field.FieldHandle, 0); return; } +#endif + il.Emit(OpCodes.Ldflda, field); + } + + +#if SUPPORTS_IL_EMIT_HACK + // Specialized emit helpers where the call site already knows the opcode/isStatic/return-type/param-count, + // skipping the reflection-based checks inside HackEmitMethod/Ctor/Field. + + // True when il is a DynamicILGenerator and the declaring type (if any) is not generic/array. + [MethodImpl((MethodImplOptions)256)] + private static bool CanUseHackEmit(Type declaringType, ILGenerator il) => + (declaringType == null || (!declaringType.IsGenericType && !declaringType.IsArray)) + && il.GetType() == DynamicMethodHacks.DynamicILGeneratorType; + + /// Emits call to a static void method. + [MethodImpl((MethodImplOptions)256)] + public static void DemitCallStaticVoid(this ILGenerator il, MethodInfo meth, int paramCount) + { + if (UseILEmitHack && CanUseHackEmit(meth.DeclaringType, il)) + { DynamicMethodHacks.HackEmitMethodToken(il, OpCodes.Call, meth.MethodHandle, -paramCount); return; } + il.Emit(OpCodes.Call, meth); + } + + /// Emits call to a static method returning a value (net stack: 1 - paramCount). + [MethodImpl((MethodImplOptions)256)] + public static void DemitCallStatic(this ILGenerator il, MethodInfo meth, int paramCount) + { + if (UseILEmitHack && CanUseHackEmit(meth.DeclaringType, il)) + { DynamicMethodHacks.HackEmitMethodToken(il, OpCodes.Call, meth.MethodHandle, 1 - paramCount); return; } + il.Emit(OpCodes.Call, meth); + } + + /// Emits call/callvirt to an instance void method (net stack: -paramCount - 1). + [MethodImpl((MethodImplOptions)256)] + public static void DemitCallInstanceVoid(this ILGenerator il, OpCode opcode, MethodInfo meth, int paramCount) + { + if (UseILEmitHack && CanUseHackEmit(meth.DeclaringType, il)) + { DynamicMethodHacks.HackEmitMethodToken(il, opcode, meth.MethodHandle, -paramCount - 1); return; } + il.Emit(opcode, meth); + } + + /// Emits call/callvirt to an instance method returning a value (net stack: -paramCount). + [MethodImpl((MethodImplOptions)256)] + public static void DemitCallInstance(this ILGenerator il, OpCode opcode, MethodInfo meth, int paramCount) + { + if (UseILEmitHack && CanUseHackEmit(meth.DeclaringType, il)) + { DynamicMethodHacks.HackEmitMethodToken(il, opcode, meth.MethodHandle, -paramCount); return; } + il.Emit(opcode, meth); + } + + /// Emits newobj (net stack: 1 - paramCount). + [MethodImpl((MethodImplOptions)256)] + public static void DemitNewobj(this ILGenerator il, ConstructorInfo ctor, int paramCount) + { + if (UseILEmitHack && CanUseHackEmit(ctor.DeclaringType, il)) + { DynamicMethodHacks.HackEmitMethodToken(il, OpCodes.Newobj, ctor.MethodHandle, 1 - paramCount); return; } + il.Emit(OpCodes.Newobj, ctor); + } + + // todo: @perf ILEmitContext - cache refs to m_length/m_ILStream for a sequence of emits once batch helpers cover enough patterns. +#endif // SUPPORTS_IL_EMIT_HACK +#endif // no DEMIT + } + + /// Reflecting the internal methods to access the more performant for defining the local variable + [RequiresUnreferencedCode(Trimming.Message)] + public static class DynamicMethodHacks + { + [ThreadStatic] + internal static ILGenerator _pooledILGenerator; + +#if SUPPORTS_IL_GENERATOR_REUSE + /// Get new or pool and configure existing DynamicILGenerator + [MethodImpl((MethodImplOptions)256)] + public static ILGenerator RentPooledOrNewILGenerator(DynamicMethod dynMethod, Type returnType, Type[] paramTypes, + // the default ILGenerator size is 64 in .NET 8.0+ + int newStreamSize = 64) + { + var reuseILGenerator = DynamicMethodHacks.ReuseDynamicILGenerator; + if (reuseILGenerator != null) + { + var pooledIL = _pooledILGenerator; + _pooledILGenerator = null; + if (pooledIL != null) + { + reuseILGenerator(dynMethod, pooledIL, returnType, paramTypes); + return pooledIL; + } + else + { + Debug.WriteLine("Unexpected: using a New ILGenerator instead of the pooled one"); + } + } + return dynMethod.GetILGenerator(newStreamSize); + } + + /// Should be called only after call to DynamicMethod.CreateDelegate + [MethodImpl((MethodImplOptions)256)] + public static void FreePooledILGenerator(DynamicMethod dynMethod, ILGenerator il) + { + if (DynamicMethodHacks.ReuseDynamicILGenerator != null) + _pooledILGenerator = il; + } +#else + /// Get new or pool and configure existing DynamicILGenerator + [MethodImpl((MethodImplOptions)256)] + public static ILGenerator RentPooledOrNewILGenerator(DynamicMethod dynMethod, Type returnType, Type[] paramTypes, + int newStreamSize = 64) => + dynMethod.GetILGenerator(newStreamSize); + + /// Should be called only after call to DynamicMethod.CreateDelegate + [MethodImpl((MethodImplOptions)256)] + public static void FreePooledILGenerator(DynamicMethod dynMethod, ILGenerator il) { /* do nothing */ } +#endif + + internal static readonly Func GetNextLocalVarLocation; + + internal static int PostInc(ref int i) => i++; + + internal static Action ReuseDynamicILGenerator; + +#pragma warning disable CS0649 // Warning is about the unused field, but it is used by the generated ReuseDynamicILGenerator + [ThreadStatic] + internal static SignatureHelper _pooledSignatureHelper; +#pragma warning restore CS0649 + + internal static FieldInfo ILGeneratorField; + internal static Type DynamicILGeneratorType; +#if SUPPORTS_IL_EMIT_HACK + // m_scope is on DynamicILGenerator (non-public type). Since its return type DynamicScope is also + // non-public, UnsafeAccessorType cannot express this field access — reflection is still used here. + // m_tokens on the returned scope is then accessed via UnsafeAccessorType (GetMTokens below). + internal static FieldInfo _mScopeField; +#endif + + static DynamicMethodHacks() + { + const BindingFlags instanceNonPublic = BindingFlags.Instance | BindingFlags.NonPublic; + const BindingFlags instancePublic = BindingFlags.Instance | BindingFlags.Public; + const BindingFlags staticNonPublic = BindingFlags.Static | BindingFlags.NonPublic; + const BindingFlags staticPublic = BindingFlags.Static | BindingFlags.Public; + + ILGeneratorField = typeof(DynamicMethod).GetField("_ilGenerator", instanceNonPublic); + if (ILGeneratorField == null) + return; // nothing to do here + + DynamicILGeneratorType = ILGeneratorField.FieldType; +#if SUPPORTS_IL_EMIT_HACK + _mScopeField = DynamicILGeneratorType.GetField("m_scope", instanceNonPublic); +#endif + + // Avoid demit polluting the output of the the initialization phase + var prevDemitValue = ILGeneratorTools.DisableDemit; + ILGeneratorTools.DisableDemit = true; + + if (!ILGeneratorTools.DisableILGeneratorPooling) + { + // ## 1. Reuse the DynamicILGenerator + // + // Reference code - NET. v8, v9: + /* + public ILGenerator GetILGenerator(int streamSize) + { + if (_ilGenerator == null) + { + byte[] methodSignature = SignatureHelper.GetMethodSigHelper( + null, CallingConvention, ReturnType, null, null, _parameterTypes, null, null).GetSignature(true); + _ilGenerator = new DynamicILGenerator(this, methodSignature, streamSize); + } + return _ilGenerator; + } + + internal sealed class DynamicScope + { + internal readonly List m_tokens = new List { null }; + public int GetTokenFor(byte[] signature) + { + m_tokens.Add(signature); + return m_tokens.Count - 1 | (int)MetadataTokenType.Signature; + } + } + + internal DynamicScope m_scope; + private readonly int m_methodSigToken; + + public DynamicILGenerator( + DynamicMethod method, + byte[] methodSignature, + int streamSize) + { + m_scope = new DynamicScope(); + m_methodSigToken = m_scope.GetTokenFor(methodSignature); + + m_ScopeTree = new ScopeTree(); + m_ILStream = new byte[Math.Max(size, DefaultSize)]; + + m_localSignature = SignatureHelper.GetLocalVarSigHelper((method as RuntimeMethodBuilder)?.GetTypeBuilder().Module); + m_methodBuilder = method; // set to the new DynamicMethod + } + */ + var m_ScopeTreeField = DynamicILGeneratorType.GetField("m_ScopeTree", instanceNonPublic); + if (m_ScopeTreeField == null) + goto endOfReuse; + + var ScopeTreeCtor = m_ScopeTreeField.FieldType.GetConstructor(instanceNonPublic, null, Type.EmptyTypes, null); + if (ScopeTreeCtor == null) + goto endOfReuse; + + var DynamicILGeneratorScopeField = DynamicILGeneratorType.GetField("m_scope", instanceNonPublic); + if (DynamicILGeneratorScopeField == null) + goto endOfReuse; + + var DynamicILGeneratorScopeType = DynamicILGeneratorScopeField.FieldType; + var DynamicScopeTokensField = DynamicILGeneratorScopeType.GetField("m_tokens", instanceNonPublic); + if (DynamicScopeTokensField == null) + goto endOfReuse; + + var DynamicScopeTokensItem = DynamicScopeTokensField.FieldType.GetProperty("Item"); + Debug.Assert(DynamicScopeTokensItem != null, "DynamicScopeTokens.Item should not be null"); + + var getMethodSigHelperParams = ExpressionCompiler.RentPooledOrNewParamTypes(typeof(Module), typeof(Type), typeof(Type[])); + var GetMethodSigHelperMethod = typeof(SignatureHelper).GetMethod("GetMethodSigHelper", staticPublic, null, getMethodSigHelperParams, null); + ExpressionCompiler.FreePooledParamTypes(getMethodSigHelperParams); + if (GetMethodSigHelperMethod == null) + goto endOfReuse; + + var GetLocalVarSigHelperMethod = typeof(SignatureHelper).GetMethod("GetLocalVarSigHelper", staticPublic, null, Type.EmptyTypes, null); + if (GetLocalVarSigHelperMethod == null) + goto endOfReuse; + + var getSignatureParams = ExpressionCompiler.RentPooledOrNewParamTypes(typeof(bool)); + var SignatureHelper_GetSignatureMethod = typeof(SignatureHelper).GetMethod("GetSignature", instanceNonPublic, null, getSignatureParams, null); + ExpressionCompiler.FreePooledParamTypes(getSignatureParams); + if (SignatureHelper_GetSignatureMethod == null) + goto endOfReuse; + + var getTokenForParams = ExpressionCompiler.RentPooledOrNewParamTypes(typeof(byte[])); + var GetTokenForMethod = DynamicILGeneratorScopeType.GetMethod("GetTokenFor", instancePublic, null, getTokenForParams, null); + ExpressionCompiler.FreePooledParamTypes(getTokenForParams); + if (GetTokenForMethod == null) + goto endOfReuse; + + var MethodSigTokenField = DynamicILGeneratorType.GetField("m_methodSigToken", instanceNonPublic); + if (MethodSigTokenField == null) + goto endOfReuse; + + var dynMethodParamTypes = ExpressionCompiler.RentPooledOrNewParamTypes( + typeof(ExpressionCompiler.ArrayClosure), typeof(DynamicMethod), typeof(ILGenerator), typeof(Type), typeof(Type[])); + + var dynMethod = new DynamicMethod(string.Empty, typeof(void), dynMethodParamTypes, typeof(ExpressionCompiler.ArrayClosure), true); + + var il = dynMethod.GetILGenerator(512); // precalculated size to avoid waste + + var baseFields = DynamicILGeneratorType.BaseType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly); + foreach (var field in baseFields) + { + var fieldName = field.Name; + if (fieldName == "m_localSignature") + { + il.Demit(OpCodes.Ldarg_2); + il.Demit(OpCodes.Call, GetLocalVarSigHelperMethod); + il.DemitStfld(field); + continue; + } + + // m_ScopeTree = new ScopeTree(); + if (fieldName == "m_ScopeTree") + { + il.Demit(OpCodes.Ldarg_2); + il.Demit(OpCodes.Newobj, ScopeTreeCtor); + il.DemitStfld(field); + continue; + } + + // m_methodBuilder = method; // dynamicMethod + if (fieldName == "m_methodBuilder") + { + il.Demit(OpCodes.Ldarg_2); + il.Demit(OpCodes.Ldarg_1); + il.DemitStfld(field); + continue; + } + + if (fieldName == "m_ILStream") + continue; // reuse the previous il stream + + il.Demit(OpCodes.Ldarg_2); + ExpressionCompiler.EmittingVisitor.EmitDefault(il, field.FieldType); + il.DemitStfld(field); + } + + // var scope = new DynamicScope(); + var dynamicScopeCtor = DynamicILGeneratorScopeType.GetConstructor(Type.EmptyTypes); + if (dynamicScopeCtor == null) + goto endOfReuse; + il.Emit(OpCodes.Newobj, dynamicScopeCtor); + var scopeVar = il.DeclareLocal(DynamicILGeneratorScopeType).LocalIndex; + ExpressionCompiler.EmittingVisitor.EmitStoreLocalVariable(il, scopeVar); + + /* + private byte[] m_signature; // todo: @perf keep it, because it would be copied anyway + private int m_currSig; // index into m_signature buffer for next available byte + private int m_sizeLoc; // index into m_signature buffer to put m_argCount (will be NO_SIZE_IN_SIG if no arg count is needed) + private ModuleBuilder? m_module; + private bool m_sigDone; + private int m_argCount; // tracking number of arguments in the signature + + internal static SignatureHelper GetMethodSigHelper( + Module? scope, CallingConventions callingConvention, int cGenericParam, + Type? returnType, Type[]? requiredReturnTypeCustomModifiers, Type[]? optionalReturnTypeCustomModifiers, + Type[]? parameterTypes, Type[][]? requiredParameterTypeCustomModifiers, Type[][]? optionalParameterTypeCustomModifiers) + { + SignatureHelper sigHelp; + MdSigCallingConvention intCall; + + // not needed, always provided + returnType ??= typeof(void); + + // not needed, always CallingConventions.Standard + intCall = MdSigCallingConvention.Default; + if ((callingConvention & CallingConventions.VarArgs) == CallingConventions.VarArgs) + intCall = MdSigCallingConvention.Vararg; + + // not needed, always 0 + if (cGenericParam > 0) + { + intCall |= MdSigCallingConvention.Generic; + } + + // not needed, can be externalized as a const, precalculate the `unchecked((byte)CallingConventions.Standard) & Mask)` + const byte Mask = (byte)(CallingConventions.HasThis | CallingConventions.ExplicitThis); + intCall = (MdSigCallingConvention)((byte)intCall | (unchecked((byte)callingConvention) & Mask)); + + sigHelp = new SignatureHelper(scope, intCall, cGenericParam, returnType, + requiredReturnTypeCustomModifiers, optionalReturnTypeCustomModifiers); + + // m_signature = new byte[32]; + // m_currSig = 0; + // m_module = mod as ModuleBuilder; + // m_argCount = 0; + // m_sigDone = false; + // m_sizeLoc = NO_SIZE_IN_SIG; + + sigHelp.AddArguments(parameterTypes, requiredParameterTypeCustomModifiers, optionalParameterTypeCustomModifiers); + + return sigHelp; + } + */ + // pseudo code to reuse the pooled SignatureHelper + /* + var signatureHelper = _pooledSignatureHelper; + _pooledSignatureHelper = null; + if (signatureHelper == null) + signatureBytes = SignatureHelper.GetMethodSigHelper(null, returnType, paramTypes).GetSignature(true); + else + { + // no need to set, can reuse the previous value + // m_signature = new byte[32]; + // signatureHelper.m_module = null; + signatureHelper.m_currSig = 0; + signatureHelper.m_argCount = 0; + signatureHelper.m_sigDone = false; + signatureHelper.m_sizeLoc = -1; + signatureHelper.AddOneArgTypeHelper(returnType, null, null); + signatureHelper.AddArguments(paramTypes, null, null); + + var signatureBytes = signatureHelper.GetSignature(true); + _pooledSignatureHelper = signatureHelper; + } + */ + var sigBytesVar = il.DeclareLocal(typeof(byte[])).LocalIndex; + + var pooledSignatureHelperField = typeof(DynamicMethodHacks).GetField(nameof(_pooledSignatureHelper), staticNonPublic); + Debug.Assert(pooledSignatureHelperField != null, "_pooledSignatureHelper field not found!"); + + il.Emit(OpCodes.Ldsfld, pooledSignatureHelperField); + var sigHelperVar = il.DeclareLocal(typeof(SignatureHelper)).LocalIndex; + ExpressionCompiler.EmittingVisitor.EmitStoreAndLoadLocalVariable(il, sigHelperVar); // loading it here to do a Brfalse below, after setting the field to null + il.Emit(OpCodes.Ldnull); // set the pooled instance to null + il.Emit(OpCodes.Stsfld, pooledSignatureHelperField); + + var labelSigHelperNull = il.DefineLabel(); + il.Emit(OpCodes.Brfalse, labelSigHelperNull); + + // signatureHelper.m_currSig = 0 + ExpressionCompiler.EmittingVisitor.EmitLoadLocalVariable(il, sigHelperVar); + il.Emit(OpCodes.Ldc_I4_0); + var m_currSig = typeof(SignatureHelper).GetField("m_currSig", instanceNonPublic); + if (m_currSig == null) + goto endOfReuse; + il.Emit(OpCodes.Stfld, m_currSig); + + //signatureHelper.m_argCount = 0; + ExpressionCompiler.EmittingVisitor.EmitLoadLocalVariable(il, sigHelperVar); + il.Emit(OpCodes.Ldc_I4_0); + var m_argCount = typeof(SignatureHelper).GetField("m_argCount", instanceNonPublic); + if (m_argCount == null) + goto endOfReuse; + il.Emit(OpCodes.Stfld, m_argCount); + + // signatureHelper.m_sigDone = false; + ExpressionCompiler.EmittingVisitor.EmitLoadLocalVariable(il, sigHelperVar); + il.Emit(OpCodes.Ldc_I4_0); + var m_sigDone = typeof(SignatureHelper).GetField("m_sigDone", instanceNonPublic); + if (m_sigDone == null) + goto endOfReuse; + il.Emit(OpCodes.Stfld, m_sigDone); + + // signatureHelper.m_sizeLoc = -1; + ExpressionCompiler.EmittingVisitor.EmitLoadLocalVariable(il, sigHelperVar); + il.Emit(OpCodes.Ldc_I4_M1); + var m_sizeLoc = typeof(SignatureHelper).GetField("m_sizeLoc", instanceNonPublic); + if (m_sizeLoc == null) + goto endOfReuse; + il.Emit(OpCodes.Stfld, m_sizeLoc); + + // signatureHelper.AddOneArgTypeHelper(returnType, null, null); + ExpressionCompiler.EmittingVisitor.EmitLoadLocalVariable(il, sigHelperVar); + il.Emit(OpCodes.Ldarg_3); // load return type + il.Emit(OpCodes.Ldnull); // load required return type custom modifiers + il.Emit(OpCodes.Ldnull); // load optional return type custom modifiers + var addOneArgTypeHelperParams = ExpressionCompiler.RentPooledOrNewParamTypes(typeof(Type), typeof(Type[]), typeof(Type[])); + var AddOneArgTypeHelperMethod = typeof(SignatureHelper).GetMethod("AddOneArgTypeHelper", instanceNonPublic, null, addOneArgTypeHelperParams, null); + ExpressionCompiler.FreePooledParamTypes(addOneArgTypeHelperParams); + if (AddOneArgTypeHelperMethod == null) + goto endOfReuse; + il.Emit(OpCodes.Call, AddOneArgTypeHelperMethod); + + // signatureHelper.AddArguments(paramTypes, null, null); + ExpressionCompiler.EmittingVisitor.EmitLoadLocalVariable(il, sigHelperVar); + il.Emit(OpCodes.Ldarg_S, 4); // load parameter types arrays + il.Emit(OpCodes.Ldnull); // load required parameter type custom modifiers + il.Emit(OpCodes.Ldnull); // load optional parameter type custom modifiers + + var addArgumentsParams = ExpressionCompiler.RentPooledOrNewParamTypes(typeof(Type[]), typeof(Type[][]), typeof(Type[][])); + var AddArgumentsMethod = typeof(SignatureHelper).GetMethod("AddArguments", instancePublic, null, addArgumentsParams, null); + ExpressionCompiler.FreePooledParamTypes(addArgumentsParams); + if (AddArgumentsMethod == null) + goto endOfReuse; + il.Emit(OpCodes.Call, AddArgumentsMethod); + + // signatureBytes = signatureHelper.GetSignature(true); + ExpressionCompiler.EmittingVisitor.EmitLoadLocalVariable(il, sigHelperVar); + il.Emit(OpCodes.Ldc_I4_1); // load true + il.Emit(OpCodes.Call, SignatureHelper_GetSignatureMethod); + ExpressionCompiler.EmittingVisitor.EmitStoreLocalVariable(il, sigBytesVar); + + // free the signature helper to the pool + ExpressionCompiler.EmittingVisitor.EmitLoadLocalVariable(il, sigHelperVar); + il.Emit(OpCodes.Stsfld, pooledSignatureHelperField); + + // done + var labelSigHelperDone = il.DefineLabel(); + il.Emit(OpCodes.Br, labelSigHelperDone); + + // Standard handling: + // signatureBytes = SignatureHelper.GetMethodSigHelper(null, returnType, paramTypes).GetSignature(true); + il.MarkLabel(labelSigHelperNull); + + il.Emit(OpCodes.Ldnull); // for the module + il.Emit(OpCodes.Ldarg_3); // load return type + il.Emit(OpCodes.Ldarg_S, 4); // load parameter types arrays + il.Emit(OpCodes.Call, GetMethodSigHelperMethod); + il.Emit(OpCodes.Ldc_I4_1); // load true + il.Emit(OpCodes.Call, SignatureHelper_GetSignatureMethod); + ExpressionCompiler.EmittingVisitor.EmitStoreLocalVariable(il, sigBytesVar); + + // todo: @perf GetSignature(true) will copy internal byte buffer almost always, see + /* + internal byte[] GetSignature(bool appendEndOfSig) + { + // Chops the internal signature to the appropriate length. Adds the + // end token to the signature and marks the signature as finished so that + // no further tokens can be added. Return the full signature in a trimmed array. + if (!m_sigDone) + { + if (appendEndOfSig) + AddElementType(CorElementType.ELEMENT_TYPE_END); + SetNumberOfSignatureElements(true); + m_sigDone = true; + } + + // This case will only happen if the user got the signature through + // InternalGetSignature first and then called GetSignature. + if (m_signature.Length > m_currSig) + { + byte[] temp = new byte[m_currSig]; + Array.Copy(m_signature, temp, m_currSig); + m_signature = temp; + } + + return m_signature; + } + */ + + il.MarkLabel(labelSigHelperDone); + + // m_methodSigToken = scope.GetTokenFor(methodSignature); + il.Emit(OpCodes.Ldarg_2); + ExpressionCompiler.EmittingVisitor.EmitLoadLocalVariable(il, scopeVar); + ExpressionCompiler.EmittingVisitor.EmitLoadLocalVariable(il, sigBytesVar); + il.Emit(OpCodes.Call, GetTokenForMethod); + il.Emit(OpCodes.Stfld, MethodSigTokenField); + + // m_scope = scope; + il.Emit(OpCodes.Ldarg_2); + ExpressionCompiler.EmittingVisitor.EmitLoadLocalVariable(il, scopeVar); + il.Emit(OpCodes.Stfld, DynamicILGeneratorScopeField); + + // store the reused ILGenerator to + il.Emit(OpCodes.Ldarg_1); + il.Emit(OpCodes.Ldarg_2); + il.Emit(OpCodes.Stfld, ILGeneratorField); + + il.Emit(OpCodes.Ret); + + ReuseDynamicILGenerator = (Action) + dynMethod.CreateDelegate(typeof(Action), ExpressionCompiler.EmptyArrayClosure); + + // Put the first used ILGenerator into the pool, let's not waste it from te get go + _pooledILGenerator = il; + + ExpressionCompiler.FreePooledParamTypes(dynMethodParamTypes); + endOfReuse:; + } + { + // ## 2. Get Next Local Variable Index/Location + + // The original ILGenerator methods we are trying to hack without allocating the `LocalBuilder` + /* + public virtual LocalBuilder DeclareLocal(Type localType) + { + return this.DeclareLocal(localType, false); + } + + public virtual LocalBuilder DeclareLocal(Type localType, bool pinned) + { + MethodBuilder methodBuilder = this.m_methodBuilder as MethodBuilder; + if ((MethodInfo)methodBuilder == (MethodInfo)null) + throw new NotSupportedException(); + if (methodBuilder.IsTypeCreated()) + throw new InvalidOperationException(SR.InvalidOperation_TypeHasBeenCreated); + if (localType == (Type)null) + throw new ArgumentNullException(nameof(localType)); + if (methodBuilder.m_bIsBaked) + throw new InvalidOperationException(SR.InvalidOperation_MethodBaked); + this.m_localSignature.AddArgument(localType, pinned); + LocalBuilder localBuilder = new LocalBuilder(this.m_localCount, localType, (MethodInfo)methodBuilder, pinned); + ++this.m_localCount; + return localBuilder; + } + */ +#if NET10_0_OR_GREATER + // In .NET 10+, use UnsafeAccessorType to directly access RuntimeILGenerator's private fields + // without the need for reflection-based DynamicMethod generation at startup + GetNextLocalVarLocation = static (il, t) => + { + GetMLocalSignature(il).AddArgument(t, false); + return PostInc(ref GetMLocalCount(il)); + }; +#else + // Let's try to acquire the more efficient less allocating method + var m_localSignatureField = DynamicILGeneratorType.GetField("m_localSignature", instanceNonPublic); + if (m_localSignatureField == null) + goto endOfGetNextVar; + + var m_localCountField = DynamicILGeneratorType.GetField("m_localCount", instanceNonPublic); + if (m_localCountField == null) + goto endOfGetNextVar; + + // looking for the `SignatureHelper.AddArgument(Type argument, bool pinned)` + var typeAndBoolParamTypes = ExpressionCompiler.RentPooledOrNewParamTypes(typeof(Type), typeof(bool)); + var SignatureHelper_AddArgumentMethod = typeof(SignatureHelper).GetMethod("AddArgument", typeAndBoolParamTypes); + ExpressionCompiler.FreePooledParamTypes(typeAndBoolParamTypes); + if (SignatureHelper_AddArgumentMethod == null) + goto endOfGetNextVar; + + // our own helper - always available + var postIncMethod = typeof(DynamicMethodHacks).GetMethod(nameof(PostInc), staticNonPublic); + Debug.Assert(postIncMethod != null, "PostInc method not found!"); + + var paramTypes = ExpressionCompiler.RentPooledOrNewParamTypes(typeof(ExpressionCompiler.ArrayClosure), typeof(ILGenerator), typeof(Type)); + var dynMethod = new DynamicMethod(string.Empty, typeof(int), paramTypes, typeof(ExpressionCompiler.ArrayClosure), true); + + // it does not use the pooled il generator here, to isolate this variable hack from the il generator pooling hack and for the better problem diagnostics + var il = dynMethod.GetILGenerator(32); + + // emitting `il.m_localSignature.AddArgument(type);` + il.Emit(OpCodes.Ldarg_1); // load `il` argument (arg_0 is the empty closure object) + il.Emit(OpCodes.Ldfld, m_localSignatureField); + il.Emit(OpCodes.Ldarg_2); // load `type` argument + il.Emit(OpCodes.Ldc_I4_0); // load `pinned: false` argument + il.Emit(OpCodes.Call, SignatureHelper_AddArgumentMethod); + + // emitting `return PostInc(ref il.LocalCount);` + il.Emit(OpCodes.Ldarg_1); // load `il` argument + il.Emit(OpCodes.Ldflda, m_localCountField); + il.Emit(OpCodes.Call, postIncMethod); + + il.Emit(OpCodes.Ret); + + GetNextLocalVarLocation = (Func) + dynMethod.CreateDelegate(typeof(Func), ExpressionCompiler.EmptyArrayClosure); + + ExpressionCompiler.FreePooledParamTypes(paramTypes); + endOfGetNextVar:; +#endif + } + + // Restore the demit + ILGeneratorTools.DisableDemit = prevDemitValue; +#if SUPPORTS_IL_EMIT_HACK + // Enable the IL emit hack now that initialization is complete. + // Only activate if _mScopeField was successfully resolved (required for GetScopeTokens). + if (_mScopeField != null) + ILGeneratorTools.UseILEmitHack = true; +#endif + + // ## 3 TBD + // + // todo: @perf do batch Emit by manually calling `EnsureCapacity` once then `InternalEmit` multiple times + // todo: @perf Replace the `Emit(opcode, int)` with the more specialized `Emit(opcode)`, `Emit(opcode, byte)` or `Emit(opcode, short)` + // avoiding internal check for Ldc_I4, Ldarg, Ldarga, Starg then call `PutInteger4` only if needed see https://source.dot.net/#System.Private.CoreLib/src/System/Reflection/Emit/ILGenerator.cs,690f350859394132 + // var ensureCapacityMethod = ilGenTypeInfo.GetDeclaredMethod("EnsureCapacity"); + // var internalEmitMethod = ilGenTypeInfo.GetDeclaredMethod("InternalEmit"); + // var putInteger4Method = ilGenTypeInfo.GetDeclaredMethod("PutInteger4"); + } + + +#if DEBUG_INFO_LOCAL_VARIABLE_USAGE + [ThreadStatic] + public static SmallMap8> LocalVarUsage; +#endif + // todo: @perf add the map of the used local variables that can be reused, e.g. we are getting the variable used in the local scope but then we may return them into POOL and reuse (many of int variable can be reuses, say for indexes) + /// Efficiently returns the next variable index, hopefully without unnecessary allocations. + [MethodImpl((MethodImplOptions)256)] + public static int GetNextLocalVarIndex(this ILGenerator il, Type t) + { +#if DEBUG_INFO_LOCAL_VARIABLE_USAGE + try + { + ref var varUsage = ref LocalVarUsage.Map.AddOrGetValueRef(t, out var found); + if (!found) + varUsage = 1; + else + ++varUsage; + } + catch (Exception ex) + { + Debug.WriteLine("Error tracking the local variable usage: " + ex); + } +#endif + return GetNextLocalVarLocation?.Invoke(il, t) ?? il.DeclareLocal(t).LocalIndex; + } + + // todo: @perf add MultiOpCodes emit to save on the EnsureCapacity calls + // todo: @perf create EmitMethod without additional GetParameters call + /* + // original code: + public override void Emit(OpCode opcode, MethodInfo meth) + { + ArgumentNullException.ThrowIfNull(meth); + + int stackchange = 0; + int token; + DynamicMethod? dynMeth = meth as DynamicMethod; + if (dynMeth == null) + { + RuntimeMethodInfo? rtMeth = meth as RuntimeMethodInfo; + if (rtMeth == null) + throw new ArgumentException(SR.Argument_MustBeRuntimeMethodInfo, nameof(meth)); + + RuntimeType declaringType = rtMeth.GetRuntimeType(); + if (declaringType != null && (declaringType.IsGenericType || declaringType.IsArray)) + token = GetTokenFor(rtMeth, declaringType); + else + token = GetTokenFor(rtMeth); + } + else + { + // rule out not allowed operations on DynamicMethods + if (opcode.Equals(OpCodes.Ldtoken) || opcode.Equals(OpCodes.Ldftn) || opcode.Equals(OpCodes.Ldvirtftn)) + { + throw new ArgumentException(SR.Argument_InvalidOpCodeOnDynamicMethod); + } + token = GetTokenFor(dynMeth); + } + + EnsureCapacity(7); + InternalEmit(opcode); + + if (opcode.StackBehaviourPush == StackBehaviour.Varpush + && meth.ReturnType != typeof(void)) + { + stackchange++; + } + if (opcode.StackBehaviourPop == StackBehaviour.Varpop) + { + stackchange -= meth.GetParametersNoCopy().Length; + } + // Pop the "this" parameter if the method is non-static, + // and the instruction is not newobj/ldtoken/ldftn. + if (!meth.IsStatic && + !(opcode.Equals(OpCodes.Newobj) || opcode.Equals(OpCodes.Ldtoken) || opcode.Equals(OpCodes.Ldftn))) + { + stackchange--; + } + + UpdateStackSize(opcode, stackchange); + PutInteger4(token); + } + + // stripped down code for not generic method and not array method: + public override void Emit(OpCode opcode, MethodInfo meth, int paramCount) + { + m_scope.m_tokens.Add(((RuntimeMethodInfo)meth).MethodHandle); + var token = m_scope.m_tokens.Count - 1 | (int)MetadataTokenType.MethodDef; // MethodDef is 0x06000000 + + // Guarantees an array capable of holding at least size elements. + if (m_length + size >= m_ILStream.Length) + IncreaseCapacity(7); + + m_ILStream[m_length++] = (byte)opcode.Value; + UpdateStackSize(opcode, 0); + + int stackchange = 0; + if (meth.ReturnType != typeof(void)) + stackchange++; + stackchange -= paramCount; + if (!meth.IsStatic) + stackchange--; + + UpdateStackSize(opcode, stackchange); + + BinaryPrimitives.WriteInt32LittleEndian(m_ILStream.AsSpan(m_length), token); + m_length += 4; + } + */ + +#if NET10_0_OR_GREATER + // UnsafeAccessorType methods for accessing private fields of the non-public RuntimeILGenerator class. + // RuntimeILGenerator is the internal base class of DynamicILGenerator that holds the core IL generation state. + // Using UnsafeAccessorType avoids reflection at call time and is compatible with AOT compilation. + + /// Gets a ref to the m_localSignature field of the ILGenerator (declared in the internal RuntimeILGenerator). + [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "m_localSignature")] + internal static extern ref SignatureHelper GetMLocalSignature( + [UnsafeAccessorType("System.Reflection.Emit.RuntimeILGenerator")] object il); + + /// Gets a ref to the m_localCount field of the ILGenerator (declared in the internal RuntimeILGenerator). + [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "m_localCount")] + internal static extern ref int GetMLocalCount( + [UnsafeAccessorType("System.Reflection.Emit.RuntimeILGenerator")] object il); + + /// Gets a ref to the m_length field of the ILGenerator (declared in the internal RuntimeILGenerator). + [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "m_length")] + internal static extern ref int GetMLength( + [UnsafeAccessorType("System.Reflection.Emit.RuntimeILGenerator")] object il); + + /// Gets a ref to the m_ILStream field of the ILGenerator (declared in the internal RuntimeILGenerator). + [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "m_ILStream")] + internal static extern ref byte[] GetMILStream( + [UnsafeAccessorType("System.Reflection.Emit.RuntimeILGenerator")] object il); + + /// Calls the internal UpdateStackSize method on the ILGenerator (declared in the internal RuntimeILGenerator). + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "UpdateStackSize")] + internal static extern void UpdateStackSize( + [UnsafeAccessorType("System.Reflection.Emit.RuntimeILGenerator")] object il, + OpCode opcode, int stackchange); + + /// Gets a ref to the m_tokens field on a DynamicScope instance (internal type). + [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "m_tokens")] + internal static extern ref System.Collections.Generic.List GetMTokens( + [UnsafeAccessorType("System.Reflection.Emit.DynamicScope")] object scope); + + // Core helpers for the SUPPORTS_IL_EMIT_HACK fast-emit path. + // GetScopeTokens: one reflection get for m_scope (non-public return type prevents UAT), then UAT for m_tokens. + // HackEmitMethodToken/HackEmitFieldToken: write opcode bytes + token + UpdateStackSize. + // HackEmitMethod/Ctor/Field: compute stack change from reflection metadata; fall back for generic/array types and non-DynamicILGenerator. + // todo: @perf batch candidates: Ldarg_0+Ldfld (closure load), Dup+Brtrue/Brfalse+Pop (null check) + + // Gets m_tokens from the dynamic scope; one reflection get for m_scope (non-public return type prevents UAT). + [MethodImpl((MethodImplOptions)256)] + private static ref List GetScopeTokens(ILGenerator il) + { + var scope = _mScopeField.GetValue(il); + return ref GetMTokens(scope); + } + + // Core IL-stream write for a method/ctor token (non-generic, non-array declaring types only). + [MethodImpl((MethodImplOptions)256)] + internal static void HackEmitMethodToken(ILGenerator il, OpCode opcode, RuntimeMethodHandle handle, int stackChange) + { + ref int mLength = ref GetMLength(il); + ref byte[] mILStream = ref GetMILStream(il); + + // opcode (1 or 2 bytes) + token (4 bytes) + int needed = opcode.Size + 4; + if (mLength + needed > mILStream.Length) + Array.Resize(ref mILStream, Math.Max(mILStream.Length * 2, mLength + needed)); + + if (opcode.Size == 1) + mILStream[mLength++] = (byte)opcode.Value; + else + { + mILStream[mLength++] = (byte)((int)opcode.Value >> 8); + mILStream[mLength++] = (byte)opcode.Value; + } + + // Store RuntimeMethodHandle directly; HackEmitMethod/Ctor ensure non-generic types reach here. + ref var mTokens = ref GetScopeTokens(il); + mTokens.Add(handle); + int token = (mTokens.Count - 1) | 0x06000000; + BinaryPrimitives.WriteInt32LittleEndian(mILStream.AsSpan(mLength), token); + mLength += 4; + + UpdateStackSize(il, opcode, stackChange); + } + + // Core IL-stream write for a field token. Stores RuntimeFieldHandle directly (DynamicResolver handles it). + [MethodImpl((MethodImplOptions)256)] + internal static void HackEmitFieldToken(ILGenerator il, OpCode opcode, RuntimeFieldHandle handle, int stackChange) + { + ref int mLength = ref GetMLength(il); + ref byte[] mILStream = ref GetMILStream(il); + + int needed = opcode.Size + 4; + if (mLength + needed > mILStream.Length) + Array.Resize(ref mILStream, Math.Max(mILStream.Length * 2, mLength + needed)); + + if (opcode.Size == 1) + mILStream[mLength++] = (byte)opcode.Value; + else + { + mILStream[mLength++] = (byte)((int)opcode.Value >> 8); + mILStream[mLength++] = (byte)opcode.Value; + } + + // Store the field handle; DynamicResolver.ResolveToken handles RuntimeFieldHandle directly. + ref var mTokens = ref GetScopeTokens(il); + mTokens.Add(handle); + int token = (mTokens.Count - 1) | 0x04000000; + BinaryPrimitives.WriteInt32LittleEndian(mILStream.AsSpan(mLength), token); + mLength += 4; + + UpdateStackSize(il, opcode, stackChange); + } + + [MethodImpl((MethodImplOptions)256)] + internal static void HackEmitMethod(ILGenerator il, OpCode opcode, MethodInfo meth) + { + var dt = meth.DeclaringType; + if (il.GetType() != DynamicILGeneratorType || (dt != null && (dt.IsGenericType || dt.IsArray))) + { il.Emit(opcode, meth); return; } + + // Mirror ILGenerator.Emit(OpCode, MethodInfo): +1 for non-void return, -params, -1 for instance (except newobj/ldtoken/ldftn) + int stackChange = 0; + if (meth.ReturnType != typeof(void)) + stackChange++; + if (opcode.StackBehaviourPop == StackBehaviour.Varpop) + stackChange -= meth.GetParameters().Length; + if (!meth.IsStatic && + !(opcode.Equals(OpCodes.Newobj) || opcode.Equals(OpCodes.Ldtoken) || opcode.Equals(OpCodes.Ldftn))) + stackChange--; + HackEmitMethodToken(il, opcode, meth.MethodHandle, stackChange); + } + + [MethodImpl((MethodImplOptions)256)] + internal static void HackEmitCtor(ILGenerator il, OpCode opcode, ConstructorInfo ctor) + { + var dt = ctor.DeclaringType; + if (il.GetType() != DynamicILGeneratorType || (dt != null && (dt.IsGenericType || dt.IsArray))) + { il.Emit(opcode, ctor); return; } + + // Mirror ILGenerator.Emit(OpCode, ConstructorInfo): newobj pushes 1, pops all parameters + int stackChange = opcode.Equals(OpCodes.Newobj) ? 1 - ctor.GetParameters().Length : 0; + HackEmitMethodToken(il, opcode, ctor.MethodHandle, stackChange); + } + + [MethodImpl((MethodImplOptions)256)] + internal static void HackEmitField(ILGenerator il, OpCode opcode, FieldInfo field) + { + // Fall back for generic/array types: ILReader's DynamicScopeTokenResolver.AsField calls + // GetFieldFromHandle(handle) without type context, which throws for generic types. + var dt = field.DeclaringType; + if (il.GetType() != DynamicILGeneratorType || (dt != null && (dt.IsGenericType || dt.IsArray))) + { il.Emit(opcode, field); return; } + + // Ldfld/Ldflda: net 0 (pop obj, push value/addr); Stfld: -2 (pop obj+value); + // Ldsfld/Ldsflda: +1 (push value/addr); Stsfld: -1 (pop value) + int stackChange; + if (opcode.Equals(OpCodes.Ldfld) || opcode.Equals(OpCodes.Ldflda)) + stackChange = 0; + else if (opcode.Equals(OpCodes.Stfld)) + stackChange = -2; + else if (opcode.Equals(OpCodes.Ldsfld) || opcode.Equals(OpCodes.Ldsflda)) + stackChange = 1; + else // Stsfld + stackChange = -1; + HackEmitFieldToken(il, opcode, field.FieldHandle, stackChange); + } +#endif + } + + [RequiresUnreferencedCode(Trimming.Message)] + public static class ToExpressionPrinter + { + /// + /// Prints the expression in its constructing syntax - + /// helpful to get the expression from the debug session and put into it the code for the test. + /// + public static string ToExpressionString(this Expression expr, ObjectToCode notRecognizedToCode = null) => + expr.ToExpressionString(out var _, out var _, out var _, notRecognizedToCode: notRecognizedToCode); + + // todo: @api There should be a version returning StringBuilder the same as for ToCSharpString + /// + /// Prints the expression in its constructing syntax - + /// helpful to get the expression from the debug session and put into it the code for the test. + /// In addition, returns the gathered expressions, parameters ad labels. + /// + public static string ToExpressionString(this Expression expr, + out List paramsExprs, out List uniqueExprs, out List lts, + bool stripNamespace = false, Func printType = null, int indentSpaces = 2, ObjectToCode notRecognizedToCode = null) + { + var sb = new StringBuilder(1024); + sb.Append("var expr = "); + paramsExprs = new List(); + uniqueExprs = new List(); + lts = new List(); + sb = expr.CreateExpressionString(sb, paramsExprs, uniqueExprs, lts, 2, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(';'); + + if (lts.Count > 0) + sb.Insert(0, $"var l = new LabelTarget[{lts.Count}]; // the labels{NewLine}"); + if (uniqueExprs.Count > 0) + sb.Insert(0, $"var e = new Expression[{uniqueExprs.Count}]; // the unique expressions{NewLine}"); + if (paramsExprs.Count > 0) + sb.Insert(0, $"var p = new ParameterExpression[{paramsExprs.Count}]; // the parameter expressions{NewLine}"); + + return sb.ToString(); + } + + // Searches first for the expression reference in the `uniqueExprs` and adds the reference to expression by index, + // otherwise delegates to `CreateExpressionCodeString` + internal static StringBuilder ToExpressionString(this Expression expr, StringBuilder sb, + List paramsExprs, List uniqueExprs, List lts, + int lineIndent, bool stripNamespace, Func printType, int indentSpaces, ObjectToCode notRecognizedToCode) + { + if (expr is ParameterExpression p) + return p.ToExpressionString(sb, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + + if (uniqueExprs.TryGetIndex(out var i, expr, uniqueExprs.Count, default(RefEq))) + return sb.Append("e[").Append(i) + // output expression type and kind to help to understand what is it + .Append(" // ").Append(expr.NodeType.ToString()).Append(" of ") + .Append(expr.Type.ToCode(stripNamespace, printType)) + .NewLineIndent(lineIndent).Append("]"); + + uniqueExprs.Add(expr); + sb.Append("e[").Append(uniqueExprs.Count - 1).Append("]="); + return expr.CreateExpressionString(sb, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + } + + internal static StringBuilder ToExpressionString(this ParameterExpression pe, StringBuilder sb, + List paramsExprs, List uniqueExprs, List lts, + int lineIndent, bool stripNamespace, Func printType, int indentSpaces, ObjectToCode notRecognizedToCode) + { + if (paramsExprs.TryGetIndex(out var i, pe, paramsExprs.Count, default(RefEq))) + { + PrintContext ctx = default; + return sb + .Append("p[").Append(i) + .Append(" // (") + .Append(!pe.Type.IsPrimitive && pe.Type.IsValueType ? "[struct] " : string.Empty) + .Append(pe.Type.ToCode(stripNamespace, printType)) + .Append(' ') + .AppendName(pe, pe.Name, pe.Type.ToCode(stripNamespace, printType), ref ctx, pe.GetHashCode()).Append(')') // todo: @wip #434 but for ToExpressionString + .NewLineIndent(lineIndent) + .Append(']'); + } + paramsExprs.Add(pe); + sb.Append("p[").Append(paramsExprs.Count - 1).Append("]="); + return pe.CreateExpressionString(sb, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + } + + internal static StringBuilder ToExpressionString(this LabelTarget lt, StringBuilder sb, List labelTargets, + int lineIndent, bool stripNamespace, Func printType) + { + if (labelTargets.TryGetIndex(out var i, lt, labelTargets.Count, default(RefEq))) + { + PrintContext ctx = default; + return sb.Append("l[").Append(i) + .Append(" // (").AppendName(lt, lt.Name, lt.Type.ToCode(stripNamespace, printType), ref ctx, lt.GetHashCode()).Append(')') + .NewLineIndent(lineIndent).Append(']'); + } + labelTargets.Add(lt); + sb.Append("l[").Append(labelTargets.Count - 1).Append("]=Label("); + sb.AppendTypeOf(lt.Type, stripNamespace, printType); + + return (lt.Name != null ? sb.Append(", \"").Append(lt.Name).Append("\"") : sb).Append(")"); + } + + private static StringBuilder ToExpressionString(this IReadOnlyList bs, StringBuilder sb, + List paramsExprs, List uniqueExprs, List lts, + int lineIndent, bool stripNamespace, Func printType, int indentSpaces, ObjectToCode notRecognizedToCode) + { + if (bs.Count == 0) + return sb.Append("new CatchBlock[0]"); + for (var i = 0; i < bs.Count; i++) + bs[i].ToExpressionString((i > 0 ? sb.Append(',') : sb).NewLineIndent(lineIndent), + paramsExprs, uniqueExprs, lts, lineIndent + indentSpaces, stripNamespace, printType, indentSpaces, notRecognizedToCode); + return sb; + } + + private static StringBuilder ToExpressionString(this CatchBlock b, StringBuilder sb, + List paramsExprs, List uniqueExprs, List lts, + int lineIndent, bool stripNamespace, Func printType, int indentSpaces, ObjectToCode notRecognizedToCode) + { + sb.Append("MakeCatchBlock("); + sb.NewLineIndent(lineIndent).AppendTypeOf(b.Test, stripNamespace, printType).Append(','); + sb.NewLineIndentExpr(b.Variable, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(','); + sb.NewLineIndentExpr(b.Body, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(','); + sb.NewLineIndentExpr(b.Filter, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + return sb.Append(')'); + } + + private static StringBuilder ToExpressionString(this IReadOnlyList items, StringBuilder sb, + List paramsExprs, List uniqueExprs, List lts, + int lineIndent, bool stripNamespace, Func printType, int indentSpaces, ObjectToCode notRecognizedToCode) + { + if (items.Count == 0) + return sb.Append("new SwitchCase[0]"); + for (var i = 0; i < items.Count; i++) + items[i].ToExpressionString((i > 0 ? sb.Append(',') : sb).NewLineIndent(lineIndent), + paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + return sb; + } + + private static StringBuilder ToExpressionString(this SwitchCase s, StringBuilder sb, + List paramsExprs, List uniqueExprs, List lts, + int lineIndent, bool stripNamespace, Func printType, int indentSpaces, ObjectToCode notRecognizedToCode) + { + sb.Append("SwitchCase("); + sb.NewLineIndentExpr(s.Body, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(','); + sb.NewLineIndentArgumentExprs(s.TestValues, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + return sb.Append(')'); + } + + private static StringBuilder ToExpressionString(this MemberBinding mb, StringBuilder sb, + List paramsExprs, List uniqueExprs, List lts, + int lineIndent, bool stripNamespace, Func printType, int indentSpaces, ObjectToCode notRecognizedToCode) + { + if (mb is MemberAssignment ma) + { + sb.Append("Bind("); + sb.NewLineIndent(lineIndent).AppendMember(mb.Member, stripNamespace, printType).Append(", "); + sb.NewLineIndentExpr(ma.Expression, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + return sb.Append(")"); + } + + if (mb is MemberMemberBinding mmb) + { + sb.NewLineIndent(lineIndent).Append(NotSupportedExpression).Append(nameof(MemberMemberBinding)).NewLineIndent(lineIndent); + sb.Append("MemberBind("); + sb.NewLineIndent(lineIndent).AppendMember(mb.Member, stripNamespace, printType); + + for (int i = 0; i < mmb.Bindings.Count; i++) + mmb.Bindings[i].ToExpressionString(sb.Append(", ").NewLineIndent(lineIndent), + paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + return sb.Append(")"); + } + + if (mb is MemberListBinding mlb) + { + sb.NewLineIndent(lineIndent).Append(NotSupportedExpression).Append(nameof(MemberListBinding)).NewLineIndent(lineIndent); + sb.Append("ListBind("); + sb.NewLineIndent(lineIndent).AppendMember(mb.Member, stripNamespace, printType); + + for (int i = 0; i < mlb.Initializers.Count; i++) + mlb.Initializers[i].ToExpressionString(sb.Append(", ").NewLineIndent(lineIndent), + paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + + return sb.Append(")"); + } + + return sb; + } + + private static StringBuilder ToExpressionString(this ElementInit ei, StringBuilder sb, + List paramsExprs, List uniqueExprs, List lts, + int lineIndent, bool stripNamespace, Func printType, int indentSpaces, ObjectToCode notRecognizedToCode) + { + sb.Append("ElementInit("); + sb.NewLineIndent(lineIndent).AppendMethod(ei.AddMethod, stripNamespace, printType).Append(", "); + sb.NewLineIndentArgumentExprs(ei.Arguments, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + return sb.Append(")"); + } + + private const string NotSupportedExpression = "// NOT_SUPPORTED_EXPRESSION: "; + + internal static StringBuilder CreateExpressionString(this Expression e, StringBuilder sb, + List paramsExprs, List uniqueExprs, List lts, + int lineIndent = 0, bool stripNamespace = false, Func printType = null, int indentSpaces = 2, ObjectToCode notRecognizedToCode = null) + { + switch (e.NodeType) + { + case ExpressionType.Constant: + { + var x = (ConstantExpression)e; + sb.Append("Constant("); + if (x.Value == null) + { + sb.Append("null"); + if (x.Type != typeof(object)) + sb.Append(", ").AppendTypeOf(x.Type, stripNamespace, printType); + } + else if (x.Value is Type t) + sb.AppendTypeOf(t, stripNamespace, printType); + else + { + sb.Append(x.Value.ToCode(notRecognizedToCode ?? CodePrinter.DefaultNotRecognizedToCode, stripNamespace, printType)); + if (x.Value.GetType() != x.Type) + sb.Append(", ").AppendTypeOf(x.Type, stripNamespace, printType); + } + return sb.Append(')'); + } + case ExpressionType.Parameter: + { + var x = (ParameterExpression)e; + sb.Append("Parameter(").AppendTypeOf(x.Type, stripNamespace, printType); + if (x.IsByRef) + sb.Append(".MakeByRefType()"); + if (x.Name != null) + sb.Append(", \"").Append(x.Name).Append('"'); + return sb.Append(')'); + } + case ExpressionType.New: + { + var x = (NewExpression)e; + var args = x.Arguments; + + if (args.Count == 0 && e.Type.IsValueType) + return sb.Append("New(").AppendTypeOf(e.Type, stripNamespace, printType).Append(')'); + + sb.Append("New( // ").Append(args.Count).Append(" args"); + var ctorIndex = x.Constructor.DeclaringType.GetTypeInfo().DeclaredConstructors.AsArray().GetFirstIndex(x.Constructor, default(RefEq)); + sb.NewLineIndent(lineIndent).AppendTypeOf(x.Type, stripNamespace, printType) + .Append(".GetTypeInfo().DeclaredConstructors.AsArray()[").Append(ctorIndex).Append("],"); + sb.NewLineIndentArgumentExprs(args, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + return sb.Append(')'); + } + case ExpressionType.Call: + { + var mc = (MethodCallExpression)e; + var diffTypes = mc.Type != mc.Method.ReturnType; + sb.Append(diffTypes ? "Convert(Call(" : "Call("); + sb.NewLineIndentExpr(mc.Object, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(", "); + sb.NewLineIndent(lineIndent).AppendMethod(mc.Method, stripNamespace, printType); + if (mc.Arguments.Count > 0) + sb.Append(',').NewLineIndentArgumentExprs(mc.Arguments, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + return diffTypes ? sb.Append("), ").AppendTypeOf(e.Type, stripNamespace, printType).Append(')') : sb.Append(')'); + } + case ExpressionType.MemberAccess: + { + var x = (MemberExpression)e; + if (x.Member is PropertyInfo p) + { + sb.Append("Property("); + sb.NewLineIndentExpr(x.Expression, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(','); + sb.NewLineIndent(lineIndent).AppendProperty(p, stripNamespace, printType); + } + else + { + sb.Append("Field("); + sb.NewLineIndentExpr(x.Expression, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(','); + sb.NewLineIndent(lineIndent).AppendField((FieldInfo)x.Member, stripNamespace, printType); + } + return sb.Append(')'); + } + + case ExpressionType.NewArrayBounds: + case ExpressionType.NewArrayInit: + { + var x = (NewArrayExpression)e; + if (e.NodeType == ExpressionType.NewArrayInit) + { + // todo: @feature multi-dimensional array initializers are not supported yet, they also are not supported by the hoisted expression + if (e.Type.GetArrayRank() > 1) + sb.NewLineIndent(lineIndent).Append(NotSupportedExpression).Append(e.NodeType).NewLineIndent(lineIndent); + sb.Append("NewArrayInit("); + } + else + { + sb.Append("NewArrayBounds("); + } + sb.NewLineIndent(lineIndent).AppendTypeOf(x.Type.GetElementType(), stripNamespace, printType).Append(", "); + sb.NewLineIndentArgumentExprs(x.Expressions, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + return sb.Append(')'); + } + case ExpressionType.MemberInit: + { + var x = (MemberInitExpression)e; + sb.Append("MemberInit((NewExpression)("); + sb.NewLineIndentExpr(x.NewExpression, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode) + .Append(')'); + for (var i = 0; i < x.Bindings.Count; i++) + x.Bindings[i].ToExpressionString(sb.Append(", ").NewLineIndent(lineIndent), + paramsExprs, uniqueExprs, lts, lineIndent + indentSpaces, stripNamespace, printType, indentSpaces, notRecognizedToCode); + return sb.Append(')'); + } + case ExpressionType.Lambda: + { + var x = (LambdaExpression)e; + sb.Append("Lambda<").Append(x.Type.ToCode(stripNamespace, printType)).Append(">("); + sb.NewLineIndentExpr(x.Body, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(','); + sb.NewLineIndentArgumentExprs(x.Parameters, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + return sb.Append(')'); + } + case ExpressionType.Invoke: + { + var x = (InvocationExpression)e; + sb.Append("Invoke("); + sb.NewLineIndentExpr(x.Expression, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(','); + sb.NewLineIndentArgumentExprs(x.Arguments, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + return sb.Append(")"); + } + case ExpressionType.Conditional: + { + var x = (ConditionalExpression)e; + sb.Append("Condition("); + sb.NewLineIndentExpr(x.Test, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(','); + sb.NewLineIndentExpr(x.IfTrue, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(','); + sb.NewLineIndentExpr(x.IfFalse, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(','); + sb.NewLineIndent(lineIndent).AppendTypeOf(x.Type, stripNamespace, printType); + return sb.Append(')'); + } + case ExpressionType.Block: + { + var x = (BlockExpression)e; + sb.Append("Block("); + sb.NewLineIndent(lineIndent).AppendTypeOf(x.Type, stripNamespace, printType).Append(','); + + if (x.Variables.Count == 0) + sb.NewLineIndent(lineIndent).Append("new ParameterExpression[0], "); + else + { + sb.NewLineIndent(lineIndent).Append("new[] {"); + for (var i = 0; i < x.Variables.Count; i++) + x.Variables[i].ToExpressionString( + (i > 0 ? sb.Append(',') : sb).NewLineIndent(lineIndent + indentSpaces), + paramsExprs, uniqueExprs, lts, lineIndent + indentSpaces, stripNamespace, printType, indentSpaces, notRecognizedToCode); + sb.NewLineIndent(lineIndent).Append("},"); + } + + sb.NewLineIndentArgumentExprs(x.Expressions, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + return sb.Append(')'); + } + case ExpressionType.Loop: + { + var x = (LoopExpression)e; + sb.Append("Loop("); + sb.NewLineIndentExpr(x.Body, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + + if (x.BreakLabel != null) + x.BreakLabel.ToExpressionString(sb.Append(',').NewLineIndent(lineIndent), lts, lineIndent, stripNamespace, printType); + + if (x.ContinueLabel != null) + x.ContinueLabel.ToExpressionString(sb.Append(',').NewLineIndent(lineIndent), lts, lineIndent, stripNamespace, printType); + + return sb.Append(')'); + } + case ExpressionType.Index: + { + var x = (IndexExpression)e; + sb.Append(x.Indexer != null ? "MakeIndex(" : "ArrayAccess("); + sb.NewLineIndentExpr(x.Object, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(", "); + + if (x.Indexer != null) + sb.NewLineIndent(lineIndent).AppendProperty(x.Indexer, stripNamespace, printType).Append(", "); + + sb.Append("new Expression[] {"); + for (var i = 0; i < x.Arguments.Count; i++) + (i > 0 ? sb.Append(',') : sb) + .NewLineIndentExpr(x.Arguments[i], paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + return sb.Append("})"); + } + case ExpressionType.Try: + { + var x = (TryExpression)e; + if (x.Fault != null) + { + sb.Append("TryFault("); + sb.NewLineIndentExpr(x.Body, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(','); + sb.NewLineIndentExpr(x.Fault, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + } + else if (x.Finally == null) + { + sb.Append("TryCatch("); + sb.NewLineIndentExpr(x.Body, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(','); + x.Handlers.ToExpressionString(sb, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + } + else if (x.Handlers == null) + { + sb.Append("TryFinally("); + sb.NewLineIndentExpr(x.Body, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(','); + sb.NewLineIndentExpr(x.Finally, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + } + else + { + sb.Append("TryCatchFinally("); + sb.NewLineIndentExpr(x.Body, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(','); + sb.NewLineIndentExpr(x.Finally, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(','); + x.Handlers.ToExpressionString(sb, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + } + + return sb.Append(')'); + } + case ExpressionType.Label: + { + var x = (LabelExpression)e; + sb.Append("Label("); + x.Target.ToExpressionString(sb, lts, lineIndent, stripNamespace, printType); + if (x.DefaultValue != null) + sb.Append(',').NewLineIndentExpr(x.DefaultValue, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + return sb.Append(')'); + } + case ExpressionType.Goto: + { + var x = (GotoExpression)e; + sb.Append("MakeGoto(").AppendEnum(x.Kind, stripNamespace, printType).Append(','); + + sb.NewLineIndent(lineIndent); + x.Target.ToExpressionString(sb, lts, lineIndent, stripNamespace, printType).Append(','); + + sb.NewLineIndentExpr(x.Value, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(','); + sb.NewLineIndent(lineIndent).AppendTypeOf(x.Type, stripNamespace, printType); + return sb.Append(')'); + } + case ExpressionType.Switch: + { + var x = (SwitchExpression)e; + sb.Append("Switch("); + sb.NewLineIndentExpr(x.SwitchValue, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(','); + sb.NewLineIndentExpr(x.DefaultBody, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(','); + sb.NewLineIndent(lineIndent).AppendMethod(x.Comparison, stripNamespace, printType); + ToExpressionString(x.Cases, sb, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + return sb.Append(')'); + } + case ExpressionType.Default: + { + return e.Type == typeof(void) ? sb.Append("Empty()") + : sb.Append("Default(").AppendTypeOf(e.Type, stripNamespace, printType).Append(')'); + } + case ExpressionType.TypeIs: + case ExpressionType.TypeEqual: + { + var x = (TypeBinaryExpression)e; + sb.Append(e.NodeType == ExpressionType.TypeIs ? "TypeIs(" : "TypeEqual("); + sb.NewLineIndentExpr(x.Expression, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(','); + sb.NewLineIndent(lineIndent).AppendTypeOf(x.TypeOperand, stripNamespace, printType); + return sb.Append(')'); + } + case ExpressionType.Coalesce: + { + var x = (BinaryExpression)e; + sb.Append("Coalesce("); + sb.NewLineIndentExpr(x.Left, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(','); + sb.NewLineIndentExpr(x.Right, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + if (x.Conversion != null) + sb.Append(',').NewLineIndentExpr(x.Conversion, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + return sb.Append(')'); + } + case ExpressionType.ListInit: + { + var x = (ListInitExpression)e; + sb.Append("ListInit((NewExpression)("); + sb.NewLineIndentExpr(x.NewExpression, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(')'); + for (var i = 0; i < x.Initializers.Count; i++) + x.Initializers[i].ToExpressionString(sb.Append(", ").NewLineIndent(lineIndent), + paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + return sb.Append(")"); + } + case ExpressionType.Extension: + { + var reduced = e.Reduce(); // proceed with the reduced expression + return reduced.CreateExpressionString(sb, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + } + case ExpressionType.Dynamic: + case ExpressionType.RuntimeVariables: + case ExpressionType.DebugInfo: + case ExpressionType.Quote: + { + return sb.NewLineIndent(lineIndent).Append(NotSupportedExpression).Append(e.NodeType).NewLineIndent(lineIndent); + } + default: + { + var name = Enum.GetName(typeof(ExpressionType), e.NodeType); + if (e is UnaryExpression u) + { + sb.Append(name).Append('('); + // todo: @feature maybe for big expression it makes sense to print the Type in comment here so you don't navigate to the closing parentheses to find it + sb.NewLineIndentExpr(u.Operand, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + + if (e.NodeType == ExpressionType.Convert || + e.NodeType == ExpressionType.ConvertChecked || + e.NodeType == ExpressionType.Unbox || + e.NodeType == ExpressionType.Throw || + e.NodeType == ExpressionType.TypeAs) + sb.Append(',').NewLineIndent(lineIndent).AppendTypeOf(e.Type, stripNamespace, printType); + + if ((e.NodeType == ExpressionType.Convert || e.NodeType == ExpressionType.ConvertChecked) + && u.Method != null) + sb.Append(',').NewLineIndent(lineIndent).AppendMethod(u.Method, stripNamespace, printType); + } + + if (e is BinaryExpression b) + { + sb.Append("MakeBinary(").Append(typeof(ExpressionType).Name).Append('.').Append(name).Append(','); + sb.NewLineIndentExpr(b.Left, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(','); + sb.NewLineIndentExpr(b.Right, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + if (b.IsLiftedToNull || b.Method != null) + { + sb.Append(',').NewLineIndent(lineIndent).Append("liftToNull: ").Append(b.IsLiftedToNull.ToCode()); + sb.Append(',').NewLineIndent(lineIndent).AppendMethod(b.Method, stripNamespace, printType); + if (b.Conversion != null) + sb.Append(',').NewLineIndentExpr(b.Conversion, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + } + + if (b.Conversion != null) + sb.Append(',').NewLineIndentExpr(b.Conversion, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + } + + return sb.Append(')'); + } + } + } + } + + /// Converts the expression into the valid C# code representation + [RequiresUnreferencedCode(Trimming.Message)] + public static class ToCSharpPrinter + { + /// Tries hard to convert the expression into the valid C# code. Avoids parens by default for the root expr. + public static string ToCSharpString(this Expression expr, EnclosedIn enclosedIn = EnclosedIn.AvoidParens) => + expr.ToCSharpString(new StringBuilder(1024), enclosedIn, stripNamespace: true).ToString(); + + /// Tries hard to convert the expression into the valid C# code. Avoids parens by default for the root expr. + public static string ToCSharpString(this Expression expr, ObjectToCode notRecognizedToCode, EnclosedIn enclosedIn = EnclosedIn.AvoidParens) => + expr.ToCSharpString(new StringBuilder(1024), stripNamespace: true, notRecognizedToCode: notRecognizedToCode).ToString(); + + /// Tries hard to convert the expression into the valid C# code + public static StringBuilder ToCSharpString(this Expression e, StringBuilder sb, EnclosedIn enclosedIn = EnclosedIn.ParensByDefault, + int lineIndent = 0, bool stripNamespace = false, Func printType = null, int indentSpaces = 4, ObjectToCode notRecognizedToCode = null) + { + PrintContext ctx = default; + return e.ToCSharpString(sb, enclosedIn, ref ctx, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + } + + /// Indicates the expression container + public enum EnclosedIn + { + /// Prefers the parens by default + ParensByDefault = 0, + /// The test part of the If expression + IfTest, + /// The `if (test)` part + Block, + /// The lambda + LambdaBody, + /// Return expression + Return, + /// Instructs the client code to avoid parenthesis for the generated C# code, e.g. if we have as single argument in a method + AvoidParens, + /// The instance when calling the instance method or accessing the instance member + Instance, + } + + private static StringBuilder NullConstantOrDefaultToCSharpString(Type exprType, StringBuilder sb, EnclosedIn encloseIn, + bool stripNamespace, Func printType) => + exprType == typeof(object) + ? sb.Append("null") + : exprType.IsValueType && !exprType.IsNullable() + ? sb.Append("default(").Append(exprType.ToCode(stripNamespace, printType)).Append(')') + : sb.Append(encloseIn == EnclosedIn.Instance ? "((" : "(") + .Append(exprType.ToCode(stripNamespace, printType)).Append(")null") + .Append(encloseIn == EnclosedIn.Instance ? ")" : ""); + + private static StringBuilder InsertTopFFuncDefinitionOnce(StringBuilder sb) => + sb[0] != 'T' || sb[2] != '_' || sb[3] != '_' || sb[4] != 'f' || sb[5] != '<' + ? sb.Insert(0, "T __f(System.Func f) => f();\n") + : sb; + + internal static StringBuilder ToCSharpString(this Expression e, StringBuilder sb, EnclosedIn enclosedIn, ref PrintContext ctx, + int lineIndent = 0, bool stripNamespace = false, Func printType = null, int indentSpaces = 4, + ObjectToCode notRecognizedToCode = null, bool isReturnByRef = false, bool containerIgnoresResult = false) + { +#if LIGHT_EXPRESSION + if (e.IsCustomToCSharpString) + return e.CustomToCSharpString(sb, enclosedIn, ref ctx, + lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); +#endif + switch (e.NodeType) + { + case ExpressionType.Constant: + { + var x = (ConstantExpression)e; + var val = x.Value; + if (val == null) + return x.Type == null ? sb.Append("null") : NullConstantOrDefaultToCSharpString(x.Type, sb, enclosedIn, stripNamespace, printType); + + if (val is Type t) + return sb.AppendTypeOf(t, stripNamespace, printType); + + var actualType = val.GetType(); + if (actualType != x.Type && (actualType.IsValueType || x.Type != typeof(object))) // add the Type cast, but avoid cast to object for the reference types + sb.Append('(').Append(x.Type.ToCode(stripNamespace, printType)).Append(')'); + + // value output may also add the cast for the primitive values + return sb.Append(val.ToCode(notRecognizedToCode ?? CodePrinter.DefaultNotRecognizedToCode, stripNamespace, printType)); + } + case ExpressionType.Parameter: + { + if (isReturnByRef) + sb.Append("ref "); + return sb.AppendName(e, ((ParameterExpression)e).Name, e.Type.ToCode(stripNamespace, printType), ref ctx); + } + case ExpressionType.New: + { + var x = (NewExpression)e; + lineIndent = sb.GetRealLineIndent(lineIndent); + var argIndent = lineIndent + indentSpaces; + + sb.Append("new ").Append(e.Type.ToCode(stripNamespace, printType)).Append('('); + + var args = x.Arguments; + if (args.Count == 1) + args[0].ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, + lineIndent, // don't increase indent the chain of single arguments inline, e.g. for `new A(new B(\n....a,\n....b))` we still want padding to be 4, not 8 + stripNamespace, printType, indentSpaces, notRecognizedToCode); + else if (args.Count > 1) + for (var i = 0; i < args.Count; i++) + { + (i > 0 ? sb.Append(',') : sb).NewLineIndent(argIndent); + args[i].ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, + argIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + } + return sb.Append(')'); + } + case ExpressionType.Call: + { + var mc = (MethodCallExpression)e; + + // before adding anything about method call to the builder, + // let's measure the current indent to avoid the double indenting the arguments below in some cases + lineIndent = sb.GetRealLineIndent(lineIndent); + var argIndent = lineIndent + indentSpaces; + + var method = mc.Method; + var methodReturnType = method.ReturnType; + if (enclosedIn != EnclosedIn.Instance && methodReturnType.IsByRef) + sb.AppendRefOnce(); + + // output convert only if it is required, e.g. it may happen for custom expressions designed by users + var diffTypes = mc.Type != methodReturnType; + if (diffTypes) sb.Append("((").Append(mc.Type.ToCode(stripNamespace, printType)).Append(')'); + + if (mc.Object != null) + mc.Object.ToCSharpString(sb, EnclosedIn.Instance, ref ctx, + lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + else // for the static method or the static extension method we need to qualify with the class + sb.Append(method.DeclaringType.ToCode(stripNamespace, printType)); + + var name = method.Name; + // check for the special methods, e.g. property access `get_` or `set_` and output them as properties + if (method.IsSpecialName) + { + if (name.StartsWith("get_")) + return sb.Append('.').Append(name.Substring(4)); + if (name.StartsWith("set_")) + { + sb.Append('.').Append(name.Substring(4)).Append(" = "); + mc.Arguments[0].ToCSharpExpression(sb, EnclosedIn.AvoidParens, ref ctx, + false, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + return sb.AppendSemicolonOnce(); + } + } + + sb.Append('.').Append(name); + if (method.IsGenericMethod) + { + sb.Append('<'); + var typeArgs = method.GetGenericArguments(); + for (var i = 0; i < typeArgs.Length; i++) + (i == 0 ? sb : sb.Append(", ")).Append(typeArgs[i].ToCode(stripNamespace, printType)); + sb.Append('>'); + } + + sb.Append('('); + var pars = method.GetParameters(); + var args = mc.Arguments; + if (args.Count == 1) + { + var p = pars[0]; + var a = args[0]; + if (p.ParameterType.IsByRef && !a.IsConstantOrDefault()) + sb.Append(p.IsOut ? "out " : p.IsIn ? "in " : "ref "); + a.ToCSharpExpression(sb, EnclosedIn.AvoidParens, ref ctx, + false, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + } + else if (args.Count > 1) + { + for (var i = 0; i < args.Count; i++) + { + // arguments will start at that minimal indent + (i == 0 ? sb : sb.Append(',')).NewLineIndent(argIndent); + var p = pars[i]; + var a = args[i]; + if (p.ParameterType.IsByRef && !a.IsConstantOrDefault()) + sb.Append(p.IsOut ? "out " : p.IsIn ? "in " : "ref "); + a.ToCSharpExpression(sb, EnclosedIn.AvoidParens, ref ctx, + false, argIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + } + } + // for the different return and expression types wrapping the whole expression including the cast with additional parentheses + return diffTypes ? sb.Append("))") : sb.Append(')'); + } + case ExpressionType.MemberAccess: + { + var x = (MemberExpression)e; + var inst = x.Expression; + if (inst != null) + { + // wrap the `new X().Y` into parens as `(new X()).Y` as it is may be a part of the bigger expression + if (inst.NodeType == ExpressionType.New) + sb.Append('('); + inst.ToCSharpString(sb, EnclosedIn.Instance, ref ctx, + lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + if (inst.NodeType == ExpressionType.New) + sb.Append(')'); + } + else + sb.Append(x.Member.DeclaringType.ToCode(stripNamespace, printType)); + return sb.Append('.').Append(x.Member.GetCSharpName()); + } + case ExpressionType.NewArrayBounds: + case ExpressionType.NewArrayInit: + { + var x = (NewArrayExpression)e; + lineIndent = sb.GetRealLineIndent(lineIndent); + var nextIndent = lineIndent + indentSpaces; + + sb.Append("new ").Append(e.Type.GetElementType().ToCode(stripNamespace, printType)); + sb.Append(e.NodeType == ExpressionType.NewArrayInit ? "[]{" : "["); + + var exprs = x.Expressions; + if (exprs.Count == 1) + exprs[0].ToCSharpString(sb, EnclosedIn.AvoidParens, ref ctx, + lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + else if (exprs.Count > 1) + for (var i = 0; i < exprs.Count; i++) + { + sb = (i > 0 ? sb.Append(',') : sb).NewLineIndent(nextIndent); + exprs[i].ToCSharpString(sb, EnclosedIn.AvoidParens, ref ctx, + nextIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + } + + return sb.Append(e.NodeType == ExpressionType.NewArrayInit ? "}" : "]"); + } + case ExpressionType.MemberInit: + { + var x = (MemberInitExpression)e; + lineIndent = sb.GetRealLineIndent(lineIndent); + + x.NewExpression.ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, + lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + sb.NewLineIndent(lineIndent).Append('{'); + x.Bindings.ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, lineIndent + indentSpaces, stripNamespace, printType, indentSpaces, notRecognizedToCode); + return sb.NewLineIndent(lineIndent).Append('}'); + } + case ExpressionType.ListInit: + { + var x = (ListInitExpression)e; + lineIndent = sb.GetRealLineIndent(lineIndent); + var elemIndent = lineIndent + indentSpaces; + + x.NewExpression.ToCSharpString(sb, EnclosedIn.AvoidParens, ref ctx, + lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + + sb.NewLineIndent(lineIndent).Append('{'); + + var inits = x.Initializers; + for (var i = 0; i < inits.Count; ++i) + { + (i == 0 ? sb : sb.Append(", ")).NewLineIndent(elemIndent); + var elemInit = inits[i]; + var args = elemInit.Arguments; + if (args.Count == 1) + { + args.GetArgument(0).ToCSharpString(sb, EnclosedIn.AvoidParens, ref ctx, + elemIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + } + else + { + sb.Append('{'); + for (var j = 0; j < args.Count; ++j) + { + sb = j == 0 ? sb : sb.Append(", "); + args.GetArgument(j).ToCSharpString(sb, EnclosedIn.AvoidParens, ref ctx, + elemIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + } + sb.Append('}'); + } + } + return sb.NewLineIndent(lineIndent).Append("}"); + } + case ExpressionType.Lambda: + { + var x = (LambdaExpression)e; + lineIndent = sb.GetRealLineIndent(lineIndent); + // The result should be something like this (taken from the #237) + // + // `(DeserializerDlg)((ref ReadOnlySequence input, Word value, out Int64 bytesRead) => {...})` + // + sb.Append('(').Append(e.Type.ToCode(stripNamespace, printType)).Append(")(("); + var lambdaMethod = x.Type.FindDelegateInvokeMethod(); +#if LIGHT_EXPRESSION + var paramExprs = x; +#else + var paramExprs = x.Parameters; +#endif + var count = paramExprs.GetCount(); + if (count > 0) + { + var parInfos = lambdaMethod.GetParameters(); + Debug.Assert(count == parInfos.Length, "Expecting the same amount of parameter expressions and infos"); + for (var i = 0; i < count; i++) + { + if (i > 0) + sb.Append(", "); + if (count > 1) + sb.NewLineIndent(lineIndent + indentSpaces); + + var pe = paramExprs.GetParameter(i); + var pi = parInfos[i]; + if (pe.IsByRef) + sb.Append(pi.IsOut ? "out " : pi.IsIn ? "in " : "ref "); + var typeCode = pe.Type.ToCode(stripNamespace, printType); + sb.Append(typeCode).Append(' '); + sb.AppendName(pe, pe.Name, typeCode, ref ctx); + } + ctx.LambdaPars.Add(new() { Exprs = paramExprs, Infos = parInfos }); + } + + sb.Append(") => //").Append(lambdaMethod.ReturnType.ToCode(stripNamespace, printType)); + var body = x.Body; + var isReturnable = body.IsReturnable(); + var lambdaIgnoresResult = x.ReturnType == typeof(void); + if (isReturnable & !lambdaIgnoresResult) + { + var newLineIndent = lineIndent + indentSpaces; + sb.NewLineIndent(newLineIndent); + body.ToCSharpString(sb, EnclosedIn.LambdaBody, ref ctx, + newLineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode, lambdaMethod.ReturnType.IsByRef); + } + else + { + sb.NewLineIndent(lineIndent).Append('{'); + // Body handles `;` itself + if (body is BlockExpression bb) + bb.BlockToCSharpString(sb, ref ctx, + lineIndent + indentSpaces, stripNamespace, printType, indentSpaces, notRecognizedToCode, + inTheLastBlock: true, containerIgnoresResult: lambdaIgnoresResult); + else + { + sb.NewLineIndent(lineIndent + indentSpaces); + body.ToCSharpString(sb, EnclosedIn.LambdaBody, ref ctx, + lineIndent + indentSpaces, stripNamespace, printType, indentSpaces, notRecognizedToCode, + containerIgnoresResult: lambdaIgnoresResult); + if (isReturnable || lambdaIgnoresResult) + sb.AppendSemicolonOnce(); + } + sb.NewLineIndent(lineIndent).Append('}'); + } + ctx.LambdaPars.TryRemoveLastNoDrop(); + return sb.Append(')'); + } + case ExpressionType.Invoke: + { + var x = (InvocationExpression)e; + + lineIndent = sb.GetRealLineIndent(lineIndent); + var argIndent = lineIndent + indentSpaces; + + // wrap the expression in the possibly excessive parentheses, because usually the expression is the delegate (except if delegate is parameter) + // which should be cast to the proper delegate type, e.g. `(Func)(() => 1)`, so we need an additional `()` to call `.Invoke`. + var encloseInParens = x.Expression.NodeType != ExpressionType.Parameter; + if (encloseInParens) + sb.Append('('); + x.Expression.ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, + lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + if (encloseInParens) + sb.Append(')'); + + // Indicates the lambda invocation more explicitly with the new line, + // Keep Invoke indentation the same as the lambda closing brace indicating their bond + if (x.Expression.NodeType == ExpressionType.Lambda) + sb.NewLineIndent(lineIndent); + sb.Append(".Invoke("); + + for (var i = 0; i < x.Arguments.Count; i++) + { + sb = i > 0 ? sb.Append(',') : sb; + sb.NewLineIndent(argIndent); + x.Arguments[i].ToCSharpString(sb, EnclosedIn.AvoidParens, ref ctx, + argIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + } + return sb.Append(")"); + } + case ExpressionType.Conditional: + { + var x = (ConditionalExpression)e; + lineIndent = sb.GetRealLineIndent(lineIndent); + + if (e.Type == typeof(void)) // otherwise output as ternary expression + { + sb.Append("if ("); + x.Test.ToCSharpString(sb, EnclosedIn.IfTest, ref ctx, + lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + sb.Append(')'); + + x.IfTrue.ToCSharpBlock(sb, ref ctx, + lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + + if (x.IfFalse.NodeType != ExpressionType.Default || x.IfFalse.Type != typeof(void)) + { + sb.NewLineIndent(lineIndent).Append("else"); + x.IfFalse.ToCSharpBlock(sb, ref ctx, + lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + } + } + else + { + var avoidParens = AvoidParens(enclosedIn); + sb = avoidParens ? sb : sb.Append('('); + + x.Test.ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, + lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + + sb.Append(" ? "); + var doNewLine = !x.IfTrue.IsParamOrConstantOrDefault(); + x.IfTrue.ToCSharpExpression(sb, EnclosedIn.AvoidParens, ref ctx, + doNewLine, lineIndent + indentSpaces, stripNamespace, printType, indentSpaces, notRecognizedToCode); + + sb.Append(" : "); + doNewLine = !x.IfFalse.IsParamOrConstantOrDefault(); + x.IfFalse.ToCSharpExpression(sb, EnclosedIn.AvoidParens, ref ctx, + doNewLine, lineIndent + indentSpaces, stripNamespace, printType, indentSpaces, notRecognizedToCode); + + sb = avoidParens ? sb : sb.Append(')'); + } + return sb; + } + case ExpressionType.Block: + { + return ((BlockExpression)e).BlockToCSharpString(sb, ref ctx, + lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + } + case ExpressionType.Loop: + { + var x = (LoopExpression)e; + lineIndent = sb.GetRealLineIndent(lineIndent); + + sb.NewLineIndent(lineIndent).Append("while (true)"); + sb.NewLineIndent(lineIndent).Append("{"); + + if (x.ContinueLabel != null) + { + sb.NewLineIndent(lineIndent); + sb.AppendLabelName(x.ContinueLabel, ref ctx, nameUsage: NameUsage.MarkLabel).Append(":;"); // the label is with the semicolon, because it will invalid code at the end of lambda without it + } + + sb.NewLineIndent(lineIndent + indentSpaces); + x.Body.ToCSharpString(sb, EnclosedIn.AvoidParens, ref ctx, + lineIndent + indentSpaces, stripNamespace, printType, indentSpaces, notRecognizedToCode); + + sb.NewLineIndent(lineIndent).Append("}"); + + // the label is with the semicolon, because it will invalid code at the end of lambda without it + if (x.BreakLabel != null) + sb.NewLineIndent(lineIndent).AppendLabelName(x.BreakLabel, ref ctx, nameUsage: NameUsage.MarkLabel).Append(":;"); + + return sb; + } + case ExpressionType.Index: + { + var x = (IndexExpression)e; + x.Object.ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, + lineIndent + indentSpaces, stripNamespace, printType, indentSpaces, notRecognizedToCode); + + var isStandardIndexer = x.Indexer == null || x.Indexer.Name == "Item"; + if (isStandardIndexer) + sb.Append('['); + else + sb.Append('.').Append(x.Indexer.Name).Append('('); + + for (var i = 0; i < x.Arguments.Count; i++) + x.Arguments[i].ToCSharpString(i > 0 ? sb.Append(", ") : sb, EnclosedIn.AvoidParens, ref ctx, + lineIndent + indentSpaces, stripNamespace, printType, indentSpaces, notRecognizedToCode); + + return sb.Append(isStandardIndexer ? ']' : ')'); + } + case ExpressionType.Try: + { + var x = (TryExpression)e; + lineIndent = sb.GetRealLineIndent(lineIndent); + + var returnsValue = e.Type != typeof(void); + void PrintPart(Expression part, ref PrintContext ctx) + { + var incIndent = lineIndent + indentSpaces; + if (part is BlockExpression pb) + pb.BlockToCSharpString(sb, ref ctx, + incIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode, inTheLastBlock: true); + else + { + sb.NewLineIndent(incIndent); + var isReturnable = returnsValue && part.IsReturnable() && + // todo: @improve right now it is a hack - usually to Assign something means no return + !part.NodeType.IsAssignNodeType(); + if (isReturnable) + sb.Append("return "); + part.ToCSharpString(sb, EnclosedIn.AvoidParens, ref ctx, + incIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + sb.AppendSemicolonOnce(part); + } + } + + sb.AppendNewLineOnce(); + + var isTryFault = x.Fault != null; + if (isTryFault) + { + sb.Append("var fault = 0; // emulating try-fault"); + sb.NewLineIndent(lineIndent); + } + + sb.Append("try"); + sb.NewLineIndent(lineIndent).Append('{'); + PrintPart(x.Body, ref ctx); + sb.NewLineIndent(lineIndent).Append('}'); + if (isTryFault) + sb.NewLineIndent(lineIndent).Append("catch (Exception) when (fault++ != 0) {}"); + + var handlers = x.Handlers; + if (handlers != null && handlers.Count > 0) + { + for (var i = 0; i < handlers.Count; i++) + { + var h = handlers[i]; + sb.NewLineIndent(lineIndent).Append("catch ("); + var exTypeName = h.Test.ToCode(stripNamespace, printType); + sb.Append(exTypeName); + + var hVar = h.Variable; + if (hVar != null) + { + // todo: @wip @feat add around the ex var the "#pragma warning disable CS0168 // unused var" "#pragma warning restore CS0168", see #495, or better check inside the catch block for the usage. We need to completely remove the var, because prefix '_' does not remove the warning. + sb.Append(' ').AppendName(hVar, hVar.Name, hVar.Type.ToCode(stripNamespace, printType), ref ctx); + } + + sb.Append(')'); + if (h.Filter != null) + { + sb.Append("when ("); + sb.NewLineIndent(lineIndent); + h.Filter.ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, + lineIndent + indentSpaces, stripNamespace, printType, indentSpaces, notRecognizedToCode); + sb.NewLineIndent(lineIndent).Append(')'); + } + + sb.NewLineIndent(lineIndent).Append('{'); + PrintPart(h.Body, ref ctx); + sb.NewLineIndent(lineIndent).Append('}'); + } + } + + var faultOrFinally = x.Fault ?? x.Finally; + if (faultOrFinally != null) + { + sb.NewLineIndent(lineIndent).Append("finally"); + sb.NewLineIndent(lineIndent).Append('{'); + if (isTryFault) sb.Append("if (fault != 0) {"); + + PrintPart(faultOrFinally, ref ctx); + + sb.NewLineIndent(lineIndent).Append('}'); + if (isTryFault) sb.Append('}'); + } + return sb; + } + case ExpressionType.Label: + { + // we don't output the default value and relying on the Goto Return `return` instead, otherwise we may change the logic of the code + return sb.NewLineIndent(lineIndent).AppendLabelName(((LabelExpression)e).Target, ref ctx, nameUsage: NameUsage.MarkLabel).Append(":;"); + } + case ExpressionType.Goto: + { + var gt = (GotoExpression)e; + if (gt.Kind == GotoExpressionKind.Return || gt.Value != null) + { + var gtValue = gt.Value; + if (gtValue == null) + return enclosedIn == EnclosedIn.Return ? sb.Append(";") : sb.Append("return;"); + + var isReturnable = gtValue.IsReturnable(); + if (isReturnable & enclosedIn != EnclosedIn.Return) + sb.Append("return "); + + gtValue.ToCSharpString(sb, EnclosedIn.AvoidParens, ref ctx, + lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + + if (isReturnable) + sb.AppendSemicolonOnce(); + return sb; + } + return sb.Append("goto ").AppendLabelName(gt.Target, ref ctx, nameUsage: NameUsage.GotoLabel); + } + case ExpressionType.Switch: + { + var x = (SwitchExpression)e; + if (x.Cases.Count == 0 && x.DefaultBody == null) return sb; // do not output the empty switch + + lineIndent = sb.GetRealLineIndent(lineIndent); + + sb.Append("switch ("); + x.SwitchValue.ToCSharpString(sb, EnclosedIn.AvoidParens, ref ctx, + lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + sb.Append(')'); + sb.NewLineIndent(lineIndent).Append('{'); + + var caseValueIndent = lineIndent + indentSpaces; + var caseBodyIndent = caseValueIndent + indentSpaces; + foreach (var cs in x.Cases) + { + foreach (var tv in cs.TestValues) + { + sb.NewLineIndent(caseValueIndent).Append("case "); + tv.ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, + caseValueIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(':'); + } + + sb.NewLineIndent(caseBodyIndent); + var caseBody = cs.Body; + if (enclosedIn == EnclosedIn.LambdaBody) + { + if (caseBody is BlockExpression bl) + { + bl.BlockToCSharpString(sb, ref ctx, + caseBodyIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode, inTheLastBlock: true); + sb.NewLineIndent(caseBodyIndent).Append("break;"); + } + else + { + var bodyIn = caseBody.Type != typeof(void) ? EnclosedIn.Return : EnclosedIn.AvoidParens; + caseBody.ToCSharpString(bodyIn == EnclosedIn.Return ? sb.Append("return ") : sb, bodyIn, ref ctx, + caseBodyIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).AppendSemicolonOnce(caseBody); + if (bodyIn != EnclosedIn.Return) + sb.NewLineIndent(caseBodyIndent).Append("break;"); + } + } + else + { + caseBody.ToCSharpString(sb, enclosedIn, ref ctx, + caseBodyIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).AppendSemicolonOnce(caseBody); + sb.NewLineIndent(caseBodyIndent).Append("break;"); + } + } + + if (x.DefaultBody != null) + { + var defaultBody = x.DefaultBody; + sb.NewLineIndent(caseValueIndent).Append("default:"); + sb.NewLineIndent(caseBodyIndent); + if (enclosedIn == EnclosedIn.LambdaBody | enclosedIn == EnclosedIn.Return) + { + if (defaultBody is BlockExpression bl) + bl.BlockToCSharpString(sb, ref ctx, + caseBodyIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode, inTheLastBlock: true); + else + { + var bodyIn = enclosedIn == EnclosedIn.Return ? EnclosedIn.Return + : defaultBody.Type != typeof(void) ? EnclosedIn.Return : EnclosedIn.AvoidParens; + defaultBody.ToCSharpString(bodyIn == EnclosedIn.Return ? sb.Append("return ") : sb, bodyIn, ref ctx, + caseBodyIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).AppendSemicolonOnce(defaultBody); + } + } + else + { + defaultBody.ToCSharpString(sb, enclosedIn, ref ctx, + caseBodyIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).AppendSemicolonOnce(defaultBody); + sb.NewLineIndent(caseBodyIndent).Append("break;"); + } + } + + return sb.NewLineIndent(lineIndent).Append("}"); + } + case ExpressionType.Default: + return e.Type == typeof(void) + ? sb // `default(void)` does not make sense in the C# + : NullConstantOrDefaultToCSharpString(e.Type, sb, enclosedIn, stripNamespace, printType); + + case ExpressionType.TypeIs: + case ExpressionType.TypeEqual: + { + var x = (TypeBinaryExpression)e; + sb.Append('('); + // Use C# `is T` even for TypeEqual if the two are equivalent (this syntax is nicer) + // IsSealed returns true for arrays, but arrays are cursed and don't behave like sealed classes (`new string[0] is object[]`, and even `new int[0] is uint[]`) + if (x.NodeType == ExpressionType.TypeIs || (x.TypeOperand.IsSealed && !x.TypeOperand.IsArray)) + { + x.Expression.ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, + lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + sb.Append(" is ").Append(x.TypeOperand.ToCode(stripNamespace, printType)); + } + else + { + x.Expression.ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, + lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + sb.Append(".GetType() == typeof(").Append(x.TypeOperand.ToCode(stripNamespace, printType)).Append(')'); + + } + return sb.Append(')'); + } + case ExpressionType.Coalesce: + { + var x = (BinaryExpression)e; + x.Left.ToCSharpExpression(sb, EnclosedIn.ParensByDefault, ref ctx, + false, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + sb.Append(" ?? "); + x.Right.ToCSharpExpression(sb, EnclosedIn.ParensByDefault, ref ctx, + false, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + return sb; + } + case ExpressionType.Extension: + { + var reduced = e.Reduce(); // proceed with the reduced expression + return reduced.ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, + lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + } + case ExpressionType.Dynamic: + case ExpressionType.RuntimeVariables: + case ExpressionType.DebugInfo: + case ExpressionType.Quote: + { + return sb.NewLineIndent(lineIndent).Append(NotSupportedExpression).Append(e.NodeType).NewLineIndent(lineIndent); + } + default: + { + var avoidParens = AvoidParens(enclosedIn); + + var name = Enum.GetName(typeof(ExpressionType), e.NodeType); + if (e is UnaryExpression u) + { + var op = u.Operand; + switch (e.NodeType) + { + case ExpressionType.ArrayLength: + return op.ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, + lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode) + .Append(".Length"); + + case ExpressionType.Not: // either the bool not or the binary not + return op.ToCSharpString( + e.Type == typeof(bool) ? sb.Append("!(") : sb.Append("~("), enclosedIn, ref ctx, + lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode) + .Append(')'); + + case ExpressionType.Convert: + case ExpressionType.ConvertChecked: + if (e.Type == op.Type || e.Type == typeof(Enum) && op.Type.IsEnum) + return op.ToCSharpExpression(sb, EnclosedIn.AvoidParens, ref ctx, + false, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + + sb = avoidParens ? sb.Append('(') : sb.Append("(("); + sb.Append(e.Type.ToCode(stripNamespace, printType)).Append(')'); + var opParens = op is BinaryExpression || op.NodeType.IsBlockLikeOrConditional() ? EnclosedIn.ParensByDefault : EnclosedIn.AvoidParens; + sb = op.ToCSharpExpression(sb, opParens, ref ctx, + false, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + return avoidParens ? sb : sb.Append(')'); + + case ExpressionType.Decrement: + case ExpressionType.Increment: + if (!avoidParens) sb.Append('('); + sb = op.ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, + lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + sb = e.NodeType == ExpressionType.Decrement ? sb.Append(" - 1") : sb.Append(" + 1"); + if (!avoidParens) sb.Append(')'); + return sb; + + case ExpressionType.Negate: + case ExpressionType.NegateChecked: + if (!avoidParens) sb.Append('('); + op.ToCSharpString(sb.Append('-'), EnclosedIn.ParensByDefault, ref ctx, + lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + if (!avoidParens) sb.Append(')'); + return sb; + + case ExpressionType.PostIncrementAssign: + case ExpressionType.PreIncrementAssign: + case ExpressionType.PostDecrementAssign: + case ExpressionType.PreDecrementAssign: + if (!avoidParens) sb.Append('('); + sb = e.NodeType == ExpressionType.PreIncrementAssign ? sb.Append("++") : e.NodeType == ExpressionType.PreDecrementAssign ? sb.Append("--") : sb; + op.ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, + lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + sb = e.NodeType == ExpressionType.PostIncrementAssign ? sb.Append("++") : e.NodeType == ExpressionType.PostDecrementAssign ? sb.Append("--") : sb; + if (!avoidParens) sb.Append(')'); + return sb; + + case ExpressionType.IsTrue: + return op.ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, + lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append("==true"); + + case ExpressionType.IsFalse: + return op.ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, + lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append("==false"); + + case ExpressionType.TypeAs: + case ExpressionType.TypeIs: + if (!avoidParens) sb.Append('('); + + var valueOp = op.Type.IsValueType && e.NodeType == ExpressionType.TypeAs; // `as` cannot be directly applied to the value type operand, that's why convert to object first. + if (valueOp) sb.Append("(object)("); // see Issue455_TypeAs_should_return_null.Original_case + + op.ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, + lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + + if (valueOp) sb.Append(')'); + + sb = e.NodeType == ExpressionType.TypeAs ? sb.Append(" as ") : sb.Append(" is "); + sb.Append(e.Type.ToCode(stripNamespace, printType)); + + if (!avoidParens) sb.Append(')'); + return sb; + + case ExpressionType.Throw: + return op is null ? sb.Append("throw") : + op.ToCSharpString(sb.Append("throw "), EnclosedIn.ParensByDefault, ref ctx, + lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + + case ExpressionType.Unbox: // output it as the cast + sb = avoidParens ? sb.Append("(") : sb.Append("(("); + sb.Append(e.Type.ToCode(stripNamespace, printType)).Append(')'); + op.ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, + lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + return avoidParens ? sb : sb.Append(')'); + + default: + return sb.Append(e.ToString()); // falling back ro ToString as a closest to C# code output + } + } + + if (e is BinaryExpression b) + { + var nodeType = e.NodeType; + if (nodeType == ExpressionType.ArrayIndex) + { + if (containerIgnoresResult | enclosedIn == EnclosedIn.Block) + sb.Append("_ = "); + var arrInParens = b.Left.NodeType != ExpressionType.Parameter; + if (arrInParens) sb.Append('('); + b.Left.ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, + lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + if (arrInParens) sb.Append(')'); + return b.Right.ToCSharpString(sb.Append("["), EnclosedIn.ParensByDefault, ref ctx, + lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append("]"); + } + + if (nodeType.IsAssignNodeType()) + { + // todo: @perf handle the right part is condition with the blocks for If and/or Else, e.g. see #261 test `Serialize_the_nullable_struct_array` + if (b.Right is BlockExpression rightBlock) // it is valid to assign the block and it is used to my surprise + { + sb.Append("// { The block result will be assigned to `") + .Append(b.Left.ToCSharpString(new StringBuilder(), EnclosedIn.ParensByDefault, ref ctx, + lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode)) + .Append('`'); + rightBlock.BlockToCSharpString(sb, ref ctx, + lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode, false, blockResultAssignment: b); + return sb.NewLineIndent(lineIndent).Append("// } end of block assignment"); + } + + b.Left.ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, + lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + if (nodeType == ExpressionType.PowerAssign) + { + sb.Append(" = System.Math.Pow("); + b.Left.ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, + lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(", "); + return b.Right.ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, + lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(")"); + } + + sb.Append(OperatorToCSharpString(nodeType)); + + if (b.Left is ParameterExpression leftParam && leftParam.IsByRef && !b.Right.IsConstantOrDefault() + && !leftParam.IsOut(ref ctx)) + sb.Append("ref "); + + return b.Right.ToCSharpExpression(sb, EnclosedIn.AvoidParens, ref ctx, + false, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + } + + if (containerIgnoresResult | enclosedIn == EnclosedIn.Block) + sb.Append("_ = "); // apply for non assignments + + sb = sb.AddParenIfNeeded('(', avoidParens); + b.Left.ToCSharpExpression(sb, EnclosedIn.ParensByDefault, ref ctx, + false, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + + if (nodeType == ExpressionType.Equal) + { + if (b.Right is ConstantExpression r && r.Value is bool rb && rb) + return sb.AddParenIfNeeded(')', avoidParens); + sb.Append(" == "); + } + else if (nodeType == ExpressionType.NotEqual) + { + if (b.Right is ConstantExpression r && r.Value is bool rb) + return (rb ? sb.Append(" == false") : sb).AddParenIfNeeded(')', avoidParens); + sb.Append(" != "); + } + else + sb.Append(OperatorToCSharpString(nodeType)); + + b.Right.ToCSharpExpression(sb, EnclosedIn.ParensByDefault, ref ctx, + false, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + return !avoidParens ? sb.Append(')') : sb; + } + + return sb.Append(e.ToString()); // falling back ToString and hoping for the best + } + } + } + + private static bool AvoidParens(EnclosedIn enclosedIn) => + enclosedIn == EnclosedIn.AvoidParens | + enclosedIn == EnclosedIn.LambdaBody | + enclosedIn == EnclosedIn.Block | // statement in a block don't need the parens as well + enclosedIn == EnclosedIn.Return | + enclosedIn == EnclosedIn.IfTest; + + private static StringBuilder AddParenIfNeeded(this StringBuilder sb, char paren, bool avoidParen = false) => + avoidParen ? sb : sb.Append(paren); + + private static StringBuilder ToCSharpBlock(this Expression expr, StringBuilder sb, ref PrintContext ctx, + int lineIndent, bool stripNamespace, Func printType, int indentSpaces, ObjectToCode notRecognizedToCode) + { + sb.NewLineIndent(lineIndent).Append('{'); + if (expr is BlockExpression fb) + fb.BlockToCSharpString(sb, ref ctx, + lineIndent + indentSpaces, stripNamespace, printType, indentSpaces, notRecognizedToCode, inTheLastBlock: false); + else + { + sb.NewLineIndent(lineIndent + indentSpaces); + sb = expr?.ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, + lineIndent + indentSpaces, stripNamespace, printType, indentSpaces, notRecognizedToCode) ?? sb.Append("null"); + sb.AppendSemicolonOnce(expr); + } + return sb.NewLineIndent(lineIndent).Append('}'); + } + + private static StringBuilder ToCSharpExpression(this Expression expr, + StringBuilder sb, EnclosedIn enclosedIn, ref PrintContext ctx, + bool newLineExpr, int lineIndent, bool stripNamespace, Func printType, int indentSpaces, ObjectToCode notRecognizedToCode) + { + if (!expr.NodeType.IsBracedBlockLike()) + { + if (!newLineExpr) + return expr.ToCSharpString(sb, enclosedIn, ref ctx, + lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + + sb.NewLineIndent(lineIndent); + return expr.ToCSharpString(sb, enclosedIn, ref ctx, + lineIndent + indentSpaces, stripNamespace, printType, indentSpaces, notRecognizedToCode); + } + + InsertTopFFuncDefinitionOnce(sb); + sb.NewLineIndent(lineIndent).Append("__f(() => {"); + if (expr is BlockExpression bl) + bl.BlockToCSharpString(sb, ref ctx, + lineIndent + indentSpaces, stripNamespace, printType, indentSpaces, notRecognizedToCode, inTheLastBlock: true); + else + { + sb.NewLineIndent(lineIndent + indentSpaces); + if (expr != null) + expr.ToCSharpString(sb, EnclosedIn.LambdaBody, ref ctx, + lineIndent + indentSpaces, stripNamespace, printType, indentSpaces, notRecognizedToCode); + else + sb.Append("null"); + + } + return sb.NewLineIndent(lineIndent).Append("})"); + } + + internal static StringBuilder AppendSemicolonOnce(this StringBuilder sb, Expression expr = null) => + expr?.NodeType.IsBracedBlockLike() == true ? sb : sb[sb.Length - 1] != ';' ? sb.Append(";") : sb; + + internal static StringBuilder AppendRefOnce(this StringBuilder sb) + { + var len = sb.Length; + return len >= 4 && + sb[len - 4] == 'r' && + sb[len - 3] == 'e' && + sb[len - 2] == 'f' && + sb[len - 1] == ' ' ? sb : sb.Append("ref "); + } + + internal static StringBuilder AppendNewLineOnce(this StringBuilder sb) + { + for (var end = sb.Length - 1; end >= 0; --end) + { + if (sb[end] == '\n') + return sb; // return the unchanged sb when new line is already present + if (sb[end] != ' ') // skip spaces if any + break; + } + return sb.Append(NewLine); + } + + // Returns the number of consecutive spaces from the current position, + // or from the first non-space character to the prev newline. + // e.g. for `\n foo.Bar = ` and for `\n ` indent is 4 + internal static int GetRealLineIndent(this StringBuilder sb, int defaultIndent) + { + var lastSpacePos = -1; + // go back from the last char in the builder + for (var pos = sb.Length - 1; pos >= 0; --pos) + { + var ch = sb[pos]; + if (ch == '\n') + return lastSpacePos == -1 ? defaultIndent : lastSpacePos - pos; + + if (ch != ' ') + lastSpacePos = -1; // reset space position when non-space char is found + else if (lastSpacePos == -1) + lastSpacePos = pos; // set the last space position + } + return defaultIndent; + } + + private const string NotSupportedExpression = "// NOT_SUPPORTED_EXPRESSION: "; + + private static StringBuilder ToCSharpString(this IReadOnlyList bindings, + StringBuilder sb, EnclosedIn enclosedIn, ref PrintContext ctx, + int lineIndent = 0, bool stripNamespace = false, Func printType = null, int indentSpaces = 4, ObjectToCode notRecognizedToCode = null) + { + var count = bindings.Count; + for (var i = 0; i < count; i++) + { + var b = bindings[i]; + sb.NewLineIndent(lineIndent); + sb.Append(b.Member.Name).Append(" = "); + + if (b is MemberAssignment ma) + { + ma.Expression.ToCSharpString(sb, EnclosedIn.ParensByDefault, ref ctx, + lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + } + else if (b is MemberMemberBinding mmb) + { + sb.Append("{"); + mmb.Bindings.ToCSharpString(sb, enclosedIn, ref ctx, + lineIndent + indentSpaces, stripNamespace, printType, indentSpaces, notRecognizedToCode); + sb.NewLineIndent(lineIndent + indentSpaces).Append("}"); + } + else if (b is MemberListBinding mlb) + { + sb.Append("{"); + var elemInits = mlb.Initializers; + var elemCount = elemInits.Count; + for (var e = 0; e < elemCount; e++) + { + var args = elemInits[e].Arguments; + sb.NewLineIndent(lineIndent + indentSpaces); + var manyArgs = args.Count > 1; + if (manyArgs) + sb.Append("("); + + var n = 0; + foreach (var arg in args) + arg.ToCSharpString((++n > 1 ? sb.Append(", ") : sb), EnclosedIn.ParensByDefault, ref ctx, + lineIndent + indentSpaces, stripNamespace, printType, indentSpaces, notRecognizedToCode); + + if (manyArgs) + sb.Append(")"); + + if (e < elemCount - 1) + sb.Append(","); + } + sb.NewLineIndent(lineIndent + indentSpaces).Append("}"); + } + + // don't place comma after the last binding + if (i < count - 1) + sb.Append(","); + } + return sb; + } + + private static StringBuilder BlockToCSharpString(this BlockExpression b, StringBuilder sb, ref PrintContext ctx, + int lineIndent = 0, bool stripNamespace = false, Func printType = null, int indentSpaces = 4, + ObjectToCode notRecognizedToCode = null, bool inTheLastBlock = false, BinaryExpression blockResultAssignment = null, + bool containerIgnoresResult = false) // in case of the container is lambda which is the Action/void delegate and ignores result, we don't need the `return` - it will be invalid c# + { + var vars = b.Variables.AsList(); + var exprs = b.Expressions; + + // handling the special case, AutoMapper like using the tmp variable to reassign the property + if (vars.Count == 1 & exprs.Count == 2 && + exprs[0] is BinaryExpression st0 && st0.NodeType == ExpressionType.Assign && + exprs[1] is BinaryExpression st1 && st1.NodeType == ExpressionType.Assign && + st0.Left == vars[0] && st1.Right == vars[0]) + return Assign(st1.Left, st0.Right).ToCSharpString(sb.NewLineIndent(lineIndent), + EnclosedIn.Block, ref ctx, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode) + .AppendSemicolonOnce(); + + foreach (var v in vars) + { + sb.NewLineIndent(lineIndent); + var vType = v.Type; + var vIsByRef = v.IsByRef; + var vNameSuffix = !vIsByRef ? "" : "__discard_init_by_ref"; + + var vTypeCode = vType.ToCode(stripNamespace, printType); + var vName = new StringBuilder().AppendName(v, v.Name + vNameSuffix, vTypeCode, ref ctx); + sb.Append(vTypeCode).Append(' ').Append(vName).Append(vType.IsValueType && !vType.IsNullable() ? " = default;" : " = null;"); + + if (vIsByRef) + sb.Append(" ref var ").AppendName(v, v.Name, vTypeCode, ref ctx).Append(" = ref ").Append(vName).Append(';'); + } + + // we don't inline a single expression case because it can always go crazy with assignment, e.g. `var a; a = 1 + (a = 2) + a * 2` + for (var i = 0; i < exprs.Count - 1; i++) + { + var expr = exprs[i]; + + // this is basically the return pattern (see #237) so we don't care for the rest of the expressions + // Note (#300) the sentence above is slightly wrong because that may be a goto to this specific label, so we still need to print the label + if (expr is GotoExpression gt && gt.Kind == GotoExpressionKind.Return && + exprs[i + 1] is LabelExpression label && label.Target == gt.Target) + { + sb.NewLineIndent(lineIndent); + if (gt.Value == null) + sb.Append("return;"); + else + gt.Value.ToCSharpString(sb.Append("return "), + EnclosedIn.Return, ref ctx, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode) + .AppendSemicolonOnce(); + return sb; + + // todo: @wip @remove do we need this, let explore what code ouputput will blow up + // sb.NewLineIndent(lineIndent); + + // // In principle the label mark should mark a single place only. So we we found that it used already -> ??? skip it, rename it? + // // see Issue237_Trying_to_implement_For_Foreach_loop_but_getting_an_InvalidProgramException_thrown.Should_Deserialize_Simple + // sb.AppendLabelName(label.Target, ref ctx, nameUsage: NameUsage.MarkLabel).Append(":;"); + + // if (label.DefaultValue == null) + // return sb.AppendLine(); // no return because we may have other expressions after label + // sb.NewLineIndent(lineIndent); + // sb.Append("return "); + // label.DefaultValue.ToCSharpString(sb, EnclosedIn.Return, ref ctx, + // lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + // return sb.AppendSemicolonOnce(); + } + + if (expr is BlockExpression bl) + { + // Unrolling the block on the same vertical line + bl.BlockToCSharpString(sb, ref ctx, + lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode, inTheLastBlock: false); + } + else + { + sb.NewLineIndent(lineIndent); + var nodeType = expr.NodeType; + var returningCondOrCoalesce = expr.Type != typeof(void) + && nodeType == ExpressionType.Conditional | nodeType == ExpressionType.Coalesce; + if (returningCondOrCoalesce) // it requires some assignment target to avoid error or warning + sb.Append("_ = "); + + expr.ToCSharpString(sb, EnclosedIn.Block, ref ctx, + lineIndent + indentSpaces, stripNamespace, printType, indentSpaces, notRecognizedToCode); + + // Preventing the `};` kind of situation and separating the conditional block with empty line + if (nodeType.IsBlockLikeOrConditional()) + { + sb = returningCondOrCoalesce ? sb.AppendSemicolonOnce() : sb; + sb.NewLineIndent(lineIndent); + } + else if (nodeType != ExpressionType.Label & nodeType != ExpressionType.Default) + sb.AppendSemicolonOnce(); + } + } + + var lastExpr = exprs[exprs.Count - 1]; + if (lastExpr.NodeType == ExpressionType.Default && lastExpr.Type == typeof(void)) + return sb; + + if (lastExpr is BlockExpression lastBlock) + return lastBlock.BlockToCSharpString(sb, ref ctx, + lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode, + inTheLastBlock, // the last block is marked so if only it is itself in the last block + blockResultAssignment); + + // todo: @improve the label is already used by the Return GoTo we should skip it output here OR we need to replace the Return Goto `return` with `goto` + if (lastExpr is LabelExpression lastLabel) // keep the last label on the same vertical line + { + lastExpr.ToCSharpString(sb, EnclosedIn.Block, ref ctx, + lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + if (inTheLastBlock) + { + // todo: @improve @hack no return, need to track returns, see Issue320_Bad_label_content_in_ILGenerator_when_creating_through_DynamicModule.Test_instance_call + var labelDefault = lastLabel.DefaultValue; + if (labelDefault != null && // for now just provide return for variable and such, that for soe reason were put in label as default + (labelDefault.NodeType != ExpressionType.Constant & labelDefault.NodeType != ExpressionType.Default)) + { + sb.NewLineIndent(lineIndent); + labelDefault.ToCSharpString(sb.Append("return "), + EnclosedIn.Return, ref ctx, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + } + sb.AppendSemicolonOnce(); // the last label forms the invalid C#, so we need at least ';' at the end + } + return sb; + } + + sb.NewLineIndent(lineIndent); + var enclosedIn = EnclosedIn.Block; + var isBraceLikeBlock = lastExpr.NodeType.IsBracedBlockLike(); + if (blockResultAssignment != null) + { + blockResultAssignment.Left.ToCSharpString(sb, enclosedIn, ref ctx, + lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + if (blockResultAssignment.NodeType != ExpressionType.PowerAssign) + sb.Append(OperatorToCSharpString(blockResultAssignment.NodeType)); + else + { + sb.Append(" = System.Math.Pow("); + blockResultAssignment.Left.ToCSharpString(sb, enclosedIn, ref ctx, + lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(", "); + } + } + else if (inTheLastBlock & !containerIgnoresResult && + b.Type != typeof(void) && lastExpr.Type != typeof(void)) + { + // A Trow may have the non void type, yes, so this check will avoid `return throw Ex` thingy, see #457, case2 + if (!containerIgnoresResult && lastExpr.NodeType != ExpressionType.Throw) + { + enclosedIn = EnclosedIn.Return; + if (!isBraceLikeBlock) sb.Append("return "); // for the braced block like switch, loop, etc. move the return inside the block, see #440 + } + } + + if (isBraceLikeBlock || lastExpr is DefaultExpression d && d.Type == typeof(void)) + { + lastExpr.ToCSharpString(sb, enclosedIn, ref ctx, + lineIndent + indentSpaces, stripNamespace, printType, indentSpaces, notRecognizedToCode); + } + else if (lastExpr.NodeType == ExpressionType.Assign && ((BinaryExpression)lastExpr).Right is BlockExpression) + { + lastExpr.ToCSharpString(sb, enclosedIn, ref ctx, + lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + if (enclosedIn == EnclosedIn.Return) + sb.AppendSemicolonOnce(); + } + else + { + lastExpr.ToCSharpString(sb, enclosedIn, ref ctx, + lineIndent + indentSpaces, stripNamespace, printType, indentSpaces, notRecognizedToCode); + sb = blockResultAssignment?.NodeType == ExpressionType.PowerAssign ? sb.Append(')') : sb; + if (lastExpr.NodeType != ExpressionType.Conditional || lastExpr.Type != typeof(void)) + sb.AppendSemicolonOnce(); + } + return sb; + } + + private static string OperatorToCSharpString(ExpressionType nodeType) => + nodeType switch + { + ExpressionType.And => " & ", + ExpressionType.AndAssign => " &= ", + ExpressionType.AndAlso => " && ", + ExpressionType.Or => " | ", + ExpressionType.OrAssign => " |= ", + ExpressionType.OrElse => " || ", + ExpressionType.GreaterThan => " > ", + ExpressionType.GreaterThanOrEqual => " >= ", + ExpressionType.LessThan => " < ", + ExpressionType.LessThanOrEqual => " <= ", + ExpressionType.Equal => " == ", + ExpressionType.NotEqual => " != ", + ExpressionType.Add => " + ", + ExpressionType.AddChecked => " + ", + ExpressionType.AddAssign => " += ", + ExpressionType.AddAssignChecked => " += ", + ExpressionType.Subtract => " - ", + ExpressionType.SubtractChecked => " - ", + ExpressionType.SubtractAssign => " -= ", + ExpressionType.SubtractAssignChecked => " -= ", + ExpressionType.Assign => " = ", + ExpressionType.ExclusiveOr => " ^ ", + ExpressionType.ExclusiveOrAssign => " ^= ", + ExpressionType.LeftShift => " << ", + ExpressionType.LeftShiftAssign => " <<= ", + ExpressionType.RightShift => " >> ", + ExpressionType.RightShiftAssign => " >>= ", + ExpressionType.Modulo => " % ", + ExpressionType.ModuloAssign => " %= ", + ExpressionType.Multiply => " * ", + ExpressionType.MultiplyChecked => " * ", + ExpressionType.MultiplyAssign => " *= ", + ExpressionType.MultiplyAssignChecked => " *= ", + ExpressionType.Divide => " / ", + ExpressionType.DivideAssign => " /= ", + _ => "???" // todo: @unclear wanna be good + }; + + } + + [RequiresUnreferencedCode(Trimming.Message)] + public static class CodePrinter + { + public static readonly Func PrintTypeStripOuterClasses = (type, name) => + { + if (!type.IsNested) + return name; + var index = name.LastIndexOf('.'); + return index == -1 ? name : name.Substring(index + 1); + }; + + public static StringBuilder AppendTypeOf(this StringBuilder sb, Type type, + bool stripNamespace = false, Func printType = null, bool printGenericTypeArgs = false) + { + if (type == null) + return sb.Append("null"); + sb.Append("typeof(").Append(type.ToCode(stripNamespace, printType, printGenericTypeArgs)).Append(')'); + return type.IsByRef ? sb.Append(".MakeByRefType()") : sb; + } + + public static StringBuilder AppendTypeOfList(this StringBuilder sb, Type[] types, + bool stripNamespace = false, Func printType = null, bool printGenericTypeArgs = false) + { + for (var i = 0; i < types.Length; i++) + (i > 0 ? sb.Append(", ") : sb).AppendTypeOf(types[i], stripNamespace, printType, printGenericTypeArgs); + return sb; + } + + internal static StringBuilder AppendMember(this StringBuilder sb, MemberInfo member, + bool stripNamespace = false, Func printType = null) => + member is FieldInfo f + ? sb.AppendField(f, stripNamespace, printType) + : sb.AppendProperty((PropertyInfo)member, stripNamespace, printType); + + internal static StringBuilder AppendField(this StringBuilder sb, FieldInfo field, + bool stripNamespace = false, Func printType = null) => + sb.AppendTypeOf(field.DeclaringType, stripNamespace, printType) + .Append(".GetTypeInfo().GetDeclaredField(\"").Append(field.Name).Append("\")"); + + internal static StringBuilder AppendProperty(this StringBuilder sb, PropertyInfo property, + bool stripNamespace = false, Func printType = null) => + sb.AppendTypeOf(property.DeclaringType, stripNamespace, printType) + .Append(".GetTypeInfo().GetDeclaredProperty(\"").Append(property.Name).Append("\")"); + + internal static StringBuilder AppendEnum(this StringBuilder sb, TEnum value, + bool stripNamespace = false, Func printType = null) => + sb.Append(typeof(TEnum).ToCode(stripNamespace, printType)).Append('.') + .Append(Enum.GetName(typeof(TEnum), value)); + + private const string _nonPubStatMethods = "BindingFlags.NonPublic|BindingFlags.Static"; + private const string _nonPubInstMethods = "BindingFlags.NonPublic|BindingFlags.Instance"; + + public static StringBuilder AppendMethod(this StringBuilder sb, MethodInfo method, + bool stripNamespace = false, Func printType = null) + { + if (method == null) + return sb.Append("null"); + + sb.AppendTypeOf(method.DeclaringType, stripNamespace, printType); + sb.Append(".GetMethods("); + + if (!method.IsPublic) + sb.Append(method.IsStatic ? _nonPubStatMethods : _nonPubInstMethods); + + var mp = method.GetParameters(); + if (!method.IsGenericMethod) + { + sb.Append(").Single(x => !x.IsGenericMethod && x.Name == \"").Append(method.Name).Append("\" && "); + return mp.Length == 0 + ? sb.Append("x.GetParameters().Length == 0)") + : sb.Append("x.GetParameters().Select(y => y.ParameterType).SequenceEqual(new[] { ") + .AppendTypeOfList(mp.Select(x => x.ParameterType).ToArray(), stripNamespace, printType) + .Append(" }))"); + } + + var tp = method.GetGenericArguments(); + sb.Append(").Where(x => x.IsGenericMethod && x.Name == \"").Append(method.Name).Append("\" && "); + if (mp.Length == 0) + { + sb.Append("x.GetParameters().Length == 0 && x.GetGenericArguments().Length == ").Append(tp.Length); + sb.Append(").Select(x => x.IsGenericMethodDefinition ? x.MakeGenericMethod(").AppendTypeOfList(tp, stripNamespace, printType); + return sb.Append(") : x).Single()"); + } + + sb.Append("x.GetGenericArguments().Length == ").Append(tp.Length); + sb.Append(").Select(x => x.IsGenericMethodDefinition ? x.MakeGenericMethod(").AppendTypeOfList(tp, stripNamespace, printType); + sb.Append(") : x).Single(x => x.GetParameters().Select(y => y.ParameterType).SequenceEqual(new[] { "); + sb.AppendTypeOfList(mp.Select(x => x.ParameterType).ToArray(), stripNamespace, printType); + return sb.Append(" }))"); + } + + /// Name usage + public enum NameUsage + { + /// Whatever + Whatever, + /// Used in goto + GotoLabel, + /// Used in mark + MarkLabel + } + + /// Named with index indeed + public struct NameIndex + { + /// Object to name + public object Obj; + /// Provides an unique suffix for the same named objects + public int Index; + /// Usage context + public NameUsage Usage; + } + + /// Contains the lambda with its paramete infos info. + /// Used to search for parameter expression related props/attrs in the respective Parameterinfo + public struct LambdaPars + { + /// The parameter expressions +#if LIGHT_EXPRESSION + public IParameterProvider Exprs; +#else + public IReadOnlyList Exprs; +#endif + /// The corresponding parameter infos + public ParameterInfo[] Infos; + } + + /// The context propagated through the CSharp to string methods + public struct PrintContext + { + /// Used to generated the unique names for the variables/parameters + public SmallList, NoArrayPool> SameNamed; + /// Use to find additional info about the parameter, like IsOut, which is available in ParameterInfo and not in parameter expression + public SmallList, NoArrayPool> LambdaPars; + } + + internal static bool IsOut(this PE parExpr, ref PrintContext ctx) + { + if (ctx.LambdaPars.Count == 0) + return false; + for (var i = ctx.LambdaPars.Count - 1; i >= 0; --i) + { + ref var it = ref ctx.LambdaPars.GetSurePresentRef(i); + var count = it.Infos.Length; + for (var j = 0; j < count; ++j) + if (it.Exprs.GetParameter(j) == parExpr) + return it.Infos[j].IsOut; + } + return false; + } + + internal static StringBuilder AppendName(this StringBuilder sb, + object parOrTarget, string name, string typeCode, ref PrintContext ctx, + int customIndex = 0, NameUsage nameUsage = NameUsage.Whatever) + { + var nameIndex = 0; + if (customIndex == 0) + { + var found = false; + for (var i = 0; i < ctx.SameNamed.Count; ++i) + { + ref var n = ref ctx.SameNamed.GetSurePresentRef(i); + if (found = ReferenceEquals(n.Obj, parOrTarget)) + { + nameIndex = n.Index; + break; + } + // If param or label target are not the same object but have the same name, say `bool p` and `int p`, + // then we increment assigned index to distinguish p_1 from p_0. + if (n.Obj is ParameterExpression pe1 && parOrTarget is ParameterExpression pe2 && pe1.Name == pe2.Name || + n.Obj is LabelTarget lt1 && parOrTarget is LabelTarget lt2 && lt1.Name == lt2.Name) + ++nameIndex; + } + if (!found) + ctx.SameNamed.Add(new() { Obj = parOrTarget, Index = nameIndex, Usage = nameUsage }); + customIndex = nameIndex; + } + + if (!string.IsNullOrWhiteSpace(name)) + { + if (name[0] != '@' && IsCSharpKeyword(name)) + sb.Append('@'); + sb.Append(name); + return nameIndex == 0 ? sb : sb.Append('_').Append(nameIndex); + } + + var validCsIdentFromTypeName = new StringBuilder(typeCode.Length); + for (var k = 0; k < typeCode.Length; ++k) + { + var ch = typeCode[k]; + var toUnder = ch == '.' | ch == '<' | ch == '>' | ch == ',' | ch == '?' | ch == ' ' | ch == '^' | ch == '['; + var toArray = ch == ']'; + var newChar = toUnder | toArray ? (toUnder ? '_' : 'a') : k == 0 ? Char.ToLowerInvariant(ch) : ch; + validCsIdentFromTypeName.Append(newChar); + } + + return sb.Append(validCsIdentFromTypeName).Append('_').Append(customIndex); + } + + private static bool IsCSharpKeyword(string name) => name switch + { + // Reserved keywords + "abstract" or "as" or "base" or "bool" or "break" or "byte" or "case" or "catch" or "char" or + "checked" or "class" or "const" or "continue" or "decimal" or "default" or "delegate" or "do" or + "double" or "else" or "enum" or "event" or "explicit" or "extern" or "false" or "finally" or + "fixed" or "float" or "for" or "foreach" or "goto" or "if" or "implicit" or "in" or "int" or + "interface" or "internal" or "is" or "lock" or "long" or "namespace" or "new" or "null" or + "object" or "operator" or "out" or "override" or "params" or "private" or "protected" or + "public" or "readonly" or "ref" or "return" or "sbyte" or "sealed" or "short" or "sizeof" or + "stackalloc" or "static" or "string" or "struct" or "switch" or "this" or "throw" or "true" or + "try" or "typeof" or "uint" or "ulong" or "unchecked" or "unsafe" or "ushort" or "using" or + "virtual" or "void" or "volatile" or "while" => true, + + // Contextual keywords (still require @ to be used safely as identifiers in generated code) + "add" or "alias" or "and" or "ascending" or "args" or "async" or "await" or "by" or "descending" or + "dynamic" or "equals" or "file" or "from" or "get" or "global" or "group" or "init" or "into" or + "join" or "let" or "managed" or "nameof" or "nint" or "not" or "notnull" or "nuint" or "on" or + "or" or "orderby" or "partial" or "record" or "remove" or "required" or "scoped" or "select" or + "set" or "unmanaged" or "value" or "var" or "when" or "where" or "with" or "yield" => true, + + _ => false + }; + + internal static StringBuilder AppendLabelName(this StringBuilder sb, LabelTarget target, ref PrintContext ctx, + int customIndex = 0, NameUsage nameUsage = NameUsage.Whatever) => + sb.AppendName(target, target.Name, target.Type.ToCode(stripNamespace: true, printType: null), ref ctx, customIndex, nameUsage); + + /// Returns the standard name (alias) for the well-known primitive type, e.g. Int16 -> short + public static string GetPrimitiveTypeNameAliasOrNull(this Type type) => + Type.GetTypeCode(type) switch + { + TypeCode.Byte => "byte", + TypeCode.SByte => "sbyte", + TypeCode.Int16 => "short", + TypeCode.Int32 => "int", + TypeCode.Int64 => "long", + TypeCode.UInt16 => "ushort", + TypeCode.UInt32 => "uint", + TypeCode.UInt64 => "ulong", + TypeCode.Single => "float", + TypeCode.Double => "double", + TypeCode.Boolean => "bool", + TypeCode.Char => "char", + TypeCode.String => "string", + _ => type == typeof(void) ? "void" : + type == typeof(object) ? "object" : + null + }; + + // todo: @simplify add `addTypeof = false` or use `AppendTypeOf` generally + /// Converts the into the proper C# representation. + public static string ToCode(this Type type, + bool stripNamespace = false, Func printType = null, bool printGenericTypeArgs = false) + { + if (type == null) + return "null"; + + if (type.IsGenericParameter) + return !printGenericTypeArgs ? string.Empty : (printType?.Invoke(type, type.Name) ?? type.Name); + + if (Nullable.GetUnderlyingType(type) is Type nullableElementType && !type.IsGenericTypeDefinition) + { + var result = nullableElementType.ToCode(stripNamespace, printType, printGenericTypeArgs) + "?"; + return printType?.Invoke(type, result) ?? result; + } + + Type arrayType = null; + if (type.IsArray) + { + // store the original type for the later and process its element type further here + arrayType = type; + type = type.GetElementType(); + } + + if (type.IsEnum) + { + var result = !stripNamespace && !string.IsNullOrEmpty(type.Namespace) + ? string.Concat(type.Namespace, ".", type.Name) : type.Name; + return printType?.Invoke(type, result) ?? result; + } + + var buildInTypeString = type.GetPrimitiveTypeNameAliasOrNull(); + if (buildInTypeString != null) + { + if (arrayType != null) + { + var rank = arrayType.GetArrayRank(); + buildInTypeString += rank == 1 ? "[]" : ("[" + new string(',', rank-1) + "]"); + } + return printType?.Invoke(arrayType ?? type, buildInTypeString) ?? buildInTypeString; + } + + var parentCount = 0; + for (var ti = type.GetTypeInfo(); ti.IsNested; ti = ti.DeclaringType.GetTypeInfo()) + ++parentCount; + + Type[] parentTypes = null; + if (parentCount > 0) + { + parentTypes = new Type[parentCount]; + var pt = type.DeclaringType; + for (var i = 0; i < parentTypes.Length; i++, pt = pt.DeclaringType) + parentTypes[i] = pt; + } + + var typeInfo = type.GetTypeInfo(); + Type[] typeArgs = null; + var isTypeClosedGeneric = false; + if (type.IsGenericType) + { + isTypeClosedGeneric = !typeInfo.IsGenericTypeDefinition; + typeArgs = isTypeClosedGeneric ? typeInfo.GenericTypeArguments : typeInfo.GenericTypeParameters; + } + + var typeArgsConsumedByParentsCount = 0; + var s = new StringBuilder(); + if (!stripNamespace && !string.IsNullOrEmpty(type.Namespace)) // for the auto-generated classes Namespace may be empty and in general it may be empty + s.Append(type.Namespace).Append('.'); + + if (parentTypes != null) + { + for (var p = parentTypes.Length - 1; p >= 0; --p) + { + var parentType = parentTypes[p]; + if (!parentType.IsGenericType) + { + s.Append(parentType.Name).Append('.'); + } + else + { + var parentTypeInfo = parentType.GetTypeInfo(); + Type[] parentTypeArgs = null; + if (parentTypeInfo.IsGenericTypeDefinition) + { + parentTypeArgs = parentTypeInfo.GenericTypeParameters; + + // replace the open parent args with the closed child args, + // and close the parent + if (isTypeClosedGeneric) + for (var t = 0; t < parentTypeArgs.Length; ++t) + parentTypeArgs[t] = typeArgs[t]; + + var parentTypeArgCount = parentTypeArgs.Length; + if (typeArgsConsumedByParentsCount > 0) + { + int ownArgCount = parentTypeArgCount - typeArgsConsumedByParentsCount; + if (ownArgCount == 0) + parentTypeArgs = null; + else + { + var ownArgs = new Type[ownArgCount]; + for (var a = 0; a < ownArgs.Length; ++a) + ownArgs[a] = parentTypeArgs[a + typeArgsConsumedByParentsCount]; + parentTypeArgs = ownArgs; + } + } + typeArgsConsumedByParentsCount = parentTypeArgCount; + } + else + { + parentTypeArgs = parentTypeInfo.GenericTypeArguments; + } + + var parentTickIndex = parentType.Name.IndexOf('`'); + s.Append(parentType.Name.Substring(0, parentTickIndex)); + + // The owned parentTypeArgs maybe empty because all args are defined in the parent's parents + if (parentTypeArgs?.Length > 0) + { + s.Append('<'); + for (var t = 0; t < parentTypeArgs.Length; ++t) + (t == 0 ? s : s.Append(", ")).Append(parentTypeArgs[t].ToCode(stripNamespace, printType, printGenericTypeArgs)); + s.Append('>'); + } + s.Append('.'); + } + } + } + var name = type.Name.TrimStart('<', '>').TrimEnd('&'); + + if (typeArgs != null && typeArgsConsumedByParentsCount < typeArgs.Length) + { + var tickIndex = name.IndexOf('`'); + s.Append(name.Substring(0, tickIndex)).Append('<'); + for (var i = 0; i < typeArgs.Length - typeArgsConsumedByParentsCount; ++i) + (i == 0 ? s : s.Append(", ")).Append(typeArgs[i + typeArgsConsumedByParentsCount].ToCode(stripNamespace, printType, printGenericTypeArgs)); + s.Append('>'); + } + else + { + s.Append(name); + } + + if (arrayType != null) + s.Append("[]"); + + return printType?.Invoke(arrayType ?? type, s.ToString()) ?? s.ToString(); + } + + /// Prints valid C# Boolean + public static string ToCode(this bool x) => x ? "true" : "false"; + + /// Prints valid C# String escaping the things + public static string ToCode(this string x) => + x == null ? "null" + : $"\"{x.Replace("\"", "\\\"").Replace("\r", "\\r").Replace("\n", "\\n")}\""; + + private static readonly char[] _enumValueSeparators = new[] { ',', ' ' }; + + /// Prints valid C# Enum literal + public static string ToEnumValueCode(this Type enumType, object x, + bool stripNamespace = false, Func printType = null) + { + var typeStr = enumType.ToCode(stripNamespace, printType); + var valueStr = x.ToString(); + var flags = valueStr.Split(_enumValueSeparators, StringSplitOptions.RemoveEmptyEntries); + if (flags.Length == 1) + { + if (int.TryParse(valueStr, out _)) + return "(" + typeStr + ")" + valueStr; + return typeStr + "." + valueStr; + } + var orTypeDot = "|" + typeStr + "."; + return typeStr + "." + string.Join(orTypeDot, flags); + } + + private static Type[] GetGenericTypeParametersOrArguments(this TypeInfo typeInfo) => + typeInfo.IsGenericTypeDefinition ? typeInfo.GenericTypeParameters : typeInfo.GenericTypeArguments; + + /// Custom handler for output the object in valid C#. + /// Note, the `printGenericTypeArgs` is excluded because it cannot be a open-generic object. + /// This handler is also used to allow user to fully control a Constant expression output + public delegate string ObjectToCode(object x, bool stripNamespace = false, Func printType = null); + + /// Outputs the `default(Type)` for the unknown constant with the comment message + public static readonly ObjectToCode DefaultNotRecognizedToCode = (x, stripNamespace, printType) => + { + var t = x.GetType(); + var isCompGen = t.IsCompilerGenerated(); + var typeCs = x.GetType().ToCode(stripNamespace, printType); + return $"default({typeCs})/*NOTE: Provide the non-default value for the Constant{(isCompGen ? " of compiler-generated type" : "")}!*/"; + }; + + /// Prints many code items as the array initializer. + public static string ToCommaSeparatedCode(this IEnumerable items, ObjectToCode notRecognizedToCode, + bool stripNamespace = false, Func printType = null) + { + var s = new StringBuilder(); + var first = true; + foreach (var item in items) + { + if (!first) + s.Append(", "); + first = false; + s.Append(item.ToCode(notRecognizedToCode, stripNamespace, printType)); + } + return s.ToString(); + } + + /// Prints many code items as array initializer. + public static string ToArrayInitializerCode(this IEnumerable items, Type itemType, ObjectToCode notRecognizedToCode, + bool stripNamespace = false, Func printType = null) + { + var s = new StringBuilder("new "); + // todo: @simplify should we avoid type for the `new Type` because the values also will include the type? + s.Append(itemType.ToCode(stripNamespace, printType)); + s.Append("[]{"); + s.Append(items.ToCommaSeparatedCode(notRecognizedToCode, stripNamespace, printType)); + s.Append('}'); + return s.ToString(); + } + + private static readonly Type[] TypesImplementedByArray = + typeof(object[]).GetInterfaces().Where(t => t.GetTypeInfo().IsGenericType).Select(t => t.GetGenericTypeDefinition()).ToArray(); + + // todo: @simplify convert to using StringBuilder and simplify usage call-sites, or ADD the method + // todo: @simplify add `addTypeof = false` + /// + /// Prints a valid C# for known , + /// otherwise uses passed or falls back to `ToString()`. + /// + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode(Trimming.Message)] + public static string ToCode(this T x, + ObjectToCode notRecognizedToCode = null, bool stripNamespace = false, Func printType = null) + { + if (x == null) + return "null"; + + if (x is bool b) + return b.ToCode(); + + if (x is int i) + return i.ToString(); + + if (x is double d) + return d.ToString(); + + if (x is string s) + return s.ToCode(); + + if (x is char c) + return "'" + c + "'"; + + if (x is Decimal m) + return $"{m}m"; + + if (x is Type t) + return t.ToCode(stripNamespace, printType); + + if (x is Guid guid) + return "Guid.Parse(" + guid.ToString().ToCode() + ")"; + + if (x is DateTime date) + return "DateTime.Parse(" + date.ToString().ToCode() + ")"; + + if (x is TimeSpan time) + return "TimeSpan.Parse(" + time.ToString().ToCode() + ")"; + + var xType = x.GetType(); + var xTypeInfo = xType.GetTypeInfo(); + + // check if item is implemented by array and then use the array initializer only for these types, + // otherwise we may produce the array initializer but it will be incompatible with e.g. `List` + if (xTypeInfo.IsArray || + xTypeInfo.IsGenericType && TypesImplementedByArray.Contains(xType.GetGenericTypeDefinition())) + { + var elemType = xTypeInfo.IsArray + ? xTypeInfo.GetElementType() + : xTypeInfo.GetGenericTypeParametersOrArguments().GetFirst(); + if (elemType != null && elemType != xType) // avoid self recurring types e.g. `class A : IEnumerable` + return ((IEnumerable)x).ToArrayInitializerCode(elemType, notRecognizedToCode, stripNamespace, printType); + } + + // unwrap the Nullable struct + if (xTypeInfo.IsGenericType && xTypeInfo.GetGenericTypeDefinition() == typeof(Nullable<>)) + { + xType = xTypeInfo.GetElementType(); + xTypeInfo = xType.GetTypeInfo(); + } + + if (xTypeInfo.IsEnum) + return x.GetType().ToEnumValueCode(x, stripNamespace, printType); + + if (xTypeInfo.IsPrimitive) // output the primitive casted to the type + return "(" + x.GetType().ToCode(true, null) + ")" + x.ToString(); + + return notRecognizedToCode?.Invoke(x, stripNamespace, printType) ?? x.ToString(); + } + + internal static StringBuilder NewLineIndent(this StringBuilder sb, int lineIndent) + { + var originalLength = sb.Length; + sb.AppendNewLineOnce(); + return originalLength == sb.Length ? sb : sb.Append(' ', lineIndent); + } + + internal static StringBuilder NewLineIndentExpr(this StringBuilder sb, + Expression expr, List paramsExprs, List uniqueExprs, List lts, + int lineIndent, bool stripNamespace, Func printType, int indentSpaces, ObjectToCode notRecognizedToCode) + { + sb.NewLineIndent(lineIndent); + return expr?.ToExpressionString(sb, paramsExprs, uniqueExprs, lts, + lineIndent + indentSpaces, stripNamespace, printType, indentSpaces, notRecognizedToCode) ?? sb.Append("null"); + } + + internal static StringBuilder NewLineIndentArgumentExprs(this StringBuilder sb, IEnumerable exprs, + List paramsExprs, List uniqueExprs, List lts, + int lineIndent, bool stripNamespace, Func printType, int indentSpaces, ObjectToCode notRecognizedToCode) + where T : Expression + { + if (exprs == null || !exprs.Any()) + return sb.Append(" new ").Append(typeof(T).ToCode(true)).Append("[0]"); + var i = 0; + foreach (var e in exprs) + (i++ > 0 ? sb.Append(", ") : sb).NewLineIndentExpr(e, + paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode); + return sb; + } + + /// Helper method to find the number of lambdas in the C# code string + public static int CountLambdas(string code) + { + int lambdaCount = 0, lambdaIndex = 0; + while (true) + { + lambdaIndex = code.IndexOf("=>", lambdaIndex + 2); + if (lambdaIndex == -1) + break; + ++lambdaCount; + } + return lambdaCount; + } + } + + internal static class FecHelpers + { + public static int GetFirstIndex(this IReadOnlyList source, T item, TEq eq = default) + where TEq : struct, IEq + { + if (source.Count != 0) + for (var i = 0; i < source.Count; ++i) + if (eq.Equals(source[i], item)) + return i; + return -1; + } + + [MethodImpl((MethodImplOptions)256)] + public static T GetArgument(this IReadOnlyList source, int index) => source[index]; + + [MethodImpl((MethodImplOptions)256)] + public static ParameterExpression GetParameter(this IReadOnlyList source, int index) => source[index]; + +#if LIGHT_EXPRESSION + public static IReadOnlyList ToReadOnlyList(this IParameterProvider source) + { + var count = source.ParameterCount; + var ps = new ParameterExpression[count]; + for (var i = 0; i < count; ++i) + ps[i] = source.GetParameter(i); + return ps; + } + + public static int GetCount(this IParameterProvider p) => p.ParameterCount; +#else + public static IReadOnlyList ToReadOnlyList(this IReadOnlyList source) => source; + + public static int GetCount(this IReadOnlyList p) => p.Count; +#endif + +#if SUPPORTS_ARGUMENT_PROVIDER + public static int GetCount(this IArgumentProvider p) => p.ArgumentCount; +#else + public static int GetCount(this IReadOnlyList p) => p.Count; +#endif + } + + internal static class Trimming + { + public const string Message = "FastExpressionCompiler is not supported in trimming scenarios."; + } +} + +#if !NET5_0_OR_GREATER +namespace System.Diagnostics.CodeAnalysis +{ + [AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)] + internal sealed class UnconditionalSuppressMessageAttribute : Attribute + { + public string Category { get; } + public string CheckId { get; } + public string Justification { get; set; } + public UnconditionalSuppressMessageAttribute(string category, string checkId) + { + Category = category; + CheckId = checkId; + } + } + + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Class, Inherited = false)] + internal sealed class RequiresUnreferencedCodeAttribute : Attribute + { + public string Message { get; } + public RequiresUnreferencedCodeAttribute(string message) => Message = message; + } + + [AttributeUsage( + AttributeTargets.Field | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter | + AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Method | + AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct, + Inherited = false)] + internal sealed class DynamicallyAccessedMembersAttribute : Attribute + { + public DynamicallyAccessedMembersAttribute(DynamicallyAccessedMemberTypes memberTypes) { } + } + + [Flags] + internal enum DynamicallyAccessedMemberTypes + { + None = 0, + PublicParameterlessConstructor = 0x0001, + PublicConstructors = 0x0002 | PublicParameterlessConstructor, + NonPublicConstructors = 0x0004, + PublicMethods = 0x0008, + NonPublicMethods = 0x0010, + PublicFields = 0x0020, + NonPublicFields = 0x0040, + PublicNestedTypes = 0x0080, + NonPublicNestedTypes = 0x0100, + PublicProperties = 0x0200, + NonPublicProperties = 0x0400, + + Interfaces = 0x2000, + All = ~None + } +} +#endif +#if !NET7_0_OR_GREATER +namespace System.Diagnostics.CodeAnalysis +{ + [AttributeUsage(System.AttributeTargets.Method | System.AttributeTargets.Parameter | System.AttributeTargets.Property, AllowMultiple = false, Inherited = false)] + public sealed class UnscopedRefAttribute : Attribute { } +} +#endif diff --git a/test/FastExpressionCompiler.Benchmarks/EmitHacks.cs b/test/FastExpressionCompiler.Benchmarks/EmitHacks.cs index ec833dca..5f10f718 100644 --- a/test/FastExpressionCompiler.Benchmarks/EmitHacks.cs +++ b/test/FastExpressionCompiler.Benchmarks/EmitHacks.cs @@ -1,4 +1,4 @@ -#if NET7_0 && !LIGHT_EXPRESSION +#if NET8_0_OR_GREATER && !LIGHT_EXPRESSION using System; using System.Reflection; @@ -108,6 +108,62 @@ public int MethodInfo_Invoke() => (int)EmitHacksTest.MethodStaticNoArgs.Invoke(null, null); } +#if NET10_0_OR_GREATER + /// + /// Benchmarks comparing NET10+ fast-emit paths for a minimal DynamicMethod (Ldarg_1, Call, Ret): + /// - Baseline: standard il.Emit() + /// - Hack_UAT: manual byte-writes using UnsafeAccessorType (the raw NET10 approach) + /// - Demit_NoHack: Demit() with UseILEmitHack = false (falls back to il.Emit) + /// - Demit_WithHack: Demit() with UseILEmitHack = true (uses UAT fast path) + /// + /// Run with: BenchmarkRunner.Run<EmitHacks.Net10Emit>(); + /// + [MemoryDiagnoser(displayGenColumns: false)] + public class Net10Emit + { + [GlobalSetup] + public void Setup() + { + // pre-warm the static ctor so startup cost is not measured + _ = ILGeneratorTools.UseILEmitHack; + } + + [Benchmark(Baseline = true)] + public int DynamicMethod_Emit_OpCodes_Call() + { + var f = EmitHacksTest.Get_DynamicMethod_Emit_OpCodes_Call(); + return f(41); + } + + [Benchmark] + public int DynamicMethod_Emit_Hack_UAT() + { + var f = EmitHacksTest.Get_DynamicMethod_Emit_Hack_Net10(); + return f(41); + } + + [Benchmark] + public int DynamicMethod_Demit_NoHack() + { + ILGeneratorTools.UseILEmitHack = false; + try + { + var f = EmitHacksTest.Get_DynamicMethod_Demit(); + return f(41); + } + finally { ILGeneratorTools.UseILEmitHack = true; } + } + + [Benchmark] + public int DynamicMethod_Demit_WithHack() + { + ILGeneratorTools.UseILEmitHack = true; + var f = EmitHacksTest.Get_DynamicMethod_Demit(); + return f(41); + } + } +#endif + } } -#endif \ No newline at end of file +#endif diff --git a/test/FastExpressionCompiler.Benchmarks/FastExpressionCompiler.Benchmarks.csproj b/test/FastExpressionCompiler.Benchmarks/FastExpressionCompiler.Benchmarks.csproj index 10a5283e..5a4b89b9 100644 --- a/test/FastExpressionCompiler.Benchmarks/FastExpressionCompiler.Benchmarks.csproj +++ b/test/FastExpressionCompiler.Benchmarks/FastExpressionCompiler.Benchmarks.csproj @@ -1,8 +1,8 @@  - net9.0;net8.0 - net9.0 + net10.0;net9.0;net8.0 + net10.0;net9.0 Exe false diff --git a/test/FastExpressionCompiler.IssueTests/EmitHacksTest.cs b/test/FastExpressionCompiler.IssueTests/EmitHacksTest.cs index d69ec704..a3daee06 100644 --- a/test/FastExpressionCompiler.IssueTests/EmitHacksTest.cs +++ b/test/FastExpressionCompiler.IssueTests/EmitHacksTest.cs @@ -17,7 +17,9 @@ public int Run() DynamicMethod_Emit_Hack(); #if NET10_0_OR_GREATER DynamicMethod_Emit_Hack_Net10(); - return 4; + DynamicMethod_Demit_WithHack(); + DynamicMethod_Demit_NoHack(); + return 6; #else return 3; #endif @@ -355,6 +357,50 @@ public A() { } private static readonly ConstructorInfo _ctor = typeof(A).GetConstructor(Type.EmptyTypes); public static readonly MethodInfo MethodStaticNoArgs = typeof(A).GetMethod(nameof(A.M)); public static readonly MethodInfo MethodStatic1Arg = typeof(A).GetMethod(nameof(A.M1)); + +#if NET10_0_OR_GREATER + /// + /// Demonstrates using Demit() which (with UseILEmitHack=true, the NET10+ default) routes through + /// direct IL stream writes via UnsafeAccessorType instead of ILGenerator.Emit(). + /// This is the production-ready counterpart to Get_DynamicMethod_Emit_Hack_Net10(). + /// + public static Func Get_DynamicMethod_Demit() + { + var dynMethod = new DynamicMethod(string.Empty, + typeof(int), new[] { typeof(ExpressionCompiler.ArrayClosure), typeof(int) }, + typeof(ExpressionCompiler), skipVisibility: true); + + var il = dynMethod.GetILGenerator(16); + + // These Demit calls route through DynamicMethodHacks.HackEmitMethod (NET10+) + // when ILGeneratorTools.UseILEmitHack is true (the default). + il.Demit(OpCodes.Ldarg_1); + il.Demit(OpCodes.Call, MethodStatic1Arg); + il.Demit(OpCodes.Ret); + + return (Func)dynMethod.CreateDelegate(typeof(Func), ExpressionCompiler.EmptyArrayClosure); + } + + public void DynamicMethod_Demit_WithHack() + { + ILGeneratorTools.UseILEmitHack = true; + var f = Get_DynamicMethod_Demit(); + var a = f(41); + Asserts.AreEqual(42, a); + } + + public void DynamicMethod_Demit_NoHack() + { + ILGeneratorTools.UseILEmitHack = false; + try + { + var f = Get_DynamicMethod_Demit(); + var a = f(41); + Asserts.AreEqual(42, a); + } + finally { ILGeneratorTools.UseILEmitHack = true; } + } +#endif } } #endif \ No newline at end of file