Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Identify DotvvmProperty with systematic 32-bit IDs #1867

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@
<NoWarn>$(NoWarn);CS1591;CS1573</NoWarn>
<Deterministic>true</Deterministic>
<PreserveCompilationContext>true</PreserveCompilationContext>
<DefaultTargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">netstandard2.1;net6.0;net472</DefaultTargetFrameworks>
<DefaultTargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netstandard2.1;net6.0</DefaultTargetFrameworks>
<DefaultTargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">net8.0;net6.0;netstandard2.1;net472</DefaultTargetFrameworks>
<DefaultTargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">net8.0;net6.0;netstandard2.1</DefaultTargetFrameworks>
<DisableImplicitNuGetFallbackFolder>true</DisableImplicitNuGetFallbackFolder>
<AutomaticallyUseReferenceAssemblyPackages>false</AutomaticallyUseReferenceAssemblyPackages>
</PropertyGroup>
Expand Down
4 changes: 4 additions & 0 deletions src/Framework/Framework/Binding/ActiveDotvvmProperty.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ namespace DotVVM.Framework.Binding
/// <summary> An abstract DotvvmProperty which contains code to be executed when the assigned control is being rendered. </summary>
public abstract class ActiveDotvvmProperty : DotvvmProperty
{
internal ActiveDotvvmProperty(string name, Type declaringType, bool isValueInherited) : base(name, declaringType, isValueInherited)
{
}

public abstract void AddAttributesToRender(IHtmlWriter writer, IDotvvmRequestContext context, DotvvmControl control);


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace DotVVM.Framework.Binding
/// </summary>
public class CompileTimeOnlyDotvvmProperty : DotvvmProperty
{
public CompileTimeOnlyDotvvmProperty()
private CompileTimeOnlyDotvvmProperty(string name, Type declaringType) : base(name, declaringType, isValueInherited: false)
{
}

Expand All @@ -37,7 +37,7 @@ public override bool IsSet(DotvvmBindableObject control, bool inherit = true)
/// </summary>
public static CompileTimeOnlyDotvvmProperty Register<TPropertyType, TDeclaringType>(string propertyName)
{
var property = new CompileTimeOnlyDotvvmProperty();
var property = new CompileTimeOnlyDotvvmProperty(propertyName, typeof(TDeclaringType));
return (CompileTimeOnlyDotvvmProperty)Register<TPropertyType, TDeclaringType>(propertyName, property: property);
}
}
Expand Down
7 changes: 4 additions & 3 deletions src/Framework/Framework/Binding/DelegateActionProperty.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ namespace DotVVM.Framework.Binding
/// <summary> DotvvmProperty which calls the function passed in the Register method, when the assigned control is being rendered. </summary>
public sealed class DelegateActionProperty<TValue>: ActiveDotvvmProperty
{
private Action<IHtmlWriter, IDotvvmRequestContext, DotvvmProperty, DotvvmControl> func;
private readonly Action<IHtmlWriter, IDotvvmRequestContext, DotvvmProperty, DotvvmControl> func;

public DelegateActionProperty(Action<IHtmlWriter, IDotvvmRequestContext, DotvvmProperty, DotvvmControl> func)
public DelegateActionProperty(Action<IHtmlWriter, IDotvvmRequestContext, DotvvmProperty, DotvvmControl> func, string name, Type declaringType, bool isValueInherited) : base(name, declaringType, isValueInherited)
{
this.func = func;
}
Expand All @@ -27,7 +27,8 @@ public override void AddAttributesToRender(IHtmlWriter writer, IDotvvmRequestCon

public static DelegateActionProperty<TValue> Register<TDeclaringType>(string name, Action<IHtmlWriter, IDotvvmRequestContext, DotvvmProperty, DotvvmControl> func, [AllowNull] TValue defaultValue = default(TValue))
{
return (DelegateActionProperty<TValue>)DotvvmProperty.Register<TValue, TDeclaringType>(name, defaultValue, false, new DelegateActionProperty<TValue>(func));
var property = new DelegateActionProperty<TValue>(func, name, typeof(TDeclaringType), isValueInherited: false);
return (DelegateActionProperty<TValue>)DotvvmProperty.Register<TValue, TDeclaringType>(name, defaultValue, false, property);
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,36 +105,42 @@ public static (LambdaExpression getter, LambdaExpression setter) CreatePropertyG
valueParameter
)
);

}

static readonly ConstructorInfo DotvvmPropertyIdConstructor = typeof(DotvvmPropertyId).GetConstructor(new [] { typeof(uint) }).NotNull();

public static (LambdaExpression getter, LambdaExpression setter) CreatePropertyAccessors(Type type, DotvvmProperty property)
{
if (property is DotvvmPropertyAlias propertyAlias)
return CreatePropertyAccessors(type, propertyAlias.Aliased);

// if the property does not override GetValue/SetValue, we'll use
// control.properties dictionary directly to avoid virtual method calls
var canUseDirectAccess =
!property.IsValueInherited && (
property.GetType() == typeof(DotvvmProperty) ||
property.GetType().GetMethod(nameof(DotvvmProperty.GetValue), new [] { typeof(DotvvmBindableObject), typeof(bool) })!.DeclaringType == typeof(DotvvmProperty) &&
property.GetType().GetMethod(nameof(DotvvmProperty.SetValue), new [] { typeof(DotvvmBindableObject), typeof(object) })!.DeclaringType == typeof(DotvvmProperty));
var canUseDirectAccess = !property.IsValueInherited && DotvvmPropertyIdAssignment.TypeCanUseAnyDirectAccess(property.GetType());

var valueParameter = Expression.Parameter(type, "value");
var unwrappedType = type.UnwrapNullableType();

var defaultObj = TypeConversion.BoxToObject(Constant(property.DefaultValue));
// try to access the readonly static field, as .NET can optimize that better than whatever Linq.Expression Constant compiles to
var propertyExpr =
property.AttributeProvider is FieldInfo field && field.IsStatic && field.IsInitOnly && field.GetValue(null) == property
? Field(null, field)
: (Expression)Constant(property);
var propertyIdExpr = New(DotvvmPropertyIdConstructor, Constant(property.Id.Id, typeof(uint)));

var boxedValueParameter = TypeConversion.BoxToObject(valueParameter);
var setValueRaw =
canUseDirectAccess
? Call(typeof(Helpers), nameof(Helpers.SetValueDirect), Type.EmptyTypes, currentControlParameter, Constant(property), boxedValueParameter)
: Call(currentControlParameter, nameof(DotvvmBindableObject.SetValueRaw), Type.EmptyTypes, Constant(property), boxedValueParameter);
? Call(typeof(Helpers), nameof(Helpers.SetValueDirect), Type.EmptyTypes, currentControlParameter, propertyIdExpr, defaultObj, boxedValueParameter)
: Call(currentControlParameter, nameof(DotvvmBindableObject.SetValueRaw), Type.EmptyTypes, propertyExpr, boxedValueParameter);

if (typeof(IBinding).IsAssignableFrom(type))
{
var getValueRaw =
canUseDirectAccess
? Call(typeof(Helpers), nameof(Helpers.GetValueRawDirect), Type.EmptyTypes, currentControlParameter, Constant(property))
: Call(currentControlParameter, nameof(DotvvmBindableObject.GetValueRaw), Type.EmptyTypes, Constant(property), Constant(property.IsValueInherited));
? Call(typeof(Helpers), nameof(Helpers.GetValueRawDirect), Type.EmptyTypes, currentControlParameter, propertyIdExpr, defaultObj)
: Call(currentControlParameter, nameof(DotvvmBindableObject.GetValueRaw), Type.EmptyTypes, propertyExpr, Constant(property.IsValueInherited));
return (
Lambda(
Convert(getValueRaw, type),
Expand Down Expand Up @@ -173,11 +179,17 @@ public static (LambdaExpression getter, LambdaExpression setter) CreatePropertyA
Expression.Call(
getValueOrBindingMethod,
currentControlParameter,
Constant(property)),
canUseDirectAccess ? propertyIdExpr : propertyExpr,
defaultObj),
currentControlParameter
),
Expression.Lambda(
Expression.Call(setValueOrBindingMethod, currentControlParameter, Expression.Constant(property), valueParameter),
Expression.Call(
setValueOrBindingMethod,
currentControlParameter,
canUseDirectAccess ? propertyIdExpr : propertyExpr,
defaultObj,
valueParameter),
currentControlParameter, valueParameter
)
);
Expand All @@ -191,13 +203,13 @@ public static (LambdaExpression getter, LambdaExpression setter) CreatePropertyA
Expression getValue;
if (canUseDirectAccess && unwrappedType.IsValueType)
{
getValue = Call(typeof(Helpers), nameof(Helpers.GetStructValueDirect), new Type[] { unwrappedType }, currentControlParameter, Constant(property));
getValue = Call(typeof(Helpers), nameof(Helpers.GetStructValueDirect), [ unwrappedType ], currentControlParameter, propertyIdExpr, Constant(property.DefaultValue, type.MakeNullableType()));
if (!type.IsNullable())
getValue = Expression.Property(getValue, "Value");
}
else
{
getValue = Call(currentControlParameter, getValueMethod, Constant(property), Constant(property.IsValueInherited));
getValue = Call(currentControlParameter, getValueMethod, propertyExpr, Constant(property.IsValueInherited));
getValue = Convert(getValue, type);
}
return (
Expand Down
40 changes: 20 additions & 20 deletions src/Framework/Framework/Binding/DotvvmCapabilityProperty.Helpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public partial class DotvvmCapabilityProperty
{
internal static class Helpers
{
public static ValueOrBinding<T>? GetOptionalValueOrBinding<T>(DotvvmBindableObject c, DotvvmProperty p)
public static ValueOrBinding<T>? GetOptionalValueOrBinding<T>(DotvvmBindableObject c, DotvvmPropertyId p, object? defaultValue)
{
if (c.properties.TryGet(p, out var x))
{
Expand All @@ -25,13 +25,13 @@ internal static class Helpers
}
else return null;
}
public static ValueOrBinding<T> GetValueOrBinding<T>(DotvvmBindableObject c, DotvvmProperty p)
public static ValueOrBinding<T> GetValueOrBinding<T>(DotvvmBindableObject c, DotvvmPropertyId p, object? defaultValue)
{
if (!c.properties.TryGet(p, out var x))
x = p.DefaultValue;
x = defaultValue;
return ValueOrBinding<T>.FromBoxedValue(x);
}
public static ValueOrBinding<T>? GetOptionalValueOrBindingSlow<T>(DotvvmBindableObject c, DotvvmProperty p)
public static ValueOrBinding<T>? GetOptionalValueOrBindingSlow<T>(DotvvmBindableObject c, DotvvmProperty p, object? defaultValue)
{
if (c.IsPropertySet(p))
{
Expand All @@ -45,42 +45,42 @@ public static ValueOrBinding<T> GetValueOrBinding<T>(DotvvmBindableObject c, Dot
}
else return null;
}
public static ValueOrBinding<T> GetValueOrBindingSlow<T>(DotvvmBindableObject c, DotvvmProperty p)
public static ValueOrBinding<T> GetValueOrBindingSlow<T>(DotvvmBindableObject c, DotvvmProperty p, object? defaultValue)
{
return ValueOrBinding<T>.FromBoxedValue(c.GetValue(p));
}
public static void SetOptionalValueOrBinding<T>(DotvvmBindableObject c, DotvvmProperty p, ValueOrBinding<T>? val)
public static void SetOptionalValueOrBinding<T>(DotvvmBindableObject c, DotvvmPropertyId p, object? defaultValue, ValueOrBinding<T>? val)
{
if (val.HasValue)
{
SetValueOrBinding<T>(c, p, val.GetValueOrDefault());
SetValueOrBinding<T>(c, p, defaultValue, val.GetValueOrDefault());
}
else
{
c.properties.Remove(p);
}
}
public static void SetValueOrBinding<T>(DotvvmBindableObject c, DotvvmProperty p, ValueOrBinding<T> val)
public static void SetValueOrBinding<T>(DotvvmBindableObject c, DotvvmPropertyId p, object? defaultValue, ValueOrBinding<T> val)
{
var boxedVal = val.UnwrapToObject();
SetValueDirect(c, p, boxedVal);
SetValueDirect(c, p, defaultValue, boxedVal);
}
public static void SetOptionalValueOrBindingSlow<T>(DotvvmBindableObject c, DotvvmProperty p, ValueOrBinding<T>? val)
public static void SetOptionalValueOrBindingSlow<T>(DotvvmBindableObject c, DotvvmProperty p, object? defaultValue, ValueOrBinding<T>? val)
{
if (val.HasValue)
{
SetValueOrBindingSlow<T>(c, p, val.GetValueOrDefault());
SetValueOrBindingSlow<T>(c, p, defaultValue, val.GetValueOrDefault());
}
else
{
c.SetValue(p, p.DefaultValue); // set to default value, just in case this property is backed in a different place than c.properties[p]
c.SetValue(p, defaultValue); // set to default value, just in case this property is backed in a different place than c.properties[p]
c.properties.Remove(p);
}
}
public static void SetValueOrBindingSlow<T>(DotvvmBindableObject c, DotvvmProperty p, ValueOrBinding<T> val)
public static void SetValueOrBindingSlow<T>(DotvvmBindableObject c, DotvvmProperty p, object? defaultValue, ValueOrBinding<T> val)
{
var boxedVal = val.UnwrapToObject();
if (Object.Equals(boxedVal, p.DefaultValue) && !c.IsPropertySet(p))
if (Object.Equals(boxedVal, defaultValue) && !c.IsPropertySet(p))
{
// setting to default value and the property is not set -> do nothing
}
Expand All @@ -90,15 +90,15 @@ public static void SetValueOrBindingSlow<T>(DotvvmBindableObject c, DotvvmProper
}
}

public static object? GetValueRawDirect(DotvvmBindableObject c, DotvvmProperty p)
public static object? GetValueRawDirect(DotvvmBindableObject c, DotvvmPropertyId p, object defaultValue)
{
if (c.properties.TryGet(p, out var x))
{
return x;
}
else return p.DefaultValue;
else return defaultValue;
}
public static T? GetStructValueDirect<T>(DotvvmBindableObject c, DotvvmProperty p)
public static T? GetStructValueDirect<T>(DotvvmBindableObject c, DotvvmPropertyId p, T? defaultValue)
where T: struct
{
// T being a struct allows us to invert the rather expensive `is IBinding` typecheck in EvalPropertyValue
Expand All @@ -111,11 +111,11 @@ public static void SetValueOrBindingSlow<T>(DotvvmBindableObject c, DotvvmProper
return xValue;
return (T?)c.EvalPropertyValue(p, x);
}
else return (T?)p.DefaultValue;
else return defaultValue;
}
public static void SetValueDirect(DotvvmBindableObject c, DotvvmProperty p, object? value)
public static void SetValueDirect(DotvvmBindableObject c, DotvvmPropertyId p, object? defaultValue, object? value)
{
if (Object.Equals(p.DefaultValue, value) && !c.properties.Contains(p))
if (Object.Equals(defaultValue, value) && !c.properties.Contains(p))
{
// setting to default value and the property is not set -> do nothing
}
Expand Down
20 changes: 5 additions & 15 deletions src/Framework/Framework/Binding/DotvvmCapabilityProperty.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,9 @@ private DotvvmCapabilityProperty(
Type declaringType,
ICustomAttributeProvider? attributeProvider,
DotvvmCapabilityProperty? declaringCapability
): base()
): base(name ?? prefix + type.Name, declaringType, isValueInherited: false)
{
name ??= prefix + type.Name;

this.Name = name;
this.PropertyType = type;
this.DeclaringType = declaringType;
this.Prefix = prefix;
this.AddUsedInCapability(declaringCapability);

Expand All @@ -63,14 +59,15 @@ private DotvvmCapabilityProperty(

AssertPropertyNotDefined(this, postContent: false);

var dotnetFieldName = ToPascalCase(name.Replace("-", "_").Replace(":", "_"));
var dotnetFieldName = ToPascalCase(Name.Replace("-", "_").Replace(":", "_"));
attributeProvider ??=
declaringType.GetProperty(dotnetFieldName) ??
declaringType.GetField(dotnetFieldName) ??
(ICustomAttributeProvider?)declaringType.GetField(dotnetFieldName + "Property") ??
throw new Exception($"Capability backing field could not be found and capabilityAttributeProvider argument was not provided. Property: {declaringType.Name}.{name}. Please declare a field or property named {dotnetFieldName}.");
throw new Exception($"Capability backing field could not be found and capabilityAttributeProvider argument was not provided. Property: {declaringType.Name}.{Name}. Please declare a field or property named {dotnetFieldName}.");

DotvvmProperty.InitializeProperty(this, attributeProvider);
this.MarkupOptions._mappingMode ??= MappingMode.Exclude;
}

public override object GetValue(DotvvmBindableObject control, bool inherit = true) => Getter(control);
Expand Down Expand Up @@ -200,15 +197,8 @@ static DotvvmCapabilityProperty RegisterCapability(DotvvmCapabilityProperty prop
{
var declaringType = property.DeclaringType.NotNull();
var capabilityType = property.PropertyType.NotNull();
var name = property.Name.NotNull();
AssertPropertyNotDefined(property);
var attributes = new CustomAttributesProvider(
new MarkupOptionsAttribute
{
MappingMode = MappingMode.Exclude
}
);
DotvvmProperty.Register(name, capabilityType, declaringType, DBNull.Value, false, property, attributes);
DotvvmProperty.Register(property);
if (!capabilityRegistry.TryAdd((declaringType, capabilityType, property.Prefix), property))
throw new($"unhandled naming conflict when registering capability {capabilityType}.");
capabilityListRegistry.AddOrUpdate(
Expand Down
Loading
Loading