baking default parameter values in static fields
instead of representing parameter defaults in serialized byte array, use a synthesized static readonly field that initialized the value once - updated parameter reflection - updated callsites - updated compiler to emit load of the field - removed unnecessary serialization routines from compiler + perf, mem, clean + fixes issues with default values + tests added
This commit is contained in:
Родитель
315541833e
Коммит
1e313cd5a5
|
@ -2475,17 +2475,23 @@ namespace Pchp.CodeAnalysis.CodeGen
|
|||
// emit targetp default value:
|
||||
ConstantValue cvalue;
|
||||
BoundExpression boundinitializer;
|
||||
FieldSymbol defaultvaluefield;
|
||||
|
||||
if ((cvalue = targetp.ExplicitDefaultConstantValue) != null)
|
||||
{
|
||||
ptype = EmitLoadConstant(cvalue.Value, targetp.Type);
|
||||
}
|
||||
else if ((defaultvaluefield = targetp.DefaultValueField) != null)
|
||||
{
|
||||
Debug.Assert(defaultvaluefield.IsStatic);
|
||||
ptype = defaultvaluefield.EmitLoad(this);
|
||||
}
|
||||
else if ((boundinitializer = (targetp as IPhpValue)?.Initializer) != null)
|
||||
{
|
||||
// DEPRECATED AND NOT USED ANYMORE:
|
||||
|
||||
var cg = this;
|
||||
|
||||
// this is basically all wrong:
|
||||
|
||||
if (targetp.OriginalDefinition is SourceParameterSymbol)
|
||||
{
|
||||
// emit using correct TypeRefContext:
|
||||
|
@ -2513,7 +2519,7 @@ namespace Pchp.CodeAnalysis.CodeGen
|
|||
}
|
||||
else if (targetp.IsParams)
|
||||
{
|
||||
// new T[0]
|
||||
// Template: System.Array.Empty<T>()
|
||||
Emit_EmptyArray(((ArrayTypeSymbol)targetp.Type).ElementType);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -99,7 +99,7 @@ namespace Pchp.CodeAnalysis.Symbols
|
|||
for (int i = 0; i <= srcparams.Length; i++) // how many to be copied from {srcparams}
|
||||
{
|
||||
var isfake = /*srcparams[i - 1].IsFake*/ implicitVarArgs != null && i > 0 && srcparams[i - 1].Ordinal >= implicitVarArgs.Ordinal; // parameter was replaced with [params]
|
||||
var hasdefault = i < srcparams.Length && srcparams[i].HasUnmappedDefaultValue; // ConstantValue couldn't be resolved for optional parameter
|
||||
var hasdefault = i < srcparams.Length && srcparams[i].HasUnmappedDefaultValue(); // ConstantValue couldn't be resolved for optional parameter
|
||||
|
||||
if (isfake || hasdefault)
|
||||
{
|
||||
|
@ -141,66 +141,116 @@ namespace Pchp.CodeAnalysis.Symbols
|
|||
return list ?? (IList<MethodSymbol>)Array.Empty<MethodSymbol>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emits initializers of all parameter's non-standard default values (such as PhpArray)
|
||||
/// within the type's static .cctor
|
||||
/// </summary>
|
||||
private void EmitParametersDefaultValue(PEModuleBuilder module)
|
||||
{
|
||||
foreach (var p in this.SourceParameters)
|
||||
{
|
||||
var field = p.DefaultValueField;
|
||||
if (field is SynthesizedFieldSymbol)
|
||||
{
|
||||
Debug.Assert(p.Initializer != null);
|
||||
|
||||
module.SynthesizedManager.AddField(field.ContainingType, p.DefaultValueField);
|
||||
|
||||
// .cctor() {
|
||||
|
||||
var cctor = module.GetStaticCtorBuilder(field.ContainingType);
|
||||
lock (cctor)
|
||||
{
|
||||
using (var cg = new CodeGenerator(cctor, module, DiagnosticBag.GetInstance(), module.Compilation.Options.OptimizationLevel, false, ContainingType, null, thisPlace: null)
|
||||
{
|
||||
CallerType = ContainingType,
|
||||
ContainingFile = ContainingFile,
|
||||
IsInCachedArrayExpression = true, // do not cache array initializers twice
|
||||
})
|
||||
{
|
||||
// {field} = {Initializer};
|
||||
var fldplace = new FieldPlace(null, field, module);
|
||||
fldplace.EmitStorePrepare(cg.Builder);
|
||||
cg.EmitConvert(p.Initializer, field.Type);
|
||||
fldplace.EmitStore(cg.Builder);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Generate(CodeGenerator cg)
|
||||
{
|
||||
if (!this.IsGeneratorMethod())
|
||||
if (this.IsGeneratorMethod())
|
||||
{
|
||||
//Proceed with normal method generation
|
||||
cg.GenerateScope(this.ControlFlowGraph.Start, int.MaxValue);
|
||||
// generate method as a state machine (SM) in a separate synthesized method
|
||||
// this routine returns instance of new SM:
|
||||
GenerateGeneratorMethod(cg);
|
||||
}
|
||||
else
|
||||
{
|
||||
var genSymbol = new SourceGeneratorSymbol(this);
|
||||
var il = cg.Builder;
|
||||
|
||||
/* Template:
|
||||
* return BuildGenerator( <ctx>, this, new PhpArray(){ p1, p2, ... }, new GeneratorStateMachineDelegate((IntPtr)<genSymbol>), (RuntimeMethodHandle)this )
|
||||
*/
|
||||
|
||||
cg.EmitLoadContext(); // ctx for generator
|
||||
cg.EmitThisOrNull(); // @this for generator
|
||||
|
||||
// new PhpArray for generator's locals
|
||||
cg.EmitCall(ILOpCode.Newobj, cg.CoreMethods.Ctors.PhpArray);
|
||||
|
||||
var generatorsLocals = cg.GetTemporaryLocal(cg.CoreTypes.PhpArray);
|
||||
cg.Builder.EmitLocalStore(generatorsLocals);
|
||||
|
||||
// initialize parameters (set their _isOptimized and copy them to locals array)
|
||||
InitializeParametersForGeneratorMethod(cg, il, generatorsLocals);
|
||||
cg.Builder.EmitLoad(generatorsLocals);
|
||||
cg.ReturnTemporaryLocal(generatorsLocals);
|
||||
|
||||
// new PhpArray for generator's synthesizedLocals
|
||||
cg.EmitCall(ILOpCode.Newobj, cg.CoreMethods.Ctors.PhpArray);
|
||||
|
||||
// new GeneratorStateMachineDelegate(<genSymbol>) delegate for generator
|
||||
cg.Builder.EmitNullConstant(); // null
|
||||
cg.EmitOpCode(ILOpCode.Ldftn); // method
|
||||
cg.EmitSymbolToken(genSymbol, null);
|
||||
cg.EmitCall(ILOpCode.Newobj, cg.CoreTypes.GeneratorStateMachineDelegate.Ctor(cg.CoreTypes.Object, cg.CoreTypes.IntPtr)); // GeneratorStateMachineDelegate(object @object, IntPtr method)
|
||||
|
||||
// handleof(this)
|
||||
cg.EmitLoadToken(this, null);
|
||||
|
||||
// create generator object via Operators factory method
|
||||
cg.EmitCall(ILOpCode.Call, cg.CoreMethods.Operators.BuildGenerator_Context_Object_PhpArray_PhpArray_GeneratorStateMachineDelegate_RuntimeMethodHandle);
|
||||
|
||||
// .UseDynamicScope( scope ) : Generator
|
||||
if (this is SourceLambdaSymbol lambda)
|
||||
{
|
||||
lambda.GetCallerTypePlace().EmitLoad(cg.Builder); // RuntimeTypeContext
|
||||
cg.EmitCall(ILOpCode.Call, cg.CoreTypes.Operators.Method("UseDynamicScope", cg.CoreTypes.Generator, cg.CoreTypes.RuntimeTypeHandle))
|
||||
.Expect(cg.CoreTypes.Generator);
|
||||
}
|
||||
|
||||
// Convert to return type (Generator or PhpValue, depends on analysis)
|
||||
cg.EmitConvert(cg.CoreTypes.Generator, 0, this.ReturnType);
|
||||
il.EmitRet(false);
|
||||
|
||||
// Generate SM method. Must be generated after EmitInit of parameters (it sets their _isUnoptimized field).
|
||||
CreateStateMachineNextMethod(cg, genSymbol);
|
||||
// normal method generation:
|
||||
cg.GenerateScope(this.ControlFlowGraph.Start, int.MaxValue);
|
||||
}
|
||||
|
||||
//
|
||||
EmitParametersDefaultValue(cg.Module);
|
||||
}
|
||||
|
||||
private void GenerateGeneratorMethod(CodeGenerator cg)
|
||||
{
|
||||
Debug.Assert(this.IsGeneratorMethod());
|
||||
|
||||
var genSymbol = new SourceGeneratorSymbol(this);
|
||||
var il = cg.Builder;
|
||||
|
||||
/* Template:
|
||||
* return BuildGenerator( <ctx>, this, new PhpArray(){ p1, p2, ... }, new GeneratorStateMachineDelegate((IntPtr)<genSymbol>), (RuntimeMethodHandle)this )
|
||||
*/
|
||||
|
||||
cg.EmitLoadContext(); // ctx for generator
|
||||
cg.EmitThisOrNull(); // @this for generator
|
||||
|
||||
// new PhpArray for generator's locals
|
||||
cg.EmitCall(ILOpCode.Newobj, cg.CoreMethods.Ctors.PhpArray);
|
||||
|
||||
var generatorsLocals = cg.GetTemporaryLocal(cg.CoreTypes.PhpArray);
|
||||
cg.Builder.EmitLocalStore(generatorsLocals);
|
||||
|
||||
// initialize parameters (set their _isOptimized and copy them to locals array)
|
||||
InitializeParametersForGeneratorMethod(cg, il, generatorsLocals);
|
||||
cg.Builder.EmitLoad(generatorsLocals);
|
||||
cg.ReturnTemporaryLocal(generatorsLocals);
|
||||
|
||||
// new PhpArray for generator's synthesizedLocals
|
||||
cg.EmitCall(ILOpCode.Newobj, cg.CoreMethods.Ctors.PhpArray);
|
||||
|
||||
// new GeneratorStateMachineDelegate(<genSymbol>) delegate for generator
|
||||
cg.Builder.EmitNullConstant(); // null
|
||||
cg.EmitOpCode(ILOpCode.Ldftn); // method
|
||||
cg.EmitSymbolToken(genSymbol, null);
|
||||
cg.EmitCall(ILOpCode.Newobj, cg.CoreTypes.GeneratorStateMachineDelegate.Ctor(cg.CoreTypes.Object, cg.CoreTypes.IntPtr)); // GeneratorStateMachineDelegate(object @object, IntPtr method)
|
||||
|
||||
// handleof(this)
|
||||
cg.EmitLoadToken(this, null);
|
||||
|
||||
// create generator object via Operators factory method
|
||||
cg.EmitCall(ILOpCode.Call, cg.CoreMethods.Operators.BuildGenerator_Context_Object_PhpArray_PhpArray_GeneratorStateMachineDelegate_RuntimeMethodHandle);
|
||||
|
||||
// .UseDynamicScope( scope ) : Generator
|
||||
if (this is SourceLambdaSymbol lambda)
|
||||
{
|
||||
lambda.GetCallerTypePlace().EmitLoad(cg.Builder); // RuntimeTypeContext
|
||||
cg.EmitCall(ILOpCode.Call, cg.CoreTypes.Operators.Method("UseDynamicScope", cg.CoreTypes.Generator, cg.CoreTypes.RuntimeTypeHandle))
|
||||
.Expect(cg.CoreTypes.Generator);
|
||||
}
|
||||
|
||||
// Convert to return type (Generator or PhpValue, depends on analysis)
|
||||
cg.EmitConvert(cg.CoreTypes.Generator, 0, this.ReturnType);
|
||||
il.EmitRet(false);
|
||||
|
||||
// Generate SM method. Must be generated after EmitInit of parameters (it sets their _isUnoptimized field).
|
||||
CreateStateMachineNextMethod(cg, genSymbol);
|
||||
}
|
||||
|
||||
private void InitializeParametersForGeneratorMethod(CodeGenerator cg, Microsoft.CodeAnalysis.CodeGen.ILBuilder il, Microsoft.CodeAnalysis.CodeGen.LocalDefinition generatorsLocals)
|
||||
|
|
|
@ -531,7 +531,7 @@ namespace Pchp.CodeAnalysis.Symbols
|
|||
var ps = m.Parameters;
|
||||
for (int i = 0; i < ps.Length; i++)
|
||||
{
|
||||
if (ps[i].HasUnmappedDefaultValue) // => ConstantValue couldn't be resolved for optional parameter
|
||||
if (ps[i].HasUnmappedDefaultValue()) // => ConstantValue couldn't be resolved for optional parameter
|
||||
{
|
||||
// create ghost stub foo(p0, .. pi-1) => foo(p0, .. , pN)
|
||||
GhostMethodBuilder.CreateGhostOverload(m, module, DiagnosticBag.GetInstance(), i);
|
||||
|
|
|
@ -126,7 +126,7 @@ namespace Pchp.CodeAnalysis.Emit
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a synthedized method to the class.
|
||||
/// Adds a synthesized method to the class.
|
||||
/// </summary>
|
||||
public void AddMethod(TypeSymbol container, MethodSymbol method)
|
||||
{
|
||||
|
@ -137,7 +137,7 @@ namespace Pchp.CodeAnalysis.Emit
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a synthedized property to the class.
|
||||
/// Adds a synthesized property to the class.
|
||||
/// </summary>
|
||||
public void AddProperty(TypeSymbol container, PropertySymbol property)
|
||||
{
|
||||
|
@ -146,6 +146,14 @@ namespace Pchp.CodeAnalysis.Emit
|
|||
AddMember(container, property);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a synthesized symbol to the class.
|
||||
/// </summary>
|
||||
public void AddField(TypeSymbol container, FieldSymbol field)
|
||||
{
|
||||
AddMember(container, field);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets synthezised members contained in <paramref name="container"/>.
|
||||
/// </summary>
|
||||
|
|
|
@ -444,8 +444,7 @@ namespace Pchp.CodeAnalysis.FlowAnalysis.Passes
|
|||
}
|
||||
else
|
||||
{
|
||||
bool isoptional = ps[i].IsOptional || (ps[i] is IPhpValue srcp && srcp.Initializer != null);
|
||||
if (!isoptional && (i < ps.Length - 1 /*check for IsParams only for last parameter*/ || !ps[i].IsParams))
|
||||
if (!ps[i].IsPhpOptionalParameter() && (i < ps.Length - 1 /*check for IsParams only for last parameter*/ || !ps[i].IsParams))
|
||||
{
|
||||
expectsmin = i - skippedps + 1;
|
||||
}
|
||||
|
|
|
@ -852,7 +852,7 @@ namespace Pchp.CodeAnalysis.Symbols
|
|||
PhpFieldsOnlyCtorAttribute = ct.PhpFieldsOnlyCtorAttribute.Ctor();
|
||||
PhpHiddenAttribute = ct.PhpHiddenAttribute.Ctor();
|
||||
NotNullAttribute = ct.NotNullAttribute.Ctor();
|
||||
DefaultValueAttribute = ct.DefaultValueAttribute.Ctor();
|
||||
DefaultValueAttribute_string = ct.DefaultValueAttribute.Ctor(ct.String);
|
||||
|
||||
ScriptDiedException = ct.ScriptDiedException.Ctor();
|
||||
ScriptDiedException_Long = ct.ScriptDiedException.Ctor(ct.Long);
|
||||
|
@ -867,7 +867,8 @@ namespace Pchp.CodeAnalysis.Symbols
|
|||
PhpString_Blob, PhpString_PhpString, PhpString_string_string, PhpString_PhpValue_Context,
|
||||
Blob,
|
||||
IntStringKey_int, IntStringKey_string,
|
||||
ScriptAttribute_string, PhpTraitAttribute, PharAttribute_string, PhpTypeAttribute_string_string, PhpFieldsOnlyCtorAttribute, PhpHiddenAttribute, NotNullAttribute, DefaultValueAttribute,
|
||||
ScriptAttribute_string, PhpTraitAttribute, PharAttribute_string, PhpTypeAttribute_string_string, PhpFieldsOnlyCtorAttribute, PhpHiddenAttribute, NotNullAttribute,
|
||||
DefaultValueAttribute_string,
|
||||
ScriptDiedException, ScriptDiedException_Long, ScriptDiedException_PhpValue,
|
||||
IndirectLocal_PhpArray_IntStringKey;
|
||||
}
|
||||
|
|
|
@ -140,7 +140,7 @@ namespace Pchp.CodeAnalysis.Symbols
|
|||
|
||||
public readonly CoreType
|
||||
Context, Operators, Convert, Comparison, StrictComparison, PhpException,
|
||||
ScriptAttribute, PhpTraitAttribute, PharAttribute, PhpTypeAttribute, PhpHiddenAttribute, PhpFieldsOnlyCtorAttribute, NotNullAttribute, DefaultValueAttribute, DefaultValueType, PhpMemberVisibilityAttribute, PhpStaticLocalAttribute,
|
||||
ScriptAttribute, PhpTraitAttribute, PharAttribute, PhpTypeAttribute, PhpHiddenAttribute, PhpFieldsOnlyCtorAttribute, NotNullAttribute, DefaultValueAttribute, PhpMemberVisibilityAttribute, PhpStaticLocalAttribute,
|
||||
ScriptDiedException,
|
||||
IStaticInit, RoutineInfo, IndirectLocal,
|
||||
BinderFactory, GetClassConstBinder, GetFieldBinder, SetFieldBinder, AccessMask,
|
||||
|
@ -199,7 +199,6 @@ namespace Pchp.CodeAnalysis.Symbols
|
|||
PhpFieldsOnlyCtorAttribute = Create(PhpFieldsOnlyCtorAttributeName);
|
||||
NotNullAttribute = Create("NotNullAttribute");
|
||||
DefaultValueAttribute = Create("DefaultValueAttribute");
|
||||
DefaultValueType = Create("DefaultValueAttribute+DefaultValueType");
|
||||
PhpMemberVisibilityAttribute = Create(PhpMemberVisibilityAttributeName);
|
||||
IStaticInit = Create("IStaticInit");
|
||||
RoutineInfo = Create("Reflection.RoutineInfo");
|
||||
|
|
|
@ -654,6 +654,25 @@ namespace Pchp.CodeAnalysis.Symbols
|
|||
}
|
||||
}
|
||||
|
||||
public override FieldSymbol DefaultValueField
|
||||
{
|
||||
get
|
||||
{
|
||||
var attr = DefaultValueAttribute;
|
||||
if (attr != null)
|
||||
{
|
||||
// [DefaultValueAttribute( FieldName ) { ExplicitType }]
|
||||
var fldname = (string)attr.ConstructorArguments[0].Value;
|
||||
var container = attr.NamedArguments.SingleOrDefault(pair => pair.Key == "ExplicitType").Value.Value as ITypeSymbol
|
||||
?? ContainingType;
|
||||
|
||||
return container.GetMembers(fldname).OfType<FieldSymbol>().Single();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public override ImmutableArray<Location> Locations
|
||||
{
|
||||
get
|
||||
|
|
|
@ -24,13 +24,23 @@ namespace Pchp.CodeAnalysis.Symbols
|
|||
{
|
||||
get
|
||||
{
|
||||
ConstantValue cvalue;
|
||||
return
|
||||
PhpOperationExtensions.FromDefaultValueAttribute(this.DefaultValueAttribute) ??
|
||||
((cvalue = ExplicitDefaultConstantValue) != null ? new BoundLiteral(cvalue.Value) : null);
|
||||
// MAYBE: if (DefaultValueField != null) BoundFieldRef.CreateStaticField(DefaultValueField)
|
||||
var cvalue = ExplicitDefaultConstantValue;
|
||||
return cvalue != null ? new BoundLiteral(cvalue.Value) : null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// In case there is a default value that cannot be represented by <see cref="ConstantValue"/>,
|
||||
/// this gets a static readonly field containing the value.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// In PHP it is possible to set parameter's default value which cannot be represented using <see cref="ConstantValue"/>.
|
||||
/// In such case, the value is set to this runtime field and read if needed.
|
||||
///
|
||||
/// If this field is set, <see cref="HasUnmappedDefaultValue"/> will be <c>true</c>.</remarks>
|
||||
public virtual FieldSymbol DefaultValueField => null;
|
||||
|
||||
public virtual bool IsOptional => false;
|
||||
|
||||
public virtual bool IsParams => false;
|
||||
|
@ -108,12 +118,6 @@ namespace Pchp.CodeAnalysis.Symbols
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets value indicating the parameter has default value that is not supported by CLR metadata.
|
||||
/// The default value will be then stored within [<see cref="DefaultValueAttribute"/>] as array of bytes (the original object serialized using PHP serialization).
|
||||
/// </summary>
|
||||
internal bool HasUnmappedDefaultValue => this is SourceParameterSymbol sp ? sp.Initializer != null && sp.ExplicitDefaultConstantValue == null : DefaultValueAttribute != null;
|
||||
|
||||
/// <summary>
|
||||
/// Returns data decoded from Obsolete attribute or null if there is no Obsolete attribute.
|
||||
/// This property returns ObsoleteAttributeData.Uninitialized if attribute arguments haven't been decoded yet.
|
||||
|
|
|
@ -37,6 +37,49 @@ namespace Pchp.CodeAnalysis.Symbols
|
|||
public override BoundExpression Initializer => _initializer;
|
||||
readonly BoundExpression _initializer;
|
||||
|
||||
public override FieldSymbol DefaultValueField
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_lazyDefaultValueField == null && Initializer != null && ExplicitDefaultConstantValue == null)
|
||||
{
|
||||
TypeSymbol fldtype; // type of the field
|
||||
|
||||
if (Initializer is BoundArrayEx arr)
|
||||
{
|
||||
// special case: empty array
|
||||
if (arr.Items.Length == 0)
|
||||
{
|
||||
// PhpArray.Empty
|
||||
return DeclaringCompilation.CoreMethods.PhpArray.Empty;
|
||||
}
|
||||
|
||||
//
|
||||
fldtype = DeclaringCompilation.CoreTypes.PhpArray;
|
||||
}
|
||||
else if (Initializer is BoundPseudoClassConst)
|
||||
{
|
||||
fldtype = DeclaringCompilation.GetSpecialType(SpecialType.System_String);
|
||||
}
|
||||
else
|
||||
{
|
||||
fldtype = DeclaringCompilation.CoreTypes.PhpValue;
|
||||
}
|
||||
|
||||
// internal static readonly PhpArray ..;
|
||||
var field = new SynthesizedFieldSymbol(
|
||||
ContainingType,
|
||||
fldtype, $"<{ContainingSymbol.Name}.{Name}>_DefaultValue",
|
||||
Accessibility.Internal, isStatic: true, isReadOnly: true);
|
||||
|
||||
//
|
||||
Interlocked.CompareExchange(ref _lazyDefaultValueField, field, null);
|
||||
}
|
||||
return _lazyDefaultValueField;
|
||||
}
|
||||
}
|
||||
FieldSymbol _lazyDefaultValueField;
|
||||
|
||||
public SourceParameterSymbol(SourceRoutineSymbol routine, FormalParam syntax, int relindex, PHPDocBlock.ParamTag ptagOpt)
|
||||
{
|
||||
Contract.ThrowIfNull(routine);
|
||||
|
@ -199,7 +242,7 @@ namespace Pchp.CodeAnalysis.Symbols
|
|||
public override bool IsParams => _syntax.IsVariadic;
|
||||
|
||||
public override int Ordinal => _relindex + _routine.ImplicitParameters.Length;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Zero-based index of the source parameter.
|
||||
/// </summary>
|
||||
|
@ -236,9 +279,9 @@ namespace Pchp.CodeAnalysis.Symbols
|
|||
}
|
||||
|
||||
// [DefaultValue]
|
||||
if (this.Initializer is BoundArrayEx arr)
|
||||
if (DefaultValueField != null)
|
||||
{
|
||||
yield return DeclaringCompilation.CreateDefaultValueAttribute(Routine, arr);
|
||||
yield return DeclaringCompilation.CreateDefaultValueAttribute(ContainingType, DefaultValueField);
|
||||
}
|
||||
|
||||
//
|
||||
|
|
|
@ -1431,15 +1431,19 @@ namespace Pchp.CodeAnalysis.Symbols
|
|||
yield return t.TraitInstanceField;
|
||||
}
|
||||
|
||||
foreach (var f in GetMembers().OfType<FieldSymbol>())
|
||||
foreach (var m in GetMembers())
|
||||
{
|
||||
if (f is SourceFieldSymbol srcf && srcf.IsRedefinition)
|
||||
if (m is FieldSymbol f)
|
||||
{
|
||||
// field redeclares its parent member, discard
|
||||
continue;
|
||||
}
|
||||
// declared fields
|
||||
if (m is SourceFieldSymbol srcf && srcf.IsRedefinition)
|
||||
{
|
||||
// field redeclares its parent member, discard
|
||||
continue;
|
||||
}
|
||||
|
||||
yield return f;
|
||||
yield return f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -182,6 +182,19 @@ namespace Pchp.CodeAnalysis.Symbols
|
|||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if parameter has a default value.
|
||||
/// </summary>
|
||||
public static bool IsPhpOptionalParameter(this ParameterSymbol p)
|
||||
{
|
||||
return p.ExplicitDefaultConstantValue != null || p.DefaultValueField != null || p.Initializer != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets value indicating the parameter has default value that is not supported by CLR metadata.
|
||||
/// </summary>
|
||||
public static bool HasUnmappedDefaultValue(this ParameterSymbol p) => p.DefaultValueField != null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets [PhpType] attribute and its parameters.
|
||||
/// </summary>
|
||||
|
|
|
@ -212,7 +212,7 @@ namespace Pchp.CodeAnalysis.Symbols
|
|||
var ps = phpconstruct.Parameters;
|
||||
for (int i = 0; i < ps.Length; i++)
|
||||
{
|
||||
if (ps[i].HasUnmappedDefaultValue)
|
||||
if (ps[i].HasUnmappedDefaultValue())
|
||||
{
|
||||
yield return new SynthesizedPhpCtorSymbol(type, phpconstruct.DeclaredAccessibility, fieldsinitctor, phpconstruct, i);
|
||||
}
|
||||
|
|
|
@ -27,8 +27,9 @@ namespace Pchp.CodeAnalysis.Symbols
|
|||
|
||||
private int _ordinal;
|
||||
|
||||
public override BoundExpression Initializer => _initializer;
|
||||
private readonly BoundExpression _initializer; // TODO: sanitize this
|
||||
public override BoundExpression Initializer => null;
|
||||
|
||||
public override FieldSymbol DefaultValueField { get; }
|
||||
|
||||
public SynthesizedParameterSymbol(
|
||||
MethodSymbol container,
|
||||
|
@ -40,7 +41,7 @@ namespace Pchp.CodeAnalysis.Symbols
|
|||
ImmutableArray<CustomModifier> customModifiers = default(ImmutableArray<CustomModifier>),
|
||||
ushort countOfCustomModifiersPrecedingByRef = 0,
|
||||
ConstantValue explicitDefaultConstantValue = null,
|
||||
BoundExpression initializer = null)
|
||||
FieldSymbol defaultValueField = null)
|
||||
{
|
||||
Debug.Assert((object)type != null);
|
||||
Debug.Assert(name != null);
|
||||
|
@ -55,16 +56,41 @@ namespace Pchp.CodeAnalysis.Symbols
|
|||
_customModifiers = customModifiers.NullToEmpty();
|
||||
_countOfCustomModifiersPrecedingByRef = countOfCustomModifiersPrecedingByRef;
|
||||
_explicitDefaultConstantValue = explicitDefaultConstantValue;
|
||||
_initializer = initializer;
|
||||
|
||||
this.DefaultValueField = defaultValueField;
|
||||
}
|
||||
|
||||
public static SynthesizedParameterSymbol Create(MethodSymbol container, ParameterSymbol p)
|
||||
{
|
||||
var defaultValueField = ((ParameterSymbol)p.OriginalDefinition).DefaultValueField;
|
||||
if (defaultValueField != null && defaultValueField.ContainingType.IsTraitType())
|
||||
{
|
||||
var selfcontainer = container.ContainingType;
|
||||
var fieldcontainer = defaultValueField.ContainingType;
|
||||
var newowner = fieldcontainer;
|
||||
|
||||
// this is trait ?
|
||||
if (selfcontainer.OriginalDefinition is SourceTraitTypeSymbol st)
|
||||
{
|
||||
newowner = fieldcontainer.ConstructedFrom.Construct(st.TSelfParameter);
|
||||
}
|
||||
else if (fieldcontainer.IsTraitType())
|
||||
{
|
||||
newowner = fieldcontainer.ConstructedFrom.Construct(selfcontainer);
|
||||
}
|
||||
|
||||
//
|
||||
if (newowner != fieldcontainer)
|
||||
{
|
||||
defaultValueField = defaultValueField.AsMember(newowner);
|
||||
}
|
||||
}
|
||||
|
||||
return new SynthesizedParameterSymbol(container, p.Type, p.Ordinal, p.RefKind,
|
||||
name: p.Name,
|
||||
isParams: p.IsParams,
|
||||
explicitDefaultConstantValue: p.ExplicitDefaultConstantValue,
|
||||
initializer: ((IPhpValue)p.OriginalDefinition).Initializer); // TODO: sanitize BoundExpression
|
||||
defaultValueField: defaultValueField);
|
||||
}
|
||||
|
||||
internal override TypeSymbol Type
|
||||
|
@ -113,9 +139,9 @@ namespace Pchp.CodeAnalysis.Symbols
|
|||
// TODO: preserve [NotNull]
|
||||
|
||||
// [DefaultValue]
|
||||
if (this.Initializer is BoundArrayEx arr)
|
||||
if (DefaultValueField != null)
|
||||
{
|
||||
yield return DeclaringCompilation.CreateDefaultValueAttribute(_container, arr);
|
||||
yield return DeclaringCompilation.CreateDefaultValueAttribute(ContainingType, DefaultValueField);
|
||||
}
|
||||
|
||||
//
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Devsense.PHP.Syntax;
|
||||
|
@ -50,32 +51,58 @@ namespace Pchp.CodeAnalysis.Symbols
|
|||
ImmutableArray<TypedConstant>.Empty, ImmutableArray<KeyValuePair<string, TypedConstant>>.Empty);
|
||||
}
|
||||
|
||||
public static AttributeData CreateDefaultValueAttribute(this PhpCompilation compilation, IMethodSymbol method, BoundArrayEx arr)
|
||||
//public static AttributeData CreateDefaultValueAttribute(this PhpCompilation compilation, IMethodSymbol method, BoundArrayEx arr)
|
||||
//{
|
||||
// var typeParameter = new KeyValuePair<string, TypedConstant>("Type", new TypedConstant(compilation.CoreTypes.DefaultValueType.Symbol, TypedConstantKind.Enum, 1/*PhpArray*/));
|
||||
// var namedparameters = ImmutableArray.Create(typeParameter);
|
||||
|
||||
// if (arr.Items.Length != 0)
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// var byteSymbol = compilation.GetSpecialType(SpecialType.System_Byte);
|
||||
// var serializedValue = Encoding.UTF8.GetBytes(arr.PhpSerializeOrThrow());
|
||||
// var p = new KeyValuePair<string, TypedConstant>(
|
||||
// "SerializedValue",
|
||||
// new TypedConstant(compilation.CreateArrayTypeSymbol(byteSymbol), serializedValue.Select(compilation.CreateTypedConstant).AsImmutable()));
|
||||
|
||||
// namedparameters = namedparameters.Add(p);
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// throw new InvalidOperationException($"Cannot construct serialized parameter default value. Routine '{method.Name}', {ex.Message}.", ex);
|
||||
// }
|
||||
// }
|
||||
|
||||
// return new SynthesizedAttributeData(
|
||||
// compilation.CoreMethods.Ctors.DefaultValueAttribute,
|
||||
// ImmutableArray<TypedConstant>.Empty, namedparameters);
|
||||
//}
|
||||
|
||||
public static AttributeData CreateDefaultValueAttribute(this PhpCompilation compilation, TypeSymbol containingType, FieldSymbol field)
|
||||
{
|
||||
var typeParameter = new KeyValuePair<string, TypedConstant>("Type", new TypedConstant(compilation.CoreTypes.DefaultValueType.Symbol, TypedConstantKind.Enum, 1/*PhpArray*/));
|
||||
var namedparameters = ImmutableArray.Create(typeParameter);
|
||||
var namedparameters = ImmutableArray<KeyValuePair<string, TypedConstant>>.Empty;
|
||||
|
||||
if (arr.Items.Length != 0)
|
||||
var fieldContainer = field.ContainingType;
|
||||
|
||||
if (fieldContainer != containingType)
|
||||
{
|
||||
try
|
||||
if (fieldContainer.IsTraitType())
|
||||
{
|
||||
var byteSymbol = compilation.GetSpecialType(SpecialType.System_Byte);
|
||||
var serializedValue = Encoding.UTF8.GetBytes(arr.PhpSerializeOrThrow());
|
||||
var p = new KeyValuePair<string, TypedConstant>(
|
||||
"SerializedValue",
|
||||
new TypedConstant(compilation.CreateArrayTypeSymbol(byteSymbol), serializedValue.Select(compilation.CreateTypedConstant).AsImmutable()));
|
||||
fieldContainer = fieldContainer.ConstructedFrom.Construct(
|
||||
(containingType as SourceTraitTypeSymbol)?.TSelfParameter ?? containingType);
|
||||
}
|
||||
|
||||
namedparameters = namedparameters.Add(p);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException($"Cannot construct serialized parameter default value. Routine '{method.Name}', {ex.Message}.", ex);
|
||||
}
|
||||
namedparameters = ImmutableArray.Create(new KeyValuePair<string, TypedConstant>(
|
||||
"ExplicitType",
|
||||
compilation.CreateTypedConstant(fieldContainer)));
|
||||
}
|
||||
|
||||
// [DefaultValueAttribute(name) { ExplicitType = ... }]
|
||||
return new SynthesizedAttributeData(
|
||||
compilation.CoreMethods.Ctors.DefaultValueAttribute,
|
||||
ImmutableArray<TypedConstant>.Empty, namedparameters);
|
||||
compilation.CoreMethods.Ctors.DefaultValueAttribute_string,
|
||||
ImmutableArray.Create(compilation.CreateTypedConstant(field.Name)),
|
||||
namedparameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,6 +56,8 @@ namespace Pchp.CodeAnalysis.Symbols
|
|||
|
||||
public override BoundExpression Initializer => underlyingParameter.Initializer;
|
||||
|
||||
public override FieldSymbol DefaultValueField => underlyingParameter.DefaultValueField;
|
||||
|
||||
internal override TypeSymbol Type => underlyingParameter.Type;
|
||||
|
||||
public sealed override RefKind RefKind => underlyingParameter.RefKind;
|
||||
|
|
|
@ -33,217 +33,5 @@ namespace Pchp.CodeAnalysis.Semantics
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static void PhpSerialize(StringBuilder result, object value)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
result.Append('N');
|
||||
}
|
||||
else if (value is string s)
|
||||
{
|
||||
result.Append('s');
|
||||
result.Append(':');
|
||||
result.Append(s.Length); // TODO: encode to byte[] first
|
||||
result.Append(':');
|
||||
result.Append('"');
|
||||
result.Append(s);
|
||||
result.Append('"');
|
||||
}
|
||||
else if (value is int i)
|
||||
{
|
||||
PhpSerialize(result, (long)i);
|
||||
return;
|
||||
}
|
||||
else if (value is long l)
|
||||
{
|
||||
result.Append('i');
|
||||
result.Append(':');
|
||||
result.Append(l);
|
||||
}
|
||||
else if (value is bool b)
|
||||
{
|
||||
result.Append('b');
|
||||
result.Append(':');
|
||||
result.Append(b ? '1' : '0');
|
||||
}
|
||||
else if (value is double d)
|
||||
{
|
||||
result.Append('d');
|
||||
result.Append(':');
|
||||
result.Append(d.ToString("R", NumberFormatInfo.InvariantInfo));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw ExceptionUtilities.UnexpectedValue(value);
|
||||
}
|
||||
|
||||
result.Append(';');
|
||||
}
|
||||
|
||||
static void PhpSerialize(StringBuilder result, BoundExpression expr)
|
||||
{
|
||||
if (expr is BoundArrayEx nestedArr)
|
||||
PhpSerialize(result, nestedArr);
|
||||
else if (expr.ConstantValue.HasValue)
|
||||
PhpSerialize(result, expr.ConstantValue.Value);
|
||||
else if (expr is BoundCopyValue copy)
|
||||
PhpSerialize(result, copy.Expression);
|
||||
else
|
||||
throw ExceptionUtilities.UnexpectedValue(expr);
|
||||
}
|
||||
|
||||
static void PhpSerialize(StringBuilder result, BoundArrayEx array)
|
||||
{
|
||||
int idx = 0;
|
||||
|
||||
result.Append('a');
|
||||
result.Append(':');
|
||||
result.Append(array.Items.Length);
|
||||
result.Append(':');
|
||||
result.Append('{');
|
||||
foreach (var item in array.Items)
|
||||
{
|
||||
// key
|
||||
PhpSerialize(result, item.Key != null ? item.Key.ConstantValue.Value : (idx++));
|
||||
|
||||
// value
|
||||
PhpSerialize(result, item.Value);
|
||||
}
|
||||
result.Append('}');
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize array using the PHP serialization format.
|
||||
/// </summary>
|
||||
public static string PhpSerializeOrThrow(this BoundArrayEx array)
|
||||
{
|
||||
var result = new StringBuilder(16);
|
||||
PhpSerialize(result, array);
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simple unserialize PHP object. Supports only arrays and literals.
|
||||
/// </summary>
|
||||
static BoundExpression PhpUnserializeOrThrow(ImmutableArray<byte> bytes)
|
||||
{
|
||||
var arr = new byte[bytes.Length];
|
||||
bytes.CopyTo(arr);
|
||||
|
||||
int start = 0;
|
||||
var obj = PhpUnserializeOrThrow(arr, ref start);
|
||||
Debug.Assert(start == arr.Length);
|
||||
return obj;
|
||||
}
|
||||
|
||||
static BoundExpression PhpUnserializeOrThrow(byte[] bytes, ref int start)
|
||||
{
|
||||
if (bytes == null) throw ExceptionUtilities.ArgumentNull(nameof(bytes));
|
||||
if (bytes.Length == 0) throw ExceptionUtilities.UnexpectedValue(bytes);
|
||||
if (start >= bytes.Length) throw new ArgumentOutOfRangeException();
|
||||
|
||||
char t = (char)bytes[start++];
|
||||
|
||||
switch (t)
|
||||
{
|
||||
case 'a': // array
|
||||
if (bytes[start++] != (byte)':') throw null;
|
||||
var from = start;
|
||||
while (bytes[start++] != (byte)':') { }
|
||||
var length = int.Parse(Encoding.UTF8.GetString(bytes, from, start - from - 1));
|
||||
if (bytes[start++] != (byte)'{') throw null;
|
||||
var items = new List<KeyValuePair<BoundExpression, BoundExpression>>(length);
|
||||
while (length-- > 0)
|
||||
{
|
||||
items.Add(new KeyValuePair<BoundExpression, BoundExpression>(
|
||||
PhpUnserializeOrThrow(bytes, ref start),
|
||||
PhpUnserializeOrThrow(bytes, ref start)));
|
||||
}
|
||||
if (bytes[start++] != (byte)'}') throw null;
|
||||
return new BoundArrayEx(items).WithAccess(BoundAccess.Read);
|
||||
|
||||
case 'N': // NULL
|
||||
if (bytes[start++] != (byte)';') throw null;
|
||||
return new BoundLiteral(null).WithAccess(BoundAccess.Read);
|
||||
|
||||
case 'b': // bool
|
||||
if (bytes[start++] != (byte)':') throw null;
|
||||
var b = bytes[start++];
|
||||
if (bytes[start++] != (byte)';') throw null;
|
||||
return new BoundLiteral((b != 0).AsObject()).WithAccess(BoundAccess.Read);
|
||||
|
||||
case 'i': // int
|
||||
case 'd': // double
|
||||
if (bytes[start++] != (byte)':') throw null;
|
||||
from = start;
|
||||
while (bytes[start++] != (byte)';') { }
|
||||
var value = Encoding.UTF8.GetString(bytes, from, start - from - 1);
|
||||
var literal = (t == 'i') ? long.Parse(value) : double.Parse(value, CultureInfo.InvariantCulture);
|
||||
return new BoundLiteral(literal).WithAccess(BoundAccess.Read);
|
||||
|
||||
case 's': // string
|
||||
if (bytes[start++] != (byte)':') throw null;
|
||||
from = start;
|
||||
while (bytes[start++] != (byte)':') { }
|
||||
length = int.Parse(Encoding.UTF8.GetString(bytes, from, start - from - 1));
|
||||
if (bytes[start++] != (byte)'"') throw null;
|
||||
from = start; // from points after the first "
|
||||
start += length;
|
||||
while (bytes[start++] != (byte)'"') { } // start points after the closing " // even we know the length, just look for the " to be sure (UTF8 etc..)
|
||||
value = Encoding.UTF8.GetString(bytes, from, start - from - 1);
|
||||
if (bytes[start++] != (byte)';') throw null;
|
||||
return new BoundLiteral(value).WithAccess(BoundAccess.Read);
|
||||
|
||||
default:
|
||||
throw ExceptionUtilities.UnexpectedValue(t);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decodes [DefaultValueAttribute].
|
||||
/// </summary>
|
||||
public static BoundExpression FromDefaultValueAttribute(AttributeData defaultValueAttribute)
|
||||
{
|
||||
if (defaultValueAttribute == null || defaultValueAttribute.NamedArguments.IsDefaultOrEmpty)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
//[DefaultValue(Type = PhpArray, SerializedValue = new byte[]{ ... })]
|
||||
var p = defaultValueAttribute.NamedArguments;
|
||||
|
||||
// SerializedValue?
|
||||
for (int i = 0; i < p.Length; i++)
|
||||
{
|
||||
if (p[i].Key == "SerializedValue" && !p[i].Value.IsNull)
|
||||
{
|
||||
var data = p[i].Value.Values.SelectAsArray(c => (byte)c.Value);
|
||||
if (data.Length != 0)
|
||||
{
|
||||
// unserialize {bytes}
|
||||
return PhpUnserializeOrThrow(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Type?
|
||||
for (int i = 0; i < p.Length; i++)
|
||||
{
|
||||
if (p[i].Key == "Type" && p[i].Value.Value is int itype)
|
||||
{
|
||||
switch (itype)
|
||||
{
|
||||
case 1: // PhpArray
|
||||
return new BoundArrayEx(Array.Empty<KeyValuePair<BoundExpression, BoundExpression>>()).WithAccess(BoundAccess.Read);
|
||||
default:
|
||||
throw new ArgumentException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
throw new ArgumentException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -174,7 +174,7 @@ namespace Pchp.Library.Reflection
|
|||
|
||||
public ReflectionFunctionAbstract getDeclaringFunction() => _function;
|
||||
|
||||
public PhpValue getDefaultValue() => _defaultValue.IsDefault ? throw new ReflectionException() : _defaultValue;
|
||||
public PhpValue getDefaultValue() => _defaultValue.IsDefault ? throw new ReflectionException() : _defaultValue.DeepCopy();
|
||||
|
||||
public string getDefaultValueConstantName() => null; // we don't know
|
||||
|
||||
|
|
|
@ -48,26 +48,39 @@ namespace Pchp.Library.Reflection
|
|||
/// TODO: move to Peachpie.Runtime
|
||||
/// Creates PhpValue from this attribute.
|
||||
/// </summary>
|
||||
public static PhpValue ResolveDefaultValueAttribute(this DefaultValueAttribute attr)
|
||||
public static PhpValue ResolveDefaultValueAttribute(this DefaultValueAttribute attr, Type containingType)
|
||||
{
|
||||
if (attr.SerializedValue != null && attr.SerializedValue.Length != 0)
|
||||
if (attr == null)
|
||||
{
|
||||
using (var stream = new MemoryStream(attr.SerializedValue))
|
||||
{
|
||||
var reader = new PhpSerialization.PhpSerializer.ObjectReader(null, Encoding.UTF8, stream, default);
|
||||
return reader.Deserialize();
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
// special case, empty array:
|
||||
if (attr.FieldName == "Empty" && attr.ExplicitType == typeof(PhpArray))
|
||||
{
|
||||
// NOTE: make sure the value will be copied when accessed!
|
||||
return PhpArray.Empty;
|
||||
}
|
||||
|
||||
// resolve declaring type (bind trait definitions)
|
||||
if (Core.Reflection.ReflectionUtils.IsTraitType(containingType) && !containingType.IsConstructedGenericType)
|
||||
{
|
||||
// construct something! T<object>
|
||||
// NOTE: "self::class" will refer to "System.Object"
|
||||
containingType = containingType.MakeGenericType(typeof(object));
|
||||
}
|
||||
|
||||
//
|
||||
var field = (attr.ExplicitType ?? containingType).GetField(attr.FieldName, BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.GetField);
|
||||
if (field != null)
|
||||
{
|
||||
Debug.Assert(field.IsStatic);
|
||||
return PhpValue.FromClr(field.GetValue(null));
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (attr.Type)
|
||||
{
|
||||
case DefaultValueAttribute.DefaultValueType.PhpArray:
|
||||
return PhpArray.NewEmpty();
|
||||
default:
|
||||
Debug.Fail("Unexpected data.");
|
||||
return default;
|
||||
}
|
||||
Debug.Fail($"Backing field {attr.FieldName} for parameter default value not found.");
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -97,7 +110,7 @@ namespace Pchp.Library.Reflection
|
|||
}
|
||||
else if ((defaultValueAttr = p.GetCustomAttribute<DefaultValueAttribute>()) != null)
|
||||
{
|
||||
defaultValue = ResolveDefaultValueAttribute(defaultValueAttr);
|
||||
defaultValue = defaultValueAttr.ResolveDefaultValueAttribute(overloads[mi].DeclaringType);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -294,23 +294,20 @@ namespace Pchp.Core
|
|||
public sealed class DefaultValueAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Denotates the type of the value.
|
||||
/// The type containing the backing field.
|
||||
/// <c>Null</c> indicates the containing type.
|
||||
/// </summary>
|
||||
public enum DefaultValueType
|
||||
public Type ExplicitType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Name of the backing field.
|
||||
/// </summary>
|
||||
public string FieldName { get; private set; }
|
||||
|
||||
public DefaultValueAttribute(string fieldName)
|
||||
{
|
||||
None = 0,
|
||||
PhpArray = 1,
|
||||
FieldName = fieldName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The type of the default value.
|
||||
/// </summary>
|
||||
public DefaultValueType Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional serialized data using PHP serialization.
|
||||
/// </summary>
|
||||
public byte[] SerializedValue { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -401,29 +401,20 @@ namespace Pchp.Core.Dynamic
|
|||
/// <summary>
|
||||
/// Creates expression representing value from [DefaultValueAttribute]
|
||||
/// </summary>
|
||||
protected Expression BindDefaultValue(DefaultValueAttribute/*!*/attr)
|
||||
protected Expression BindDefaultValue(Type containingType, DefaultValueAttribute/*!*/attr)
|
||||
{
|
||||
Debug.Assert(attr != null);
|
||||
Debug.Assert(attr.Type == DefaultValueAttribute.DefaultValueType.PhpArray); // supported values
|
||||
|
||||
if (attr.SerializedValue != null && attr.SerializedValue.Length != 0)
|
||||
{
|
||||
// _ctx.Call(string "unserialize", PhpValue[] { SerializedValue });
|
||||
return Expression.Call(_ctx, "Call", Array.Empty<Type>(),
|
||||
Expression.Constant("unserialize"),
|
||||
Expression.NewArrayInit(Cache.Types.PhpValue, Expression.Constant(PhpValue.Create(attr.SerializedValue)))
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (attr.Type)
|
||||
{
|
||||
case DefaultValueAttribute.DefaultValueType.PhpArray:
|
||||
return Expression.Constant(PhpArray.Empty); // will be deep-copied if needed
|
||||
default:
|
||||
throw new ArgumentException();
|
||||
}
|
||||
}
|
||||
//if (ReflectionUtils.IsTraitType(containingType) && !containingType.IsConstructedGenericType)
|
||||
//{
|
||||
// // UNREACHABLE
|
||||
|
||||
// // construct something! T<object>
|
||||
// // NOTE: "self::class" will refer to "System.Object"
|
||||
// containingType = containingType.MakeGenericType(typeof(object));
|
||||
//}
|
||||
|
||||
return Expression.Field(null, attr.ExplicitType ?? containingType, attr.FieldName);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
@ -517,7 +508,7 @@ namespace Pchp.Core.Dynamic
|
|||
defaultValueAttr = targetparam.GetCustomAttribute<DefaultValueAttribute>();
|
||||
if (defaultValueAttr != null)
|
||||
{
|
||||
return ConvertExpression.Bind(BindDefaultValue(defaultValueAttr), targetparam.ParameterType, _ctx);
|
||||
return ConvertExpression.Bind(BindDefaultValue(targetparam.Member.DeclaringType, defaultValueAttr), targetparam.ParameterType, _ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -650,7 +641,7 @@ namespace Pchp.Core.Dynamic
|
|||
var defaultValueAttr = targetparam.GetCustomAttribute<DefaultValueAttribute>();
|
||||
if (defaultValueAttr != null)
|
||||
{
|
||||
return ConvertExpression.Bind(BindDefaultValue(defaultValueAttr), targetparam.ParameterType, _ctx);
|
||||
return ConvertExpression.Bind(BindDefaultValue(targetparam.Member.DeclaringType, defaultValueAttr), targetparam.ParameterType, _ctx);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?php
|
||||
namespace functions\param_default;
|
||||
namespace functions\param_default_001;
|
||||
|
||||
class C {
|
||||
const C = 1;
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
namespace functions\param_default_002;
|
||||
|
||||
trait T
|
||||
{
|
||||
public function foo($a = ['*'], $b = self::class ) {
|
||||
print_r($a);
|
||||
print_r($b);
|
||||
echo PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
class X
|
||||
{
|
||||
use T;
|
||||
|
||||
function bar($a = ['*'], $b = []) {
|
||||
print_r($a);
|
||||
print_r($b);
|
||||
}
|
||||
}
|
||||
|
||||
// test 1
|
||||
$x = new X;
|
||||
$x->foo();
|
||||
$x->bar();
|
||||
|
||||
// test 2
|
||||
(new X)->foo();
|
||||
|
||||
// test 3
|
||||
(new X)->bar();
|
||||
|
||||
// test 4
|
||||
foreach ((new \ReflectionMethod(X::class , 'foo'))->getParameters() as $p) {
|
||||
print_r($p->getDefaultValue());
|
||||
}
|
||||
|
||||
//
|
||||
echo "Done.";
|
Загрузка…
Ссылка в новой задаче