Răsfoiți Sursa

XAML warnings/diagnostics support (#13447)

* Add diagnostics support to the Avalonia.Build.Tasks

* HostApp and generators build fix

* Diagnostics support in Avalonia XAML

* Support multiple style selector errors at once

* Improve avalonia intrinsics error handling + add tests

* Add CompiledBindings multiple errors tests

* Fix name generator

* Make AvaloniaXamlIlDuplicateSettersChecker a warning

* Fix Style_Parser_Throws_For_Duplicate_Setter test

* Make XamlLoaderUnreachable respect warnings settings

* Add AvaloniaXamlIlStyleValidatorTransformer

* Throw more specific exceptions instead of XamlParseException

* Get rid of XamlXDiagnosticCode to simplify diagnostics code

* Simplify XAML exceptions by avoiding DiagnosticCode in them

* Simplify XamlCompilerDiagnosticsFilter

* Don't use AvaloniaXamlDiagnosticCodes in Avalonia.Generators

* Fix some error handlings in compiler task

* Update editor config for in-solution analysis

* Update XamlX

* Fix missing document path

* Avoid Description field usage

* Add AvaloniaXamlVerboseExceptions property and make exception formatting customizable

* Make Avalonia.NameGenerator not crash if there are XAML errors, members should still be generated

* Update tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleIncludeTests.cs

---------

Co-authored-by: Jumar Macato <[email protected]>
Max Katz 1 an în urmă
părinte
comite
ea64505600
48 a modificat fișierele cu 923 adăugiri și 342 ștergeri
  1. 6 0
      .editorconfig
  2. 1 0
      build/BuildTargets.targets
  3. 4 1
      packages/Avalonia/AvaloniaBuildTasks.targets
  4. 9 9
      packages/Avalonia/AvaloniaRules.Project.xml
  5. 1 0
      src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
  6. 0 14
      src/Avalonia.Build.Tasks/BuildEngineErrorCode.cs
  7. 7 1
      src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs
  8. 89 20
      src/Avalonia.Build.Tasks/Extensions.cs
  9. 3 2
      src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs
  10. 70 0
      src/Avalonia.Build.Tasks/XamlCompilerDiagnosticsFilter.cs
  11. 72 45
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
  12. 1 0
      src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj
  13. 28 2
      src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs
  14. 0 8
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AstNodes/AvaloniaXamlIlArrayConstantAstNode.cs
  15. 63 0
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlDiagnosticCodes.cs
  16. 5 4
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
  17. 3 2
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs
  18. 41 15
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs
  19. 10 33
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/IXamlAstGroupTransformer.cs
  20. 31 29
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs
  21. 10 6
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlMergeResourceGroupTransformer.cs
  22. 10 1
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaBindingExtensionTransformer.cs
  23. 12 12
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs
  24. 1 1
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs
  25. 2 2
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlThemeTransformer.cs
  26. 17 5
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs
  27. 4 1
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDuplicateSettersChecker.cs
  28. 8 8
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlOptionMarkupExtensionTransformer.cs
  29. 18 10
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlPropertyPathTransformer.cs
  30. 25 17
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs
  31. 1 1
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTargetTypeMetadataTransformer.cs
  32. 15 6
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs
  33. 34 0
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlStyleValidatorTransformer.cs
  34. 0 26
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlDocumentParseException.cs
  35. 2 2
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs
  36. 13 14
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs
  37. 1 1
      src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github
  38. 47 0
      src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderConfiguration.cs
  39. 1 0
      src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj
  40. 6 2
      src/tools/Avalonia.Generators/Compiler/MiniCompiler.cs
  41. 1 12
      tests/Avalonia.Generators.Tests/MiniCompilerTests.cs
  42. 3 2
      tests/Avalonia.Generators.Tests/Views/xNamedControls.xml
  43. 29 24
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs
  44. 134 0
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/AvaloniaIntrinsicsTests.cs
  45. 28 0
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleIncludeTests.cs
  46. 24 0
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs
  47. 32 4
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs
  48. 1 0
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlTestHelpers.cs

+ 6 - 0
.editorconfig

@@ -208,6 +208,12 @@ dotnet_diagnostic.AVA2001.severity = error
 # Xaml files
 [*.{xaml,axaml}]
 indent_size = 2
+# DuplicateSetterError
+avalonia_xaml_diagnostic.AVLN2203.severity = error
+# StyleInMergedDictionaries
+avalonia_xaml_diagnostic.AVLN2204.severity = error
+# Obsolete
+avalonia_xaml_diagnostic.AVLN5001.severity = error
 
 # Xml project files
 [*.{csproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}]

+ 1 - 0
build/BuildTargets.targets

@@ -4,6 +4,7 @@
     <AvaloniaUseExternalMSBuild>true</AvaloniaUseExternalMSBuild>
     <AvaloniaXamlIlVerifyIl>true</AvaloniaXamlIlVerifyIl>
     <AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
+    <AvaloniaXamlVerboseExceptions Condition="'$(AvaloniaXamlVerboseExceptions)' == ''">true</AvaloniaXamlVerboseExceptions>
   </PropertyGroup>
   <Import Project="$(MSBuildThisFileDirectory)\..\packages\Avalonia\AvaloniaBuildTasks.props"/>
   <Import Project="$(MSBuildThisFileDirectory)\..\packages\Avalonia\AvaloniaBuildTasks.targets"/>

+ 4 - 1
packages/Avalonia/AvaloniaBuildTasks.targets

@@ -130,6 +130,7 @@
       <AvaloniaXamlOriginalCopyFilePath Condition="'$(AvaloniaXamlOriginalCopyFilePath)' == ''">$(IntermediateOutputPath)/Avalonia/original.dll</AvaloniaXamlOriginalCopyFilePath>
       <AvaloniaXamlIlVerifyIl Condition="'$(AvaloniaXamlIlVerifyIl)' == ''">false</AvaloniaXamlIlVerifyIl>
       <AvaloniaXamlIlDebuggerLaunch Condition="'$(AvaloniaXamlIlDebuggerLaunch)' == ''">false</AvaloniaXamlIlDebuggerLaunch>
+      <AvaloniaXamlVerboseExceptions Condition="'$(AvaloniaXamlVerboseExceptions)' == ''">false</AvaloniaXamlVerboseExceptions>
     </PropertyGroup>
     <WriteLinesToFile
       Condition="'$(_AvaloniaForceInternalMSBuild)' != 'true'"
@@ -153,7 +154,9 @@
       DelaySign="$(DelaySign)"
       SkipXamlCompilation="$(_AvaloniaSkipXamlCompilation)"
       DebuggerLaunch="$(AvaloniaXamlIlDebuggerLaunch)"
-      DefaultCompileBindings="$(AvaloniaUseCompiledBindingsByDefault)">
+      DefaultCompileBindings="$(AvaloniaUseCompiledBindingsByDefault)"
+      VerboseExceptions="$(AvaloniaXamlVerboseExceptions)"
+      AnalyzerConfigFiles="@(EditorConfigFiles)">
       <Output TaskParameter="WrittenFilePaths" ItemName="FileWrites" />
     </CompileAvaloniaXamlTask>
     <Exec

+ 9 - 9
packages/Avalonia/AvaloniaRules.Project.xml

@@ -18,11 +18,6 @@
                 Description="Enable/Disable XAML Compiling"
                 Category="Compile" />
 
-  <BoolProperty Name="AvaloniaXamlIlVerifyIl"
-                DisplayName="Verify IL"
-                Description="Enable/Disable Verify IL after XAML Compiling"
-                Category="Compile" />
-
   <BoolProperty Name="AvaloniaUseCompiledBindingsByDefault"
               DisplayName="Use CompiledBindings"
               Description="Use compiled bindings by default"
@@ -32,10 +27,15 @@
   <!-- Debug -->
 
   <BoolProperty Name="AvaloniaXamlIlDebuggerLaunch"
-          DisplayName="Debug XAML Compiler"
-          Description="Allow debug XAML compilation"
-          Category="Debug" />
-
+                DisplayName="Debug XAML Compiler"
+                Description="Allow debug XAML compilation"
+                Category="Debug" />
+
+  <BoolProperty Name="AvaloniaXamlVerboseExceptions"
+                DisplayName="Report verbose internal exceptions with stack traces"
+                Description="Also includes inner exceptions"
+                Category="Debug" />
+  
   <EnumProperty Name="AvaloniaXamlReportImportance"
                 DisplayName="XAML Report Importance"
                 Description="Provides levels of importance for XAML Compiler messages"

+ 1 - 0
src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj

@@ -119,6 +119,7 @@
       <Compile Include="..\Avalonia.Base\Compatibility\TrimmingAttributes.cs" Link="TrimmingAttributes.cs" Visible="False" />
       <Compile Include="..\Avalonia.Base\Utilities\SpanHelpers.cs" Link="Utilities\SpanHelpers.cs" />
       <Compile Include="..\Shared\StringCompatibilityExtensions.cs" Link="Compatibility\StringCompatibilityExtensions.cs" />
+      <Compile Include="..\Shared\IsExternalInit.cs" Link="Compatibility\IsExternalInit.cs" />
       <Compile Remove="../Markup/Avalonia.Markup.Xaml.Loader\xamlil.github\**\obj\**\*.cs" />
       <Compile Remove="../Markup/Avalonia.Markup.Xaml.Loader\xamlil.github\src\XamlX\IL\SreTypeSystem.cs" />
       <PackageReference Include="Mono.Cecil" Version="0.11.5" />

+ 0 - 14
src/Avalonia.Build.Tasks/BuildEngineErrorCode.cs

@@ -1,14 +0,0 @@
-namespace Avalonia.Build.Tasks
-{
-    public enum BuildEngineErrorCode
-    {
-        InvalidXAML = 1,
-        DuplicateXClass = 2,
-        LegacyResmScheme = 3,
-        TransformError = 4,
-        EmitError = 4,
-        Loader = 5,
-
-        Unknown = 9999
-    }
-}

+ 7 - 1
src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs

@@ -56,7 +56,9 @@ namespace Avalonia.Build.Tasks
                 refInput, RefOutputPath,
                 File.ReadAllLines(ReferencesFilePath).Where(l => !string.IsNullOrWhiteSpace(l)).ToArray(),
                 ProjectDirectory, VerifyIl, DefaultCompileBindings, outputImportance,
-                (SignAssembly && !DelaySign) ? AssemblyOriginatorKeyFile : null, SkipXamlCompilation, DebuggerLaunch);
+                new XamlCompilerDiagnosticsFilter(AnalyzerConfigFiles),
+                (SignAssembly && !DelaySign) ? AssemblyOriginatorKeyFile : null,
+                SkipXamlCompilation, DebuggerLaunch, VerboseExceptions);
             if (!res.Success)
             {
                 WrittenFilePaths = writtenFilePaths.ToArray();
@@ -121,6 +123,10 @@ namespace Avalonia.Build.Tasks
 
         public bool DebuggerLaunch { get; set; }
 
+        public bool VerboseExceptions { get; set; }
+        
+        public ITaskItem[] AnalyzerConfigFiles { get; set; }
+        
         [Output]
         public string[] WrittenFilePaths { get; private set; } = Array.Empty<string>();
     }

+ 89 - 20
src/Avalonia.Build.Tasks/Extensions.cs

@@ -1,41 +1,110 @@
 using System;
+using System.Text;
+using System.Xml;
 using Microsoft.Build.Framework;
+using XamlX;
 
-namespace Avalonia.Build.Tasks
+namespace Avalonia.Build.Tasks;
+
+internal static class Extensions
 {
-    static class Extensions
+    public static void LogError(this IBuildEngine engine, string code, string file, Exception ex,
+        int? lineNumber = null, int? linePosition = null)
     {
-        static string FormatErrorCode(BuildEngineErrorCode code) => FormattableString.Invariant($"AVLN:{(int)code:0000}");
-
-        public static void LogError(this IBuildEngine engine, BuildEngineErrorCode code, string file, Exception ex,
-            int lineNumber = 0, int linePosition = 0)
+        if (lineNumber is null && linePosition is null
+                               && ex is XmlException xe)
         {
+            lineNumber = xe.LineNumber;
+            linePosition = xe.LinePosition;
+        }
+            
 #if DEBUG
-            LogError(engine, code, file, ex.ToString(), lineNumber, linePosition);
+        LogError(engine, code, file, ex.ToString(), lineNumber, linePosition);
 #else
-            LogError(engine, code, file, ex.Message, lineNumber, linePosition);
+        LogError(engine, code, file, ex.Message, lineNumber, linePosition);
 #endif
+    }
+
+    public static void LogDiagnostic(this IBuildEngine engine, XamlDiagnostic diagnostic)
+    {
+        var message = diagnostic.Title;
+
+        if (diagnostic.Severity == XamlDiagnosticSeverity.None)
+        {
+            // Skip.
         }
-        
-        public static void LogError(this IBuildEngine engine, BuildEngineErrorCode code, string file, string message,
-            int lineNumber = 0, int linePosition = 0)
+        else if (diagnostic.Severity == XamlDiagnosticSeverity.Warning)
         {
-            engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", FormatErrorCode(code), file ?? "",
-                lineNumber, linePosition, lineNumber, linePosition, message,
+            engine.LogWarningEvent(new BuildWarningEventArgs("Avalonia", diagnostic.Code, diagnostic.Document ?? "",
+                diagnostic.LineNumber ?? 0, diagnostic.LinePosition ?? 0, 
+                diagnostic.LineNumber ?? 0, diagnostic.LinePosition ?? 0,
+                message,
                 "", "Avalonia"));
         }
-
-        public static void LogWarning(this IBuildEngine engine, BuildEngineErrorCode code, string file, string message,
-            int lineNumber = 0, int linePosition = 0)
+        else
         {
-            engine.LogWarningEvent(new BuildWarningEventArgs("Avalonia", FormatErrorCode(code), file ?? "",
-                lineNumber, linePosition, lineNumber, linePosition, message,
+            engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", diagnostic.Code, diagnostic.Document ?? "",
+                diagnostic.LineNumber ?? 0, diagnostic.LinePosition ?? 0, 
+                diagnostic.LineNumber ?? 0, diagnostic.LinePosition ?? 0,
+                message,
                 "", "Avalonia"));
         }
+    }
+        
+    public static void LogError(this IBuildEngine engine, string code, string file, string message,
+        int? lineNumber = null, int? linePosition = null)
+    {
+        engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", code, file ?? "",
+            lineNumber ?? 0, linePosition ?? 0, lineNumber ?? 0, linePosition ?? 0,
+            message, "", "Avalonia"));
+    }
 
-        public static void LogMessage(this IBuildEngine engine, string message, MessageImportance imp)
+    public static void LogWarning(this IBuildEngine engine, string code, string file, string message,
+        int lineNumber = 0, int linePosition = 0)
+    {
+        engine.LogWarningEvent(new BuildWarningEventArgs("Avalonia", code, file ?? "",
+            lineNumber, linePosition, lineNumber, linePosition, message,
+            "", "Avalonia"));
+    }
+
+    public static void LogMessage(this IBuildEngine engine, string message, MessageImportance imp)
+    {
+        engine.LogMessageEvent(new BuildMessageEventArgs(message, "", "Avalonia", imp));
+    }
+
+    public static string FormatException(this Exception exception, bool verbose)
+    {
+        if (!verbose)
         {
-            engine.LogMessageEvent(new BuildMessageEventArgs(message, "", "Avalonia", imp));
+            return exception.Message;
+        }
+        
+        var builder = new StringBuilder();
+        Process(exception);
+        return builder.ToString();
+         
+        // Inspired by https://github.com/dotnet/msbuild/blob/e6409007d3a09255431eb28af01835ce1cd316b5/src/Shared/TaskLoggingHelper.cs#L909   
+        void Process(Exception exception)
+        {
+            if (exception is AggregateException aggregateException)
+            {
+                foreach (Exception innerException in aggregateException.Flatten().InnerExceptions)
+                {
+                    Process(innerException);
+                }
+
+                return;
+            }
+
+            do
+            {
+                builder.Append(exception.GetType().Name);
+                builder.Append(": ");
+                builder.AppendLine(exception.Message);
+                builder.AppendLine(exception.StackTrace);
+
+                exception = exception.InnerException;
+            } while (exception != null);
         }
     }
 }

+ 3 - 2
src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs

@@ -5,6 +5,7 @@ using System.Linq;
 using System.Runtime.Serialization;
 using System.Text;
 using Avalonia.Markup.Xaml.PortableXaml;
+using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions;
 using Avalonia.Utilities;
 using Microsoft.Build.Framework;
 using SPath = System.IO.Path;
@@ -100,7 +101,7 @@ namespace Avalonia.Build.Tasks
                     }
                     catch (Exception e)
                     {
-                        BuildEngine.LogError(BuildEngineErrorCode.InvalidXAML, s.SystemPath, "File doesn't contain valid XAML: " + e);
+                        BuildEngine.LogError(AvaloniaXamlDiagnosticCodes.InvalidXAML, s.SystemPath, "File doesn't contain valid XAML: " + e);
                         return false;
                     }
 
@@ -109,7 +110,7 @@ namespace Avalonia.Build.Tasks
                         if (typeToXamlIndex.ContainsKey(info.XClass))
                         {
 
-                            BuildEngine.LogError(BuildEngineErrorCode.DuplicateXClass, s.SystemPath,
+                            BuildEngine.LogError(AvaloniaXamlDiagnosticCodes.DuplicateXClass, s.SystemPath,
                                 $"Duplicate x:Class directive, {info.XClass} is already used in {typeToXamlIndex[info.XClass]}");
                             return false;
                         }

+ 70 - 0
src/Avalonia.Build.Tasks/XamlCompilerDiagnosticsFilter.cs

@@ -0,0 +1,70 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text.RegularExpressions;
+using Microsoft.Build.Framework;
+using XamlX;
+
+namespace Avalonia.Build.Tasks;
+
+// With MSBuild, we don't need to read for TreatWarningsAsErrors/WarningsAsErrors/WarningsNotAsErrors/NoWarn properties.
+// Just by reporting them with LogWarning MSBuild will do the rest for us.
+// But we still need to read EditorConfig manually.
+public class XamlCompilerDiagnosticsFilter
+{
+    private static readonly Regex s_editorConfigRegex =
+        new("""avalonia_xaml_diagnostic\.([\w\d]+)\.severity\s*=\s*(\w*)""");
+
+    private readonly Lazy<Dictionary<string, string>> _lazyEditorConfig;
+
+    public XamlCompilerDiagnosticsFilter(
+        ITaskItem[]? analyzerConfigFiles)
+    {
+        _lazyEditorConfig = new Lazy<Dictionary<string, string>>(() => ParseEditorConfigFiles(analyzerConfigFiles));
+    }
+
+    internal XamlDiagnosticSeverity Handle(XamlDiagnostic diagnostic)
+    {
+        return Handle(diagnostic.Severity, diagnostic.Code);
+    }
+
+    internal XamlDiagnosticSeverity Handle(XamlDiagnosticSeverity currentSeverity, string diagnosticCode)
+    {
+        if (_lazyEditorConfig.Value.TryGetValue(diagnosticCode, out var severity))
+        {
+            return severity.ToLowerInvariant() switch
+            {
+                "default" => currentSeverity,
+                "error" => XamlDiagnosticSeverity.Error,
+                "warning" => XamlDiagnosticSeverity.Warning,
+                _ => XamlDiagnosticSeverity.None // "suggestion", "silent", "none"
+            };
+        }
+
+        return currentSeverity;
+    }
+
+    private Dictionary<string, string> ParseEditorConfigFiles(ITaskItem[]? analyzerConfigFiles)
+    {
+        // Very naive EditorConfig parser, supporting minimal properties set via regex:
+        var severities = new Dictionary<string, string>();
+        if (analyzerConfigFiles is not null)
+        {
+            foreach (var fileItem in analyzerConfigFiles)
+            {
+                if (File.Exists(fileItem.ItemSpec))
+                {
+                    var fileContent = File.ReadAllText(fileItem.ItemSpec);
+                    var matches = s_editorConfigRegex.Matches(fileContent);
+                    foreach (Match match in matches)
+                    {
+                        severities[match.Groups[1].Value] = match.Groups[2].Value;
+                    }
+                }
+            }
+        }
+
+        return severities;
+    }
+}

+ 72 - 45
src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Collections;
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
@@ -44,21 +45,13 @@ namespace Avalonia.Build.Tasks
             }
         }
 
-        public static CompileResult Compile(IBuildEngine engine,
-            string input, string output,
-            string refInput, string refOutput,
-            string[] references, string projectDirectory,
-            bool verifyIl, bool defaultCompileBindings, MessageImportance logImportance, string strongNameKey,
-            bool skipXamlCompilation)
-        {
-            return Compile(engine, input, output, refInput, refOutput, references, projectDirectory, verifyIl, defaultCompileBindings, logImportance, strongNameKey, skipXamlCompilation, debuggerLaunch:false);
-        }
-
         internal static CompileResult Compile(IBuildEngine engine,
             string input, string output,
             string refInput, string refOutput,
             string[] references, string projectDirectory,
-            bool verifyIl, bool defaultCompileBindings, MessageImportance logImportance, string strongNameKey, bool skipXamlCompilation, bool debuggerLaunch)
+            bool verifyIl, bool defaultCompileBindings, MessageImportance logImportance,
+            XamlCompilerDiagnosticsFilter diagnosticsFilter, string strongNameKey,
+            bool skipXamlCompilation, bool debuggerLaunch, bool verboseExceptions)
         {
             try
             {
@@ -70,7 +63,10 @@ namespace Avalonia.Build.Tasks
                 var refAsm = refTypeSystem?.TargetAssemblyDefinition;
                 if (!skipXamlCompilation)
                 {
-	                var compileRes = CompileCore(engine, typeSystem, projectDirectory, verifyIl, defaultCompileBindings, logImportance, debuggerLaunch);
+	                var compileRes = CompileCore(
+                        engine, typeSystem, projectDirectory, verifyIl,
+                        defaultCompileBindings, logImportance, diagnosticsFilter,
+                        debuggerLaunch, verboseExceptions);
 	                if (compileRes == null)
 	                    return new CompileResult(true);
 	                if (compileRes == false)
@@ -99,7 +95,7 @@ namespace Avalonia.Build.Tasks
             }
             catch (Exception ex)
             {
-                engine.LogError(BuildEngineErrorCode.Unknown, "", ex);
+                engine.LogError(AvaloniaXamlDiagnosticCodes.Unknown, "", ex);
                 return new CompileResult(false);
             }
         }
@@ -107,8 +103,10 @@ namespace Avalonia.Build.Tasks
         static bool? CompileCore(IBuildEngine engine, CecilTypeSystem typeSystem,
             string projectDirectory, bool verifyIl,
             bool defaultCompileBindings,
-            MessageImportance logImportance
-            , bool debuggerLaunch = false)
+            MessageImportance logImportance,
+            XamlCompilerDiagnosticsFilter diagnosticsFilter,
+            bool debuggerLaunch,
+            bool verboseExceptions)
         {
             if (debuggerLaunch)
             {
@@ -170,6 +168,20 @@ namespace Avalonia.Build.Tasks
             asm.MainModule.Types.Add(trampolineBuilder);
 
             var (xamlLanguage , emitConfig) = AvaloniaXamlIlLanguage.Configure(typeSystem);
+            var diagnostics = new List<XamlDiagnostic>();
+            var diagnosticsHandler = new XamlDiagnosticsHandler()
+            {
+                HandleDiagnostic = diagnostic =>
+                {
+                    var newSeverity = diagnosticsFilter.Handle(diagnostic);
+                    diagnostic = diagnostic with { Severity = newSeverity };
+                    diagnostics.Add(diagnostic);
+                    return newSeverity;
+                },
+                CodeMappings = AvaloniaXamlDiagnosticCodes.XamlXDiagnosticCodeToAvalonia,
+                ExceptionFormatter = ex => ex.FormatException(verboseExceptions)
+            };
+
             var compilerConfig = new AvaloniaXamlIlCompilerConfiguration(typeSystem,
                 typeSystem.TargetAssembly,
                 xamlLanguage,
@@ -178,7 +190,8 @@ namespace Avalonia.Build.Tasks
                 new XamlIlClrPropertyInfoEmitter(typeSystem.CreateTypeBuilder(clrPropertiesDef)),
                 new XamlIlPropertyInfoAccessorFactoryEmitter(typeSystem.CreateTypeBuilder(indexerAccessorClosure)),
                 new XamlIlTrampolineBuilder(typeSystem.CreateTypeBuilder(trampolineBuilder)),
-                new DeterministicIdGenerator());
+                new DeterministicIdGenerator(),
+                diagnosticsHandler);
 
 
             var contextDef = new TypeDefinition(CompiledAvaloniaXamlNamespace, "XamlIlContext", 
@@ -259,6 +272,7 @@ namespace Avalonia.Build.Tasks
             {
                 var typeDef = new TypeDefinition(CompiledAvaloniaXamlNamespace, "!"+ group.Name,
                     TypeAttributes.Class | TypeAttributes.Public, asm.MainModule.TypeSystem.Object);
+                var transformFailed = false;
 
                 typeDef.CustomAttributes.Add(new CustomAttribute(editorBrowsableCtor)
                 {
@@ -277,6 +291,7 @@ namespace Avalonia.Build.Tasks
                         // StreamReader is needed here to handle BOM
                         var xaml = new StreamReader(new MemoryStream(res.FileContents)).ReadToEnd();
                         var parsed = XDocumentXamlParser.Parse(xaml);
+                        parsed.Document = res.FilePath;
 
                         var initialRoot = (XamlAstObjectNode)parsed.Root;
                         
@@ -338,8 +353,7 @@ namespace Avalonia.Build.Tasks
                                 new XamlAstClrTypeReference(classDirective, classType, false));
                             initialRoot.Children.Remove(classDirective);
                         }
-                        
-                        
+
                         compiler.Transform(parsed);
 
                         var populateName = classType == null ? "Populate:" + res.Name : "!XamlIlPopulate";
@@ -376,29 +390,28 @@ namespace Avalonia.Build.Tasks
                     }
                     catch (Exception e)
                     {
-                        int lineNumber = 0, linePosition = 0;
-                        if (e is XamlParseException xe)
-                        {
-                            lineNumber = xe.LineNumber;
-                            linePosition = xe.LinePosition;
-                        }
-
-                        engine.LogError(BuildEngineErrorCode.TransformError, res.FilePath, e, lineNumber, linePosition);
-                        return false;
+                        transformFailed = true;
+                        engine.LogError(AvaloniaXamlDiagnosticCodes.TransformError, res.FilePath, e);
                     }
                 }
 
                 try
                 {
-                    compiler.TransformGroup(parsedXamlDocuments);
+                    if (!transformFailed)
+                    {
+                        compiler.TransformGroup(parsedXamlDocuments);
+                    }
                 }
-                catch (XamlDocumentParseException e)
+                catch (Exception e)
                 {
-                    engine.LogError(BuildEngineErrorCode.TransformError, e.FilePath, e, e.LineNumber, e.LinePosition);
+                    transformFailed = true;
+                    engine.LogError(AvaloniaXamlDiagnosticCodes.TransformError, "", e);
                 }
-                catch (XamlParseException e)
+                
+                var hasAnyError = ReportDiagnostics(engine, diagnostics) || transformFailed;
+                if (hasAnyError)
                 {
-                    engine.LogError(BuildEngineErrorCode.TransformError, "", e, e.LineNumber, e.LinePosition);
+                    return false;
                 }
 
                 foreach (var document in parsedXamlDocuments)
@@ -605,21 +618,23 @@ namespace Avalonia.Build.Tasks
                             }
                             else
                             {
-                                engine.LogWarning(BuildEngineErrorCode.Loader, "",
-                                    $"XAML resource \"{res.Uri}\" won't be reachable via runtime loader, as no public constructor was found");
+                                var diagnostic = new XamlDiagnostic(
+                                    AvaloniaXamlDiagnosticCodes.XamlLoaderUnreachable,
+                                    diagnosticsFilter.Handle(
+                                        XamlDiagnosticSeverity.Warning,
+                                        AvaloniaXamlDiagnosticCodes.XamlLoaderUnreachable),
+                                    $"XAML resource \"{res.Uri}\" won't be reachable via runtime loader, as no public constructor was found")
+                                {
+                                    Document = document.FileSource?.FilePath
+                                };
+                                engine.LogDiagnostic(diagnostic);
                             }
                         }
 
                     }
                     catch (Exception e)
                     {
-                        int lineNumber = 0, linePosition = 0;
-                        if (e is XamlParseException xe)
-                        {
-                            lineNumber = xe.LineNumber;
-                            linePosition = xe.LinePosition;
-                        }
-                        engine.LogError(BuildEngineErrorCode.EmitError, res.FilePath, e, lineNumber, linePosition);
+                        engine.LogError(AvaloniaXamlDiagnosticCodes.EmitError, res.FilePath, e);
                         return false;
                     }
                     res.Remove();
@@ -636,7 +651,6 @@ namespace Avalonia.Build.Tasks
                     }
                 }
                 
-                
                 return true;
             }
             
@@ -652,6 +666,21 @@ namespace Avalonia.Build.Tasks
             return true;
         }
 
+        static bool ReportDiagnostics(IBuildEngine engine, IReadOnlyCollection<XamlDiagnostic> diagnostics)
+        {
+            var hasAnyError = diagnostics.Any(d => d.Severity >= XamlDiagnosticSeverity.Error);
+
+            const int maxErrorsPerDocument = 100;
+            foreach (var diagnostic in diagnostics
+                         .GroupBy(d => d.Document)
+                         .SelectMany(d => d.Take(maxErrorsPerDocument)))
+            {
+                engine.LogDiagnostic(diagnostic);
+            }
+
+            return hasAnyError;
+        }
+
         static bool? CompileCoreForRefAssembly(
             IBuildEngine engine, CecilTypeSystem sourceTypeSystem, CecilTypeSystem refTypeSystem)
         {
@@ -693,9 +722,7 @@ namespace Avalonia.Build.Tasks
             }
             catch (Exception e)
             {
-                engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", "XAMLIL", "",
-                    0, 0, 0, 0,
-                    e.Message, "", "Avalonia"));
+                engine.LogError(AvaloniaXamlDiagnosticCodes.Unknown, e.Message, e);
                 return false;
             }
 

+ 1 - 0
src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj

@@ -13,6 +13,7 @@
   <ItemGroup>
     <Compile Include="..\..\Avalonia.Base\Utilities\StringBuilderCache.cs" Link="Utilities\StringBuilderCache.cs" />
     <Compile Include="..\..\Avalonia.Base\Compatibility\TrimmingAttributes.cs" Link="TrimmingAttributes.cs" Visible="False" />
+    <Compile Include="..\..\Shared\IsExternalInit.cs" Link="Compatibility\IsExternalInit.cs" />
   </ItemGroup>
   <ItemGroup>
     <PackageReference Include="System.Reflection.Emit" Version="4.3.0" />

+ 28 - 2
src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs

@@ -209,11 +209,34 @@ namespace Avalonia.Markup.Xaml.XamlIl
             var indexerClosureType = _sreBuilder.DefineType("IndexerClosure_" + Guid.NewGuid().ToString("N"));
             var trampolineBuilder = _sreBuilder.DefineType("Trampolines_" + Guid.NewGuid().ToString("N"));
 
+            var diagnostics = new List<XamlDiagnostic>();
+            var diagnosticsHandler = new XamlDiagnosticsHandler()
+            {
+                HandleDiagnostic = (diagnostic) =>
+                {
+                    var runtimeDiagnostic = new RuntimeXamlDiagnostic(diagnostic.Code.ToString(),
+                        (RuntimeXamlDiagnosticSeverity)diagnostic.Severity,
+                        diagnostic.Title, diagnostic.LineNumber, diagnostic.LinePosition)
+                    {
+                        Document = diagnostic.Document
+                    };
+                    var newSeverity =
+                        (XamlDiagnosticSeverity?)configuration.DiagnosticHandler?.Invoke(runtimeDiagnostic) ??
+                        diagnostic.Severity;
+                    diagnostic = diagnostic with { Severity = newSeverity };
+                    diagnostics.Add(diagnostic);
+                    return newSeverity;
+                },
+                CodeMappings = AvaloniaXamlDiagnosticCodes.XamlXDiagnosticCodeToAvalonia
+            };
+            
             var compiler = new AvaloniaXamlIlCompiler(new AvaloniaXamlIlCompilerConfiguration(_sreTypeSystem, asm,
                     _sreMappings, _sreXmlns, AvaloniaXamlIlLanguage.CustomValueConverter,
                     new XamlIlClrPropertyInfoEmitter(_sreTypeSystem.CreateTypeBuilder(clrPropertyBuilder)),
                     new XamlIlPropertyInfoAccessorFactoryEmitter(_sreTypeSystem.CreateTypeBuilder(indexerClosureType)),
-                    new XamlIlTrampolineBuilder(_sreTypeSystem.CreateTypeBuilder(trampolineBuilder))),
+                    new XamlIlTrampolineBuilder(_sreTypeSystem.CreateTypeBuilder(trampolineBuilder)),
+                    null,
+                    diagnosticsHandler),
                 _sreEmitMappings,
                 _sreContextType)
             {
@@ -236,8 +259,9 @@ namespace Avalonia.Markup.Xaml.XamlIl
                 {
                     overrideType = _sreTypeSystem.GetType(document.RootInstance.GetType());
                 }
-                
+
                 var parsed = compiler.Parse(xaml, overrideType);
+                parsed.Document = "runtimexaml:" + parsedDocuments.Count;
                 compiler.Transform(parsed);
 
                 var xamlName = GetSafeUriIdentifier(document.BaseUri)
@@ -263,6 +287,8 @@ namespace Avalonia.Markup.Xaml.XamlIl
 
             compiler.TransformGroup(parsedDocuments);
 
+            diagnostics.ThrowExceptionIfAnyError();
+
             var createdTypes = parsedDocuments.Select(document =>
             {
                 compiler.Compile(document.XamlDocument, document.TypeBuilderProvider, document.Uri, document.FileSource);

+ 0 - 8
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AstNodes/AvaloniaXamlIlArrayConstantAstNode.cs

@@ -22,14 +22,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.AstNodes
             _values = values;
             
             Type = new XamlAstClrTypeReference(lineInfo, arrayType, false);
-
-            foreach (var element in values)
-            {
-                if (!elementType.IsAssignableFrom(element.Type.GetClrType()))
-                {
-                    throw new XamlParseException("x:Array element is not assignable to the array element type!", lineInfo);
-                }
-            }
         }
 
         public IXamlAstTypeReference Type { get; }

+ 63 - 0
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlDiagnosticCodes.cs

@@ -0,0 +1,63 @@
+using System;
+using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
+using XamlX;
+using XamlX.Ast;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions;
+
+internal static class AvaloniaXamlDiagnosticCodes
+{
+    public const string Unknown = "AVLN9999";
+
+    // XML/XAML parsing errors 1000-1999.
+    public const string ParseError = "AVLN1000";
+    public const string InvalidXAML = "AVLN1001";
+
+    // XAML transform errors 2000-2999.
+    public const string TransformError = "AVLN2000";
+    public const string DuplicateXClass = "AVLN2002";
+    public const string TypeSystemError = "AVLN2003";
+    public const string AvaloniaIntrinsicsError = "AVLN2005";
+    public const string BindingsError = "AVLN2100";
+    public const string DataContextResolvingError = "AVLN2101";
+    public const string StyleTransformError = "AVLN2200";
+    public const string SelectorsTransformError = "AVLN2201";
+    public const string PropertyPathError = "AVLN2202";
+    public const string DuplicateSetterError = "AVLN2203";
+    public const string StyleInMergedDictionaries = "AVLN2204";
+
+    // XAML emit errors 3000-3999.
+    public const string EmitError = "AVLN3000";
+    public const string XamlLoaderUnreachable = "AVLN3001";
+
+    // Generator specific errors 4000-4999.
+    public const string NameGeneratorError = "AVLN4001";
+
+    // Reserved 5000-9998
+    public const string Obsolete = "AVLN5001";
+
+    internal static string XamlXDiagnosticCodeToAvalonia(object xamlException)
+    {
+        return xamlException switch
+        {
+            XamlXWellKnownDiagnosticCodes wellKnownDiagnosticCodes => wellKnownDiagnosticCodes switch
+            {
+                XamlXWellKnownDiagnosticCodes.Obsolete => Obsolete,
+                _ => throw new ArgumentOutOfRangeException()
+            },
+
+            XamlDataContextException => DataContextResolvingError,
+            XamlBindingsTransformException => BindingsError,
+            XamlPropertyPathException => PropertyPathError,
+            XamlStyleTransformException => StyleTransformError,
+            XamlSelectorsTransformException => SelectorsTransformError,
+
+            XamlTransformException => TransformError,
+            XamlTypeSystemException => TypeSystemError,
+            XamlLoadException => EmitError,
+            XamlParseException => ParseError,
+            
+            _ => Unknown
+        };
+    }
+}

+ 5 - 4
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs

@@ -57,6 +57,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
                 new AvaloniaXamlIlPropertyPathTransformer(),
                 new AvaloniaXamlIlSetterTargetTypeMetadataTransformer(),
                 new AvaloniaXamlIlSetterTransformer(),
+                new AvaloniaXamlIlStyleValidatorTransformer(),
                 new AvaloniaXamlIlConstructorServiceProviderTransformer(),
                 new AvaloniaXamlIlTransitionsTypeMetadataTransformer(),
                 new AvaloniaXamlIlResolveByNameMarkupExtensionReplacer(),
@@ -126,9 +127,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
 
         public List<IXamlAstGroupTransformer> GroupTransformers { get; }
 
-        public void TransformGroup(IReadOnlyCollection<IXamlDocumentResource> documents, bool strict = true)
+        public void TransformGroup(IReadOnlyCollection<IXamlDocumentResource> documents)
         {
-            var ctx = new AstGroupTransformationContext(documents, _configuration, strict);
+            var ctx = new AstGroupTransformationContext(documents, _configuration);
             foreach (var transformer in GroupTransformers)
             {
                 foreach (var doc in documents)
@@ -166,8 +167,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
                     new XamlAstClrTypeReference(classDirective,
                         _configuration.TypeSystem.GetType(((XamlAstTextNode)classDirective.Values[0]).Text),
                         false) :
-                    TypeReferenceResolver.ResolveType(CreateTransformationContext(parsed, true),
-                        (XamlAstXmlTypeReference)rootObject.Type, true);
+                    TypeReferenceResolver.ResolveType(CreateTransformationContext(parsed),
+                        (XamlAstXmlTypeReference)rootObject.Type);
 
 
             if (overrideRootType != null)

+ 3 - 2
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs

@@ -17,8 +17,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
             XamlIlClrPropertyInfoEmitter clrPropertyEmitter,
             XamlIlPropertyInfoAccessorFactoryEmitter accessorFactoryEmitter,
             XamlIlTrampolineBuilder trampolineBuilder,
-            IXamlIdentifierGenerator identifierGenerator = null)
-            : base(typeSystem, defaultAssembly, typeMappings, xmlnsMappings, customValueConverter, identifierGenerator)
+            IXamlIdentifierGenerator identifierGenerator,
+            XamlDiagnosticsHandler diagnosticsHandler)
+            : base(typeSystem, defaultAssembly, typeMappings, xmlnsMappings, customValueConverter, identifierGenerator, diagnosticsHandler)
         {
             ClrPropertyEmitter = clrPropertyEmitter;
             AccessorFactoryEmitter = accessorFactoryEmitter;

+ 41 - 15
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs

@@ -6,6 +6,7 @@ using Avalonia.Controls;
 using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.AstNodes;
 using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
 using Avalonia.Media;
+using XamlX;
 using XamlX.Ast;
 using XamlX.Transform;
 using XamlX.TypeSystem;
@@ -16,6 +17,21 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
     {
         public static bool TryConvert(AstTransformationContext context, IXamlAstValueNode node, string text, IXamlType type, AvaloniaXamlIlWellKnownTypes types, out IXamlAstValueNode result)
         {
+            bool ReturnOnParseError(string title, out IXamlAstValueNode result)
+            {
+                context.ReportDiagnostic(new XamlDiagnostic(
+                    AvaloniaXamlDiagnosticCodes.AvaloniaIntrinsicsError,
+                    XamlDiagnosticSeverity.Error,
+                    title,
+                    node)
+                {
+                    // Only one instance when we can lower Error to a Warning
+                    MinSeverity = XamlDiagnosticSeverity.Warning
+                });
+                result = null;
+                return false;
+            }
+
             if (type.FullName == "System.TimeSpan")
             {
                 var tsText = text.Trim();
@@ -24,11 +40,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
                 {
                     // // shorthand seconds format (ie. "0.25")
                     if (!tsText.Contains(":") && double.TryParse(tsText,
-                        NumberStyles.Float | NumberStyles.AllowThousands,
-                        CultureInfo.InvariantCulture, out var seconds))
+                            NumberStyles.Float | NumberStyles.AllowThousands,
+                            CultureInfo.InvariantCulture, out var seconds))
                         timeSpan = TimeSpan.FromSeconds(seconds);
                     else
-                        throw new XamlX.XamlLoadException($"Unable to parse {text} as a time span", node);
+                    {
+                        return ReturnOnParseError($"Unable to parse {text} as a time span", out result);
+                    }
                 }
 
                 result = new XamlStaticOrTargetedReturnMethodCallNode(node,
@@ -56,7 +74,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
                 }
                 catch
                 {
-                    throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a thickness", node);
+                    return ReturnOnParseError($"Unable to parse \"{text}\" as a thickness", out result);
                 }
             }
 
@@ -73,7 +91,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
                 }
                 catch
                 {
-                    throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a point", node);
+                    return ReturnOnParseError($"Unable to parse \"{text}\" as a point", out result);
                 }
             }
 
@@ -90,7 +108,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
                 }
                 catch
                 {
-                    throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a vector", node);
+                    return ReturnOnParseError($"Unable to parse \"{text}\" as a vector", out result);
                 }
             }
 
@@ -107,7 +125,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
                 }
                 catch
                 {
-                    throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a size", node);
+                    return ReturnOnParseError($"Unable to parse \"{text}\" as a size", out result);
                 }
             }
 
@@ -124,7 +142,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
                 }
                 catch
                 {
-                    throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a matrix", node);
+                    return ReturnOnParseError($"Unable to parse \"{text}\" as a matrix", out result);
                 }
             }
 
@@ -141,7 +159,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
                 }
                 catch
                 {
-                    throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a corner radius", node);
+                    return ReturnOnParseError($"Unable to parse \"{text}\" as a corner radius", out result);
                 }
             }
 
@@ -149,7 +167,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
             {
                 if (!Color.TryParse(text, out Color color))
                 {
-                    throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a color", node);
+                    return ReturnOnParseError($"Unable to parse \"{text}\" as a color", out result);
                 }
 
                 result = new XamlStaticOrTargetedReturnMethodCallNode(node,
@@ -179,7 +197,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
                 }
                 catch
                 {
-                    throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a relative point", node);
+                    return ReturnOnParseError($"Unable to parse \"{text}\" as a relative point", out result);
                 }
             }
 
@@ -195,7 +213,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
                 }
                 catch
                 {
-                    throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a grid length", node);
+                    return ReturnOnParseError($"Unable to parse \"{text}\" as a grid length", out result);
                 }
             }
             
@@ -218,7 +236,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
                 }
                 catch
                 {
-                    throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a grid length", node);
+                    return ReturnOnParseError($"Unable to parse \"{text}\" as a grid length", out result);
                 }
             }
 
@@ -295,7 +313,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
 
                 if (string.IsNullOrWhiteSpace(uriText) || !Uri.TryCreate(uriText, kind, out var _))
                 {
-                        throw new XamlX.XamlLoadException($"Unable to parse text {uriText} as a {kind} uri.", node);
+                    return ReturnOnParseError($"Unable to parse text \"{uriText}\" as a {kind} uri", out result);
                 }
                 result = new XamlAstNewClrObjectNode(node
                     , new(node, types.Uri, false)
@@ -341,7 +359,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
                     }
                     else
                     {
-                        throw new XamlX.XamlLoadException($"Invalid PointsList.", node);
+                        return ReturnOnParseError($"Unable to parse text \"{text}\" as a Points list", out result);
                     }
                 }
                 else
@@ -388,6 +406,14 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
                     nodes[index] = itemNode;
                 }
 
+                foreach (var element in nodes)
+                {
+                    if (!elementType.IsAssignableFrom(element.Type.GetClrType()))
+                    {
+                        return ReturnOnParseError($"x:Array element {element.Type.GetClrType().Name} is not assignable to the array element type {elementType.Name}", out result);
+                    }
+                }
+                
                 if (types.AvaloniaList.MakeGenericType(elementType).IsAssignableFrom(type))
                 {
                     result = new AvaloniaXamlIlAvaloniaListConstantAstNode(node, types, type, elementType, nodes);

+ 10 - 33
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/IXamlAstGroupTransformer.cs

@@ -9,55 +9,32 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.GroupTransformers;
 
 internal class AstGroupTransformationContext : AstTransformationContext
 {
-    public AstGroupTransformationContext(IReadOnlyCollection<IXamlDocumentResource> documents, TransformerConfiguration configuration, bool strictMode = true)
-        : base(configuration, null, strictMode)
+    public AstGroupTransformationContext(
+        IReadOnlyCollection<IXamlDocumentResource> documents,
+        TransformerConfiguration configuration)
+        : base(configuration, null)
     {
         Documents = documents;
     }
 
+    public override string Document => CurrentDocument?.FileSource?.FilePath ?? "{unknown document}";
     public IXamlDocumentResource CurrentDocument { get; set; }
     
     public IReadOnlyCollection<IXamlDocumentResource> Documents { get; }
 
-    public new IXamlAstNode ParseError(string message, IXamlAstNode node) =>
-        Error(node, new XamlDocumentParseException(CurrentDocument?.FileSource?.FilePath, message, node));
-
-    public new IXamlAstNode ParseError(string message, IXamlAstNode offender, IXamlAstNode ret) =>
-        Error(ret, new XamlDocumentParseException(CurrentDocument?.FileSource?.FilePath, message, offender));
-
-    class Visitor : IXamlAstVisitor
+    class Visitor : ContextXamlAstVisitor
     {
-        private readonly AstGroupTransformationContext _context;
         private readonly IXamlAstGroupTransformer _transformer;
 
-        public Visitor(AstGroupTransformationContext context, IXamlAstGroupTransformer transformer)
+        public Visitor(AstGroupTransformationContext context, IXamlAstGroupTransformer transformer) : base(context)
         {
-            _context = context;
             _transformer = transformer;
         }
-            
-        public IXamlAstNode Visit(IXamlAstNode node)
-        {
-#if Xaml_DEBUG
-                return _transformer.Transform(_context, node);
-#else
-            try
-            {
-                return _transformer.Transform(_context, node);
-            }
-            catch (Exception e) when (!(e is XmlException))
-            {
-                throw new XamlDocumentParseException(
-                    _context.CurrentDocument?.FileSource?.FilePath,
-                    "Internal compiler error while transforming node " + node + ":\n" + e,
-                    node);
-            }
-#endif
-        }
 
-        public void Push(IXamlAstNode node) => _context.PushParent(node);
+        public override string GetTransformerInfo() => _transformer.GetType().Name;
 
-        public void Pop() => _context.PopParent();
+        public override IXamlAstNode VisitCore(AstTransformationContext context, IXamlAstNode node) =>
+            _transformer.Transform((AstGroupTransformationContext)context, node);
     }
     
     public IXamlAstNode Visit(IXamlAstNode root, IXamlAstGroupTransformer transformer)

+ 31 - 29
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs

@@ -38,8 +38,7 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer
 
         if (valueNode.Manipulation is not XamlObjectInitializationNode initializationNode)
         {
-            throw new XamlDocumentParseException(context.CurrentDocument,
-                $"Invalid \"{nodeTypeName}\" node initialization.", valueNode);
+            throw new InvalidOperationException($"Invalid \"{nodeTypeName}\" node initialization.");
         }
 
         var additionalProperties = new List<IXamlAstManipulationNode>();
@@ -56,8 +55,9 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer
             }
             else
             {
-                throw new XamlDocumentParseException(context.CurrentDocument,
+                context.ReportTransformError(
                     $"Source property must be set on the \"{nodeTypeName}\" node.", valueNode);
+                return node;
             }
         }
 
@@ -89,22 +89,25 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer
                 return FromType(context, targetDocument.ClassType, sourceUriNode, expectedLoadedType, node, assetPathUri, assembly, additionalProperties);
             }
 
-            return context.ParseError(
+            context.ReportTransformError(
                 $"Unable to resolve XAML resource \"{assetPathUri}\" in the current assembly.",
-                sourceUriNode, node);
+                sourceUriNode);
+            return node;
         }
 
         // If resource wasn't found in the current assembly, search in the others.
         if (context.Configuration.TypeSystem.FindAssembly(assembly) is not { } assetAssembly)
         {
-            return context.ParseError($"Assembly \"{assembly}\" was not found from the \"{assetPathUri}\" source.", sourceUriNode, node);
+            context.ReportTransformError($"Assembly \"{assembly}\" was not found from the \"{assetPathUri}\" source.", sourceUriNode);
+            return node;
         }
 
         var avaResType = assetAssembly.FindType("CompiledAvaloniaXaml.!AvaloniaResources");
         if (avaResType is null)
         {
-            return context.ParseError(
-                $"Unable to resolve \"!AvaloniaResources\" type on \"{assembly}\" assembly.", sourceUriNode, node);
+            context.ReportTransformError(
+                $"Unable to resolve \"!AvaloniaResources\" type on \"{assembly}\" assembly.", sourceUriNode);
+            return node;
         }
 
         var relativeName = "Build:" + assetPath.Substring(assemblyNameSeparator);
@@ -118,20 +121,22 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer
             return FromType(context, type, sourceUriNode, expectedLoadedType, node, assetPathUri, assembly, additionalProperties);
         }
 
-        return context.ParseError(
+        context.ReportTransformError(
             $"Unable to resolve XAML resource \"{assetPathUri}\" in the \"{assembly}\" assembly. Make sure this file exists and is public.",
-            sourceUriNode, node);
+            sourceUriNode);
+        return node;
     }
 
-    private static IXamlAstNode FromType(AstTransformationContext context, IXamlType type, IXamlAstNode li,
+    private static IXamlAstNode FromType(AstGroupTransformationContext context, IXamlType type, IXamlAstNode li,
         IXamlType expectedLoadedType, IXamlAstNode fallbackNode, string assetPathUri, string assembly,
         IEnumerable<IXamlAstManipulationNode> manipulationNodes)
     {
         if (!expectedLoadedType.IsAssignableFrom(type))
         {
-            return context.ParseError(
+            context.ReportTransformError(
                 $"Resource \"{assetPathUri}\" is defined as \"{type}\" type in the \"{assembly}\" assembly, but expected \"{expectedLoadedType}\".",
-                li, fallbackNode);
+                li);
+            return fallbackNode;
         }
 
         IXamlAstNode newObjNode = new XamlAstObjectNode(li, new XamlAstClrTypeReference(li, type, false));
@@ -141,15 +146,16 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer
         return new NewObjectTransformer().Transform(context, newObjNode);
     }
 
-    private static IXamlAstNode FromMethod(AstTransformationContext context, IXamlMethod method, IXamlAstNode li,
+    private static IXamlAstNode FromMethod(AstGroupTransformationContext context, IXamlMethod method, IXamlAstNode li,
         IXamlType expectedLoadedType, IXamlAstNode fallbackNode, string assetPathUri, string assembly,
         IEnumerable<IXamlAstManipulationNode> manipulationNodes)
     {
         if (!expectedLoadedType.IsAssignableFrom(method.ReturnType))
         {
-            return context.ParseError(
+            context.ReportTransformError(
                 $"Resource \"{assetPathUri}\" is defined as \"{method.ReturnType}\" type in the \"{assembly}\" assembly, but expected \"{expectedLoadedType}\".",
-                li, fallbackNode);
+                li);
+            return fallbackNode;
         }
         
         var sp = context.Configuration.TypeMappings.ServiceProvider;
@@ -164,6 +170,13 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer
         AstGroupTransformationContext context, string nodeTypeName, XamlPropertyAssignmentNode sourceProperty,
         bool strictSourceValueType)
     {
+        void OnInvalidSource(IXamlAstNode node) =>
+            context.ReportDiagnostic(
+                AvaloniaXamlDiagnosticCodes.TransformError,
+                strictSourceValueType ? XamlDiagnosticSeverity.Error : XamlDiagnosticSeverity.Warning,
+                $"\"{nodeTypeName}.Source\" supports only \"avares://\" absolute or relative uri. This {nodeTypeName} will be resolved in runtime instead.",
+                node);
+        
         // We expect that AvaloniaXamlIlLanguageParseIntrinsics has already parsed the Uri and created node like: `new Uri(assetPath, uriKind)`.
         if (sourceProperty.Values.OfType<XamlAstNewClrObjectNode>().FirstOrDefault() is not { } sourceUriNode
             || sourceUriNode.Type.GetClrType() != context.GetAvaloniaTypes().Uri
@@ -172,16 +185,7 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer
         {
             // Source value can be set with markup extension instead of the Uri object node, we don't support it here yet.
             var anyPropValue = sourceProperty.Values.FirstOrDefault();
-            if (strictSourceValueType)
-            {
-                context.Error(anyPropValue,
-                    new XamlDocumentParseException(context.CurrentDocument,
-                        $"\"{nodeTypeName}.Source\" supports only \"avares://\" absolute or relative uri.", anyPropValue));
-            }
-            else
-            {
-                // TODO: make it a compiler warning
-            }
+            OnInvalidSource(anyPropValue);
             return (null, anyPropValue);
         }
 
@@ -193,9 +197,7 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer
         }
         else if (!uriPath.Scheme.Equals("avares", StringComparison.CurrentCultureIgnoreCase))
         {
-            context.Error(sourceUriNode,
-                new XamlDocumentParseException(context.CurrentDocument,
-                    $"\"{nodeTypeName}.Source\" supports only \"avares://\" absolute or relative uri.", sourceUriNode));
+            OnInvalidSource(sourceUriNode);
             return (null, sourceUriNode);
         }
 

+ 10 - 6
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlMergeResourceGroupTransformer.cs

@@ -2,6 +2,7 @@
 using System.Collections.Generic;
 using System.Linq;
 using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
+using XamlX;
 using XamlX.Ast;
 using XamlX.TypeSystem;
 
@@ -22,6 +23,7 @@ internal class XamlMergeResourceGroupTransformer : IXamlAstGroupTransformer
 
         var mergeResourceIncludeType = context.GetAvaloniaTypes().MergeResourceInclude;
         var mergeSourceNodes = new List<XamlPropertyAssignmentNode>();
+        var shouldExit = false; // if any manipulation node has an error, we should stop processing further.
         foreach (var manipulationNode in resourceDictionaryManipulation.Children.ToArray())
         {
             void ProcessXamlPropertyAssignmentNode(XamlManipulationGroupNode parent, XamlPropertyAssignmentNode assignmentNode)
@@ -39,14 +41,16 @@ internal class XamlMergeResourceGroupTransformer : IXamlAstGroupTransformer
                         }
                         else
                         {
-                            throw new XamlDocumentParseException(context.CurrentDocument,
+                            shouldExit = true;
+                            context.ReportTransformError(
                                 "Invalid MergeResourceInclude node found. Make sure that Source property is set.",
                                 valueNode);
                         }
                     }
                     else if (mergeSourceNodes.Any())
                     {
-                        throw new XamlDocumentParseException(context.CurrentDocument,
+                        shouldExit = true;
+                        context.ReportTransformError(
                             "MergeResourceInclude should always be included last when mixing with other dictionaries inside of the ResourceDictionary.MergedDictionaries.",
                             valueNode);
                     }
@@ -66,7 +70,7 @@ internal class XamlMergeResourceGroupTransformer : IXamlAstGroupTransformer
             }
         }
 
-        if (!mergeSourceNodes.Any())
+        if (shouldExit || !mergeSourceNodes.Any())
         {
             return node;
         }
@@ -78,7 +82,7 @@ internal class XamlMergeResourceGroupTransformer : IXamlAstGroupTransformer
                 AvaloniaXamlIncludeTransformer.ResolveSourceFromXamlInclude(context, "MergeResourceInclude", sourceNode, true);
             if (originalAssetPath is null)
             {
-                return context.ParseError(
+                return context.ReportTransformError(
                     $"Node MergeResourceInclude is unable to resolve \"{originalAssetPath}\" path.", propertyNode, node);
             }
 
@@ -86,7 +90,7 @@ internal class XamlMergeResourceGroupTransformer : IXamlAstGroupTransformer
                 string.Equals(d.Uri, originalAssetPath, StringComparison.InvariantCultureIgnoreCase));
             if (targetDocument?.XamlDocument.Root is not XamlValueWithManipulationNode targetDocumentRoot)
             {
-                return context.ParseError(
+                return context.ReportTransformError(
                     $"Node MergeResourceInclude is unable to resolve \"{originalAssetPath}\" path.", propertyNode, node);
             }
 
@@ -94,7 +98,7 @@ internal class XamlMergeResourceGroupTransformer : IXamlAstGroupTransformer
                 .Children.OfType<XamlObjectInitializationNode>().Single();
             if (singleRootObject.Type != resourceDictionaryType)
             {
-                return context.ParseError(
+                return context.ReportTransformError(
                     "MergeResourceInclude can only include another ResourceDictionary", propertyNode, node);
             }
             

+ 10 - 1
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaBindingExtensionTransformer.cs

@@ -1,3 +1,4 @@
+using System;
 using System.Linq;
 using XamlX;
 using XamlX.Ast;
@@ -8,6 +9,14 @@ using XamlParseException = XamlX.XamlParseException;
 
 namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
 {
+    class XamlBindingsTransformException : XamlTransformException
+    {
+        public XamlBindingsTransformException(string message, IXamlLineInfo lineInfo, Exception innerException = null)
+            : base(message, lineInfo, innerException)
+        {
+        }
+    }
+    
     class AvaloniaBindingExtensionTransformer : IXamlAstTransformer
     {
         public bool CompileBindingsByDefault { get; set; }
@@ -32,7 +41,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                             if (!(directive.Values[0] is XamlAstTextNode text
                                 && bool.TryParse(text.Text, out var compileBindings)))
                             {
-                                throw new XamlParseException("The value of x:CompileBindings must be a literal boolean value.", directive.Values[0]);
+                                throw new XamlBindingsTransformException("The value of x:CompileBindings must be a literal boolean value.", directive.Values[0]);
                             }
 
                             obj.Children.Remove(directive);

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

@@ -104,12 +104,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
             }
             else if (elementNameProperty != null)
             {
-                throw new XamlParseException($"Invalid ElementName '{elementNameProperty.Values[0]}'.", elementNameProperty.Values[0]);
+                throw new XamlBindingsTransformException($"Invalid ElementName '{elementNameProperty.Values[0]}'.", elementNameProperty.Values[0]);
             }
 
             if (sourceProperty != null && convertedNode != null)
             {
-                throw new XamlParseException("Only one of ElementName, Source, or RelativeSource specified as a binding source. Only one property is allowed.", binding);
+                throw new XamlBindingsTransformException("Only one of ElementName, Source, or RelativeSource specified as a binding source. Only one property is allowed.", binding);
             }
 
             if (GetRelativeSourceObjectFromAssignment(
@@ -119,7 +119,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
             {
                 if (convertedNode != null)
                 {
-                    throw new XamlParseException("Only one of ElementName, Source, or RelativeSource specified as a binding source. Only one property is allowed.", binding);
+                    throw new XamlBindingsTransformException("Only one of ElementName, Source, or RelativeSource specified as a binding source. Only one property is allowed.", binding);
                 }
 
                 var modeProperty = relativeSourceObject.Children
@@ -159,14 +159,14 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                                 true).GetClrType(),
                             XamlTypeExtensionNode typeExtensionNode => typeExtensionNode.Value.GetClrType(),
                             null => null,
-                            _ => throw new XamlParseException($"Unsupported node for AncestorType property", relativeSourceObject)
+                            _ => throw new XamlBindingsTransformException($"Unsupported node for AncestorType property", relativeSourceObject)
                         };
 
                     if (ancestorType is null)
                     {
                         if (treeType == "Visual")
                         {
-                            throw new XamlParseException("AncestorType must be set for RelativeSourceMode.FindAncestor when searching the visual tree.", relativeSourceObject);
+                            throw new XamlBindingsTransformException("AncestorType must be set for RelativeSourceMode.FindAncestor when searching the visual tree.", relativeSourceObject);
                         }
                         else if (treeType == "Logical")
                         {
@@ -180,7 +180,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
 
                             if (ancestorType is null)
                             {
-                                throw new XamlX.XamlParseException("Unable to resolve implicit ancestor type based on XAML tree.", relativeSourceObject);
+                                throw new XamlBindingsTransformException("Unable to resolve implicit ancestor type based on XAML tree.", relativeSourceObject);
                             }
                         }
                     }
@@ -203,7 +203,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                     }
                     else
                     {
-                        throw new XamlParseException($"Unknown tree type '{treeType}'.", binding);
+                        throw new XamlBindingsTransformException($"Unknown tree type '{treeType}'.", binding);
                     }
                 }
                 else if (mode == "DataContext")
@@ -221,20 +221,20 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                             x.ScopeType == AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.ControlTemplate);
                     if (contentTemplateNode is null)
                     {
-                        throw new XamlParseException("A binding with a TemplatedParent RelativeSource has to be in a ControlTemplate.", binding);
+                        throw new XamlBindingsTransformException("A binding with a TemplatedParent RelativeSource has to be in a ControlTemplate.", binding);
                     }
 
                     var parentType = contentTemplateNode.TargetType.GetClrType();
                     if (parentType is null)
                     {
-                        throw new XamlParseException("TargetType has to be set on ControlTemplate or it should be defined inside of a Style.", binding);
+                        throw new XamlBindingsTransformException("TargetType has to be set on ControlTemplate or it should be defined inside of a Style.", binding);
                     } 
 
                     convertedNode = new TemplatedParentBindingExpressionNode { Type = parentType };
                 }
                 else
                 {
-                    throw new XamlParseException($"Unknown RelativeSource mode '{mode}'.", binding);
+                    throw new XamlBindingsTransformException($"Unknown RelativeSource mode '{mode}'.", binding);
                 }
             }
 
@@ -265,7 +265,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
             {
                 if (me.Type.GetClrType() != context.GetAvaloniaTypes().RelativeSource)
                 {
-                    throw new XamlParseException($"Expected an object of type 'Avalonia.Data.RelativeSource'. Found a object of type '{me.Type.GetClrType().GetFqn()}'", me);
+                    throw new XamlBindingsTransformException($"Expected an object of type 'Avalonia.Data.RelativeSource'. Found a object of type '{me.Type.GetClrType().GetFqn()}'", me);
                 }
 
                 relativeSourceObject = (XamlAstObjectNode)me.Value;
@@ -276,7 +276,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
             {
                 if (on.Type.GetClrType() != context.GetAvaloniaTypes().RelativeSource)
                 {
-                    throw new XamlParseException($"Expected an object of type 'Avalonia.Data.RelativeSource'. Found a object of type '{on.Type.GetClrType().GetFqn()}'", on);
+                    throw new XamlBindingsTransformException($"Expected an object of type 'Avalonia.Data.RelativeSource'. Found a object of type '{on.Type.GetClrType().GetFqn()}'", on);
                 }
 
                 relativeSourceObject = on;

+ 1 - 1
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs

@@ -116,7 +116,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                     var parentDataContextNode = context.ParentNodes().OfType<AvaloniaXamlIlDataContextTypeMetadataNode>().FirstOrDefault();
                     if (parentDataContextNode is null)
                     {
-                        throw new XamlX.XamlParseException("Cannot parse a compiled binding without an explicit x:DataType directive to give a starting data type for bindings.", binding);
+                        throw new XamlBindingsTransformException("Cannot parse a compiled binding without an explicit x:DataType directive to give a starting data type for bindings.", binding);
                     }
 
                     return parentDataContextNode.DataContextType;

+ 2 - 2
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlThemeTransformer.cs

@@ -21,7 +21,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
 
             var targetTypeNode = on.Children.OfType<XamlAstXamlPropertyValueNode>()
                 .FirstOrDefault(p => p.Property.GetClrProperty().Name == "TargetType") ??
-                throw new XamlParseException("ControlTheme must have a TargetType.", node);
+                throw new XamlTransformException("ControlTheme must have a TargetType.", node);
 
             IXamlType targetType;
 
@@ -30,7 +30,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
             else if (targetTypeNode.Values[0] is XamlAstTextNode text)
                 targetType = TypeReferenceResolver.ResolveType(context, text.Text, false, text, true).GetClrType();
             else
-                throw new XamlParseException("Could not determine TargetType for ControlTheme.", targetTypeNode);
+                throw new XamlTransformException("Could not determine TargetType for ControlTheme.", targetTypeNode);
 
             return new AvaloniaXamlIlTargetTypeMetadataNode(on,
                 new XamlAstClrTypeReference(targetTypeNode, targetType, false),

+ 17 - 5
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs

@@ -10,6 +10,14 @@ using XamlX.TypeSystem;
 
 namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
 {
+    class XamlDataContextException : XamlTransformException
+    {
+        public XamlDataContextException(string message, IXamlLineInfo lineInfo, Exception innerException = null)
+            : base(message, lineInfo, innerException)
+        {
+        }
+    }
+
     class AvaloniaXamlIlDataContextTypeTransformer : IXamlAstTransformer
     {
         public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
@@ -43,7 +51,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                             }
                             else
                             {
-                                throw new XamlX.XamlParseException("x:DataType should be set to a type name.", directive.Values[0]);
+                                throw new XamlDataContextException("x:DataType should be set to a type name.", directive.Values[0]);
                             }
                         }
                     }
@@ -136,7 +144,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                     var parentItemsDataContext = context.ParentNodes().SkipWhile(n => n != parentObject).OfType<AvaloniaXamlIlDataContextTypeMetadataNode>().FirstOrDefault();
                     if (parentItemsDataContext != null)
                     {
-                        itemsCollectionType = XamlIlBindingPathHelper.UpdateCompiledBindingExtension(context, parentItemsBinding, () => parentItemsDataContext.DataContextType, parentObject.Type.GetClrType());
+                        itemsCollectionType = XamlIlBindingPathHelper.UpdateCompiledBindingExtension(context,
+                            parentItemsBinding, () => parentItemsDataContext.DataContextType,
+                            parentObject.Type.GetClrType());
                     }
                 }
             }
@@ -179,13 +189,15 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                     var parentDataContextNode = context.ParentNodes().OfType<AvaloniaXamlIlDataContextTypeMetadataNode>().FirstOrDefault();
                     if (parentDataContextNode is null)
                     {
-                        throw new XamlX.XamlParseException("Cannot parse a compiled binding without an explicit x:DataType directive to give a starting data type for bindings.", obj);
+                        throw new XamlDataContextException("Cannot parse a compiled binding without an explicit x:DataType directive to give a starting data type for bindings.", obj);
                     }
 
                     return parentDataContextNode.DataContextType;
                 };
 
-                var bindingResultType = XamlIlBindingPathHelper.UpdateCompiledBindingExtension(context, obj, startTypeResolver, on.Type.GetClrType());
+                var bindingResultType =
+                    XamlIlBindingPathHelper.UpdateCompiledBindingExtension(context, obj, startTypeResolver,
+                        on.Type.GetClrType());
                 return new AvaloniaXamlIlDataContextTypeMetadataNode(on, bindingResultType);
             }
 
@@ -222,6 +234,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
         {
         }
 
-        public override IXamlType DataContextType => throw new XamlTransformException("Unable to infer DataContext type for compiled bindings nested within this element. Please set x:DataType on the Binding or parent.", Value);
+        public override IXamlType DataContextType => XamlPseudoType.Unknown;
     }
 }

+ 4 - 1
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDuplicateSettersChecker.cs

@@ -36,7 +36,10 @@ class AvaloniaXamlIlDuplicateSettersChecker : IXamlAstTransformer
         {
             if (!index.Add(property))
             {
-                throw new XamlParseException($"Duplicate setter encountered for property '{property}'", node);
+                context.ReportDiagnostic(new XamlDiagnostic(
+                    AvaloniaXamlDiagnosticCodes.DuplicateSetterError,
+                    XamlDiagnosticSeverity.Warning,
+                    $"Duplicate setter encountered for property '{property}'", node));
             }
         }
 

+ 8 - 8
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlOptionMarkupExtensionTransformer.cs

@@ -38,7 +38,7 @@ internal class AvaloniaXamlIlOptionMarkupExtensionTransformer : IXamlAstTransfor
                 {
                     if (objectNode.Arguments.Count > 1)
                     {
-                        throw new XamlParseException("Options MarkupExtensions allow only single argument", objectNode);
+                        throw new XamlTransformException("Options MarkupExtensions allow only single argument", objectNode);
                     }
 
                     defaultValue = TransformNode(new[] { argument }, typeArgument, objectNode);
@@ -68,20 +68,20 @@ internal class AvaloniaXamlIlOptionMarkupExtensionTransformer : IXamlAstTransfor
                             ?? Array.Empty<string>();
                         if (options.Length == 0)
                         {
-                            throw new XamlParseException("On.Options string must be set", onObj);
+                            throw new XamlTransformException("On.Options string must be set", onObj);
                         }
 
                         var content = onObj.Children.OfType<XamlAstXamlPropertyValueNode>()
                             .SingleOrDefault(v => v.Property.GetClrProperty().Name == "Content");
                         if (content is null)
                         {
-                            throw new XamlParseException("On content object must be set", onObj);
+                            throw new XamlTransformException("On content object must be set", onObj);
                         }
 
                         var propertiesSet = options
                             .Select(o => type.GetAllProperties()
                                 .FirstOrDefault(p => o.Equals(p.Name, StringComparison.Ordinal))
-                                ?? throw new XamlParseException($"Property \"{o}\" wasn't found on the \"{type.Name}\" type", onObj))
+                                ?? throw new XamlTransformException($"Property \"{o}\" wasn't found on the \"{type.Name}\" type", onObj))
                             .ToArray();
                         foreach (var propertySet in propertiesSet)
                         {
@@ -102,7 +102,7 @@ internal class AvaloniaXamlIlOptionMarkupExtensionTransformer : IXamlAstTransfor
 
             if (defaultValue is null && !values.Any())
             {
-                throw new XamlParseException("Options markup extension requires at least one option to be set", objectNode);
+                throw new XamlTransformException("Options markup extension requires at least one option to be set", objectNode);
             }
 
             return new OptionsMarkupExtensionNode(
@@ -125,7 +125,7 @@ internal class AvaloniaXamlIlOptionMarkupExtensionTransformer : IXamlAstTransfor
                     var option = optAttr.Parameters.Single();
                     if (option is null)
                     {
-                        throw new XamlParseException("MarkupExtension option must not be null", li);
+                        throw new XamlTransformException("MarkupExtension option must not be null", li);
                     }
 
                     var optionAsString = option.ToString();
@@ -173,7 +173,7 @@ internal class AvaloniaXamlIlOptionMarkupExtensionTransformer : IXamlAstTransfor
                         }
                     }
 
-                    throw new XamlParseException($"Option value \"{optionAsString}\" is not assignable to any of existing ShouldProvideOption methods", li);
+                    throw new XamlTransformException($"Option value \"{optionAsString}\" is not assignable to any of existing ShouldProvideOption methods", li);
                 }
 
                 return false;
@@ -198,7 +198,7 @@ internal class AvaloniaXamlIlOptionMarkupExtensionTransformer : IXamlAstTransfor
 
             if (values.Count > 1)
             {
-                throw new XamlParseException("Options markup extension supports only a singular value", line);
+                throw new XamlTransformException("Options markup extension supports only a singular value", line);
             }
 
             return values.Single();

+ 18 - 10
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlPropertyPathTransformer.cs

@@ -12,6 +12,14 @@ using XamlX.IL;
 
 namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
 {
+    class XamlPropertyPathException : XamlTransformException
+    {
+        public XamlPropertyPathException(string message, IXamlLineInfo lineInfo, Exception innerException = null)
+            : base(message, lineInfo, innerException)
+        {
+        }
+    }
+
     class AvaloniaXamlIlPropertyPathTransformer : IXamlAstTransformer
     {
         public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
@@ -26,9 +34,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                 var parentScope = context.ParentNodes().OfType<AvaloniaXamlIlTargetTypeMetadataNode>()
                     .FirstOrDefault();
                 if(parentScope == null)
-                    throw new XamlX.XamlParseException("No target type scope found for property path", text);
+                    throw new XamlPropertyPathException("No target type scope found for property path", text);
                 if (parentScope.ScopeType != AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style)
-                    throw new XamlX.XamlParseException("PropertyPath is currently only valid for styles", pv);
+                    throw new XamlPropertyPathException("PropertyPath is currently only valid for styles", pv);
 
 
                 IEnumerable<PropertyPathGrammar.ISyntax> parsed;
@@ -38,7 +46,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                 }
                 catch (Exception e)
                 {
-                    throw new XamlX.XamlParseException("Unable to parse PropertyPath: " + e.Message, text);
+                    throw new XamlPropertyPathException("Unable to parse PropertyPath: " + e.Message, text, innerException: e);
                 }
 
                 var elements = new List<IXamlIlPropertyPathElementNode>();
@@ -59,7 +67,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                 void HandleProperty(string name, string typeNamespace, string typeName)
                 {
                     if(!expectProperty || currentType == null)
-                        throw new XamlX.XamlParseException("Unexpected property node", text);
+                        throw new XamlPropertyPathException("Unexpected property node", text);
 
                     var propertySearchType =
                         typeName != null ? GetType(typeNamespace, typeName) : currentType;
@@ -80,7 +88,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                     }
 
                     if (prop == null)
-                        throw new XamlX.XamlParseException(
+                        throw new XamlPropertyPathException(
                             $"Unable to resolve property {name} on type {propertySearchType.GetFqn()}",
                             text);
                     
@@ -95,7 +103,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                     if (ge is PropertyPathGrammar.ChildTraversalSyntax)
                     {
                         if (!expectTraversal)
-                            throw new XamlX.XamlParseException("Unexpected child traversal .", text);
+                            throw new XamlPropertyPathException("Unexpected child traversal .", text);
                         elements.Add(new XamlIlChildTraversalPropertyPathElementNode());
                         expectTraversal = expectCast = false;
                         expectProperty = true;
@@ -103,7 +111,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                     else if (ge is PropertyPathGrammar.EnsureTypeSyntax ets)
                     {
                         if(!expectCast)
-                            throw new XamlX.XamlParseException("Unexpected cast node", text);
+                            throw new XamlPropertyPathException("Unexpected cast node", text);
                         currentType = GetType(ets.TypeNamespace, ets.TypeName);
                         elements.Add(new XamlIlCastPropertyPathElementNode(currentType, true));
                         expectProperty = false;
@@ -112,7 +120,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                     else if (ge is PropertyPathGrammar.CastTypeSyntax cts)
                     {
                         if(!expectCast)
-                            throw new XamlX.XamlParseException("Unexpected cast node", text);
+                            throw new XamlPropertyPathException("Unexpected cast node", text);
                         //TODO: Check if cast can be done
                         currentType = GetType(cts.TypeNamespace, cts.TypeName);
                         elements.Add(new XamlIlCastPropertyPathElementNode(currentType, false));
@@ -128,12 +136,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                         HandleProperty(tqps.Name, tqps.TypeNamespace, tqps.TypeName);
                     }
                     else
-                        throw new XamlX.XamlParseException("Unexpected node " + ge, text);
+                        throw new XamlPropertyPathException("Unexpected node " + ge, text);
                     
                 }
                 var propertyPathNode = new XamlIlPropertyPathNode(text, elements, types);
                 if (propertyPathNode.Type == null)
-                    throw new XamlX.XamlParseException("Unexpected end of the property path", text);
+                    throw new XamlPropertyPathException("Unexpected end of the property path", text);
                 pv.Values[0] = propertyPathNode;
             }
 

+ 25 - 17
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs

@@ -13,8 +13,14 @@ using XamlX.TypeSystem;
 
 namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
 {
-    using XamlParseException = XamlX.XamlParseException;
-    using XamlLoadException = XamlX.XamlLoadException;
+    internal class XamlSelectorsTransformException : XamlTransformException
+    {
+        public XamlSelectorsTransformException(string message, IXamlLineInfo lineInfo, Exception innerException = null)
+            : base(message, lineInfo, innerException)
+        {
+        }
+    }
+
     class AvaloniaXamlIlSelectorTransformer : IXamlAstTransformer
     {
         public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
@@ -30,14 +36,15 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                 return node;
 
             if (pn.Values.Count != 1)
-                throw new XamlParseException("Selector property should should have exactly one value", node);
+                throw new XamlSelectorsTransformException("Selector property should should have exactly one value",
+                    node);
             
             if (pn.Values[0] is XamlIlSelectorNode)
                 //Deja vu. I've just been in this place before
                 return node;
             
             if (!(pn.Values[0] is XamlAstTextNode tn))
-                throw new XamlParseException("Selector property should be a text node", node);
+                throw new XamlSelectorsTransformException("Selector property should be a text node", node);
 
             var selectorType = pn.Property.GetClrProperty().Getter.ReturnType;
             var initialNode = new XamlIlSelectorInitialNode(node, selectorType);
@@ -69,18 +76,18 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                             var type = result?.TargetType;
 
                             if (type == null)
-                                throw new XamlParseException("Property selectors must be applied to a type.", node);
+                                throw new XamlTransformException("Property selectors must be applied to a type.", node);
 
                             var targetProperty =
                                 type.GetAllProperties().FirstOrDefault(p => p.Name == property.Property);
 
                             if (targetProperty == null)
-                                throw new XamlParseException($"Cannot find '{property.Property}' on '{type}", node);
+                                throw new XamlTransformException($"Cannot find '{property.Property}' on '{type}", node);
 
                             if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context,
                                 new XamlAstTextNode(node, property.Value, type: context.Configuration.WellKnownTypes.String),
                                 targetProperty.PropertyType, out var typedValue))
-                                throw new XamlParseException(
+                                throw new XamlTransformException(
                                     $"Cannot convert '{property.Value}' to '{targetProperty.PropertyType.GetFqn()}",
                                     node);
 
@@ -92,13 +99,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                                 var targetType = result?.TargetType;
                                 if (targetType == null)
                                 {
-                                    throw new XamlParseException("Attached Property selectors must be applied to a type.",node);
+                                    throw new XamlTransformException("Attached Property selectors must be applied to a type.",node);
                                 }
                                 var attachedPropertyOwnerType = typeResolver(attachedProperty.Xmlns, attachedProperty.TypeName).Type;
 
                                 if (attachedPropertyOwnerType is null)
                                 {
-                                    throw new XamlParseException($"Cannot find '{attachedProperty.Xmlns}:{attachedProperty.TypeName}",node);
+                                    throw new XamlTransformException($"Cannot find '{attachedProperty.Xmlns}:{attachedProperty.TypeName}",node);
                                 }
 
                                 var attachedPropertyName = attachedProperty.Property + "Property";
@@ -112,7 +119,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
 
                                 if (targetPropertyField is null)
                                 {
-                                    throw new XamlParseException($"Cannot find '{attachedProperty.Property}' on '{attachedPropertyOwnerType.GetFqn()}", node);
+                                    throw new XamlTransformException($"Cannot find '{attachedProperty.Property}' on '{attachedPropertyOwnerType.GetFqn()}", node);
                                 }
 
                                 var targetPropertyType = XamlIlAvaloniaPropertyHelper
@@ -121,7 +128,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                                 if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context,
                                     new XamlAstTextNode(node, attachedProperty.Value, type: context.Configuration.WellKnownTypes.String),
                                     targetPropertyType, out var typedValue))
-                                        throw new XamlParseException(
+                                        throw new XamlTransformException(
                                             $"Cannot convert '{attachedProperty.Value}' to '{targetPropertyType.GetFqn()}",
                                             node);
 
@@ -156,12 +163,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                             var parentTargetType = context.ParentNodes().OfType<AvaloniaXamlIlTargetTypeMetadataNode>().FirstOrDefault();
 
                             if (parentTargetType is null)
-                                throw new XamlParseException($"Cannot find parent style for nested selector.", node);
+                                throw new XamlTransformException($"Cannot find parent style for nested selector.", node);
 
                             result = new XamlIlNestingSelector(result, parentTargetType.TargetType.GetClrType());
                             break;
                         default:
-                            throw new XamlParseException($"Unsupported selector grammar '{i.GetType()}'.", node);
+                            throw new XamlTransformException($"Unsupported selector grammar '{i.GetType()}'.", node);
                     }
                 }
 
@@ -180,11 +187,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
             }
             catch (Exception e)
             {
-                throw new XamlParseException("Unable to parse selector: " + e.Message, node);
+                throw new XamlSelectorsTransformException("Unable to parse selector: " + e.Message, node, e);
             }
 
             var selector = Create(parsed, (p, n) 
-                => TypeReferenceResolver.ResolveType(context, $"{p}:{n}", true, node, true));
+                => TypeReferenceResolver.ResolveType(context, $"{p}:{n}", true, node, true)
+                    ?? new XamlAstClrTypeReference(node, XamlPseudoType.Unknown, false));
             pn.Values[0] = selector;
 
             var templateType = GetLastTemplateTypeFromSelector(selector);
@@ -402,7 +410,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
         protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
         {
             if (!XamlIlAvaloniaPropertyHelper.Emit(context, codeGen, Property))
-                throw new XamlLoadException(
+                throw new XamlX.XamlLoadException(
                     $"{Property.Name} of {(Property.Setter ?? Property.Getter).DeclaringType.GetFqn()} doesn't seem to be an AvaloniaProperty",
                     this);
             context.Emit(Value, codeGen, context.Configuration.WellKnownTypes.Object);
@@ -486,7 +494,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
         protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
         {
             if (_selectors.Count == 0)
-                throw new XamlLoadException("Invalid selector count", this);
+                throw new XamlX.XamlLoadException("Invalid selector count", this);
             if (_selectors.Count == 1)
             {
                 _selectors[0].Emit(context, codeGen);

+ 1 - 1
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTargetTypeMetadataTransformer.cs

@@ -25,7 +25,7 @@ internal class AvaloniaXamlIlSetterTargetTypeMetadataTransformer : IXamlAstTrans
 
             if (type is null)
             {
-                throw new XamlParseException("Unable to resolve SetterTargetType type", typeDirective);
+                throw new XamlTransformException("Unable to resolve SetterTargetType type", typeDirective);
             }
             return new AvaloniaXamlIlTargetTypeMetadataNode(on, type, AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style);
         }

+ 15 - 6
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs

@@ -1,3 +1,4 @@
+using System;
 using System.Collections.Generic;
 using System.Linq;
 using XamlX;
@@ -9,6 +10,14 @@ using XamlX.TypeSystem;
 
 namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
 {
+    class XamlStyleTransformException : XamlTransformException
+    {
+        public XamlStyleTransformException(string message, IXamlLineInfo lineInfo, Exception innerException = null)
+            : base(message, lineInfo, innerException)
+        {
+        }
+    }
+
     class AvaloniaXamlIlSetterTransformer : IXamlAstTransformer
     {
         public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
@@ -27,13 +36,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
             if (styleParent != null)
             {
                 targetType = styleParent.TargetType.GetClrType()
-                             ?? throw new XamlParseException("Can not find parent Style Selector or ControlTemplate TargetType. If setter is not part of the style, you can set x:SetterTargetType directive on its parent.", node);
+                             ?? throw new XamlStyleTransformException("Can not find parent Style Selector or ControlTemplate TargetType. If setter is not part of the style, you can set x:SetterTargetType directive on its parent.", node);
                 lineInfo = on;
             }
 
             if (targetType == null)
             {
-                throw new XamlParseException("Could not determine target type of Setter", node);
+                throw new XamlStyleTransformException("Could not determine target type of Setter", node);
             }
 
             IXamlType propType = null;
@@ -43,7 +52,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
             {
                 var propertyName = property.Values.OfType<XamlAstTextNode>().FirstOrDefault()?.Text;
                 if (propertyName == null)
-                    throw new XamlParseException("Setter.Property must be a string", node);
+                    throw new XamlStyleTransformException("Setter.Property must be a string", node);
 
                 var avaloniaPropertyNode = XamlIlAvaloniaPropertyHelper.CreateNode(context, propertyName,
                     new XamlAstClrTypeReference(lineInfo, targetType, false), property.Values[0]);
@@ -55,12 +64,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                 var propertyPath = on.Children.OfType<XamlAstXamlPropertyValueNode>()
                     .FirstOrDefault(x => x.Property.GetClrProperty().Name == "PropertyPath");
                 if (propertyPath == null)
-                    throw new XamlX.XamlParseException("Setter without a property or property path is not valid", node);
+                    throw new XamlStyleTransformException("Setter without a property or property path is not valid", node);
                 if (propertyPath.Values[0] is IXamlIlPropertyPathNode ppn
                     && ppn.PropertyType != null)
                     propType = ppn.PropertyType;
                 else
-                    throw new XamlX.XamlParseException("Unable to get the property path property type", node);
+                    throw new XamlStyleTransformException("Unable to get the property path property type", node);
             }
 
             var valueProperty = on.Children
@@ -69,7 +78,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
             {
                 if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context, valueProperty.Values[0],
                         propType, out var converted))
-                    throw new XamlParseException(
+                    throw new XamlStyleTransformException(
                         $"Unable to convert property value to {propType.GetFqn()}",
                         valueProperty.Values[0]);
 

+ 34 - 0
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlStyleValidatorTransformer.cs

@@ -0,0 +1,34 @@
+using System.Linq;
+using XamlX;
+using XamlX.Ast;
+using XamlX.Transform;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
+
+internal class AvaloniaXamlIlStyleValidatorTransformer : IXamlAstTransformer
+{
+    // See https://github.com/AvaloniaUI/Avalonia/issues/7461
+    public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
+    {
+        if (!(node is XamlAstObjectNode on
+              && context.GetAvaloniaTypes().IStyle.IsAssignableFrom(on.Type.GetClrType())))
+            return node;
+
+        if (context.ParentNodes().FirstOrDefault() is XamlAstXamlPropertyValueNode propertyValueNode
+            && propertyValueNode.Property.GetClrProperty() is { } clrProperty
+            && clrProperty.Name == "MergedDictionaries"
+            && clrProperty.DeclaringType == context.GetAvaloniaTypes().ResourceDictionary)
+        {
+            var nodeName = on.Type.GetClrType().Name;
+            context.ReportDiagnostic(new XamlDiagnostic(
+                AvaloniaXamlDiagnosticCodes.StyleInMergedDictionaries,
+                XamlDiagnosticSeverity.Warning,
+                // Keep it single line, as MSBuild splits multiline warnings into two warnings.
+                $"Including {nodeName} as part of MergedDictionaries will ignore any nested styles." +
+                $"Instead, you can add {nodeName} to the Styles collection on the same control or application.",
+                node));
+        }
+        
+        return node;
+    }
+}

+ 0 - 26
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlDocumentParseException.cs

@@ -1,26 +0,0 @@
-using XamlX;
-using XamlX.Ast;
-
-namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions;
-
-internal class XamlDocumentParseException : XamlParseException
-{
-    public string FilePath { get; }
-
-    public XamlDocumentParseException(string path, XamlParseException parseException)
-        : base(parseException.Message, parseException.LineNumber, parseException.LinePosition)
-    {
-        FilePath = path;
-    }
-    
-    public XamlDocumentParseException(string path, string message, IXamlLineInfo lineInfo)
-        : base(message, lineInfo.Line, lineInfo.Position)
-    {
-        FilePath = path;
-    }
-    
-    public XamlDocumentParseException(IXamlDocumentResource document, string message, IXamlLineInfo lineInfo)
-        : this(document.FileSource?.FilePath, message, lineInfo)
-    {
-    }
-}

+ 2 - 2
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs

@@ -83,7 +83,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
                 var found = tref.Type.GetAllFields()
                     .FirstOrDefault(f => f.IsStatic && f.IsPublic && f.Name == propertyFieldName);
                 if (found == null)
-                    throw new XamlX.XamlParseException(
+                    throw new XamlX.XamlTransformException(
                         $"Unable to find {propertyFieldName} field on type {tref.Type.GetFullName()}", lineInfo);
                 return new XamlIlAvaloniaPropertyFieldNode(context.GetAvaloniaTypes(), lineInfo, found);
             }
@@ -110,7 +110,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
                 avaloniaPropertyType = avaloniaPropertyType.BaseType;
             }
 
-            throw new XamlX.XamlParseException(
+            throw new XamlX.XamlTransformException(
                 $"{field.Name}'s type {field.FieldType} doesn't inherit from  AvaloniaProperty<T>, make sure to use typed properties",
                 lineInfo);
 

+ 13 - 14
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs

@@ -87,8 +87,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
                 return transformed;
             }
 
-            var lastElement =
-                transformed.Elements[transformed.Elements.Count - 1];
+            var lastElement = transformed.Elements.LastOrDefault();
             
             if (parentNode.Property?.Getter?.ReturnType == context.GetAvaloniaTypes().ICommand && lastElement is XamlIlClrMethodPathElementNode methodPathElement)
             {
@@ -158,7 +157,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
                             {
                                 break;
                             }
-                            throw new XamlX.XamlParseException($"Compiled bindings do not support stream bindings for objects of type {targetType.FullName}.", lineInfo);
+                            throw new XamlX.XamlTransformException($"Compiled bindings do not support stream bindings for objects of type {targetType.FullName}.", lineInfo);
                         }
                     case BindingExpressionGrammar.PropertyNameNode propName:
                         {
@@ -182,7 +181,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
                             }
                             else
                             {
-                                throw new XamlX.XamlParseException($"Unable to resolve property or method of name '{propName.PropertyName}' on type '{targetType}'.", lineInfo);
+                                throw new XamlX.XamlTransformException($"Unable to resolve property or method of name '{propName.PropertyName}' on type '{targetType}'.", lineInfo);
                             }
                             break;
                         }
@@ -207,7 +206,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
                             }
                             if (property is null)
                             {
-                                throw new XamlX.XamlParseException($"The type '${targetType}' does not have an indexer.", lineInfo);
+                                throw new XamlX.XamlTransformException($"The type '${targetType}' does not have an indexer.", lineInfo);
                             }
 
                             IEnumerable<IXamlType> parameters = property.IndexerParameters;
@@ -219,7 +218,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
                                 var textNode = new XamlAstTextNode(lineInfo, indexer.Arguments[currentParamIndex], type: context.Configuration.WellKnownTypes.String);
                                 if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context, textNode,
                                         param, out var converted))
-                                    throw new XamlX.XamlParseException(
+                                    throw new XamlX.XamlTransformException(
                                         $"Unable to convert indexer parameter value of '{indexer.Arguments[currentParamIndex]}' to {param.GetFqn()}",
                                         textNode);
 
@@ -267,7 +266,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
 
                             if (ancestorType is null)
                             {
-                                throw new XamlX.XamlParseException("Unable to resolve implicit ancestor type based on XAML tree.", lineInfo);
+                                throw new XamlX.XamlTransformException("Unable to resolve implicit ancestor type based on XAML tree.", lineInfo);
                             }
 
                             nodes.Add(new FindAncestorPathElementNode(ancestorType, ancestor.Level));
@@ -294,7 +293,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
 
                         if (elementType is null)
                         {
-                            throw new XamlX.XamlParseException($"Unable to find element '{elementName.Name}' in the current namescope. Unable to use a compiled binding with a name binding if the name cannot be found at compile time.", lineInfo);
+                            throw new XamlX.XamlTransformException($"Unable to find element '{elementName.Name}' in the current namescope. Unable to use a compiled binding with a name binding if the name cannot be found at compile time.", lineInfo);
                         }
                         nodes.Add(new ElementNamePathElementNode(elementName.Name, elementType));
                         break;
@@ -306,7 +305,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
 
                         if (castType is null)
                         {
-                            throw new XamlX.XamlParseException($"Unable to resolve cast to type {typeCastNode.Namespace}:{typeCastNode.TypeName} based on XAML tree.", lineInfo);
+                            throw new XamlX.XamlTransformException($"Unable to resolve cast to type {typeCastNode.Namespace}:{typeCastNode.TypeName} based on XAML tree.", lineInfo);
                         }
 
                         nodes.Add(new TypeCastPathElementNode(castType));
@@ -785,7 +784,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
                 {
                     if (!int.TryParse(item, out var index))
                     {
-                        throw new XamlX.XamlParseException($"Unable to convert '{item}' to an integer.", lineInfo.Line, lineInfo.Position);
+                        throw new XamlX.XamlTransformException($"Unable to convert '{item}' to an integer.", lineInfo);
                     }
                     _values.Add(index);
                 }
@@ -866,10 +865,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
                 Elements = elements;
             }
 
-            public IXamlType BindingResultType
-                => _transformElements.Count > 0
-                    ? _transformElements[0].Type
-                    : Elements[Elements.Count - 1].Type;
+            public IXamlType BindingResultType =>
+                _transformElements.FirstOrDefault()?.Type
+                    ?? Elements.LastOrDefault()?.Type
+                    ?? XamlPseudoType.Unknown;
 
             public IXamlAstTypeReference Type { get; }
 

+ 1 - 1
src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github

@@ -1 +1 @@
-Subproject commit aa2223dec1e7c70679fdb73f9d364363a0285adb
+Subproject commit b7ed273273949a5dd9f01e682ab97f61b43697ad

+ 47 - 0
src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderConfiguration.cs

@@ -1,3 +1,4 @@
+using System;
 using System.Reflection;
 
 namespace Avalonia.Markup.Xaml;
@@ -20,4 +21,50 @@ public class RuntimeXamlLoaderConfiguration
     /// Default is 'false'.
     /// </summary>
     public bool DesignMode { get; set; } = false;
+
+    /// <summary>
+    /// XAML diagnostics handler.
+    /// </summary>
+    /// <returns>
+    /// Defines if any diagnostic severity should be overriden.
+    /// Note, severity cannot be set lower than minimal for specific diagnostic.
+    /// </returns>
+    public XamlDiagnosticFunc? DiagnosticHandler { get; set; }
+
+    /// <summary>
+    /// Delegate for <see cref="RuntimeXamlLoaderConfiguration.DiagnosticHandler"/> property.
+    /// </summary>
+    public delegate RuntimeXamlDiagnosticSeverity XamlDiagnosticFunc(RuntimeXamlDiagnostic diagnostic);
+}
+
+public enum RuntimeXamlDiagnosticSeverity
+{
+    None = 0,
+    
+    /// <summary>
+    /// Diagnostic is reported as a warning.
+    /// </summary>
+    Warning = 1,
+    
+    /// <summary>
+    /// Diagnostic is reported as an error.
+    /// Compilation process is continued until the end of the parsing and transforming stage, throwing an aggregated exception of all errors.
+    /// </summary>
+    Error,
+    
+    /// <summary>
+    /// Diagnostic is reported as an fatal error.
+    /// Compilation process is stopped right after this error.
+    /// </summary>
+    Fatal
+}
+
+public record RuntimeXamlDiagnostic(
+    string Id,
+    RuntimeXamlDiagnosticSeverity Severity,
+    string Title,
+    int? LineNumber,
+    int? LinePosition)
+{
+    public string? Document { get; init; }
 }

+ 1 - 0
src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj

@@ -22,6 +22,7 @@
     <Compile Include="..\..\..\src\Markup\Avalonia.Markup.Xaml.Loader\CompilerDynamicDependencies.cs" />
     <Compile Include="..\..\Avalonia.Base\Utilities\StringBuilderCache.cs" Link="Utilities\StringBuilderCache.cs" />
     <Compile Include="..\..\Avalonia.Base\Compatibility\TrimmingAttributes.cs" Link="TrimmingAttributes.cs" Visible="False" />
+    <Compile Include="..\..\Shared\IsExternalInit.cs" Link="Compatibility\IsExternalInit.cs" />
   </ItemGroup>
   <ItemGroup>
     <PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.22045.20230930" />

+ 6 - 2
src/tools/Avalonia.Generators/Compiler/MiniCompiler.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using XamlX;
 using XamlX.Compiler;
 using XamlX.Emit;
 using XamlX.Transform;
@@ -19,10 +20,13 @@ internal sealed class MiniCompiler : XamlCompiler<object, IXamlEmitResult>
         foreach (var additionalType in additionalTypes)
             mappings.XmlnsAttributes.Add(typeSystem.GetType(additionalType));
 
+        var diagnosticsHandler = new XamlDiagnosticsHandler();
+        
         var configuration = new TransformerConfiguration(
             typeSystem,
             typeSystem.Assemblies.First(),
-            mappings);
+            mappings,
+            diagnosticsHandler: diagnosticsHandler);
         return new MiniCompiler(configuration);
     }
         
@@ -47,4 +51,4 @@ internal sealed class MiniCompiler : XamlCompiler<object, IXamlEmitResult>
         XamlRuntimeContext<object, IXamlEmitResult> context,
         bool needContextLocal) =>
         throw new NotSupportedException();
-}
+}

+ 1 - 12
tests/Avalonia.Generators.Tests/MiniCompilerTests.cs

@@ -14,7 +14,6 @@ public class MiniCompilerTests
 {
     private const string AvaloniaXaml = "<TextBlock xmlns='clr-namespace:Avalonia.Controls;assembly=Avalonia' />";
     private const string MiniClass = "namespace Example { public class Valid { public int Foo() => 21; } }";
-    private const string MiniInvalidXaml = "<Invalid xmlns='clr-namespace:Example;assembly=Example' />";
     private const string MiniValidXaml = "<Valid xmlns='clr-namespace:Example;assembly=Example' />";
 
     [Fact]
@@ -27,16 +26,6 @@ public class MiniCompilerTests
         Assert.NotNull(xaml.Root);
     }
 
-    [Fact]
-    public void Should_Throw_When_Unable_To_Resolve_Types_From_Simple_Invalid_Markup()
-    {
-        var xaml = XDocumentXamlParser.Parse(MiniInvalidXaml);
-        var compilation = CreateBasicCompilation(MiniClass);
-        var compiler = MiniCompiler.CreateDefault(new RoslynTypeSystem(compilation));
-
-        Assert.Throws<XamlParseException>(() => compiler.Transform(xaml));
-    }
-
     [Fact]
     public void Should_Resolve_Types_From_Simple_Avalonia_Markup()
     {
@@ -56,4 +45,4 @@ public class MiniCompilerTests
             .AddReferences(MetadataReference.CreateFromFile(typeof(ISupportInitialize).Assembly.Location))
             .AddReferences(MetadataReference.CreateFromFile(typeof(TypeConverterAttribute).Assembly.Location))
             .AddSyntaxTrees(CSharpSyntaxTree.ParseText(source));
-}
+}

+ 3 - 2
tests/Avalonia.Generators.Tests/Views/xNamedControls.xml

@@ -8,7 +8,8 @@
         <TextBox x:Name="PasswordTextBox"
                  Watermark="Password input"
                  UseFloatingWatermark="True" />
+        <!-- Name generator should still generate members, even if XAML is invalid -->
         <Button x:Name="SignUpButton"
-                Content="Sign up" />
+                Content="{x:Static NonExistent.Member}" />
     </StackPanel>
-</Window>
+</Window>

+ 29 - 24
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs

@@ -8,6 +8,7 @@ using System.Linq;
 using System.Reactive.Subjects;
 using System.Runtime.CompilerServices;
 using System.Threading.Tasks;
+using System.Xml;
 using Avalonia.Collections;
 using Avalonia.Controls;
 using Avalonia.Controls.Presenters;
@@ -496,7 +497,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
     </Window.DataTemplates>
     <ContentControl Name='target' Content='{CompiledBinding}' />
 </Window>";
-                ThrowsXamlTransformException(() => AvaloniaRuntimeXamlLoader.Load(xaml));
+                Assert.ThrowsAny<XmlException>(() => AvaloniaRuntimeXamlLoader.Load(xaml));
             }
         }
 
@@ -514,7 +515,29 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
         <TextBlock Text='{CompiledBinding StringProperty}' Name='textBlock' />
     </ContentControl>
 </Window>";
-                ThrowsXamlTransformException(() => AvaloniaRuntimeXamlLoader.Load(xaml));
+                Assert.ThrowsAny<XmlException>(() => AvaloniaRuntimeXamlLoader.Load(xaml));
+            }
+        }
+
+        [Fact]
+        public void ReportsMultipleErrorsOnDataContextAndBindingPathErrors()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var xaml = @"
+<Window xmlns='https://github.com/avaloniaui'
+        xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
+        xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'>
+    <ContentControl Content='{CompiledBinding NoDataContext}'
+                    Tag='{CompiledBinding NonExistentProp, DataType=local:TestDataContext}'
+                    Height='{CompiledBinding invalid.}' />
+</Window>";
+                var ex = Assert.Throws<AggregateException>(() => AvaloniaRuntimeXamlLoader.Load(xaml));
+                Assert.Collection(
+                    ex.InnerExceptions,
+                    inner => Assert.IsAssignableFrom<XmlException>(inner),
+                    inner => Assert.IsAssignableFrom<XmlException>(inner),
+                    inner => Assert.IsAssignableFrom<XmlException>(inner));
             }
         }
 
@@ -663,7 +686,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
         </ItemsControl.DataTemplates>
     </ItemsControl>
 </Window>";
-                ThrowsXamlTransformException(() => AvaloniaRuntimeXamlLoader.Load(xaml));
+                Assert.ThrowsAny<XmlException>(() => AvaloniaRuntimeXamlLoader.Load(xaml));
             }
         }
         
@@ -1148,7 +1171,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
         x:CompileBindings='true'>
     <TextBlock Text='{Binding InvalidPath}' Name='textBlock' />
 </Window>";
-                ThrowsXamlParseException(() => AvaloniaRuntimeXamlLoader.Load(xaml));
+                Assert.ThrowsAny<XmlException>(() => AvaloniaRuntimeXamlLoader.Load(xaml));
             }
         }
 
@@ -1227,7 +1250,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
         x:DataType='local:TestDataContext'
         x:CompileBindings='notabool'>
 </Window>";
-                ThrowsXamlParseException(() => AvaloniaRuntimeXamlLoader.Load(xaml));
+                Assert.ThrowsAny<XmlException>(() => AvaloniaRuntimeXamlLoader.Load(xaml));
             }
         }
 
@@ -1843,25 +1866,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
                     , textBlock.GetValue(TextBlock.TextProperty));
             }
         }
-
-        static void Throws(string type, Action cb)
-        {
-            try
-            {
-                cb();
-            }
-            catch (Exception e) when (e.GetType().Name == type)
-            {
-                return;
-            }
-
-            throw new Exception("Expected " + type);
-        }
-
-        static void ThrowsXamlParseException(Action cb) => Throws("XamlParseException", cb);
-        static void ThrowsXamlTransformException(Action cb) => Throws("XamlTransformException", cb);
-
-
+        
         static void PerformClick(Button button)
         {
             button.RaiseEvent(new KeyEventArgs

+ 134 - 0
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/AvaloniaIntrinsicsTests.cs

@@ -0,0 +1,134 @@
+using System;
+using System.Collections.Generic;
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Media;
+using Avalonia.Media.Immutable;
+using Avalonia.Styling;
+using Xunit;
+
+namespace Avalonia.Markup.Xaml.UnitTests.Xaml;
+
+public class AvaloniaIntrinsicsTests : XamlTestBase
+{
+    [Fact]
+    public void All_Intrinsics_Are_Parsed_And_Set()
+    {
+        var xaml = @"<local:TestIntrinsicsControl 
+            xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'
+            TimeSpanProperty='00:10:10'
+            ThicknessProperty='1 1 1 1'
+            PointProperty='15, 15'
+            VectorProperty='16.6, 16.6'
+            SizeProperty='20, 20'
+            MatrixProperty='1 0 0 1 0 0'
+            CornerRadiusProperty='4'
+            ColorProperty='#44ff11'
+            RelativePointProperty='50%, 50%'
+            GridLengthProperty='10*'
+            IBrushProperty='#44ff11'
+            TextTrimmingProperty='CharacterEllipsis'
+            TextDecorationCollectionProperty='Strikethrough'
+            WindowTransparencyLevelProperty='AcrylicBlur'
+            UriProperty='https://avaloniaui.net/'
+            ThemeVariantProperty='Dark'
+            PointsProperty='1, 1, 2, 2' />";
+
+        var target = AvaloniaRuntimeXamlLoader.Parse<TestIntrinsicsControl>(xaml);
+
+        Assert.NotNull(target);
+        Assert.Equal(new TimeSpan(0, 10, 10), target.TimeSpanProperty);
+        Assert.Equal(new Thickness(1), target.ThicknessProperty);
+        Assert.Equal(new Thickness(1), target.ThicknessProperty);
+        Assert.Equal(new Point(15, 15), target.PointProperty);
+        Assert.Equal(new Vector(16.6, 16.6), target.VectorProperty);
+        Assert.Equal(new Size(20, 20), target.SizeProperty);
+        Assert.Equal(new Matrix(1, 0, 0, 1, 0, 0), target.MatrixProperty);
+        Assert.Equal(new CornerRadius(4), target.CornerRadiusProperty);
+        Assert.Equal(Color.Parse("#44ff11"), target.ColorProperty);
+        Assert.Equal(new RelativePoint(0.5, 0.5, RelativeUnit.Relative), target.RelativePointProperty);
+        Assert.Equal(new GridLength(10, GridUnitType.Star), target.GridLengthProperty);
+        Assert.Equal(new ImmutableSolidColorBrush(Color.Parse("#44ff11")), target.IBrushProperty);
+        Assert.Equal(TextTrimming.CharacterEllipsis, target.TextTrimmingProperty);
+        Assert.Equal(TextDecorations.Strikethrough, target.TextDecorationCollectionProperty);
+        Assert.Equal(WindowTransparencyLevel.AcrylicBlur, target.WindowTransparencyLevelProperty);
+        Assert.Equal(new Uri("https://avaloniaui.net/"), target.UriProperty);
+        Assert.Equal(ThemeVariant.Dark, target.ThemeVariantProperty);
+        Assert.Equal(new[] { new Point(1, 1), new Point(2, 2) }, target.PointsProperty);
+    }
+
+    [Fact]
+    public void All_Intrinsics_Report_Errors_If_Failed()
+    {
+        var xaml = @"<local:TestIntrinsicsControl 
+            xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'
+            TimeSpanProperty='00:00:10,1'
+            ThicknessProperty='1 1 1'
+            PointProperty='15% 15%'
+            VectorProperty='16.6. 16.6'
+            SizeProperty='20%, 20%'
+            MatrixProperty='1 0 1 0 0'
+            CornerRadiusProperty='4 1 4'
+            ColorProperty='#44ff1'
+            RelativePointProperty='50, 50%'
+            GridLengthProperty='10%'
+            PointsProperty='1, 1, 2' />";
+        // TODO: double check why we don't throw error on other supported types. Should it be warnings?
+
+        var diagnostics = new List<RuntimeXamlDiagnostic>();
+        Assert.Throws<AggregateException>(() => AvaloniaRuntimeXamlLoader.Load(new RuntimeXamlLoaderDocument(xaml),
+            new RuntimeXamlLoaderConfiguration
+            {
+                DiagnosticHandler = diagnostic =>
+                {
+                    diagnostics.Add(diagnostic);
+                    return diagnostic.Severity;
+                }
+            }));
+
+        Assert.Collection(
+            diagnostics,
+            d => AssertDiagnostic(d, "time span"),
+            d => AssertDiagnostic(d, "thickness"),
+            d => AssertDiagnostic(d, "point"),
+            d => AssertDiagnostic(d, "vector"),
+            d => AssertDiagnostic(d, "size"),
+            d => AssertDiagnostic(d, "matrix"),
+            d => AssertDiagnostic(d, "corner radius"),
+            d => AssertDiagnostic(d, "color"),
+            d => AssertDiagnostic(d, "relative point"),
+            d => AssertDiagnostic(d, "grid length"),
+            d => AssertDiagnostic(d, "points list"),
+            // Compiler attempts to parse PointsList twice - as a list and as a point.
+            d => AssertDiagnostic(d, "point"));
+
+        void AssertDiagnostic(RuntimeXamlDiagnostic runtimeXamlDiagnostic, string contains)
+        {
+            Assert.Equal(RuntimeXamlDiagnosticSeverity.Error, runtimeXamlDiagnostic.Severity);
+            Assert.Contains(contains, runtimeXamlDiagnostic.Title, StringComparison.OrdinalIgnoreCase);
+        }
+    }
+}
+
+public class TestIntrinsicsControl : Control
+{
+    public TimeSpan TimeSpanProperty { get; set; }
+
+    // public FontFamily FontFamilyProperty { get; set; }
+    public Thickness ThicknessProperty { get; set; }
+    public Point PointProperty { get; set; }
+    public Vector VectorProperty { get; set; }
+    public Size SizeProperty { get; set; }
+    public Matrix MatrixProperty { get; set; }
+    public CornerRadius CornerRadiusProperty { get; set; }
+    public Color ColorProperty { get; set; }
+    public RelativePoint RelativePointProperty { get; set; }
+    public GridLength GridLengthProperty { get; set; }
+    public IBrush IBrushProperty { get; set; }
+    public TextTrimming TextTrimmingProperty { get; set; }
+    public TextDecorationCollection TextDecorationCollectionProperty { get; set; }
+    public WindowTransparencyLevel WindowTransparencyLevelProperty { get; set; }
+    public Uri UriProperty { get; set; }
+    public ThemeVariant ThemeVariantProperty { get; set; }
+    public Points PointsProperty { get; set; }
+}

+ 28 - 0
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleIncludeTests.cs

@@ -277,6 +277,34 @@ public class StyleIncludeTests
         Assert.IsType<SimpleTheme>(control.Styles[0]);
         Assert.IsType<SimpleTheme>(control.Styles[1]);
     }
+    
+    [Fact]
+    public void Style_Inside_Resources_Should_Produce_Warning()
+    {
+        var diagnostics = new List<RuntimeXamlDiagnostic>();
+        var control = (ContentControl)AvaloniaRuntimeXamlLoader.Load(new RuntimeXamlLoaderDocument(@"
+<ContentControl xmlns='https://github.com/avaloniaui'
+                xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
+                xmlns:themes='clr-namespace:Avalonia.Themes.Simple;assembly=Avalonia.Themes.Simple'>
+    <ContentControl.Resources>
+        <ResourceDictionary>
+            <ResourceDictionary.MergedDictionaries>
+                <themes:SimpleTheme />
+            </ResourceDictionary.MergedDictionaries>
+        </ResourceDictionary>
+    </ContentControl.Resources>
+</ContentControl>"), new RuntimeXamlLoaderConfiguration
+        {
+            DiagnosticHandler = diagnostic =>
+            {
+                diagnostics.Add(diagnostic);
+                return diagnostic.Severity;
+            } 
+        });
+        Assert.IsAssignableFrom<IStyle>(((ResourceDictionary)control.Resources)!.MergedDictionaries[0]);
+        var warning = Assert.Single(diagnostics);
+        Assert.Equal(RuntimeXamlDiagnosticSeverity.Warning, warning.Severity);
+    }
 
     [Fact]
     public void StyleInclude_From_CodeBehind_Resolves_Compiled()

+ 24 - 0
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs

@@ -631,5 +631,29 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
                 Assert.Equal(Colors.Red, ((ISolidColorBrush)foo.Background).Color);
             }
         }
+        
+        [Fact]
+        public void Multiple_Errors_Are_Reported()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var xaml = @"
+<Window xmlns='https://github.com/avaloniaui'
+             xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
+    <Window.Styles>
+        <Style Selector='5' />
+        <Style Selector='NonExistentType' />
+        <Style Selector='Border:normal' />
+        <Style Selector='Border+invalid' />
+    </Window.Styles>
+</Window>";
+                var ex = Assert.Throws<AggregateException>(() => (Window)AvaloniaRuntimeXamlLoader.Load(xaml));
+                Assert.Collection(
+                    ex.InnerExceptions,
+                    inner => Assert.IsAssignableFrom<XmlException>(inner),
+                    inner => Assert.IsAssignableFrom<XmlException>(inner),
+                    inner => Assert.IsAssignableFrom<XmlException>(inner));
+            }
+        }
     }
 }

+ 32 - 4
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs

@@ -327,6 +327,8 @@ namespace Avalonia.Markup.Xaml.UnitTests
         [Fact]
         public void Style_Parser_Throws_For_Duplicate_Setter()
         {
+            using var _ = UnitTestApplication.Start(TestServices.StyledWindow);
+
             var xaml = @"
 <Window xmlns='https://github.com/avaloniaui'
         xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
@@ -340,13 +342,27 @@ namespace Avalonia.Markup.Xaml.UnitTests
     </Window.Styles>
     <TextBlock/>
 </Window>";
-            AssertThrows(() => AvaloniaRuntimeXamlLoader.Load(xaml, typeof(XamlIlTests).Assembly, designMode: true),
-                e => e.Message.StartsWith("Duplicate setter encountered for property 'Height'"));
+            var diagnostics = new List<RuntimeXamlDiagnostic>();
+            // We still have a runtime check in the StyleInstance class, but in this test we only care about compile warnings.
+            Assert.Throws<InvalidOperationException>(() => AvaloniaRuntimeXamlLoader.Load(new RuntimeXamlLoaderDocument(xaml), new RuntimeXamlLoaderConfiguration
+            {
+                LocalAssembly = typeof(XamlIlTests).Assembly,
+                DiagnosticHandler = diagnostic =>
+                {
+                    diagnostics.Add(diagnostic);
+                    return diagnostic.Severity;
+                }
+            }));
+            var warning = Assert.Single(diagnostics);
+            Assert.Equal(RuntimeXamlDiagnosticSeverity.Warning, warning.Severity);
+            Assert.StartsWith("Duplicate setter encountered for property 'Height'", warning.Title);
         }
 
         [Fact]
         public void Control_Theme_Parser_Throws_For_Duplicate_Setter()
         {
+            using var _ = UnitTestApplication.Start(TestServices.StyledWindow);
+
             var xaml = @"
 <Window xmlns='https://github.com/avaloniaui'
         xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
@@ -361,8 +377,20 @@ namespace Avalonia.Markup.Xaml.UnitTests
 
     <u:TestTemplatedControl Theme='{StaticResource MyTheme}'/>
 </Window>";
-            AssertThrows(() => AvaloniaRuntimeXamlLoader.Load(xaml, typeof(XamlIlTests).Assembly, designMode: true),
-                e => e.Message.StartsWith("Duplicate setter encountered for property 'Height'"));
+            var diagnostics = new List<RuntimeXamlDiagnostic>();
+            // We still have a runtime check in the StyleInstance class, but in this test we only care about compile warnings.
+            Assert.Throws<InvalidOperationException>(() => AvaloniaRuntimeXamlLoader.Load(new RuntimeXamlLoaderDocument(xaml), new RuntimeXamlLoaderConfiguration
+            {
+                LocalAssembly = typeof(XamlIlTests).Assembly,
+                DiagnosticHandler = diagnostic =>
+                {
+                    diagnostics.Add(diagnostic);
+                    return diagnostic.Severity;
+                }
+            }));
+            var warning = Assert.Single(diagnostics);
+            Assert.Equal(RuntimeXamlDiagnosticSeverity.Warning, warning.Severity);
+            Assert.StartsWith("Duplicate setter encountered for property 'Height'", warning.Title);
         }
     }
 

+ 1 - 0
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlTestHelpers.cs

@@ -15,6 +15,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
             {
                 if(e is XmlException)
                     return;
+                throw new Exception("Expected to throw xaml exception", e);
             }
             throw new Exception("Expected to throw xaml exception");
         }