diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index b42cbd3109..12f9d85b04 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -52,7 +52,9 @@ - + + + diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs index a0a014d894..ea2ebf03fa 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs @@ -114,7 +114,7 @@ namespace Avalonia.Markup.Xaml.XamlIl InitializeSre(); var asm = localAssembly == null ? null : _sreTypeSystem.GetAssembly(localAssembly); var compiler = new AvaloniaXamlIlCompiler(new XamlIlTransformerConfiguration(_sreTypeSystem, asm, - _sreMappings, _sreXmlns, CustomValueConverter)); + _sreMappings, _sreXmlns, AvaloniaXamlIlLanguage.CustomValueConverter)); var tb = _sreBuilder.DefineType("Builder_" + Guid.NewGuid().ToString("N") + "_" + uri); IXamlIlType overrideType = null; @@ -214,7 +214,8 @@ namespace Avalonia.Markup.Xaml.XamlIl var compiler = new AvaloniaXamlIlCompiler(new XamlIlTransformerConfiguration(_cecilTypeSystem, localAssembly == null ? null : _cecilTypeSystem.FindAssembly(localAssembly.GetName().Name), - _cecilMappings, XamlIlXmlnsMappings.Resolve(_cecilTypeSystem, _cecilMappings), CustomValueConverter)); + _cecilMappings, XamlIlXmlnsMappings.Resolve(_cecilTypeSystem, _cecilMappings), + AvaloniaXamlIlLanguage.CustomValueConverter)); compiler.ParseAndCompile(xaml, uri.ToString(), tb, overrideType); var asmPath = Path.Combine(_cecilEmitDir, safeUri + ".dll"); using(var f = File.Create(asmPath)) @@ -224,26 +225,5 @@ namespace Avalonia.Markup.Xaml.XamlIl _cecilGeneratedCache[safeUri] = loaded; return LoadOrPopulate(loaded, rootInstance); } - - private static bool CustomValueConverter(XamlIlAstTransformationContext context, - IXamlIlAstValueNode node, IXamlIlType type, out IXamlIlAstValueNode result) - { - if (type.FullName == "System.TimeSpan" - && node is XamlIlAstTextNode tn - && !tn.Text.Contains(":")) - { - var seconds = double.Parse(tn.Text, CultureInfo.InvariantCulture); - result = new XamlIlStaticOrTargetedReturnMethodCallNode(tn, - type.FindMethod("FromSeconds", type, false, context.Configuration.WellKnownTypes.Double), - new[] - { - new XamlIlConstantNode(tn, context.Configuration.WellKnownTypes.Double, seconds) - }); - return true; - } - - result = null; - return false; - } } } diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaPropertyDescriptorEmitter.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaPropertyDescriptorEmitter.cs deleted file mode 100644 index 97454f9082..0000000000 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaPropertyDescriptorEmitter.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Linq; -using XamlIl.Transform; -using XamlIl.TypeSystem; - -namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions -{ - public class AvaloniaPropertyDescriptorEmitter - { - public static bool Emit(XamlIlEmitContext context, IXamlIlEmitter emitter, IXamlIlProperty property) - { - var type = (property.Getter ?? property.Setter).DeclaringType; - var name = property.Name + "Property"; - var found = type.Fields.FirstOrDefault(f => f.IsStatic && f.Name == name); - if (found == null) - return false; - - emitter.Ldsfld(found); - return true; - } - } -} diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs index a19896e77b..08a4d0e4db 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs @@ -28,12 +28,14 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions new IXamlIlAstTransformer[] { new AvaloniaXamlIlSelectorTransformer(), - new AvaloniaXamlIlSetterTransformer() + new AvaloniaXamlIlSetterTransformer(), + new AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer(), } ); // After everything else Transformers.Add(new AddNameScopeRegistration()); + Transformers.Add(new AvaloniaXamlIlMetadataRemover()); } diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlLanguage.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlLanguage.cs index ff8122ad30..94ebb138eb 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlLanguage.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlLanguage.cs @@ -1,5 +1,9 @@ using System.Collections.Generic; +using System.Globalization; using System.Linq; +using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; +using XamlIl; +using XamlIl.Ast; using XamlIl.Transform; using XamlIl.TypeSystem; @@ -64,7 +68,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions return true; return false; }, - ProvideValueTargetPropertyEmitter = AvaloniaPropertyDescriptorEmitter.Emit, + ProvideValueTargetPropertyEmitter = XamlIlAvaloniaPropertyHelper.Emit, }; rv.CustomAttributeResolver = new AttributeResolver(typeSystem, rv); return rv; @@ -92,7 +96,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions => AddType(typeSystem.GetType(type), typeSystem.GetType(conv)); - Add("Avalonia.AvaloniaProperty","Avalonia.Markup.Xaml.Converters.AvaloniaPropertyTypeConverter"); + //Add("Avalonia.AvaloniaProperty","Avalonia.Markup.Xaml.Converters.AvaloniaPropertyTypeConverter"); Add("Avalonia.Media.Imaging.IBitmap","Avalonia.Markup.Xaml.Converters.BitmapTypeConverter"); var ilist = typeSystem.GetType("System.Collections.Generic.IList`1"); AddType(ilist.MakeGenericType(typeSystem.GetType("Avalonia.Point")), @@ -152,5 +156,37 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions return null; } } + + public static bool CustomValueConverter(XamlIlAstTransformationContext context, + IXamlIlAstValueNode node, IXamlIlType type, out IXamlIlAstValueNode result) + { + if (type.FullName == "System.TimeSpan" + && node is XamlIlAstTextNode tn + && !tn.Text.Contains(":")) + { + var seconds = double.Parse(tn.Text, CultureInfo.InvariantCulture); + result = new XamlIlStaticOrTargetedReturnMethodCallNode(tn, + type.FindMethod("FromSeconds", type, false, context.Configuration.WellKnownTypes.Double), + new[] + { + new XamlIlConstantNode(tn, context.Configuration.WellKnownTypes.Double, seconds) + }); + return true; + } + + if (type.FullName == "Avalonia.AvaloniaProperty") + { + var scope = context.ParentNodes().OfType().FirstOrDefault(); + if (scope == null) + throw new XamlIlLoadException("Unable to find the parent scope for AvaloniaProperty lookup", node); + if (!(node is XamlIlAstTextNode text)) + throw new XamlIlLoadException("Property should be a text node", node); + result = XamlIlAvaloniaPropertyHelper.CreateNode(context, text.Text, scope.TargetType, text); + return true; + } + + result = null; + return false; + } } } diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs new file mode 100644 index 0000000000..edc9d6a389 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs @@ -0,0 +1,65 @@ +using System.Linq; +using XamlIl.Ast; +using XamlIl.Transform; +using XamlIl.TypeSystem; + +namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers +{ + class AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer : IXamlIlAstTransformer + { + public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node) + { + if (!(node is XamlIlAstObjectNode on + && on.Type.GetClrType().FullName == "Avalonia.Markup.Xaml.Templates.ControlTemplate")) + return node; + var tt = on.Children.OfType().FirstOrDefault(ch => + ch.Property.GetClrProperty().Name == "TargetType"); + + if (context.ParentNodes().FirstOrDefault() is AvaloniaXamlIlTargetTypeMetadataNode) + // Deja vu. I've just been in this place before + return node; + + IXamlIlAstTypeReference targetType; + + if ((tt?.Values.FirstOrDefault() is XamlIlTypeExtensionNode tn)) + { + targetType = tn.Type; + } + else + { + var parentScope = context.ParentNodes().OfType() + .FirstOrDefault(); + if (parentScope?.Type == AvaloniaXamlIlTargetTypeMetadataNode.ScopeType.Style) + targetType = parentScope.TargetType; + else + targetType = new XamlIlAstClrTypeReference(node, + context.Configuration.TypeSystem.GetType("Avalonia.Controls.Control")); + } + + + + return new AvaloniaXamlIlTargetTypeMetadataNode(on, targetType, + AvaloniaXamlIlTargetTypeMetadataNode.ScopeType.ControlTemplate); + } + } + + class AvaloniaXamlIlTargetTypeMetadataNode : XamlIlValueWithSideEffectNodeBase + { + public IXamlIlAstTypeReference TargetType { get; set; } + public ScopeType Type { get; } + + public enum ScopeType + { + Style, + ControlTemplate + } + + public AvaloniaXamlIlTargetTypeMetadataNode(IXamlIlAstValueNode value, IXamlIlAstTypeReference targetType, + ScopeType type) + : base(value, value) + { + TargetType = targetType; + Type = type; + } + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlMetadataRemover.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlMetadataRemover.cs new file mode 100644 index 0000000000..7599c0b318 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlMetadataRemover.cs @@ -0,0 +1,17 @@ +using System.Linq; +using XamlIl.Ast; +using XamlIl.Transform; + +namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers +{ + public class AvaloniaXamlIlMetadataRemover : IXamlIlAstTransformer + { + public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node) + { + if (node is AvaloniaXamlIlTargetTypeMetadataNode md) + return md.Value; + + return node; + } + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs index e11390cdb5..4569accb74 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs @@ -15,14 +15,24 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers { public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node) { - if (!(node is XamlIlAstXamlPropertyValueNode pn - && pn.Property.GetClrProperty().PropertyType.FullName == "Avalonia.Styling.Selector")) + if (!(node is XamlIlAstObjectNode on && on.Type.GetClrType().FullName == "Avalonia.Styling.Style")) + return node; + + var pn = on.Children.OfType() + .FirstOrDefault(p => p.Property.GetClrProperty().Name == "Selector"); + + if (pn == null) return node; if (pn.Values.Count != 1) throw new XamlIlParseException("Selector property should should have exactly one value", node); - if (!(pn.Values[0] is XamlIlAstTextNode tn)) + + if (pn.Values[0] is XamlIlSelectorNode) + //Deja vu. I've just been in this place before return node; + + if (!(pn.Values[0] is XamlIlAstTextNode tn)) + throw new XamlIlParseException("Selector property should be a text node", node); var selectorType = pn.Property.GetClrProperty().PropertyType; @@ -104,7 +114,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers var selector = Create(parsed, (p, n) => XamlIlTypeReferenceResolver.ResolveType(context, $"{p}:{n}", node, true)); pn.Values[0] = selector; - return node; + + return new AvaloniaXamlIlTargetTypeMetadataNode(on, + new XamlIlAstClrTypeReference(selector, selector.TargetType), + AvaloniaXamlIlTargetTypeMetadataNode.ScopeType.Style); } } @@ -263,7 +276,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public override IXamlIlType TargetType => Previous?.TargetType; protected override void DoEmit(XamlIlEmitContext context, IXamlIlEmitter codeGen) { - if (!AvaloniaPropertyDescriptorEmitter.Emit(context, codeGen, Property)) + if (!XamlIlAvaloniaPropertyHelper.Emit(context, codeGen, Property)) throw new XamlIlLoadException( $"{Property.Name} of {(Property.Setter ?? Property.Getter).DeclaringType.GetFqn()} doesn't seem to be an AvaloniaProperty", this); diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs index bb439730ff..a15c162f84 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs @@ -43,36 +43,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers if (propertyName == null) throw new XamlIlParseException("Setter.Property must be a string", node); - - var selectorTypeReference = new XamlIlAstClrTypeReference(selector, selector.TargetType); - XamlIlAstNamePropertyReference forgedReference; - - var parser = new PropertyParser(); - var parsedPropertyName = parser.Parse(new CharacterReader(propertyName.AsSpan())); - if(parsedPropertyName.owner == null) - forgedReference = new XamlIlAstNamePropertyReference(property.Values[0], selectorTypeReference, - propertyName, selectorTypeReference); - else - { - var xmlOwner = parsedPropertyName.ns; - if (xmlOwner != null) - xmlOwner += ":"; - xmlOwner += parsedPropertyName.owner; - - var t = XamlIlTypeReferenceResolver.ResolveType(context, xmlOwner, property.Values[0], true); - var tref = new XamlIlAstClrTypeReference(property.Values[0], t); - forgedReference = new XamlIlAstNamePropertyReference(property.Values[0], - tref, parsedPropertyName.name, tref); - } - - var clrProperty = - ((XamlIlAstClrPropertyReference)new XamlIlPropertyReferenceResolver().Transform(context, - forgedReference)).Property; + var avaloniaPropertyNode = XamlIlAvaloniaPropertyHelper.CreateNode(context, propertyName, + new XamlIlAstClrTypeReference(selector, selector.TargetType), property.Values[0]); property.Values = new List { - new XamlIlAvaloniaPropertyNode(property.Values[0], property.Property.GetClrProperty().PropertyType, - clrProperty) + avaloniaPropertyNode }; var valueProperty = on.Children @@ -80,9 +56,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers if (valueProperty?.Values?.Count == 1 && valueProperty.Values[0] is XamlIlAstTextNode) { if (!XamlIlTransformHelpers.TryGetCorrectlyTypedValue(context, valueProperty.Values[0], - clrProperty.PropertyType, out var converted)) + avaloniaPropertyNode.Property.PropertyType, out var converted)) throw new XamlIlParseException( - $"Unable to convert property value to {clrProperty.PropertyType.GetFqn()}", + $"Unable to convert property value to {avaloniaPropertyNode.Property.PropertyType.GetFqn()}", valueProperty.Values[0]); valueProperty.Values = new List @@ -96,23 +72,4 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers } } - - class XamlIlAvaloniaPropertyNode : XamlIlAstNode, IXamlIlAstValueNode, IXamlIlAstEmitableNode - { - public XamlIlAvaloniaPropertyNode(IXamlIlLineInfo lineInfo, IXamlIlType type, IXamlIlProperty property) : base(lineInfo) - { - Type = new XamlIlAstClrTypeReference(this, type); - Property = property; - } - - public IXamlIlProperty Property { get; set; } - - public IXamlIlAstTypeReference Type { get; } - public XamlIlNodeEmitResult Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen) - { - if (!AvaloniaPropertyDescriptorEmitter.Emit(context, codeGen, Property)) - throw new XamlIlLoadException(Property.Name + " is not an AvaloniaProperty", this); - return XamlIlNodeEmitResult.Type(0, Type.GetClrType()); - } - } } diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs new file mode 100644 index 0000000000..0d7d88588d --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Avalonia.Markup.Xaml.Parsers; +using Avalonia.Utilities; +using XamlIl; +using XamlIl.Ast; +using XamlIl.Transform; +using XamlIl.Transform.Transformers; +using XamlIl.TypeSystem; + +namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions +{ + class XamlIlAvaloniaPropertyHelper + { + public static bool Emit(XamlIlEmitContext context, IXamlIlEmitter emitter, IXamlIlProperty property) + { + var type = (property.Getter ?? property.Setter).DeclaringType; + var name = property.Name + "Property"; + var found = type.Fields.FirstOrDefault(f => f.IsStatic && f.Name == name); + if (found == null) + return false; + + emitter.Ldsfld(found); + return true; + } + + public static XamlIlAvaloniaPropertyNode CreateNode(XamlIlAstTransformationContext context, + string propertyName, IXamlIlAstTypeReference selectorTypeReference, IXamlIlLineInfo lineInfo) + { + XamlIlAstNamePropertyReference forgedReference; + + var parser = new PropertyParser(); + + var parsedPropertyName = parser.Parse(new CharacterReader(propertyName.AsSpan())); + if(parsedPropertyName.owner == null) + forgedReference = new XamlIlAstNamePropertyReference(lineInfo, selectorTypeReference, + propertyName, selectorTypeReference); + else + { + var xmlOwner = parsedPropertyName.ns; + if (xmlOwner != null) + xmlOwner += ":"; + xmlOwner += parsedPropertyName.owner; + + var t = XamlIlTypeReferenceResolver.ResolveType(context, xmlOwner, lineInfo, true); + var tref = new XamlIlAstClrTypeReference(lineInfo, t); + forgedReference = new XamlIlAstNamePropertyReference(lineInfo, + tref, parsedPropertyName.name, tref); + } + + var clrProperty = + ((XamlIlAstClrPropertyReference)new XamlIlPropertyReferenceResolver().Transform(context, + forgedReference)).Property; + return new XamlIlAvaloniaPropertyNode(lineInfo, + context.Configuration.TypeSystem.GetType("Avalonia.AvaloniaProperty"), + clrProperty); + } + } + + class XamlIlAvaloniaPropertyNode : XamlIlAstNode, IXamlIlAstValueNode, IXamlIlAstEmitableNode + { + public XamlIlAvaloniaPropertyNode(IXamlIlLineInfo lineInfo, IXamlIlType type, IXamlIlProperty property) : base(lineInfo) + { + Type = new XamlIlAstClrTypeReference(this, type); + Property = property; + } + + public IXamlIlProperty Property { get; } + + public IXamlIlAstTypeReference Type { get; } + public XamlIlNodeEmitResult Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen) + { + if (!XamlIlAvaloniaPropertyHelper.Emit(context, codeGen, Property)) + throw new XamlIlLoadException(Property.Name + " is not an AvaloniaProperty", this); + return XamlIlNodeEmitResult.Type(0, Type.GetClrType()); + } + } +}