diff --git a/Directory.Build.props b/Directory.Build.props index fce83db7..7d3cfde5 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -7,5 +7,4 @@ - - + \ No newline at end of file diff --git a/src/XamlX.IL.Cecil/CecilEmitter.cs b/src/XamlX.IL.Cecil/CecilEmitter.cs index 870c413e..f3e1064e 100644 --- a/src/XamlX.IL.Cecil/CecilEmitter.cs +++ b/src/XamlX.IL.Cecil/CecilEmitter.cs @@ -132,6 +132,12 @@ public IXamlILEmitter Emit(SreOpCode code, int arg) public IXamlILEmitter Emit(SreOpCode code, long arg) => Emit(Instruction.Create(Dic[code], arg)); + + public IXamlILEmitter Emit(SreOpCode code, sbyte arg) + => Emit(Instruction.Create(Dic[code], arg)); + + public IXamlILEmitter Emit(SreOpCode code, byte arg) + => Emit(Instruction.Create(Dic[code], arg)); public IXamlILEmitter Emit(SreOpCode code, IXamlType type) => Emit(Instruction.Create(Dic[code], Import(((ITypeReference) type).Reference))); @@ -143,9 +149,11 @@ public IXamlILEmitter Emit(SreOpCode code, double arg) => Emit(Instruction.Create(Dic[code], arg)); - class CecilLocal : IXamlLocal + class CecilLocal : IXamlILLocal { public VariableDefinition Variable { get; set; } + + public int Index => Variable.Index; } class CecilLabel : IXamlLabel diff --git a/src/XamlX.IL.Cecil/CecilField.cs b/src/XamlX.IL.Cecil/CecilField.cs index ec03d907..63c91384 100644 --- a/src/XamlX.IL.Cecil/CecilField.cs +++ b/src/XamlX.IL.Cecil/CecilField.cs @@ -19,7 +19,9 @@ public CecilField(CecilTypeSystem typeSystem, FieldDefinition def, TypeReference Field = new FieldReference(def.Name, def.FieldType, declaringType); } - public bool Equals(IXamlField other) => other is CecilField cf && cf.Field == Field; + public bool Equals(IXamlField other) => other is CecilField cf && cf.Field.FullName == Field.FullName; + + public override int GetHashCode() => Field.FullName.GetHashCode(); public string Name => Field.Name; private IXamlType _type; @@ -41,4 +43,4 @@ public object GetLiteralValue() } } } -} \ No newline at end of file +} diff --git a/src/XamlX.IL.Cecil/CecilMethod.cs b/src/XamlX.IL.Cecil/CecilMethod.cs index 416403ff..62e76e20 100644 --- a/src/XamlX.IL.Cecil/CecilMethod.cs +++ b/src/XamlX.IL.Cecil/CecilMethod.cs @@ -102,6 +102,9 @@ other is CecilMethod cm && DeclaringType.Equals(cm.DeclaringType) && Reference.FullName == cm.Reference.FullName; + public override int GetHashCode() + => (DeclaringType.GetHashCode() * 397) ^ Reference.FullName.GetHashCode(); + public IXamlMethod MakeGenericMethod(IReadOnlyList typeArguments) { GenericInstanceMethod instantiation = new GenericInstanceMethod(Reference); diff --git a/src/XamlX.IL.Cecil/CecilType.cs b/src/XamlX.IL.Cecil/CecilType.cs index c358813e..2605e04c 100644 --- a/src/XamlX.IL.Cecil/CecilType.cs +++ b/src/XamlX.IL.Cecil/CecilType.cs @@ -112,10 +112,9 @@ public bool IsAssignableFrom(IXamlType type) } bool IsAssignableFromCore(IXamlType type) { - if (!type.IsValueType - && type == XamlPseudoType.Null) - return true; - + if (type == XamlPseudoType.Null) + return !IsValueType || GenericTypeDefinition?.FullName == "System.Nullable`1"; + if (type.IsValueType && GenericTypeDefinition?.FullName == "System.Nullable`1" && GenericArguments[0].Equals(type)) diff --git a/src/XamlX/Ast/Clr.cs b/src/XamlX/Ast/Clr.cs index 3d8a9c87..489ca730 100644 --- a/src/XamlX/Ast/Clr.cs +++ b/src/XamlX/Ast/Clr.cs @@ -87,15 +87,36 @@ public XamlAstClrProperty(IXamlLineInfo lineInfo, string name, IXamlType declari public override string ToString() => DeclaringType.GetFqn() + "." + Name; } - class XamlDirectCallPropertySetter : IXamlPropertySetter, IXamlEmitablePropertySetter +#if !XAMLX_INTERNAL + public +#endif + interface IXamlILOptimizedEmitablePropertySetter : IXamlEmitablePropertySetter + { + void EmitWithArguments( + XamlEmitContextWithLocals context, + IXamlILEmitter emitter, + IReadOnlyList arguments); + } + + class XamlDirectCallPropertySetter : IXamlILOptimizedEmitablePropertySetter, IEquatable { private readonly IXamlMethod _method; public IXamlType TargetType { get; } public PropertySetterBinderParameters BinderParameters { get; } = new PropertySetterBinderParameters(); public IReadOnlyList Parameters { get; } - public void Emit(IXamlILEmitter codegen) + + public void Emit(IXamlILEmitter emitter) + => emitter.EmitCall(_method, true); + + public void EmitWithArguments( + XamlEmitContextWithLocals context, + IXamlILEmitter emitter, + IReadOnlyList arguments) { - codegen.EmitCall(_method, true); + for (var i = 0; i < arguments.Count; ++i) + context.Emit(arguments[i], emitter, Parameters[i]); + + emitter.EmitCall(_method, true); } public XamlDirectCallPropertySetter(IXamlMethod method) @@ -103,17 +124,64 @@ public XamlDirectCallPropertySetter(IXamlMethod method) _method = method; Parameters = method.ParametersWithThis().Skip(1).ToList(); TargetType = method.ThisOrFirstParameter(); + + bool allowNull = Parameters.Last().AcceptsNull(); + BinderParameters = new PropertySetterBinderParameters + { + AllowMultiple = false, + AllowXNull = allowNull, + AllowRuntimeNull = allowNull + }; + } + + public bool Equals(XamlDirectCallPropertySetter other) + { + if (ReferenceEquals(null, other)) + return false; + if (ReferenceEquals(this, other)) + return true; + + return _method.Equals(other._method) && BinderParameters.Equals(other.BinderParameters); } + + public override bool Equals(object obj) + => Equals(obj as XamlDirectCallPropertySetter); + + public override int GetHashCode() + => (_method.GetHashCode() * 397) ^ BinderParameters.GetHashCode(); } #if !XAMLX_INTERNAL public #endif - class PropertySetterBinderParameters + class PropertySetterBinderParameters : IEquatable { public bool AllowMultiple { get; set; } public bool AllowXNull { get; set; } = true; public bool AllowRuntimeNull { get; set; } = true; + + public bool Equals(PropertySetterBinderParameters other) + { + if (ReferenceEquals(null, other)) + return false; + if (ReferenceEquals(this, other)) + return true; + + return AllowMultiple == other.AllowMultiple + && AllowXNull == other.AllowXNull + && AllowRuntimeNull == other.AllowRuntimeNull; + } + + public override bool Equals(object obj) + => Equals(obj as PropertySetterBinderParameters); + + public override int GetHashCode() + { + int hashCode = AllowMultiple.GetHashCode(); + hashCode = (hashCode * 397) ^ AllowXNull.GetHashCode(); + hashCode = (hashCode * 397) ^ AllowRuntimeNull.GetHashCode(); + return hashCode; + } } #if !XAMLX_INTERNAL @@ -637,6 +705,8 @@ void CompileBuilder(ILEmitContext context) context.Emit(Value, context.Emitter, context.Configuration.WellKnownTypes.Object); il.Ret(); + + context.ExecuteAfterEmitCallbacks(); } public XamlILNodeEmitResult Emit(XamlEmitContext context, IXamlILEmitter codeGen) @@ -652,7 +722,7 @@ public XamlILNodeEmitResult Emit(XamlEmitContext subType.DefineSubType(type, s, false), - defineDelegateSubType: (s, returnType, parameters) => subType.DefineDelegateSubType(s, false, returnType, parameters), + defineDelegateSubType: (s, returnType, parameters) => subType.DefineDelegateSubType(s, false, returnType, parameters), file: context.File, emitters: context.Emitters)); @@ -662,11 +732,11 @@ public XamlILNodeEmitResult Emit(XamlEmitContext ct.Parameters.Count == 2 && ct.Parameters[0].Equals(context.Configuration.WellKnownTypes.Object))); - + // Allow to save values from the parent context, pass own service provider, etc, etc if (context.Configuration.TypeMappings.DeferredContentExecutorCustomization != null) { - + var customization = context.Configuration.TypeMappings.DeferredContentExecutorCustomization; if (_deferredContentCustomizationTypeParameter != null) customization = diff --git a/src/XamlX/Ast/Intrinsics.cs b/src/XamlX/Ast/Intrinsics.cs index 7b42cc59..5a855e48 100644 --- a/src/XamlX/Ast/Intrinsics.cs +++ b/src/XamlX/Ast/Intrinsics.cs @@ -25,7 +25,7 @@ public XamlILNodeEmitResult Emit(XamlEmitContext : XamlContextBase public IFileSource File { get; } public List Emitters { get; } + private readonly List _afterEmitCallbacks = new(); private IXamlAstNode _currentNode; public TransformerConfiguration Configuration { get; } @@ -217,6 +217,17 @@ protected virtual TEmitResult EmitNodeCore(IXamlAstNode value, TBackendEmitter c foundEmitter = false; return res; } + + public void AddAfterEmitCallbacks(Action callback) + => _afterEmitCallbacks.Add(callback); + + public void ExecuteAfterEmitCallbacks() + { + foreach (var callback in _afterEmitCallbacks) + callback(); + + _afterEmitCallbacks.Clear(); + } } #if !XAMLX_INTERNAL diff --git a/src/XamlX/IL/CheckingIlEmitter.cs b/src/XamlX/IL/CheckingIlEmitter.cs index 3e7fed0b..73a74dc0 100644 --- a/src/XamlX/IL/CheckingIlEmitter.cs +++ b/src/XamlX/IL/CheckingIlEmitter.cs @@ -238,6 +238,20 @@ public IXamlILEmitter Emit(OpCode code, long arg) return this; } + public IXamlILEmitter Emit(OpCode code, sbyte arg) + { + Track(code, arg); + _inner.Emit(code, arg); + return this; + } + + public IXamlILEmitter Emit(OpCode code, byte arg) + { + Track(code, arg); + _inner.Emit(code, arg); + return this; + } + public IXamlILEmitter Emit(OpCode code, IXamlType type) { Track(code, type); diff --git a/src/XamlX/IL/Emitters/MarkupExtensionEmitter.cs b/src/XamlX/IL/Emitters/MarkupExtensionEmitter.cs index c28123cb..38872d49 100644 --- a/src/XamlX/IL/Emitters/MarkupExtensionEmitter.cs +++ b/src/XamlX/IL/Emitters/MarkupExtensionEmitter.cs @@ -36,7 +36,7 @@ void EmitPropertyDescriptor() if (me.ProvideValue.Parameters.Count > 0) ilgen - .Emit(OpCodes.Ldloc, context.ContextLocal); + .Ldloc(context.ContextLocal); if (needProvideValueTarget) { diff --git a/src/XamlX/IL/Emitters/PropertyAssignmentEmitter.cs b/src/XamlX/IL/Emitters/PropertyAssignmentEmitter.cs index 6ebd0c5d..3604f1a1 100644 --- a/src/XamlX/IL/Emitters/PropertyAssignmentEmitter.cs +++ b/src/XamlX/IL/Emitters/PropertyAssignmentEmitter.cs @@ -1,9 +1,8 @@ +using System; using System.Collections.Generic; using System.Linq; -using System.Reflection.Emit; using XamlX.Ast; using XamlX.Emit; -using XamlX.Transform; using XamlX.TypeSystem; namespace XamlX.IL.Emitters @@ -33,114 +32,288 @@ List ValidateAndGetSetters(XamlPropertyAssignmentNode an) throw new XamlLoadException("No setters found for property assignment", an); return lst; } - + public XamlILNodeEmitResult Emit(IXamlAstNode node, XamlEmitContextWithLocals context, IXamlILEmitter codeGen) { - if (!(node is XamlPropertyAssignmentNode an)) + if (node is not XamlPropertyAssignmentNode an) return null; + var dynamicValue = an.Values.Last(); + var dynamicValueType = dynamicValue.Type.GetClrType(); + var setters = ValidateAndGetSetters(an); - for (var c = 0; c < an.Values.Count - 1; c++) - { - context.Emit(an.Values[c], codeGen, an.Values[c].Type.GetClrType()); - } + RemoveRedundantSetters(dynamicValueType, setters); - var value = an.Values.Last(); - - var isValueType = value.Type.GetClrType().IsValueType; - // If there is only one available setter or if value is a value type, always use the first one - if (setters.Count == 1 || isValueType) + if (setters.Count == 1) { var setter = setters[0]; - context.Emit(value, codeGen, setter.Parameters.Last()); - context.Emit(setter, codeGen); + + if (setter is IXamlILOptimizedEmitablePropertySetter optimizedSetter) + optimizedSetter.EmitWithArguments(context, codeGen, an.Values); + else + { + for (var i = 0; i < an.Values.Count - 1; ++i) + context.Emit(an.Values[i], codeGen, an.Values[i].Type.GetClrType()); + context.Emit(dynamicValue, codeGen, setter.Parameters.Last()); + context.Emit(setter, codeGen); + } } else { - var checkedTypes = new List(); - IXamlLabel exit = codeGen.DefineLabel(); - IXamlLabel next = null; - var hadJumps = false; - context.Emit(value, codeGen, value.Type.GetClrType()); - - foreach (var setter in setters) - { - var type = setter.Parameters.Last(); - - // We have already checked this type or its base type - if (checkedTypes.Any(ch => ch.IsAssignableFrom(type))) - continue; + var valueTypes = an.Values.Select(x => x.Type.GetClrType()).ToArray(); + var method = GetOrCreateDynamicSetterMethod(an.Property.DeclaringType, valueTypes, setters, dynamicValue, context); - if (next != null) - { - codeGen.MarkLabel(next); - next = null; - } + for (var i = 0; i < an.Values.Count - 1; ++i) + context.Emit(an.Values[i], codeGen, an.Values[i].Type.GetClrType()); + context.Emit(dynamicValue, codeGen, dynamicValueType); + codeGen.EmitCall(method); + } - IXamlLabel Next() => next ?? (next = codeGen.DefineLabel()); + return XamlILNodeEmitResult.Void(1); + } - var checkNext = false; - if (setter.BinderParameters.AllowRuntimeNull) - checkedTypes.Add(type); - else - { - // Check for null; Also don't add this type to the list of checked ones because of the null check - codeGen - .Dup() - .Brfalse(Next()); - checkNext = true; - } + private static void RemoveRedundantSetters(IXamlType valueType, List setters) + { + if (setters.Count == 1) + return; - // Only do dynamic checks if we know that type is not assignable by downcast - if (!type.IsAssignableFrom(value.Type.GetClrType())) - { - codeGen - .Dup() - .Isinst(type) - .Brfalse(Next()); - checkNext = true; - } + // If the value is a value type, always use the first one + if (valueType.IsValueType) + { + setters.RemoveRange(1, setters.Count - 1); + return; + } - if (checkNext) - hadJumps = true; - - ILEmitHelpers.EmitConvert(context, codeGen, value, value.Type.GetClrType(), type); - context.Emit(setter, codeGen); - if (hadJumps) - { - codeGen.Br(exit); - } + for (int index = 0; index < setters.Count;) + { + var setter = setters[index]; + var type = setter.Parameters.Last(); - if(!checkNext) - break; + // the value is directly assignable by downcast and the setter allows null: it will always match + if (type.IsAssignableFrom(valueType) && setter.BinderParameters.AllowRuntimeNull) + { + setters.RemoveRange(index + 1, setters.Count - index - 1); + return; } + // we have already found a previous setter that matches the value's type or its base type + if (setters.Take(index).Any(previous => IsAssignableToWithNullability(setter, previous))) + { + setters.RemoveAt(index); + continue; + } + + ++index; + } + } + + private static bool IsAssignableToWithNullability(IXamlPropertySetter from, IXamlPropertySetter to) + => to.Parameters.Last().IsAssignableFrom(from.Parameters.Last()) + && (to.BinderParameters.AllowRuntimeNull || !from.BinderParameters.AllowRuntimeNull); + + private static IXamlMethod GetOrCreateDynamicSetterMethod( + IXamlType parentType, + IReadOnlyList valueTypes, + IReadOnlyList setters, + IXamlLineInfo lineInfo, + XamlEmitContextWithLocals context) + { + if (!context.TryGetItem(out DynamicSettersCache cache)) + { + var settersType = context.CreateSubType( + "DynamicSetters_" + context.Configuration.IdentifierGenerator.GenerateIdentifierPart(), + context.Configuration.WellKnownTypes.Object); + cache = new DynamicSettersCache(settersType); + context.SetItem(cache); + context.AddAfterEmitCallbacks(() => settersType.CreateType()); + } + + var cacheKey = new SettersCacheKey(parentType, valueTypes, setters); + + if (!cache.MethodByCacheKey.TryGetValue(cacheKey, out var method)) + { + method = cache.SettersType.DefineMethod( + context.Configuration.WellKnownTypes.Void, + new[] { parentType }.Concat(valueTypes), + "DynamicSetter_" + (cache.MethodByCacheKey.Count + 1), + true, true, false); + + var newContext = new ILEmitContext( + method.Generator, context.Configuration, context.EmitMappings, context.RuntimeContext, + null, + (s, type) => cache.SettersType.DefineSubType(type, s, false), + (s, returnType, parameters) => cache.SettersType.DefineDelegateSubType(s, false, returnType, parameters), + context.File, + context.Emitters); + + EmitDynamicSetterMethod(valueTypes, setters, lineInfo, newContext); + + cache.MethodByCacheKey[cacheKey] = method; + } + + return method; + } + + private static void EmitDynamicSetterMethod( + IReadOnlyList valueTypes, + IReadOnlyList setters, + IXamlLineInfo lineInfo, + XamlEmitContextWithLocals context) + { + var codeGen = context.Emitter; + + codeGen.Ldarg_0(); + for (int i = 0; i < valueTypes.Count; ++i) + codeGen.Ldarg(i + 1); + + var dynamicValueType = valueTypes.Last(); + IXamlPropertySetter firstSetterAllowingNull = null; + IXamlLabel next = null; + + void EmitSetterAfterChecks(IXamlPropertySetter setter, IXamlType typeOnStack) + { + // Convert is needed for T to T? and null to T?, wil be a no-op in other cases + ILEmitHelpers.EmitConvert(context, codeGen, lineInfo, typeOnStack, setter.Parameters.Last()); + context.Emit(setter, codeGen); + codeGen.Ret(); + } + + foreach (var setter in setters) + { + if (setter.BinderParameters.AllowRuntimeNull) + firstSetterAllowingNull ??= setter; + if (next != null) { codeGen.MarkLabel(next); + next = null; + } - if (setters.Any(x => !x.BinderParameters.AllowRuntimeNull)) - { - next = codeGen.DefineLabel(); - codeGen - .Dup() - .Brtrue(next) - .Newobj(context.Configuration.TypeSystem.GetType("System.NullReferenceException") - .FindConstructor()) - .Throw(); - codeGen.MarkLabel(next); - } + var parameterType = setter.Parameters.Last(); + IXamlType typeOnStack = dynamicValueType; + + // Only do dynamic checks if we know that the value is not assignable by downcast + if (!parameterType.IsAssignableFrom(dynamicValueType)) + { + // for Nullable, check if the value is a T, null is handled later + var checkedType = parameterType.IsNullable() ? parameterType.GenericArguments[0] : parameterType; + + next = codeGen.DefineLabel(); codeGen - .Newobj(context.Configuration.TypeSystem.GetType("System.InvalidCastException") + .Dup() + .Isinst(checkedType) + .Brfalse(next); + + if (checkedType.IsValueType) + codeGen.Unbox_Any(checkedType); + + typeOnStack = checkedType; + } + else if (!setter.BinderParameters.AllowRuntimeNull) + { + next = codeGen.DefineLabel(); + + codeGen + .Dup() + .Brfalse(next); + } + + EmitSetterAfterChecks(setter, typeOnStack); + + if (next == null) + break; + } + + if (next != null) + { + codeGen.MarkLabel(next); + + // the value didn't match any type, but it may be null: either emit a setter allowing null, or throw + next = codeGen.DefineLabel(); + codeGen + .Dup() + .Brtrue(next); + + if (firstSetterAllowingNull != null) + EmitSetterAfterChecks(firstSetterAllowingNull, XamlPseudoType.Null); + else + { + codeGen + .Newobj(context.Configuration.TypeSystem.GetType("System.NullReferenceException") .FindConstructor()) .Throw(); } - codeGen.MarkLabel(exit); + codeGen.MarkLabel(next); + + codeGen + .Newobj(context.Configuration.TypeSystem.GetType("System.InvalidCastException") + .FindConstructor()) + .Throw(); } + } - return XamlILNodeEmitResult.Void(1); + private readonly struct SettersCacheKey : IEquatable + { + public IXamlType ParentType { get; } + public IReadOnlyList ValueTypes { get; } + public IReadOnlyList Setters { get; } + + private static int GetListHashCode(IReadOnlyList list) + { + int hashCode = list.Count; + for (var i = 0; i < list.Count; ++i) + hashCode = (hashCode * 397) ^ list[i].GetHashCode(); + return hashCode; + } + + private static bool AreListEqual(IReadOnlyList x, IReadOnlyList y) + { + if (x.Count != y.Count) + return false; + + for (var i = 0; i < x.Count; ++i) + { + if (!EqualityComparer.Default.Equals(x[i], y[i])) + return false; + } + + return true; + } + + public bool Equals(SettersCacheKey other) + => ParentType == other.ParentType + && AreListEqual(ValueTypes, other.ValueTypes) + && AreListEqual(Setters, other.Setters); + + public override bool Equals(object obj) + => obj is SettersCacheKey other && Equals(other); + + public override int GetHashCode() + { + var hashCode = ParentType.GetHashCode(); + hashCode = (hashCode * 397) ^ GetListHashCode(ValueTypes); + hashCode = (hashCode * 397) ^ GetListHashCode(Setters); + return hashCode; + } + + public SettersCacheKey(IXamlType parentType, IReadOnlyList valueTypes, IReadOnlyList setters) + { + ParentType = parentType; + ValueTypes = valueTypes; + Setters = setters; + } + } + + private sealed class DynamicSettersCache + { + public IXamlTypeBuilder SettersType { get; } + + public Dictionary> MethodByCacheKey { get; } = new(); + + public DynamicSettersCache(IXamlTypeBuilder settersType) + => SettersType = settersType; } } } diff --git a/src/XamlX/IL/ILEmitContext.cs b/src/XamlX/IL/ILEmitContext.cs index 4334077f..a31cc263 100644 --- a/src/XamlX/IL/ILEmitContext.cs +++ b/src/XamlX/IL/ILEmitContext.cs @@ -97,7 +97,7 @@ protected override void EmitConvert(IXamlAstNode value, IXamlILEmitter codeGen, public override void LoadLocalValue(XamlAstCompilerLocalNode node, IXamlILEmitter codeGen) { if (_locals.TryGetValue(node, out var local)) - codeGen.Emit(OpCodes.Ldloc, local); + codeGen.Ldloc(local); else throw new XamlLoadException("Attempt to read uninitialized local variable", node); } diff --git a/src/XamlX/IL/ILEmitHelpers.cs b/src/XamlX/IL/ILEmitHelpers.cs index 812bee9d..8f027181 100644 --- a/src/XamlX/IL/ILEmitHelpers.cs +++ b/src/XamlX/IL/ILEmitHelpers.cs @@ -20,8 +20,7 @@ public static void EmitFieldLiteral(IXamlField field, IXamlILEmitter codeGen) var ftype = field.FieldType.IsEnum ? field.FieldType.GetEnumUnderlyingType() : field.FieldType; if (ftype.Name == "UInt64" || ftype.Name == "Int64") - codeGen.Emit(OpCodes.Ldc_I8, - TypeSystemHelpers.ConvertLiteralToLong(field.GetLiteralValue())); + codeGen.Emit(OpCodes.Ldc_I8, TypeSystemHelpers.ConvertLiteralToLong(field.GetLiteralValue())); else if (ftype.Name == "Double") codeGen.Emit(OpCodes.Ldc_R8, (double)field.GetLiteralValue()); else if (ftype.Name == "Single") @@ -29,14 +28,13 @@ public static void EmitFieldLiteral(IXamlField field, IXamlILEmitter codeGen) else if (ftype.Name == "String") codeGen.Emit(OpCodes.Ldstr, (string)field.GetLiteralValue()); else - codeGen.Emit(OpCodes.Ldc_I4, - TypeSystemHelpers.ConvertLiteralToInt(field.GetLiteralValue())); + codeGen.Ldc_I4(TypeSystemHelpers.ConvertLiteralToInt(field.GetLiteralValue())); } public static void EmitConvert(XamlEmitContextWithLocals context, IXamlILEmitter ilgen, IXamlLineInfo node, IXamlType what, IXamlType to, IXamlLocal local) { - EmitConvert(context, node, what, to, lda => ilgen.Emit(lda ? OpCodes.Ldloca : OpCodes.Ldloc, local)); + EmitConvert(context, node, what, to, lda => lda ? ilgen.Ldloca(local) : ilgen.Ldloc(local)); } public static void EmitConvert(XamlEmitContextWithLocals context, IXamlILEmitter ilgen, IXamlLineInfo node, diff --git a/src/XamlX/IL/IXamlILEmitter.cs b/src/XamlX/IL/IXamlILEmitter.cs index 4af8124d..9b42aa0b 100644 --- a/src/XamlX/IL/IXamlILEmitter.cs +++ b/src/XamlX/IL/IXamlILEmitter.cs @@ -21,6 +21,8 @@ interface IXamlILEmitter : IHasLocalsPool IXamlILEmitter Emit(OpCode code, string arg); IXamlILEmitter Emit(OpCode code, int arg); IXamlILEmitter Emit(OpCode code, long arg); + IXamlILEmitter Emit(OpCode code, sbyte arg); + IXamlILEmitter Emit(OpCode code, byte arg); IXamlILEmitter Emit(OpCode code, IXamlType type); IXamlILEmitter Emit(OpCode code, float arg); IXamlILEmitter Emit(OpCode code, double arg); diff --git a/src/XamlX/IL/IXamlILLocal.cs b/src/XamlX/IL/IXamlILLocal.cs new file mode 100644 index 00000000..3cd6d532 --- /dev/null +++ b/src/XamlX/IL/IXamlILLocal.cs @@ -0,0 +1,12 @@ +using XamlX.TypeSystem; + +namespace XamlX.IL +{ +#if !XAMLX_INTERNAL + public +#endif + interface IXamlILLocal : IXamlLocal + { + int Index { get; } + } +} diff --git a/src/XamlX/IL/NamespaceInfoProvider.cs b/src/XamlX/IL/NamespaceInfoProvider.cs index 1147507f..abef5125 100644 --- a/src/XamlX/IL/NamespaceInfoProvider.cs +++ b/src/XamlX/IL/NamespaceInfoProvider.cs @@ -1,6 +1,4 @@ -using System; using System.Collections.Generic; -using System.Linq; using System.Reflection.Emit; using XamlX.Ast; using XamlX.Transform; @@ -13,95 +11,177 @@ namespace XamlX.IL #endif static class NamespaceInfoProvider { - public static IXamlField EmitNamespaceInfoProvider(TransformerConfiguration configuration, - IXamlTypeBuilder typeBuilder, XamlDocument document) + public static IXamlField EmitNamespaceInfoProvider( + TransformerConfiguration configuration, + IXamlTypeBuilder typeBuilder, + XamlDocument document) { - var iface = configuration.TypeMappings.XmlNamespaceInfoProvider; - typeBuilder.AddInterfaceImplementation(iface); - var method = iface.FindMethod(m => m.Name == "get_XmlNamespaces"); - var instField = typeBuilder.DefineField(method.ReturnType, "_services", false, false); - var singletonField = typeBuilder.DefineField(iface, "Singleton", true, true); - - var impl = typeBuilder.DefineMethod(method.ReturnType, null, method.Name, true, false, true); - typeBuilder.DefineProperty(method.ReturnType, "XmlNamespaces", null, impl); - impl.Generator - .LdThisFld(instField) - .Ret(); - - var infoType = method.ReturnType.GenericArguments[1].GenericArguments[0]; - - var ctor = typeBuilder.DefineConstructor(false); - var listType = configuration.TypeSystem.FindType("System.Collections.Generic.List`1") - .MakeGenericType(infoType); - var listInterfaceType = configuration.TypeSystem.FindType("System.Collections.Generic.IReadOnlyList`1") - .MakeGenericType(infoType); - var listAdd = listType.FindMethod("Add", configuration.WellKnownTypes.Void, true, infoType); - - var dictionaryType = configuration.TypeSystem.FindType("System.Collections.Generic.Dictionary`2") - .MakeGenericType(configuration.WellKnownTypes.String, listInterfaceType); - var dictionaryAdd = dictionaryType.FindMethod("Add", configuration.WellKnownTypes.Void, true, - configuration.WellKnownTypes.String, listInterfaceType); - - var dicLocal = ctor.Generator.DefineLocal(dictionaryType); - var listLocal = ctor.Generator.DefineLocal(listType); - - ctor.Generator - .Ldarg_0() - .Emit(OpCodes.Call, configuration.WellKnownTypes.Object.FindConstructor()) - .Emit(OpCodes.Newobj, dictionaryType.FindConstructor()) - .Stloc(dicLocal) - .Ldarg_0() - .Ldloc(dicLocal) - .Stfld(instField); - - foreach (var alias in document.NamespaceAliases) + var nsInfoProviderType = configuration.TypeMappings.XmlNamespaceInfoProvider; + var getNamespacesInterfaceMethod = nsInfoProviderType.FindMethod(m => m.Name == "get_XmlNamespaces"); + var roDictionaryType = getNamespacesInterfaceMethod.ReturnType; + var nsInfoType = roDictionaryType.GenericArguments[1].GenericArguments[0]; + + typeBuilder.AddInterfaceImplementation(nsInfoProviderType); + var namespacesField = typeBuilder.DefineField(roDictionaryType, "_xmlNamespaces", false, false); + var singletonField = typeBuilder.DefineField(nsInfoProviderType, "Singleton", true, true); + + IXamlMethod EmitCreateNamespaceInfoMethod() { - ctor.Generator - .Newobj(listType.FindConstructor(new List())) - .Stloc(listLocal); + // private static XamlXmlNamespaceInfoV1 CreateNamespaceInfo(string arg0, string arg1) + var method = typeBuilder.DefineMethod( + nsInfoType, + new[] { configuration.WellKnownTypes.String, configuration.WellKnownTypes.String }, + "CreateNamespaceInfo", + false, true, false); + + // return new XamlXmlNamespaceInfoV1() { ClrNamespace = arg0, ClrAssemblyName = arg1 } + method.Generator + .Newobj(nsInfoType.FindConstructor()) + .Dup() + .Ldarg_0() + .EmitCall(nsInfoType.FindMethod(m => m.Name == "set_ClrNamespace")) + .Dup() + .Ldarg(1) + .EmitCall(nsInfoType.FindMethod(m => m.Name == "set_ClrAssemblyName")) + .Ret(); + + return method; + } + + var createNamespaceInfoMethod = EmitCreateNamespaceInfoMethod(); + + IXamlMethod EmitCreateNamespacesMethod() + { + // C#: private static IReadOnlyDictionary> CreateNamespaces() + var method = typeBuilder.DefineMethod( + roDictionaryType, + null, + "CreateNamespaces", + false, true, false); + + var roListType = configuration.TypeSystem.FindType("System.Collections.Generic.IReadOnlyList`1") + .MakeGenericType(nsInfoType); - var resolved = Transform.NamespaceInfoHelper.TryResolve(configuration, alias.Value); - if (resolved != null) + var dictionaryType = configuration.TypeSystem.FindType("System.Collections.Generic.Dictionary`2") + .MakeGenericType(configuration.WellKnownTypes.String, roListType); + + var dictionaryCtor = dictionaryType.FindConstructor(new List { configuration.WellKnownTypes.Int32 }); + + var dictionaryAddMethod = dictionaryType.FindMethod( + "Add", + configuration.WellKnownTypes.Void, true, configuration.WellKnownTypes.String, roListType); + + var codeGen = method.Generator; + var dictionaryLocal = codeGen.DefineLocal(dictionaryType); + + // C#: var dic = new Dictionary>(`capacity`); + codeGen + .Ldc_I4(document.NamespaceAliases.Count) + .Newobj(dictionaryCtor) + .Stloc(dictionaryLocal); + + foreach (var alias in document.NamespaceAliases) { - foreach (var rns in resolved) + codeGen + .Ldloc(dictionaryLocal) + .Ldstr(alias.Key); + + var resolveResults = NamespaceInfoHelper.TryResolve(configuration, alias.Value) ?? new(); + + // C#: var array = new XamlXmlNamespaceInfoV1[`count`]; + codeGen + .Ldc_I4(resolveResults.Count) + .Newarr(nsInfoType); + + for (int i = 0; i < resolveResults.Count; ++i) { - ctor.Generator - .Ldloc(listLocal) - .Newobj(infoType.FindConstructor()); - if (rns.ClrNamespace != null) - ctor.Generator - .Dup() - .Ldstr(rns.ClrNamespace) - .EmitCall(infoType.FindMethod(m => m.Name == "set_ClrNamespace")); - - var asmName = rns.AssemblyName ?? rns.Assembly?.Name; - if (asmName != null) - ctor.Generator - .Dup() - .Ldstr(asmName) - .EmitCall(infoType.FindMethod(m => m.Name == "set_ClrAssemblyName")); - - ctor.Generator.EmitCall(listAdd); + var resolveResult = resolveResults[i]; + + // C#: array[`i`] = CreateNamespace(`namespace`, `assemblyName`); + codeGen + .Dup() + .Ldc_I4(i) + .Ldstr(resolveResult.ClrNamespace) + .Ldstr(resolveResult.AssemblyName ?? resolveResult.Assembly?.Name) + .EmitCall(createNamespaceInfoMethod) + .Stelem_ref(); } + + // C#: dic.Add(`alias`, array); + codeGen.EmitCall(dictionaryAddMethod, true); } + // C#: return dic; + codeGen + .Ldloc(dictionaryLocal) + .Ret(); + + return method; + } + + var createNamespacesMethod = EmitCreateNamespacesMethod(); + + void EmitNamespacesProperty() + { + // C#: private IReadOnlyDictionary> get_XmlNamespaces() + var method = typeBuilder.DefineMethod( + roDictionaryType, + null, + getNamespacesInterfaceMethod.Name, + true, false, true); + + var hasValueLabel = method.Generator.DefineLabel(); + + method.Generator + // C#: if (this._xmlNamespaces == null) + .Ldarg_0() + .Ldfld(namespacesField) + .Brtrue(hasValueLabel) + // C#: this._xmlNamespaces = CreateNamespaces(); + .Ldarg_0() + .EmitCall(createNamespacesMethod) + .Stfld(namespacesField) + // C#: return this._xmlNamespaces + .MarkLabel(hasValueLabel) + .Ldarg_0() + .Ldfld(namespacesField) + .Ret(); + + typeBuilder.DefineProperty(roDictionaryType, "XmlNamespaces", null, method); + } + + EmitNamespacesProperty(); + + IXamlConstructor EmitConstructor() + { + var ctor = typeBuilder.DefineConstructor(false); + + // C#: base() ctor.Generator - .Ldloc(dicLocal) - .Ldstr(alias.Key) - .Ldloc(listLocal) - .EmitCall(dictionaryAdd, true); + .Ldarg_0() + .Emit(OpCodes.Call, configuration.WellKnownTypes.Object.FindConstructor()) + .Ret(); + + return ctor; } - ctor.Generator.Ret(); + var ctor = EmitConstructor(); - var sctor = typeBuilder.DefineConstructor(true); - sctor.Generator - .Newobj(ctor) - .Stsfld(singletonField) - .Ret(); + void EmitStaticConstructor() + { + var cctor = typeBuilder.DefineConstructor(true); + + // C#: _singleton = new NamespaceInfoProvider(); + cctor.Generator + .Newobj(ctor) + .Stsfld(singletonField) + .Ret(); + } + + EmitStaticConstructor(); return singletonField; - //return typeBuilder.CreateType().Fields.First(f => f.Name == "Singleton"); } + } } diff --git a/src/XamlX/IL/RecordingIlEmitter.cs b/src/XamlX/IL/RecordingIlEmitter.cs index 50e470a0..e9bbe7b6 100644 --- a/src/XamlX/IL/RecordingIlEmitter.cs +++ b/src/XamlX/IL/RecordingIlEmitter.cs @@ -98,6 +98,20 @@ public IXamlILEmitter Emit(OpCode code, long arg) _inner.Emit(code, arg); return this; } + + public IXamlILEmitter Emit(OpCode code, sbyte arg) + { + Record(code, arg); + _inner.Emit(code, arg); + return this; + } + + public IXamlILEmitter Emit(OpCode code, byte arg) + { + Record(code, arg); + _inner.Emit(code, arg); + return this; + } public IXamlILEmitter Emit(OpCode code, IXamlType type) { diff --git a/src/XamlX/IL/SreTypeSystem.cs b/src/XamlX/IL/SreTypeSystem.cs index 5143127f..bf496fc1 100644 --- a/src/XamlX/IL/SreTypeSystem.cs +++ b/src/XamlX/IL/SreTypeSystem.cs @@ -321,7 +321,11 @@ public SreMethod(SreTypeSystem system, MethodInfo method) : base(system, method) _system = system; } - public bool Equals(IXamlMethod other) => ((SreMethod) other)?.Method.Equals(Method) == true; + public bool Equals(IXamlMethod other) + => other is SreMethod typedOther && Method == typedOther.Method; + + public override int GetHashCode() + => Method.GetHashCode(); public IXamlMethod MakeGenericMethod(IReadOnlyList typeArguments) { @@ -411,7 +415,8 @@ public object GetLiteralValue() } public override string ToString() => Field.DeclaringType?.FullName + " " + Field.Name; - public bool Equals(IXamlField other) => ((SreField) other)?.Field.Equals(Field) == true; + public bool Equals(IXamlField other) => other is SreField typedOther && typedOther.Field == Field; + public override int GetHashCode() => Field.GetHashCode(); } public IXamlILEmitter CreateCodeGen(MethodBuilder mb) @@ -472,6 +477,18 @@ public IXamlILEmitter Emit(OpCode code, long arg) return this; } + public IXamlILEmitter Emit(OpCode code, sbyte arg) + { + _ilg.Emit(code, arg); + return this; + } + + public IXamlILEmitter Emit(OpCode code, byte arg) + { + _ilg.Emit(code, arg); + return this; + } + public IXamlILEmitter Emit(OpCode code, float arg) { _ilg.Emit(code, arg); @@ -542,10 +559,12 @@ public SreLabel(Label label) } } - class SreLocal : IXamlLocal + class SreLocal : IXamlILLocal { public LocalBuilder Local { get; } + public int Index => Local.LocalIndex; + public SreLocal(LocalBuilder local) { Local = local; diff --git a/src/XamlX/IL/XamlILEmitterExtensions.cs b/src/XamlX/IL/XamlILEmitterExtensions.cs index e47bcde3..5d450c06 100644 --- a/src/XamlX/IL/XamlILEmitterExtensions.cs +++ b/src/XamlX/IL/XamlILEmitterExtensions.cs @@ -36,7 +36,15 @@ public static IXamlILEmitter DebugHatch(this IXamlILEmitter emitter, string mess } public static IXamlILEmitter Ldarg(this IXamlILEmitter emitter, int arg) - => emitter.Emit(OpCodes.Ldarg, arg); + => arg switch + { + 0 => emitter.Emit(OpCodes.Ldarg_0), + 1 => emitter.Emit(OpCodes.Ldarg_1), + 2 => emitter.Emit(OpCodes.Ldarg_2), + 3 => emitter.Emit(OpCodes.Ldarg_3), + >= 4 and <= byte.MaxValue => emitter.Emit(OpCodes.Ldarg_S, (byte) arg), + _ => emitter.Emit(OpCodes.Ldarg, arg) + }; public static IXamlILEmitter Ldarg_0(this IXamlILEmitter emitter) => emitter.Emit(OpCodes.Ldarg_0); @@ -57,13 +65,32 @@ public static IXamlILEmitter Stsfld(this IXamlILEmitter emitter, IXamlField fiel => emitter.Emit(OpCodes.Stsfld, field); public static IXamlILEmitter Ldloc(this IXamlILEmitter emitter, IXamlLocal local) - => emitter.Emit(OpCodes.Ldloc, local); + => (local as IXamlILLocal)?.Index switch + { + 0 => emitter.Emit(OpCodes.Ldloc_0), + 1 => emitter.Emit(OpCodes.Ldloc_1), + 2 => emitter.Emit(OpCodes.Ldloc_2), + 3 => emitter.Emit(OpCodes.Ldloc_3), + >= 4 and <= byte.MaxValue => emitter.Emit(OpCodes.Ldloc_S, local), + _ => emitter.Emit(OpCodes.Ldloc, local) + }; public static IXamlILEmitter Ldloca(this IXamlILEmitter emitter, IXamlLocal local) - => emitter.Emit(OpCodes.Ldloca, local); + { + var index = (local as IXamlILLocal)?.Index; + return emitter.Emit(index is >= 0 and <= byte.MaxValue ? OpCodes.Ldloca_S : OpCodes.Ldloca, local); + } - public static IXamlILEmitter Stloc(this IXamlILEmitter emitter, IXamlLocal local) - => emitter.Emit(OpCodes.Stloc, local); + public static IXamlILEmitter Stloc(this IXamlILEmitter emitter, IXamlLocal local) + => (local as IXamlILLocal)?.Index switch + { + 0 => emitter.Emit(OpCodes.Stloc_0), + 1 => emitter.Emit(OpCodes.Stloc_1), + 2 => emitter.Emit(OpCodes.Stloc_2), + 3 => emitter.Emit(OpCodes.Stloc_3), + >= 4 and <= byte.MaxValue => emitter.Emit(OpCodes.Stloc_S, local), + _ => emitter.Emit(OpCodes.Stloc, local) + }; public static IXamlILEmitter Ldnull(this IXamlILEmitter emitter) => emitter.Emit(OpCodes.Ldnull); @@ -74,11 +101,21 @@ public static IXamlILEmitter Throw(this IXamlILEmitter emitter) => emitter.Emit(OpCodes.Throw); public static IXamlILEmitter Ldc_I4(this IXamlILEmitter emitter, int arg) - => arg == 0 - ? emitter.Emit(OpCodes.Ldc_I4_0) - : arg == 1 - ? emitter.Emit(OpCodes.Ldc_I4_1) - : emitter.Emit(OpCodes.Ldc_I4, arg); + => arg switch + { + 0 => emitter.Emit(OpCodes.Ldc_I4_0), + 1 => emitter.Emit(OpCodes.Ldc_I4_1), + 2 => emitter.Emit(OpCodes.Ldc_I4_2), + 3 => emitter.Emit(OpCodes.Ldc_I4_3), + 4 => emitter.Emit(OpCodes.Ldc_I4_4), + 5 => emitter.Emit(OpCodes.Ldc_I4_5), + 6 => emitter.Emit(OpCodes.Ldc_I4_6), + 7 => emitter.Emit(OpCodes.Ldc_I4_7), + 8 => emitter.Emit(OpCodes.Ldc_I4_8), + -1 => emitter.Emit(OpCodes.Ldc_I4_M1), + >= sbyte.MinValue and <= sbyte.MaxValue => emitter.Emit(OpCodes.Ldc_I4_S, (sbyte) arg), + _ => emitter.Emit(OpCodes.Ldc_I4, arg) + }; public static IXamlILEmitter Ldc_R8(this IXamlILEmitter emitter, double arg) => emitter.Emit(OpCodes.Ldc_R8, arg); diff --git a/src/XamlX/IL/XamlIlCompiler.cs b/src/XamlX/IL/XamlIlCompiler.cs index 1cc93a1d..ae4c05f6 100644 --- a/src/XamlX/IL/XamlIlCompiler.cs +++ b/src/XamlX/IL/XamlIlCompiler.cs @@ -68,7 +68,7 @@ protected override XamlEmitContext InitCod codeGen .Emit(OpCodes.Ldarg_0); context.Factory(codeGen); - codeGen.Emit(OpCodes.Stloc, contextLocal); + codeGen.Stloc(contextLocal); } var emitContext = new ILEmitContext(codeGen, _configuration, @@ -91,12 +91,14 @@ protected override void CompileBuild( var rv = codeGen.DefineLocal(rootInstance.Type.GetClrType()); emitContext.Emit(rootInstance, codeGen, rootInstance.Type.GetClrType()); codeGen - .Emit(OpCodes.Stloc, rv) - .Emit(OpCodes.Ldarg_0) - .Emit(OpCodes.Ldloc, rv) + .Stloc(rv) + .Ldarg_0() + .Ldloc(rv) .EmitCall(compiledPopulate) - .Emit(OpCodes.Ldloc, rv) - .Emit(OpCodes.Ret); + .Ldloc(rv) + .Ret(); + + emitContext.ExecuteAfterEmitCallbacks(); } /// @@ -112,15 +114,17 @@ protected override void CompilePopulate(IFileSource fileSource, IXamlAstManipula var emitContext = InitCodeGen(fileSource, createSubType, createDelegateType, codeGen, context, true); codeGen - .Emit(OpCodes.Ldloc, emitContext.ContextLocal) + .Ldloc(emitContext.ContextLocal) .Emit(OpCodes.Ldarg_1) .Emit(OpCodes.Stfld, context.RootObjectField) - .Emit(OpCodes.Ldloc, emitContext.ContextLocal) + .Ldloc(emitContext.ContextLocal) .Emit(OpCodes.Ldarg_1) .Emit(OpCodes.Stfld, context.IntermediateRootObjectField) .Emit(OpCodes.Ldarg_1); emitContext.Emit(manipulation, codeGen, null); codeGen.Emit(OpCodes.Ret); + + emitContext.ExecuteAfterEmitCallbacks(); } protected override XamlRuntimeContext CreateRuntimeContext( diff --git a/src/XamlX/Transform/TransformerConfiguration.cs b/src/XamlX/Transform/TransformerConfiguration.cs index 3e55395e..ed8bbb61 100644 --- a/src/XamlX/Transform/TransformerConfiguration.cs +++ b/src/XamlX/Transform/TransformerConfiguration.cs @@ -211,6 +211,7 @@ class XamlTypeWellKnownTypes public IXamlType IListOfT { get; } public IXamlType Object { get; } public IXamlType String { get; } + public IXamlType Int32 { get; } public IXamlType Void { get; } public IXamlType Boolean { get; } public IXamlType Double { get; } @@ -223,6 +224,7 @@ public XamlTypeWellKnownTypes(IXamlTypeSystem typeSystem) { Void = typeSystem.GetType("System.Void"); String = typeSystem.GetType("System.String"); + Int32 = typeSystem.GetType("System.Int32"); Object = typeSystem.GetType("System.Object"); Boolean = typeSystem.GetType("System.Boolean"); Double = typeSystem.GetType("System.Double"); diff --git a/src/XamlX/Transform/Transformers/ResolvePropertyValueAddersTransformer.cs b/src/XamlX/Transform/Transformers/ResolvePropertyValueAddersTransformer.cs index 43841b49..07f6e813 100644 --- a/src/XamlX/Transform/Transformers/ResolvePropertyValueAddersTransformer.cs +++ b/src/XamlX/Transform/Transformers/ResolvePropertyValueAddersTransformer.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using XamlX.Ast; @@ -23,7 +24,7 @@ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode nod return node; } - class AdderSetter : IXamlPropertySetter, IXamlEmitablePropertySetter + class AdderSetter : IXamlILOptimizedEmitablePropertySetter, IEquatable { private readonly IXamlMethod _getter; private readonly IXamlMethod _adder; @@ -34,16 +35,22 @@ public AdderSetter(IXamlMethod getter, IXamlMethod adder) _adder = adder; TargetType = getter.DeclaringType; Parameters = adder.ParametersWithThis().Skip(1).ToList(); + + bool allowNull = Parameters.Last().AcceptsNull(); + BinderParameters = new PropertySetterBinderParameters + { + AllowMultiple = true, + AllowXNull = allowNull, + AllowRuntimeNull = allowNull + }; } public IXamlType TargetType { get; } - public PropertySetterBinderParameters BinderParameters { get; } = new PropertySetterBinderParameters - { - AllowMultiple = true - }; + public PropertySetterBinderParameters BinderParameters { get; } public IReadOnlyList Parameters { get; } + public void Emit(IXamlILEmitter emitter) { var locals = new Stack(); @@ -61,6 +68,35 @@ public void Emit(IXamlILEmitter emitter) emitter.Ldloc(loc.Local); emitter.EmitCall(_adder, true); } + + public void EmitWithArguments( + XamlEmitContextWithLocals context, + IXamlILEmitter emitter, + IReadOnlyList arguments) + { + emitter.EmitCall(_getter); + + for (var i = 0; i < arguments.Count; ++i) + context.Emit(arguments[i], emitter, Parameters[i]); + + emitter.EmitCall(_adder, true); + } + + public bool Equals(AdderSetter other) + { + if (ReferenceEquals(null, other)) + return false; + if (ReferenceEquals(this, other)) + return true; + + return _getter.Equals(other._getter) && _adder.Equals(other._adder); + } + + public override bool Equals(object obj) + => Equals(obj as AdderSetter); + + public override int GetHashCode() + => (_getter.GetHashCode() * 397) ^ _adder.GetHashCode(); } } } diff --git a/src/XamlX/Transform/XamlTransformHelpers.cs b/src/XamlX/Transform/XamlTransformHelpers.cs index e141a7ce..10d147ea 100644 --- a/src/XamlX/Transform/XamlTransformHelpers.cs +++ b/src/XamlX/Transform/XamlTransformHelpers.cs @@ -57,11 +57,6 @@ IReadOnlyList FindPossibleAddersImpl() .OrderByDescending(x => x.ThisOrFirstParameter().Equals(actualType)) .ThenBy(x => x.ThisOrFirstParameter().IsInterface) .ToList(); - - // Add casts - for (var c = 0; c < rv.Count; c++) - if (!rv[c].ThisOrFirstParameter().Equals(type)) - rv[c] = new XamlMethodWithCasts(rv[c], new[] {type}.Concat(rv[c].Parameters)); if(context.Configuration.TypeMappings.IAddChildOfT != null) { diff --git a/src/XamlX/TypeSystem/TypeSystem.cs b/src/XamlX/TypeSystem/TypeSystem.cs index af8bcdc0..9b46228e 100644 --- a/src/XamlX/TypeSystem/TypeSystem.cs +++ b/src/XamlX/TypeSystem/TypeSystem.cs @@ -449,6 +449,9 @@ public static IXamlConstructor FindConstructor(this IXamlType type, List !type.IsValueType || type.IsNullable(); + public static bool IsNullable(this IXamlType type) { var def = type.GenericTypeDefinition; diff --git a/tests/XamlParserTests/DynamicSettersTests.cs b/tests/XamlParserTests/DynamicSettersTests.cs new file mode 100644 index 00000000..ec395d10 --- /dev/null +++ b/tests/XamlParserTests/DynamicSettersTests.cs @@ -0,0 +1,317 @@ +using System; +using Xunit; + +namespace XamlParserTests +{ + public class DynamicSettersClass + { + internal bool AddT1Called; + internal T1 T1Result; + + internal bool AddT2Called; + internal T2 T2Result; + + public void Add(T1 value) + { + AddT1Called = true; + T1Result = value; + } + + public void Add(T2 value) + { + AddT2Called = true; + T2Result = value; + } + } + + public class DynamicProvider + { + public enum ProvidedValueType { Null, String, Uri, Int32, TimeSpan, DateTime } + + public ProvidedValueType ProvidedValue { get; set; } + + public object ProvideValue() + { + return ProvidedValue switch + { + ProvidedValueType.Null => null, + ProvidedValueType.String => "foo", + ProvidedValueType.Uri => new Uri("https://avaloniaui.net/"), + ProvidedValueType.Int32 => 1234, + ProvidedValueType.TimeSpan => new TimeSpan(12, 34, 56, 789), + ProvidedValueType.DateTime => DateTime.Now, + _ => throw new ArgumentOutOfRangeException() + }; + } + } + + public class DynamicSettersTests : CompilerTestBase + { + [Fact] + public void Dynamic_Setter_With_Reference_Types_And_Null_Argument_Should_Match_Any() + { + var result = (DynamicSettersClass) CompileAndRun(@" + + + +"); + // we can't be sure that AddT1 is always called because it's first: metadata order isn't guaranteed + Assert.True(result.AddT1Called ^ result.AddT2Called); + Assert.Null(result.T1Result); + Assert.Null(result.T2Result); + } + + [Fact] + public void Dynamic_Setter_With_Reference_Types_And_Typed_Argument_Should_Match_1() + { + var result = (DynamicSettersClass) CompileAndRun(@" + + + +"); + Assert.True(result.AddT1Called); + Assert.Equal("foo", result.T1Result); + Assert.False(result.AddT2Called); + Assert.Null(result.T2Result); + } + + [Fact] + public void Dynamic_Setter_With_Reference_Types_And_Typed_Argument_Should_Match_2() + { + var result = (DynamicSettersClass) CompileAndRun(@" + + + +"); + Assert.False(result.AddT1Called); + Assert.Null(result.T1Result); + Assert.True(result.AddT2Called); + Assert.Equal(new Uri("https://avaloniaui.net/"), result.T2Result); + } + + [Fact] + public void Dynamic_Setter_With_Value_Types_And_Null_Argument_Should_Throw_NullReferenceException() + { + Assert.Throws(() => CompileAndRun(@" + + + +")); + } + + [Fact] + public void Dynamic_Setter_With_Value_Types_And_Typed_Argument_Should_Match_1() + { + var result = (DynamicSettersClass) CompileAndRun(@" + + + +"); + Assert.True(result.AddT1Called); + Assert.Equal(1234, result.T1Result); + Assert.False(result.AddT2Called); + Assert.Equal(default, result.T2Result); + } + + [Fact] + public void Dynamic_Setter_With_Value_Types_And_Typed_Argument_Should_Match_2() + { + var result = (DynamicSettersClass) CompileAndRun(@" + + + +"); + Assert.False(result.AddT1Called); + Assert.Equal(default, result.T1Result); + Assert.True(result.AddT2Called); + Assert.Equal(new TimeSpan(12, 34, 56, 789), result.T2Result); + } + + [Fact] + public void Dynamic_Setter_With_Nullable_Value_Types_And_Null_Argument_Should_Match_Any() + { + var result = (DynamicSettersClass) CompileAndRun(@" + + + +"); + // we can't be sure that AddT1 is always called because it's first: metadata order isn't guaranteed + Assert.True(result.AddT1Called ^ result.AddT2Called); + Assert.Null(result.T1Result); + Assert.Null(result.T2Result); + } + + [Fact] + public void Dynamic_Setter_With_Nullable_Value_Types_And_Typed_Argument_Should_Match_1() + { + var result = (DynamicSettersClass) CompileAndRun(@" + + + +"); + Assert.True(result.AddT1Called); + Assert.Equal(1234, result.T1Result); + Assert.False(result.AddT2Called); + Assert.Equal(default, result.T2Result); + } + + [Fact] + public void Dynamic_Setter_With_Nullable_Value_Types_And_Typed_Argument_Should_Match_2() + { + var result = (DynamicSettersClass) CompileAndRun(@" + + + +"); + Assert.False(result.AddT1Called); + Assert.Equal(default, result.T1Result); + Assert.True(result.AddT2Called); + Assert.Equal(new TimeSpan(12, 34, 56, 789), result.T2Result); + } + + [Fact] + public void Dynamic_Setter_With_Value_Type_And_Reference_Type_And_Null_Argument_Should_Match_Reference() + { + var result = (DynamicSettersClass) CompileAndRun(@" + + + +"); + Assert.False(result.AddT1Called); + Assert.Equal(default, result.T1Result); + Assert.True(result.AddT2Called); + Assert.Null(result.T2Result); + } + + [Fact] + public void Dynamic_Setter_With_Value_Type_And_Reference_Type_And_Value_Type_Argument_Should_Match_Value_Type() + { + var result = (DynamicSettersClass) CompileAndRun(@" + + + +"); + Assert.True(result.AddT1Called); + Assert.Equal(1234, result.T1Result); + Assert.False(result.AddT2Called); + Assert.Null(result.T2Result); + } + + [Fact] + public void Dynamic_Setter_With_Nullable_Value_Type_And_Reference_Type_And_Null_Argument_Should_Match_Any() + { + var result = (DynamicSettersClass) CompileAndRun(@" + + + +"); + // we can't be sure that AddT1 is always called because it's first: metadata order isn't guaranteed + Assert.True(result.AddT1Called ^ result.AddT2Called); + Assert.Null(result.T1Result); + Assert.Null(result.T2Result); + } + + [Fact] + public void Dynamic_Setter_With_Nullable_Value_Type_And_Reference_Type_And_Value_Type_Argument_Should_Match_Nullable_Value_Type() + { + var result = (DynamicSettersClass) CompileAndRun(@" + + + +"); + Assert.True(result.AddT1Called); + Assert.Equal(1234, result.T1Result); + Assert.False(result.AddT2Called); + Assert.Null(result.T2Result); + } + + [Fact] + public void Dynamic_Setter_With_Nullable_Value_Type_And_Reference_Type_And_Reference_Type_Argument_Should_Match_Reference_Type() + { + var result = (DynamicSettersClass) CompileAndRun(@" + + + +"); + Assert.False(result.AddT1Called); + Assert.Equal(default, result.T1Result); + Assert.True(result.AddT2Called); + Assert.Equal("foo", result.T2Result); + } + + [Theory] + [InlineData("sys:Int32", "sys:TimeSpan")] + [InlineData("sys:String", "sys:Uri")] + [InlineData("sys:Nullable(sys:Int32)", "sys:Nullable(sys:TimeSpan)")] + [InlineData("sys:Int32", "sys:String")] + [InlineData("sys:Nullable(sys:Int32)", "sys:String")] + public void Dynamic_Setter_With_Mismatched_Argument_Should_Throw_InvalidCastException(string type1, string type2) + { + Assert.Throws(() => CompileAndRun($@" + + + +")); + } + } +} diff --git a/tests/XamlParserTests/IntrinsicsTests.cs b/tests/XamlParserTests/IntrinsicsTests.cs index 9c01920a..b4c336fc 100644 --- a/tests/XamlParserTests/IntrinsicsTests.cs +++ b/tests/XamlParserTests/IntrinsicsTests.cs @@ -21,6 +21,15 @@ public class IntrinsicsTestsClass public const double DoubleConstant = 3; } + public class IntrinsicsListTestsClass + { + internal int AddInt32CallCount; + internal int AddObjectCallCount; + + public void Add(int value) => ++AddInt32CallCount; + public void Add(object value) => ++AddObjectCallCount; + } + public enum IntrinsicsTestsEnum : long { Foo = 100500 @@ -47,6 +56,19 @@ public void Null_Extension_Should_Cause_Compilation_Error_When_Applied_To_Value_ ")); } + [Fact] + public void Null_Extension_Should_Disregard_Value_Type_Overloads() + { + var res = (IntrinsicsListTestsClass) CompileAndRun($@" + + + +"); + + Assert.Equal(0, res.AddInt32CallCount); + Assert.Equal(2, res.AddObjectCallCount); + } + [Theory, InlineData(typeof(IntrinsicsTestsClass), ""), InlineData(typeof(List), "") @@ -89,8 +111,8 @@ public void Static_Extension_Resolves_Values(object expected, string r) public void Static_Extension_Resolves_Enum_Values() { Static_Extension_Resolves_Values(IntrinsicsTestsEnum.Foo, "IntrinsicsTestsEnum.Foo"); - } - + } + [Theory, InlineData(true, "x:True"), InlineData(false, "x:False")] @@ -101,8 +123,8 @@ public void Boolean_Extension_Can_Be_Set_To_Object(bool expected, string value) <{value}/> "); Assert.Equal(expected, res.ObjectProperty); - } - + } + [Theory, InlineData(true, "x:True"), InlineData(false, "x:False")] @@ -113,8 +135,8 @@ public void Boolean_Extension_Can_Be_Set_To_Bool(bool expected, string value) <{value}/> "); Assert.Equal(expected, res.BoolProperty); - } - + } + [Theory, InlineData(true, "x:True"), InlineData(false, "x:False")] @@ -125,10 +147,10 @@ public void Boolean_Extension_Can_Be_Set_To_NullableBool(bool expected, string v <{value}/> "); Assert.Equal(expected, res.NullableBoolProperty); - } - - [Theory, - InlineData(true, "x:True"), + } + + [Theory, + InlineData(true, "x:True"), InlineData(false, "x:False")] public void Boolean_Extension_Can_Be_Used_As_Markup_Ext(bool expected, string value) { @@ -136,8 +158,8 @@ public void Boolean_Extension_Can_Be_Used_As_Markup_Ext(bool expected, string va "); Assert.Equal(expected, res.ObjectProperty); - } - + } + [Fact] public void Boolean_Extension_Should_Cause_Compilation_Error_When_Applied_To_Wrong_Type() { @@ -146,4 +168,4 @@ public void Boolean_Extension_Should_Cause_Compilation_Error_When_Applied_To_Wro IntProperty='{x:True}' />")); } } -} \ No newline at end of file +} diff --git a/tests/XamlParserTests/ServiceProviderTests.cs b/tests/XamlParserTests/ServiceProviderTests.cs index 08bd7c26..25ec02de 100644 --- a/tests/XamlParserTests/ServiceProviderTests.cs +++ b/tests/XamlParserTests/ServiceProviderTests.cs @@ -225,7 +225,7 @@ public void Namespace_Info_Should_Be_Preserved() Helpers.StructDiff(nsList, new Dictionary> { - [""] = new List + [""] = new[] { new XamlXmlNamespaceInfoV1 { @@ -233,7 +233,7 @@ public void Namespace_Info_Should_Be_Preserved() ClrAssemblyName = typeof(ServiceProviderTests).Assembly.GetName().Name } }, - ["clr1"] = new List + ["clr1"] = new[] { new XamlXmlNamespaceInfoV1 { @@ -241,7 +241,7 @@ public void Namespace_Info_Should_Be_Preserved() ClrAssemblyName = "netstandard" } }, - ["clr2"] = new List + ["clr2"] = new[] { new XamlXmlNamespaceInfoV1 { @@ -272,21 +272,21 @@ public void Namespace_Info_Should_Be_Preserved_With_Using_Syntax() Helpers.StructDiff(nsList, new Dictionary> { - [""] = new List + [""] = new[] { new XamlXmlNamespaceInfoV1 { ClrNamespace = "XamlParserTests" } }, - ["clr1"] = new List + ["clr1"] = new[] { new XamlXmlNamespaceInfoV1 { ClrNamespace = "System.Collections.Generic" } }, - ["clr2"] = new List + ["clr2"] = new[] { new XamlXmlNamespaceInfoV1 { @@ -321,4 +321,4 @@ public void Unknown_Services_Should_Return_null() } } -} \ No newline at end of file +}