From eaf58247ecb07708ff817801910420219cb43210 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20M=C3=AD=C5=A1ek?= Date: Sat, 18 May 2024 13:20:48 +0200 Subject: [PATCH] run time: - unboxing value types - binding to CancellationToken - overload resolution with Closure->System.Delegate --- src/Peachpie.Runtime/Conversions.cs | 34 ++++++++++++- .../Dynamic/ConvertExpression.cs | 51 +++++++++++-------- src/Peachpie.Runtime/PhpValue.cs | 7 ++- src/Peachpie.Runtime/PhpValueConverter.cs | 3 ++ 4 files changed, 71 insertions(+), 24 deletions(-) diff --git a/src/Peachpie.Runtime/Conversions.cs b/src/Peachpie.Runtime/Conversions.cs index e38416c286..e8cba5ac2e 100644 --- a/src/Peachpie.Runtime/Conversions.cs +++ b/src/Peachpie.Runtime/Conversions.cs @@ -5,7 +5,7 @@ using System.Linq; using System.Reflection; using System.Text; -using System.Threading.Tasks; +using Pchp.Core.Dynamic; using Pchp.Core.Reflection; using Pchp.Core.Text; @@ -1376,6 +1376,36 @@ public static DateTime ToDateTime(this PhpValue value) } #endregion + + #region UnwrapStruct + + /// + /// Given is a CLR value type (not a scalar PHP type!), + /// this method tries to unwrap to . + /// No conversion is performed. The method throws an exception is the value type does not match. + /// If is NULL, the method returns default of . + /// + public static T UnwrapStruct(PhpValue value) where T : struct + { + switch (value.TypeCode) + { + case PhpTypeCode.Null: return default(T); + case PhpTypeCode.Alias: return UnwrapStruct(value.Alias.Value); + case PhpTypeCode.Object: + var obj = value.Object; + + if (obj is StructBox box_t) return box_t.Value; + if (obj is IStructBox box) return (T)box.BoxedValue; // throws + + return (T)obj; // throws if type does not match + + default: + // throws: + return (T)value.ToClr(); + } + } + + #endregion } #endregion @@ -1499,7 +1529,7 @@ public static double ToDouble(PhpValue value) PhpTypeCode.Null => null, _ => throw PhpException.TypeErrorException(string.Format(Resources.ErrResources.scalar_used_as_object, PhpVariable.GetTypeName(value))), }; - + public static object AsResource(PhpValue value) => value.TypeCode switch { PhpTypeCode.Object => value.Object as PhpResource ?? throw PhpException.TypeErrorException(), diff --git a/src/Peachpie.Runtime/Dynamic/ConvertExpression.cs b/src/Peachpie.Runtime/Dynamic/ConvertExpression.cs index b058a19b0d..1aaa3a4505 100644 --- a/src/Peachpie.Runtime/Dynamic/ConvertExpression.cs +++ b/src/Peachpie.Runtime/Dynamic/ConvertExpression.cs @@ -6,8 +6,6 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; -using System.Text; -using System.Threading.Tasks; using Pchp.Core.Reflection; namespace Pchp.Core.Dynamic @@ -126,9 +124,10 @@ public static Expression Bind(Expression arg, Type target, Expression ctx) // if (target.IsValueType == false) { - Debug.Assert(typeof(Nullable).IsValueType); - - return BindAsReferenceType(arg, target, ctx); + if (TryBindAsReferenceType(arg, target, ctx, out var expression)) + { + return expression; + } } else { @@ -160,16 +159,16 @@ public static Expression Bind(Expression arg, Type target, Expression ctx) } } - //// PhpValueConverter.Cast( (PhpValue ) : T - //return Expression.Call( - // typeof(PhpValueConverter).GetMethod("Cast", BindingFlags.Public | BindingFlags.Static).MakeGenericMethod(target), - // BindToValue(arg)); - - // - return Expression.Block( - ThrowTypeError($"{arg.Type} -> {target}"), - BindDefault(target) + // Template: PhpValueConverter.Cast( PhpValue ) : T + return Expression.Call( + Cache.Operators.Cast_PhpValue_T.MakeGenericMethod(target), + BindToValue(arg) ); + + //return Expression.Block( + // ThrowTypeError($"{arg.Type} -> {target}"), + // BindDefault(target) + //); } private static bool IsNullConstant(Expression arg) @@ -687,13 +686,17 @@ static Expression BindAsObject(Expression expr) return Expression.Call(Cache.Operators.PhpValue_AsObjectOrThrow, BindToValue(expr)); } - static Expression BindAsReferenceType(Expression expr, Type target, Expression ctx) + /// + /// Specialized conversion to a referenced type. + /// + static bool TryBindAsReferenceType(Expression expr, Type target, Expression ctx, out Expression expression) { Debug.Assert(expr.Type != typeof(PhpAlias)); if (IsNullConstant(expr)) { - return Expression.Constant(null, typeof(object)); + expression = Expression.Constant(null, typeof(object)); + return true; } // to System.Delegate, @@ -702,9 +705,10 @@ static Expression BindAsReferenceType(Expression expr, Type target, Expression c { // Template: PhpCallableToDelegate.Get( BindAsCallable(expr), Context ) var callable = BindAsCallable(expr); - return Expression.Call( + expression = Expression.Call( typeof(PhpCallableToDelegate<>).MakeGenericType(target).GetMethod("Get"), callable, ctx); + return true; } //// from PhpValue: @@ -723,10 +727,9 @@ static Expression BindAsReferenceType(Expression expr, Type target, Expression c //// just cast: //return Expression.Convert(expr, target); - // Template: PhpValueConverter.Cast( PhpValue ) - return Expression.Call( - Cache.Operators.Cast_PhpValue_T.MakeGenericMethod(target), - BindToValue(expr)); + // + expression = null; + return false; } private static Expression BindAsArray(Expression expr, Type target) @@ -913,6 +916,12 @@ public static Expression BindCost(Expression arg, Type target) return Expression.Constant(ConversionCost.NoConversion); // if value can be converted, it would be handled above } + // to System.Delegate + if (target.IsSubclassOf(typeof(Delegate)) && t == typeof(Closure)) + { + return Expression.Constant(ConversionCost.ImplicitCast); + } + if (ReflectionUtils.IsPhpClassType(target)) { if (!t.IsInterface && !target.IsInterface && !target.IsAssignableFrom(t) && !t.IsAssignableFrom(target)) diff --git a/src/Peachpie.Runtime/PhpValue.cs b/src/Peachpie.Runtime/PhpValue.cs index 161a98d777..8f364408b6 100644 --- a/src/Peachpie.Runtime/PhpValue.cs +++ b/src/Peachpie.Runtime/PhpValue.cs @@ -308,7 +308,12 @@ public object ToClass() return Array.ToObject(); case PhpTypeCode.Object: - return (Object is IPhpConvertible conv) ? conv.ToClass() : Object; + return Object is IPhpConvertible conv + ? conv.ToClass() + //: Object is IStructBox structbox // CONSIDER: IStructBox is a regular PHP interface with conversions and methods, works well in PHP + it can have a IDynamicMetaObjectProvider + // ? structbox.BoxedValue // box the value type + : Object + ; case PhpTypeCode.Alias: return Alias.Value.ToClass(); diff --git a/src/Peachpie.Runtime/PhpValueConverter.cs b/src/Peachpie.Runtime/PhpValueConverter.cs index aba8923d05..c76abbe20c 100644 --- a/src/Peachpie.Runtime/PhpValueConverter.cs +++ b/src/Peachpie.Runtime/PhpValueConverter.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Reflection; using System.Text; +using System.Threading; namespace Pchp.Core { @@ -70,6 +71,8 @@ static Delegate Create_PhpValue_to_T() if (typeU == typeof(double)) return new Func>(x => Operators.IsSet(x) ? (double?)x.ToDouble() : null); } } + + if (typeof(T) == typeof(CancellationToken)) return new Func(x => Convert.UnwrapStruct(x)); } else // type.IsReferenceType {