From 1e313cd5a5236abefcbc2ec866d8630a3ba211f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20M=C3=AD=C5=A1ek?= Date: Fri, 25 Oct 2019 16:34:10 +0200 Subject: [PATCH] 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 --- .../CodeGen/CodeGenerator.Emit.cs | 12 +- .../CodeGen/Symbols/SourceRoutineSymbol.cs | 158 ++++++++----- .../CodeGen/Symbols/SourceTypeSymbol.cs | 2 +- .../Compilation/SynthesizedManager.cs | 12 +- .../FlowAnalysis/Passes/DiagnosticWalker.cs | 3 +- .../Symbols/CoreMembers.cs | 5 +- src/Peachpie.CodeAnalysis/Symbols/CoreType.cs | 3 +- .../Symbols/PE/PEParameterSymbol.cs | 19 ++ .../Symbols/ParameterSymbol.cs | 24 +- .../Symbols/Source/SourceParameterSymbol.cs | 49 +++- .../Symbols/Source/SourceTypeSymbol.cs | 16 +- .../Symbols/SymbolExtensions.cs | 13 ++ .../Synthesized/SynthesizedCtorSymbol.cs | 2 +- .../Synthesized/SynthesizedParameterSymbol.cs | 40 +++- .../Symbols/WellKnownMembersHelper.cs | 63 ++++-- .../Symbols/WrappedParameterSymbol.cs | 2 + .../Utilities/PhpOperationExtensions.cs | 212 ------------------ .../Reflection/ReflectionParameter.cs | 2 +- .../Reflection/ReflectionUtils.cs | 45 ++-- src/Peachpie.Runtime/Attributes.cs | 25 +-- .../Dynamic/OverloadBinder.cs | 35 ++- ...aram_default.php => param_default_001.php} | 2 +- tests/functions/param_default_002.php | 40 ++++ 23 files changed, 407 insertions(+), 377 deletions(-) rename tests/functions/{param_default.php => param_default_001.php} (88%) create mode 100644 tests/functions/param_default_002.php diff --git a/src/Peachpie.CodeAnalysis/CodeGen/CodeGenerator.Emit.cs b/src/Peachpie.CodeAnalysis/CodeGen/CodeGenerator.Emit.cs index b74dfe89d..74a0403d9 100644 --- a/src/Peachpie.CodeAnalysis/CodeGen/CodeGenerator.Emit.cs +++ b/src/Peachpie.CodeAnalysis/CodeGen/CodeGenerator.Emit.cs @@ -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() Emit_EmptyArray(((ArrayTypeSymbol)targetp.Type).ElementType); return; } diff --git a/src/Peachpie.CodeAnalysis/CodeGen/Symbols/SourceRoutineSymbol.cs b/src/Peachpie.CodeAnalysis/CodeGen/Symbols/SourceRoutineSymbol.cs index c27b93378..dbf189a11 100644 --- a/src/Peachpie.CodeAnalysis/CodeGen/Symbols/SourceRoutineSymbol.cs +++ b/src/Peachpie.CodeAnalysis/CodeGen/Symbols/SourceRoutineSymbol.cs @@ -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)Array.Empty(); } + /// + /// Emits initializers of all parameter's non-standard default values (such as PhpArray) + /// within the type's static .cctor + /// + 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( , this, new PhpArray(){ p1, p2, ... }, new GeneratorStateMachineDelegate((IntPtr)), (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() 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( , this, new PhpArray(){ p1, p2, ... }, new GeneratorStateMachineDelegate((IntPtr)), (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() 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) diff --git a/src/Peachpie.CodeAnalysis/CodeGen/Symbols/SourceTypeSymbol.cs b/src/Peachpie.CodeAnalysis/CodeGen/Symbols/SourceTypeSymbol.cs index f67b702fc..abfbea322 100644 --- a/src/Peachpie.CodeAnalysis/CodeGen/Symbols/SourceTypeSymbol.cs +++ b/src/Peachpie.CodeAnalysis/CodeGen/Symbols/SourceTypeSymbol.cs @@ -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); diff --git a/src/Peachpie.CodeAnalysis/Compilation/SynthesizedManager.cs b/src/Peachpie.CodeAnalysis/Compilation/SynthesizedManager.cs index 47e367506..c519ac2e2 100644 --- a/src/Peachpie.CodeAnalysis/Compilation/SynthesizedManager.cs +++ b/src/Peachpie.CodeAnalysis/Compilation/SynthesizedManager.cs @@ -126,7 +126,7 @@ namespace Pchp.CodeAnalysis.Emit } /// - /// Adds a synthedized method to the class. + /// Adds a synthesized method to the class. /// public void AddMethod(TypeSymbol container, MethodSymbol method) { @@ -137,7 +137,7 @@ namespace Pchp.CodeAnalysis.Emit } /// - /// Adds a synthedized property to the class. + /// Adds a synthesized property to the class. /// public void AddProperty(TypeSymbol container, PropertySymbol property) { @@ -146,6 +146,14 @@ namespace Pchp.CodeAnalysis.Emit AddMember(container, property); } + /// + /// Adds a synthesized symbol to the class. + /// + public void AddField(TypeSymbol container, FieldSymbol field) + { + AddMember(container, field); + } + /// /// Gets synthezised members contained in . /// diff --git a/src/Peachpie.CodeAnalysis/FlowAnalysis/Passes/DiagnosticWalker.cs b/src/Peachpie.CodeAnalysis/FlowAnalysis/Passes/DiagnosticWalker.cs index 2b13d707d..7c8a04ba5 100644 --- a/src/Peachpie.CodeAnalysis/FlowAnalysis/Passes/DiagnosticWalker.cs +++ b/src/Peachpie.CodeAnalysis/FlowAnalysis/Passes/DiagnosticWalker.cs @@ -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; } diff --git a/src/Peachpie.CodeAnalysis/Symbols/CoreMembers.cs b/src/Peachpie.CodeAnalysis/Symbols/CoreMembers.cs index db82cac76..2ae0a4280 100644 --- a/src/Peachpie.CodeAnalysis/Symbols/CoreMembers.cs +++ b/src/Peachpie.CodeAnalysis/Symbols/CoreMembers.cs @@ -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; } diff --git a/src/Peachpie.CodeAnalysis/Symbols/CoreType.cs b/src/Peachpie.CodeAnalysis/Symbols/CoreType.cs index 6ab6c41a4..4949e6730 100644 --- a/src/Peachpie.CodeAnalysis/Symbols/CoreType.cs +++ b/src/Peachpie.CodeAnalysis/Symbols/CoreType.cs @@ -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"); diff --git a/src/Peachpie.CodeAnalysis/Symbols/PE/PEParameterSymbol.cs b/src/Peachpie.CodeAnalysis/Symbols/PE/PEParameterSymbol.cs index 62d691608..df3c4dba7 100644 --- a/src/Peachpie.CodeAnalysis/Symbols/PE/PEParameterSymbol.cs +++ b/src/Peachpie.CodeAnalysis/Symbols/PE/PEParameterSymbol.cs @@ -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().Single(); + } + + return null; + } + } + public override ImmutableArray Locations { get diff --git a/src/Peachpie.CodeAnalysis/Symbols/ParameterSymbol.cs b/src/Peachpie.CodeAnalysis/Symbols/ParameterSymbol.cs index af5ff6175..85234d1cd 100644 --- a/src/Peachpie.CodeAnalysis/Symbols/ParameterSymbol.cs +++ b/src/Peachpie.CodeAnalysis/Symbols/ParameterSymbol.cs @@ -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; } } + /// + /// In case there is a default value that cannot be represented by , + /// this gets a static readonly field containing the value. + /// + /// + /// In PHP it is possible to set parameter's default value which cannot be represented using . + /// In such case, the value is set to this runtime field and read if needed. + /// + /// If this field is set, will be true. + public virtual FieldSymbol DefaultValueField => null; + public virtual bool IsOptional => false; public virtual bool IsParams => false; @@ -108,12 +118,6 @@ namespace Pchp.CodeAnalysis.Symbols } } - /// - /// Gets value indicating the parameter has default value that is not supported by CLR metadata. - /// The default value will be then stored within [] as array of bytes (the original object serialized using PHP serialization). - /// - internal bool HasUnmappedDefaultValue => this is SourceParameterSymbol sp ? sp.Initializer != null && sp.ExplicitDefaultConstantValue == null : DefaultValueAttribute != null; - /// /// 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. diff --git a/src/Peachpie.CodeAnalysis/Symbols/Source/SourceParameterSymbol.cs b/src/Peachpie.CodeAnalysis/Symbols/Source/SourceParameterSymbol.cs index a4e2c43fb..ff172857c 100644 --- a/src/Peachpie.CodeAnalysis/Symbols/Source/SourceParameterSymbol.cs +++ b/src/Peachpie.CodeAnalysis/Symbols/Source/SourceParameterSymbol.cs @@ -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; - + /// /// Zero-based index of the source parameter. /// @@ -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); } // diff --git a/src/Peachpie.CodeAnalysis/Symbols/Source/SourceTypeSymbol.cs b/src/Peachpie.CodeAnalysis/Symbols/Source/SourceTypeSymbol.cs index 7d2a0573d..48522a8f6 100644 --- a/src/Peachpie.CodeAnalysis/Symbols/Source/SourceTypeSymbol.cs +++ b/src/Peachpie.CodeAnalysis/Symbols/Source/SourceTypeSymbol.cs @@ -1431,15 +1431,19 @@ namespace Pchp.CodeAnalysis.Symbols yield return t.TraitInstanceField; } - foreach (var f in GetMembers().OfType()) + 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; + } } } diff --git a/src/Peachpie.CodeAnalysis/Symbols/SymbolExtensions.cs b/src/Peachpie.CodeAnalysis/Symbols/SymbolExtensions.cs index aeaccef39..f17882b42 100644 --- a/src/Peachpie.CodeAnalysis/Symbols/SymbolExtensions.cs +++ b/src/Peachpie.CodeAnalysis/Symbols/SymbolExtensions.cs @@ -182,6 +182,19 @@ namespace Pchp.CodeAnalysis.Symbols return true; } + /// + /// Determines if parameter has a default value. + /// + public static bool IsPhpOptionalParameter(this ParameterSymbol p) + { + return p.ExplicitDefaultConstantValue != null || p.DefaultValueField != null || p.Initializer != null; + } + + /// + /// Gets value indicating the parameter has default value that is not supported by CLR metadata. + /// + public static bool HasUnmappedDefaultValue(this ParameterSymbol p) => p.DefaultValueField != null; + /// /// Gets [PhpType] attribute and its parameters. /// diff --git a/src/Peachpie.CodeAnalysis/Symbols/Synthesized/SynthesizedCtorSymbol.cs b/src/Peachpie.CodeAnalysis/Symbols/Synthesized/SynthesizedCtorSymbol.cs index 13f1ac662..5917870f3 100644 --- a/src/Peachpie.CodeAnalysis/Symbols/Synthesized/SynthesizedCtorSymbol.cs +++ b/src/Peachpie.CodeAnalysis/Symbols/Synthesized/SynthesizedCtorSymbol.cs @@ -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); } diff --git a/src/Peachpie.CodeAnalysis/Symbols/Synthesized/SynthesizedParameterSymbol.cs b/src/Peachpie.CodeAnalysis/Symbols/Synthesized/SynthesizedParameterSymbol.cs index 0b710aba3..d56c5031b 100644 --- a/src/Peachpie.CodeAnalysis/Symbols/Synthesized/SynthesizedParameterSymbol.cs +++ b/src/Peachpie.CodeAnalysis/Symbols/Synthesized/SynthesizedParameterSymbol.cs @@ -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 customModifiers = default(ImmutableArray), 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); } // diff --git a/src/Peachpie.CodeAnalysis/Symbols/WellKnownMembersHelper.cs b/src/Peachpie.CodeAnalysis/Symbols/WellKnownMembersHelper.cs index 46912fd46..3cdc9a855 100644 --- a/src/Peachpie.CodeAnalysis/Symbols/WellKnownMembersHelper.cs +++ b/src/Peachpie.CodeAnalysis/Symbols/WellKnownMembersHelper.cs @@ -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.Empty, ImmutableArray>.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("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( + // "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.Empty, namedparameters); + //} + + public static AttributeData CreateDefaultValueAttribute(this PhpCompilation compilation, TypeSymbol containingType, FieldSymbol field) { - var typeParameter = new KeyValuePair("Type", new TypedConstant(compilation.CoreTypes.DefaultValueType.Symbol, TypedConstantKind.Enum, 1/*PhpArray*/)); - var namedparameters = ImmutableArray.Create(typeParameter); + var namedparameters = ImmutableArray>.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( - "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( + "ExplicitType", + compilation.CreateTypedConstant(fieldContainer))); } + // [DefaultValueAttribute(name) { ExplicitType = ... }] return new SynthesizedAttributeData( - compilation.CoreMethods.Ctors.DefaultValueAttribute, - ImmutableArray.Empty, namedparameters); + compilation.CoreMethods.Ctors.DefaultValueAttribute_string, + ImmutableArray.Create(compilation.CreateTypedConstant(field.Name)), + namedparameters); } } } diff --git a/src/Peachpie.CodeAnalysis/Symbols/WrappedParameterSymbol.cs b/src/Peachpie.CodeAnalysis/Symbols/WrappedParameterSymbol.cs index e9fefb886..19b9746b4 100644 --- a/src/Peachpie.CodeAnalysis/Symbols/WrappedParameterSymbol.cs +++ b/src/Peachpie.CodeAnalysis/Symbols/WrappedParameterSymbol.cs @@ -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; diff --git a/src/Peachpie.CodeAnalysis/Utilities/PhpOperationExtensions.cs b/src/Peachpie.CodeAnalysis/Utilities/PhpOperationExtensions.cs index e786e60bb..15040b6ef 100644 --- a/src/Peachpie.CodeAnalysis/Utilities/PhpOperationExtensions.cs +++ b/src/Peachpie.CodeAnalysis/Utilities/PhpOperationExtensions.cs @@ -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('}'); - } - - /// - /// Serialize array using the PHP serialization format. - /// - public static string PhpSerializeOrThrow(this BoundArrayEx array) - { - var result = new StringBuilder(16); - PhpSerialize(result, array); - return result.ToString(); - } - - /// - /// Simple unserialize PHP object. Supports only arrays and literals. - /// - static BoundExpression PhpUnserializeOrThrow(ImmutableArray 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>(length); - while (length-- > 0) - { - items.Add(new KeyValuePair( - 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); - } - } - - /// - /// Decodes [DefaultValueAttribute]. - /// - 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>()).WithAccess(BoundAccess.Read); - default: - throw new ArgumentException(); - } - } - } - - // - throw new ArgumentException(); - } } } diff --git a/src/Peachpie.Library/Reflection/ReflectionParameter.cs b/src/Peachpie.Library/Reflection/ReflectionParameter.cs index ba5f4cd0d..a18990d9e 100644 --- a/src/Peachpie.Library/Reflection/ReflectionParameter.cs +++ b/src/Peachpie.Library/Reflection/ReflectionParameter.cs @@ -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 diff --git a/src/Peachpie.Library/Reflection/ReflectionUtils.cs b/src/Peachpie.Library/Reflection/ReflectionUtils.cs index c3e22f9f9..8a6130bdb 100644 --- a/src/Peachpie.Library/Reflection/ReflectionUtils.cs +++ b/src/Peachpie.Library/Reflection/ReflectionUtils.cs @@ -48,26 +48,39 @@ namespace Pchp.Library.Reflection /// TODO: move to Peachpie.Runtime /// Creates PhpValue from this attribute. /// - 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 + // 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()) != null) { - defaultValue = ResolveDefaultValueAttribute(defaultValueAttr); + defaultValue = defaultValueAttr.ResolveDefaultValueAttribute(overloads[mi].DeclaringType); } else { diff --git a/src/Peachpie.Runtime/Attributes.cs b/src/Peachpie.Runtime/Attributes.cs index b25bb09c1..1874806ec 100644 --- a/src/Peachpie.Runtime/Attributes.cs +++ b/src/Peachpie.Runtime/Attributes.cs @@ -294,23 +294,20 @@ namespace Pchp.Core public sealed class DefaultValueAttribute : Attribute { /// - /// Denotates the type of the value. + /// The type containing the backing field. + /// Null indicates the containing type. /// - public enum DefaultValueType + public Type ExplicitType { get; set; } + + /// + /// Name of the backing field. + /// + public string FieldName { get; private set; } + + public DefaultValueAttribute(string fieldName) { - None = 0, - PhpArray = 1, + FieldName = fieldName; } - - /// - /// The type of the default value. - /// - public DefaultValueType Type { get; set; } - - /// - /// Optional serialized data using PHP serialization. - /// - public byte[] SerializedValue { get; set; } } /// diff --git a/src/Peachpie.Runtime/Dynamic/OverloadBinder.cs b/src/Peachpie.Runtime/Dynamic/OverloadBinder.cs index 4100f625e..a79b30f22 100644 --- a/src/Peachpie.Runtime/Dynamic/OverloadBinder.cs +++ b/src/Peachpie.Runtime/Dynamic/OverloadBinder.cs @@ -401,29 +401,20 @@ namespace Pchp.Core.Dynamic /// /// Creates expression representing value from [DefaultValueAttribute] /// - 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(), - 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 + // // 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(); 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(); if (defaultValueAttr != null) { - return ConvertExpression.Bind(BindDefaultValue(defaultValueAttr), targetparam.ParameterType, _ctx); + return ConvertExpression.Bind(BindDefaultValue(targetparam.Member.DeclaringType, defaultValueAttr), targetparam.ParameterType, _ctx); } } diff --git a/tests/functions/param_default.php b/tests/functions/param_default_001.php similarity index 88% rename from tests/functions/param_default.php rename to tests/functions/param_default_001.php index dfb2d153f..6cbfe3508 100644 --- a/tests/functions/param_default.php +++ b/tests/functions/param_default_001.php @@ -1,5 +1,5 @@ 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.";