Skip to content

Commit

Permalink
run time:
Browse files Browse the repository at this point in the history
- unboxing value types
- binding to CancellationToken
- overload resolution with Closure->System.Delegate
  • Loading branch information
jakubmisek committed May 18, 2024
1 parent 5d161f4 commit eaf5824
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 24 deletions.
34 changes: 32 additions & 2 deletions src/Peachpie.Runtime/Conversions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -1376,6 +1376,36 @@ public static DateTime ToDateTime(this PhpValue value)
}

#endregion

#region UnwrapStruct

/// <summary>
/// Given <typeparamref name="T"/> is a CLR value type (not a scalar PHP type!),
/// this method tries to unwrap <paramref name="value"/> to <typeparamref name="T"/>.
/// No conversion is performed. The method throws an exception is the value type does not match.
/// If <paramref name="value"/> is <c>NULL</c>, the method returns default of <typeparamref name="T"/>.
/// </summary>
public static T UnwrapStruct<T>(PhpValue value) where T : struct
{
switch (value.TypeCode)
{
case PhpTypeCode.Null: return default(T);
case PhpTypeCode.Alias: return UnwrapStruct<T>(value.Alias.Value);
case PhpTypeCode.Object:
var obj = value.Object;

if (obj is StructBox<T> 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
Expand Down Expand Up @@ -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(),
Expand Down
51 changes: 30 additions & 21 deletions src/Peachpie.Runtime/Dynamic/ConvertExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -126,9 +124,10 @@ public static Expression Bind(Expression arg, Type target, Expression ctx)
//
if (target.IsValueType == false)
{
Debug.Assert(typeof(Nullable<bool>).IsValueType);

return BindAsReferenceType(arg, target, ctx);
if (TryBindAsReferenceType(arg, target, ctx, out var expression))
{
return expression;
}
}
else
{
Expand Down Expand Up @@ -160,16 +159,16 @@ public static Expression Bind(Expression arg, Type target, Expression ctx)
}
}

//// PhpValueConverter.Cast<T>( (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<T>( 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)
Expand Down Expand Up @@ -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)
/// <summary>
/// Specialized conversion to a referenced type.
/// </summary>
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,
Expand All @@ -702,9 +705,10 @@ static Expression BindAsReferenceType(Expression expr, Type target, Expression c
{
// Template: PhpCallableToDelegate<target>.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:
Expand All @@ -723,10 +727,9 @@ static Expression BindAsReferenceType(Expression expr, Type target, Expression c
//// just cast:
//return Expression.Convert(expr, target);

// Template: PhpValueConverter.Cast<T>( 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)
Expand Down Expand Up @@ -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))
Expand Down
7 changes: 6 additions & 1 deletion src/Peachpie.Runtime/PhpValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
3 changes: 3 additions & 0 deletions src/Peachpie.Runtime/PhpValueConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Diagnostics;
using System.Reflection;
using System.Text;
using System.Threading;

namespace Pchp.Core
{
Expand Down Expand Up @@ -70,6 +71,8 @@ static Delegate Create_PhpValue_to_T()
if (typeU == typeof(double)) return new Func<PhpValue, Nullable<double>>(x => Operators.IsSet(x) ? (double?)x.ToDouble() : null);
}
}

if (typeof(T) == typeof(CancellationToken)) return new Func<PhpValue, CancellationToken>(x => Convert.UnwrapStruct<CancellationToken>(x));
}
else // type.IsReferenceType
{
Expand Down

0 comments on commit eaf5824

Please sign in to comment.