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:
Jakub Míšek 2019-10-25 16:34:10 +02:00
Родитель 315541833e
Коммит 1e313cd5a5
23 изменённых файлов: 407 добавлений и 377 удалений

Просмотреть файл

@ -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.";