using System; using System.Collections.Generic; using System.Linq; using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; using XamlX.Ast; using XamlX.Transform; using XamlX.TypeSystem; using XamlX.IL; using XamlX.Emit; namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions { class XamlIlClrPropertyInfoEmitter { private readonly IXamlTypeBuilder _builder; private Dictionary> _fields = new Dictionary>(); public XamlIlClrPropertyInfoEmitter(IXamlTypeBuilder builder) { _builder = builder; } static string GetKey(IXamlProperty property, string indexerArgumentsKey) { var baseKey = property.Getter.DeclaringType.GetFullName() + "." + property.Name; if (indexerArgumentsKey is null) { return baseKey; } return baseKey + $"[{indexerArgumentsKey}]"; } public IXamlType Emit(XamlEmitContext context, IXamlILEmitter codeGen, IXamlProperty property, IEnumerable indexerArguments = null, string indexerArgumentsKey = null) { indexerArguments = indexerArguments ?? Enumerable.Empty(); var types = context.GetAvaloniaTypes(); IXamlMethod Get() { var key = GetKey(property, indexerArgumentsKey); if (!_fields.TryGetValue(key, out var lst)) _fields[key] = lst = new List<(IXamlProperty prop, IXamlMethod get)>(); foreach (var cached in lst) { if ( ((cached.prop.Getter == null && property.Getter == null) || cached.prop.Getter?.Equals(property.Getter) == true) && ((cached.prop.Setter == null && property.Setter == null) || cached.prop.Setter?.Equals(property.Setter) == true) ) return cached.get; } var name = lst.Count == 0 ? key : key + "_" + Guid.NewGuid().ToString("N"); var field = _builder.DefineField(types.IPropertyInfo, name + "!Field", false, true); void Load(IXamlMethod m, IXamlILEmitter cg) { cg .Ldarg_0(); if (m.DeclaringType.IsValueType) cg.Unbox(m.DeclaringType); else cg.Castclass(m.DeclaringType); foreach (var indexerArg in indexerArguments) { context.Emit(indexerArg, cg, indexerArg.Type.GetClrType()); } } var getter = property.Getter == null ? null : _builder.DefineMethod(types.XamlIlTypes.Object, new[] {types.XamlIlTypes.Object}, name + "!Getter", false, true, false); if (getter != null) { Load(property.Getter, getter.Generator); getter.Generator.EmitCall(property.Getter); if (property.Getter.ReturnType.IsValueType) getter.Generator.Box(property.Getter.ReturnType); getter.Generator.Ret(); } var setter = property.Setter == null ? null : _builder.DefineMethod(types.XamlIlTypes.Void, new[] {types.XamlIlTypes.Object, types.XamlIlTypes.Object}, name + "!Setter", false, true, false); if (setter != null) { Load(property.Setter, setter.Generator); setter.Generator.Ldarg(1); if (property.Setter.Parameters[0].IsValueType) setter.Generator.Unbox_Any(property.Setter.Parameters[0]); else setter.Generator.Castclass(property.Setter.Parameters[0]); setter.Generator .EmitCall(property.Setter, true) .Ret(); } var get = _builder.DefineMethod(types.IPropertyInfo, Array.Empty(), name + "!Property", true, true, false); var ctor = types.ClrPropertyInfo.Constructors.First(c => c.Parameters.Count == 4 && c.IsStatic == false); var cacheMiss = get.Generator.DefineLabel(); get.Generator .Ldsfld(field) .Brfalse(cacheMiss) .Ldsfld(field) .Ret() .MarkLabel(cacheMiss) .Ldstr(property.Name); void EmitFunc(IXamlILEmitter emitter, IXamlMethod method, IXamlType del) { if (method == null) emitter.Ldnull(); else { emitter .Ldnull() .Ldftn(method) .Newobj(del.Constructors.First(c => c.Parameters.Count == 2 && c.Parameters[0].Equals(context.Configuration.WellKnownTypes.Object))); } } EmitFunc(get.Generator, getter, ctor.Parameters[1]); EmitFunc(get.Generator, setter, ctor.Parameters[2]); get.Generator .Ldtype(property.PropertyType) .Newobj(ctor) .Stsfld(field) .Ldsfld(field) .Ret(); lst.Add((property, get)); return get; } codeGen.EmitCall(Get()); return types.IPropertyInfo; } } }