Browse Source

Changes after the reivew

Max Katz 3 years ago
parent
commit
cd83f8558f

+ 113 - 81
src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs

@@ -469,88 +469,9 @@ namespace Avalonia.Build.Tasks
 
                 foreach (var (populateMethod, resourceFilePath) in populateMethodsToTransform)
                 {
-                    foreach (var instruction in populateMethod.Body.Instructions.ToArray())
+                    if (!TransformXamlIncludes(engine, typeSystem, populateMethod, resourceFilePath, createRootServiceProviderMethod))
                     {
-                        const string resolveStyleIncludeName = "ResolveStyleInclude";
-                        const string resolveResourceInclude = "ResolveResourceInclude";
-                        if (instruction.OpCode == OpCodes.Call
-                            && instruction.Operand is MethodReference
-                            {
-                                Name: resolveStyleIncludeName or resolveResourceInclude,
-                                DeclaringType: { Name: "XamlIlRuntimeHelpers" }
-                            })
-                        {
-                            int lineNumber = 0, linePosition = 0;
-                            bool instructionsModified = false;
-                            try
-                            {
-                                var assetSource = (string)instruction.Previous.Previous.Previous.Operand;
-                                lineNumber = Convert.ToInt32(instruction.Previous.Previous.Operand);
-                                linePosition = Convert.ToInt32(instruction.Previous.Operand);
-
-                                var index = populateMethod.Body.Instructions.IndexOf(instruction);
-
-                                assetSource = assetSource.Replace("avares://", "");
-
-                                var assemblyNameSeparator = assetSource.IndexOf('/');
-                                var fileNameSeparator = assetSource.LastIndexOf('/');
-                                if (assemblyNameSeparator < 0 || fileNameSeparator < 0)
-                                {
-                                    throw new InvalidProgramException(
-                                        $"Invalid asset source \"{assetSource}\". It must contain assembly name and relative path.");
-                                }
-
-                                var assetAssemblyName = assetSource.Substring(0, assemblyNameSeparator);
-                                var assetAssembly = typeSystem.FindAssembly(assetAssemblyName)
-                                    ?? throw new InvalidProgramException($"Unable to resolve assembly \"{assetAssemblyName}\"");
-
-                                var fileName = Path.GetFileNameWithoutExtension(assetSource.Replace('/', '.'));
-                                if (assetAssembly.FindType(fileName) is { } type
-                                    && type.FindConstructor() is { } ctor)
-                                {
-                                    var ctorMethod =
-                                        asm.MainModule.ImportReference(typeSystem.GetMethodReference(ctor));
-                                    instructionsModified = true;
-                                    populateMethod.Body.Instructions[index - 3] = Instruction.Create(OpCodes.Nop);
-                                    populateMethod.Body.Instructions[index - 2] = Instruction.Create(OpCodes.Nop);
-                                    populateMethod.Body.Instructions[index - 1] = Instruction.Create(OpCodes.Nop);
-                                    populateMethod.Body.Instructions[index] = Instruction.Create(OpCodes.Newobj, ctorMethod);
-                                }
-                                else
-                                {
-                                    var resources = assetAssembly.FindType("CompiledAvaloniaXaml.!AvaloniaResources")
-                                        ?? throw new InvalidOperationException($"Unable to resolve \"!AvaloniaResources\" type on \"{assetAssemblyName}\" assembly");
-
-                                    var relativeName = "Build:" + assetSource.Substring(assemblyNameSeparator);
-                                    var buildMethod = resources.FindMethod(m => m.Name == relativeName)
-                                        ?? throw new InvalidOperationException($"Unable to resolve build method \"{relativeName}\" resource on the \"{assetAssemblyName}\" assembly");
-
-                                    var methodReference = asm.MainModule.ImportReference(typeSystem.GetMethodReference(buildMethod));
-                                    instructionsModified = true;
-                                    populateMethod.Body.Instructions[index - 3] = Instruction.Create(OpCodes.Nop);
-                                    populateMethod.Body.Instructions[index - 2] = Instruction.Create(OpCodes.Nop);
-                                    populateMethod.Body.Instructions[index - 1] = Instruction.Create(OpCodes.Call, createRootServiceProviderMethod);
-                                    populateMethod.Body.Instructions[index] = Instruction.Create(OpCodes.Call, methodReference);
-                                }
-                            }
-                            catch (Exception e)
-                            {
-                                if (instructionsModified)
-                                {
-                                    engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", "XAMLIL", resourceFilePath,
-                                        lineNumber, linePosition, lineNumber, linePosition,
-                                        e.Message, "", "Avalonia"));
-                                    return false;
-                                }
-                                else
-                                {
-                                    engine.LogWarningEvent(new BuildWarningEventArgs("Avalonia", "XAMLIL",
-                                        resourceFilePath,
-                                        lineNumber, linePosition, lineNumber, linePosition,
-                                        e.Message, "", "Avalonia"));
-                                }
-                            }
-                        }
+                        return false;
                     }
                 }
 
@@ -630,5 +551,116 @@ namespace Avalonia.Build.Tasks
 
             return true;
         }
+        
+        private static bool TransformXamlIncludes(
+            IBuildEngine engine, CecilTypeSystem typeSystem,
+            MethodDefinition populateMethod, string resourceFilePath,
+            MethodReference createRootServiceProviderMethod)
+        {
+            var asm = typeSystem.TargetAssemblyDefinition;
+            foreach (var instruction in populateMethod.Body.Instructions.ToArray())
+            {
+                const string resolveStyleIncludeName = "ResolveStyleInclude";
+                const string resolveResourceInclude = "ResolveResourceInclude";
+                if (instruction.OpCode == OpCodes.Call
+                    && instruction.Operand is MethodReference
+                    {
+                        Name: resolveStyleIncludeName or resolveResourceInclude,
+                        DeclaringType: { Name: "XamlIlRuntimeHelpers" }
+                    })
+                {
+                    int lineNumber = 0, linePosition = 0;
+                    bool instructionsModified = false;
+                    try
+                    {
+                        var assetSource = (string)instruction.Previous.Previous.Previous.Operand;
+                        lineNumber = GetConstValue(instruction.Previous.Previous);
+                        linePosition = GetConstValue(instruction.Previous);
+
+                        var index = populateMethod.Body.Instructions.IndexOf(instruction);
+
+                        assetSource = assetSource.Replace("avares://", "");
+
+                        var assemblyNameSeparator = assetSource.IndexOf('/');
+                        var fileNameSeparator = assetSource.LastIndexOf('/');
+                        if (assemblyNameSeparator < 0 || fileNameSeparator < 0)
+                        {
+                            throw new InvalidProgramException(
+                                $"Invalid asset source \"{assetSource}\". It must contain assembly name and relative path.");
+                        }
+
+                        var assetAssemblyName = assetSource.Substring(0, assemblyNameSeparator);
+                        var assetAssembly = typeSystem.FindAssembly(assetAssemblyName)
+                                            ?? throw new InvalidProgramException(
+                                                $"Unable to resolve assembly \"{assetAssemblyName}\"");
+
+                        var fileName = Path.GetFileNameWithoutExtension(assetSource.Replace('/', '.'));
+                        if (assetAssembly.FindType(fileName) is { } type
+                            && type.FindConstructor() is { } ctor)
+                        {
+                            var ctorMethod =
+                                asm.MainModule.ImportReference(typeSystem.GetMethodReference(ctor));
+                            instructionsModified = true;
+                            populateMethod.Body.Instructions[index - 3] = Instruction.Create(OpCodes.Nop);
+                            populateMethod.Body.Instructions[index - 2] = Instruction.Create(OpCodes.Nop);
+                            populateMethod.Body.Instructions[index - 1] = Instruction.Create(OpCodes.Nop);
+                            populateMethod.Body.Instructions[index] = Instruction.Create(OpCodes.Newobj, ctorMethod);
+                        }
+                        else
+                        {
+                            var resources = assetAssembly.FindType("CompiledAvaloniaXaml.!AvaloniaResources")
+                                            ?? throw new InvalidOperationException(
+                                                $"Unable to resolve \"!AvaloniaResources\" type on \"{assetAssemblyName}\" assembly");
+
+                            var relativeName = "Build:" + assetSource.Substring(assemblyNameSeparator);
+                            var buildMethod = resources.FindMethod(m => m.Name == relativeName)
+                                              ?? throw new InvalidOperationException(
+                                                  $"Unable to resolve build method \"{relativeName}\" resource on the \"{assetAssemblyName}\" assembly");
+
+                            var methodReference = asm.MainModule.ImportReference(typeSystem.GetMethodReference(buildMethod));
+                            instructionsModified = true;
+                            populateMethod.Body.Instructions[index - 3] = Instruction.Create(OpCodes.Nop);
+                            populateMethod.Body.Instructions[index - 2] = Instruction.Create(OpCodes.Nop);
+                            populateMethod.Body.Instructions[index - 1] =
+                                Instruction.Create(OpCodes.Call, createRootServiceProviderMethod);
+                            populateMethod.Body.Instructions[index] = Instruction.Create(OpCodes.Call, methodReference);
+                        }
+                    }
+                    catch (Exception e)
+                    {
+                        if (instructionsModified)
+                        {
+                            engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", "XAMLIL", resourceFilePath,
+                                lineNumber, linePosition, lineNumber, linePosition,
+                                e.Message, "", "Avalonia"));
+                            return false;
+                        }
+                        else
+                        {
+                            engine.LogWarningEvent(new BuildWarningEventArgs("Avalonia", "XAMLIL",
+                                resourceFilePath,
+                                lineNumber, linePosition, lineNumber, linePosition,
+                                e.Message, "", "Avalonia"));
+                        }
+                    }
+                    
+                    static int GetConstValue(Instruction instruction)
+                    {
+                        if (instruction.OpCode is { Code : >= Code.Ldc_I4_0 and <= Code.Ldc_I4_8 })
+                        {
+                            return instruction.OpCode.Code - Code.Ldc_I4_0;
+                        }
+                        if (instruction.Operand is not null)
+                        {
+                            return Convert.ToInt32(instruction.Operand);
+                        }
+
+                        return 0;
+                    }
+                }
+            }
+
+            return true;
+        }
     }
 }

+ 13 - 12
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAssetIncludeTransformer.cs

@@ -10,28 +10,28 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
 
 internal class AvaloniaXamlIlAssetIncludeTransformer : IXamlAstTransformer
 {
-    private const string StyleIncludeName = "StyleInclude";
-    private const string ResourceIncludeName = "ResourceInclude";
-
     public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
     {
         if (node is not XamlAstObjectNode objectNode
-            || objectNode.Type.GetClrType() is not {Name: StyleIncludeName or ResourceIncludeName} objectNodeType)
+            || (objectNode.Type.GetClrType() != context.GetAvaloniaTypes().StyleInclude
+                && objectNode.Type.GetClrType() != context.GetAvaloniaTypes().ResourceInclude))
         {
             return node;
         }
 
+        var nodeTypeName = objectNode.Type.GetClrType().Name;
+
         var sourceProperty = objectNode.Children.OfType<XamlAstXamlPropertyValueNode>().FirstOrDefault(n => n.Property.GetClrProperty().Name == "Source");
         var directives = objectNode.Children.OfType<XamlAstXmlDirective>().ToList();
         if (sourceProperty is null
             || objectNode.Children.Count != (directives.Count + 1))
         {
-            // Don't transform node with any other property, as we don't know how to transform them.
-            return node;
+            throw new XamlParseException($"Unexpected property on the {nodeTypeName} node", node);
         }
 
         if (sourceProperty.Values.OfType<XamlAstTextNode>().FirstOrDefault() is not { } sourceTextNode)
         {
+            // TODO: make it a compiler warning
             // Source value can be set with markup extension instead of a text node, we don't support it here yet.
             return node;
         }
@@ -39,18 +39,19 @@ internal class AvaloniaXamlIlAssetIncludeTransformer : IXamlAstTransformer
         var originalAssetPath = sourceTextNode.Text;
         if (!(originalAssetPath.StartsWith("avares://") || originalAssetPath.StartsWith("/")))
         {
-            // Only "avares" protocol supported or relative paths.
-            return node;
+            throw new XamlParseException(
+                $"{nodeTypeName}.Source supports only \"avares://\" absolute paths or relative paths starting with \"/\"",
+                sourceTextNode);
         }
 
-        var runtimeHelpers = context.Configuration.TypeSystem.FindType("Avalonia.Markup.Xaml.XamlIl.Runtime.XamlIlRuntimeHelpers");
-        var markerMethodName = "Resolve" + objectNodeType.Name;
+        var runtimeHelpers = context.GetAvaloniaTypes().RuntimeHelpers;
+        var markerMethodName = "Resolve" + nodeTypeName;
         var markerMethod = runtimeHelpers.FindMethod(m => m.Name == markerMethodName && m.Parameters.Count == 3);
         if (markerMethod is null)
         {
-            throw new XamlParseException($"Marker method \"{markerMethodName}\" was not found for the \"{objectNodeType.Name}\" node", node);
+            throw new XamlParseException($"Marker method \"{markerMethodName}\" was not found for the \"{nodeTypeName}\" node", node);
         }
-
+        
         return new XamlValueWithManipulationNode(
             node,
             new AssetIncludeMethodNode(node, markerMethod, originalAssetPath),

+ 4 - 0
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs

@@ -103,6 +103,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
         public IXamlType TextTrimming { get; }
         public IXamlType ISetter { get; }
         public IXamlType IStyle { get; }
+        public IXamlType StyleInclude { get; }
+        public IXamlType ResourceInclude { get; }
         public IXamlType IResourceDictionary { get; }
         public IXamlType ResourceDictionary { get; }
         public IXamlMethod ResourceDictionaryDeferredAdd { get; }
@@ -234,6 +236,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
             TextTrimming = cfg.TypeSystem.GetType("Avalonia.Media.TextTrimming");
             ISetter = cfg.TypeSystem.GetType("Avalonia.Styling.ISetter");
             IStyle = cfg.TypeSystem.GetType("Avalonia.Styling.IStyle");
+            StyleInclude = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Styling.StyleInclude");
+            ResourceInclude = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Styling.ResourceInclude");
             IResourceDictionary = cfg.TypeSystem.GetType("Avalonia.Controls.IResourceDictionary");
             ResourceDictionary = cfg.TypeSystem.GetType("Avalonia.Controls.ResourceDictionary");
             ResourceDictionaryDeferredAdd = ResourceDictionary.FindMethod("AddDeferred", XamlIlTypes.Void, true, XamlIlTypes.Object,