Преглед изворни кода

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 година
родитељ
комит
ea64505600
48 измењених фајлова са 923 додато и 342 уклоњено
  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 files
 [*.{xaml,axaml}]
 [*.{xaml,axaml}]
 indent_size = 2
 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
 # Xml project files
 [*.{csproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}]
 [*.{csproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}]

+ 1 - 0
build/BuildTargets.targets

@@ -4,6 +4,7 @@
     <AvaloniaUseExternalMSBuild>true</AvaloniaUseExternalMSBuild>
     <AvaloniaUseExternalMSBuild>true</AvaloniaUseExternalMSBuild>
     <AvaloniaXamlIlVerifyIl>true</AvaloniaXamlIlVerifyIl>
     <AvaloniaXamlIlVerifyIl>true</AvaloniaXamlIlVerifyIl>
     <AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
     <AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
+    <AvaloniaXamlVerboseExceptions Condition="'$(AvaloniaXamlVerboseExceptions)' == ''">true</AvaloniaXamlVerboseExceptions>
   </PropertyGroup>
   </PropertyGroup>
   <Import Project="$(MSBuildThisFileDirectory)\..\packages\Avalonia\AvaloniaBuildTasks.props"/>
   <Import Project="$(MSBuildThisFileDirectory)\..\packages\Avalonia\AvaloniaBuildTasks.props"/>
   <Import Project="$(MSBuildThisFileDirectory)\..\packages\Avalonia\AvaloniaBuildTasks.targets"/>
   <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>
       <AvaloniaXamlOriginalCopyFilePath Condition="'$(AvaloniaXamlOriginalCopyFilePath)' == ''">$(IntermediateOutputPath)/Avalonia/original.dll</AvaloniaXamlOriginalCopyFilePath>
       <AvaloniaXamlIlVerifyIl Condition="'$(AvaloniaXamlIlVerifyIl)' == ''">false</AvaloniaXamlIlVerifyIl>
       <AvaloniaXamlIlVerifyIl Condition="'$(AvaloniaXamlIlVerifyIl)' == ''">false</AvaloniaXamlIlVerifyIl>
       <AvaloniaXamlIlDebuggerLaunch Condition="'$(AvaloniaXamlIlDebuggerLaunch)' == ''">false</AvaloniaXamlIlDebuggerLaunch>
       <AvaloniaXamlIlDebuggerLaunch Condition="'$(AvaloniaXamlIlDebuggerLaunch)' == ''">false</AvaloniaXamlIlDebuggerLaunch>
+      <AvaloniaXamlVerboseExceptions Condition="'$(AvaloniaXamlVerboseExceptions)' == ''">false</AvaloniaXamlVerboseExceptions>
     </PropertyGroup>
     </PropertyGroup>
     <WriteLinesToFile
     <WriteLinesToFile
       Condition="'$(_AvaloniaForceInternalMSBuild)' != 'true'"
       Condition="'$(_AvaloniaForceInternalMSBuild)' != 'true'"
@@ -153,7 +154,9 @@
       DelaySign="$(DelaySign)"
       DelaySign="$(DelaySign)"
       SkipXamlCompilation="$(_AvaloniaSkipXamlCompilation)"
       SkipXamlCompilation="$(_AvaloniaSkipXamlCompilation)"
       DebuggerLaunch="$(AvaloniaXamlIlDebuggerLaunch)"
       DebuggerLaunch="$(AvaloniaXamlIlDebuggerLaunch)"
-      DefaultCompileBindings="$(AvaloniaUseCompiledBindingsByDefault)">
+      DefaultCompileBindings="$(AvaloniaUseCompiledBindingsByDefault)"
+      VerboseExceptions="$(AvaloniaXamlVerboseExceptions)"
+      AnalyzerConfigFiles="@(EditorConfigFiles)">
       <Output TaskParameter="WrittenFilePaths" ItemName="FileWrites" />
       <Output TaskParameter="WrittenFilePaths" ItemName="FileWrites" />
     </CompileAvaloniaXamlTask>
     </CompileAvaloniaXamlTask>
     <Exec
     <Exec

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

@@ -18,11 +18,6 @@
                 Description="Enable/Disable XAML Compiling"
                 Description="Enable/Disable XAML Compiling"
                 Category="Compile" />
                 Category="Compile" />
 
 
-  <BoolProperty Name="AvaloniaXamlIlVerifyIl"
-                DisplayName="Verify IL"
-                Description="Enable/Disable Verify IL after XAML Compiling"
-                Category="Compile" />
-
   <BoolProperty Name="AvaloniaUseCompiledBindingsByDefault"
   <BoolProperty Name="AvaloniaUseCompiledBindingsByDefault"
               DisplayName="Use CompiledBindings"
               DisplayName="Use CompiledBindings"
               Description="Use compiled bindings by default"
               Description="Use compiled bindings by default"
@@ -32,10 +27,15 @@
   <!-- Debug -->
   <!-- Debug -->
 
 
   <BoolProperty Name="AvaloniaXamlIlDebuggerLaunch"
   <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"
   <EnumProperty Name="AvaloniaXamlReportImportance"
                 DisplayName="XAML Report Importance"
                 DisplayName="XAML Report Importance"
                 Description="Provides levels of importance for XAML Compiler messages"
                 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\Compatibility\TrimmingAttributes.cs" Link="TrimmingAttributes.cs" Visible="False" />
       <Compile Include="..\Avalonia.Base\Utilities\SpanHelpers.cs" Link="Utilities\SpanHelpers.cs" />
       <Compile Include="..\Avalonia.Base\Utilities\SpanHelpers.cs" Link="Utilities\SpanHelpers.cs" />
       <Compile Include="..\Shared\StringCompatibilityExtensions.cs" Link="Compatibility\StringCompatibilityExtensions.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\**\obj\**\*.cs" />
       <Compile Remove="../Markup/Avalonia.Markup.Xaml.Loader\xamlil.github\src\XamlX\IL\SreTypeSystem.cs" />
       <Compile Remove="../Markup/Avalonia.Markup.Xaml.Loader\xamlil.github\src\XamlX\IL\SreTypeSystem.cs" />
       <PackageReference Include="Mono.Cecil" Version="0.11.5" />
       <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,
                 refInput, RefOutputPath,
                 File.ReadAllLines(ReferencesFilePath).Where(l => !string.IsNullOrWhiteSpace(l)).ToArray(),
                 File.ReadAllLines(ReferencesFilePath).Where(l => !string.IsNullOrWhiteSpace(l)).ToArray(),
                 ProjectDirectory, VerifyIl, DefaultCompileBindings, outputImportance,
                 ProjectDirectory, VerifyIl, DefaultCompileBindings, outputImportance,
-                (SignAssembly && !DelaySign) ? AssemblyOriginatorKeyFile : null, SkipXamlCompilation, DebuggerLaunch);
+                new XamlCompilerDiagnosticsFilter(AnalyzerConfigFiles),
+                (SignAssembly && !DelaySign) ? AssemblyOriginatorKeyFile : null,
+                SkipXamlCompilation, DebuggerLaunch, VerboseExceptions);
             if (!res.Success)
             if (!res.Success)
             {
             {
                 WrittenFilePaths = writtenFilePaths.ToArray();
                 WrittenFilePaths = writtenFilePaths.ToArray();
@@ -121,6 +123,10 @@ namespace Avalonia.Build.Tasks
 
 
         public bool DebuggerLaunch { get; set; }
         public bool DebuggerLaunch { get; set; }
 
 
+        public bool VerboseExceptions { get; set; }
+        
+        public ITaskItem[] AnalyzerConfigFiles { get; set; }
+        
         [Output]
         [Output]
         public string[] WrittenFilePaths { get; private set; } = Array.Empty<string>();
         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;
+using System.Text;
+using System.Xml;
 using Microsoft.Build.Framework;
 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
 #if DEBUG
-            LogError(engine, code, file, ex.ToString(), lineNumber, linePosition);
+        LogError(engine, code, file, ex.ToString(), lineNumber, linePosition);
 #else
 #else
-            LogError(engine, code, file, ex.Message, lineNumber, linePosition);
+        LogError(engine, code, file, ex.Message, lineNumber, linePosition);
 #endif
 #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"));
                 "", "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"));
                 "", "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.Runtime.Serialization;
 using System.Text;
 using System.Text;
 using Avalonia.Markup.Xaml.PortableXaml;
 using Avalonia.Markup.Xaml.PortableXaml;
+using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions;
 using Avalonia.Utilities;
 using Avalonia.Utilities;
 using Microsoft.Build.Framework;
 using Microsoft.Build.Framework;
 using SPath = System.IO.Path;
 using SPath = System.IO.Path;
@@ -100,7 +101,7 @@ namespace Avalonia.Build.Tasks
                     }
                     }
                     catch (Exception e)
                     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;
                         return false;
                     }
                     }
 
 
@@ -109,7 +110,7 @@ namespace Avalonia.Build.Tasks
                         if (typeToXamlIndex.ContainsKey(info.XClass))
                         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]}");
                                 $"Duplicate x:Class directive, {info.XClass} is already used in {typeToXamlIndex[info.XClass]}");
                             return false;
                             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;
+using System.Collections;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.IO;
 using System.IO;
 using System.Linq;
 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,
         internal static CompileResult Compile(IBuildEngine engine,
             string input, string output,
             string input, string output,
             string refInput, string refOutput,
             string refInput, string refOutput,
             string[] references, string projectDirectory,
             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
             try
             {
             {
@@ -70,7 +63,10 @@ namespace Avalonia.Build.Tasks
                 var refAsm = refTypeSystem?.TargetAssemblyDefinition;
                 var refAsm = refTypeSystem?.TargetAssemblyDefinition;
                 if (!skipXamlCompilation)
                 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)
 	                if (compileRes == null)
 	                    return new CompileResult(true);
 	                    return new CompileResult(true);
 	                if (compileRes == false)
 	                if (compileRes == false)
@@ -99,7 +95,7 @@ namespace Avalonia.Build.Tasks
             }
             }
             catch (Exception ex)
             catch (Exception ex)
             {
             {
-                engine.LogError(BuildEngineErrorCode.Unknown, "", ex);
+                engine.LogError(AvaloniaXamlDiagnosticCodes.Unknown, "", ex);
                 return new CompileResult(false);
                 return new CompileResult(false);
             }
             }
         }
         }
@@ -107,8 +103,10 @@ namespace Avalonia.Build.Tasks
         static bool? CompileCore(IBuildEngine engine, CecilTypeSystem typeSystem,
         static bool? CompileCore(IBuildEngine engine, CecilTypeSystem typeSystem,
             string projectDirectory, bool verifyIl,
             string projectDirectory, bool verifyIl,
             bool defaultCompileBindings,
             bool defaultCompileBindings,
-            MessageImportance logImportance
-            , bool debuggerLaunch = false)
+            MessageImportance logImportance,
+            XamlCompilerDiagnosticsFilter diagnosticsFilter,
+            bool debuggerLaunch,
+            bool verboseExceptions)
         {
         {
             if (debuggerLaunch)
             if (debuggerLaunch)
             {
             {
@@ -170,6 +168,20 @@ namespace Avalonia.Build.Tasks
             asm.MainModule.Types.Add(trampolineBuilder);
             asm.MainModule.Types.Add(trampolineBuilder);
 
 
             var (xamlLanguage , emitConfig) = AvaloniaXamlIlLanguage.Configure(typeSystem);
             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,
             var compilerConfig = new AvaloniaXamlIlCompilerConfiguration(typeSystem,
                 typeSystem.TargetAssembly,
                 typeSystem.TargetAssembly,
                 xamlLanguage,
                 xamlLanguage,
@@ -178,7 +190,8 @@ namespace Avalonia.Build.Tasks
                 new XamlIlClrPropertyInfoEmitter(typeSystem.CreateTypeBuilder(clrPropertiesDef)),
                 new XamlIlClrPropertyInfoEmitter(typeSystem.CreateTypeBuilder(clrPropertiesDef)),
                 new XamlIlPropertyInfoAccessorFactoryEmitter(typeSystem.CreateTypeBuilder(indexerAccessorClosure)),
                 new XamlIlPropertyInfoAccessorFactoryEmitter(typeSystem.CreateTypeBuilder(indexerAccessorClosure)),
                 new XamlIlTrampolineBuilder(typeSystem.CreateTypeBuilder(trampolineBuilder)),
                 new XamlIlTrampolineBuilder(typeSystem.CreateTypeBuilder(trampolineBuilder)),
-                new DeterministicIdGenerator());
+                new DeterministicIdGenerator(),
+                diagnosticsHandler);
 
 
 
 
             var contextDef = new TypeDefinition(CompiledAvaloniaXamlNamespace, "XamlIlContext", 
             var contextDef = new TypeDefinition(CompiledAvaloniaXamlNamespace, "XamlIlContext", 
@@ -259,6 +272,7 @@ namespace Avalonia.Build.Tasks
             {
             {
                 var typeDef = new TypeDefinition(CompiledAvaloniaXamlNamespace, "!"+ group.Name,
                 var typeDef = new TypeDefinition(CompiledAvaloniaXamlNamespace, "!"+ group.Name,
                     TypeAttributes.Class | TypeAttributes.Public, asm.MainModule.TypeSystem.Object);
                     TypeAttributes.Class | TypeAttributes.Public, asm.MainModule.TypeSystem.Object);
+                var transformFailed = false;
 
 
                 typeDef.CustomAttributes.Add(new CustomAttribute(editorBrowsableCtor)
                 typeDef.CustomAttributes.Add(new CustomAttribute(editorBrowsableCtor)
                 {
                 {
@@ -277,6 +291,7 @@ namespace Avalonia.Build.Tasks
                         // StreamReader is needed here to handle BOM
                         // StreamReader is needed here to handle BOM
                         var xaml = new StreamReader(new MemoryStream(res.FileContents)).ReadToEnd();
                         var xaml = new StreamReader(new MemoryStream(res.FileContents)).ReadToEnd();
                         var parsed = XDocumentXamlParser.Parse(xaml);
                         var parsed = XDocumentXamlParser.Parse(xaml);
+                        parsed.Document = res.FilePath;
 
 
                         var initialRoot = (XamlAstObjectNode)parsed.Root;
                         var initialRoot = (XamlAstObjectNode)parsed.Root;
                         
                         
@@ -338,8 +353,7 @@ namespace Avalonia.Build.Tasks
                                 new XamlAstClrTypeReference(classDirective, classType, false));
                                 new XamlAstClrTypeReference(classDirective, classType, false));
                             initialRoot.Children.Remove(classDirective);
                             initialRoot.Children.Remove(classDirective);
                         }
                         }
-                        
-                        
+
                         compiler.Transform(parsed);
                         compiler.Transform(parsed);
 
 
                         var populateName = classType == null ? "Populate:" + res.Name : "!XamlIlPopulate";
                         var populateName = classType == null ? "Populate:" + res.Name : "!XamlIlPopulate";
@@ -376,29 +390,28 @@ namespace Avalonia.Build.Tasks
                     }
                     }
                     catch (Exception e)
                     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
                 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)
                 foreach (var document in parsedXamlDocuments)
@@ -605,21 +618,23 @@ namespace Avalonia.Build.Tasks
                             }
                             }
                             else
                             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)
                     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;
                         return false;
                     }
                     }
                     res.Remove();
                     res.Remove();
@@ -636,7 +651,6 @@ namespace Avalonia.Build.Tasks
                     }
                     }
                 }
                 }
                 
                 
-                
                 return true;
                 return true;
             }
             }
             
             
@@ -652,6 +666,21 @@ namespace Avalonia.Build.Tasks
             return true;
             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(
         static bool? CompileCoreForRefAssembly(
             IBuildEngine engine, CecilTypeSystem sourceTypeSystem, CecilTypeSystem refTypeSystem)
             IBuildEngine engine, CecilTypeSystem sourceTypeSystem, CecilTypeSystem refTypeSystem)
         {
         {
@@ -693,9 +722,7 @@ namespace Avalonia.Build.Tasks
             }
             }
             catch (Exception e)
             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;
                 return false;
             }
             }
 
 

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

@@ -13,6 +13,7 @@
   <ItemGroup>
   <ItemGroup>
     <Compile Include="..\..\Avalonia.Base\Utilities\StringBuilderCache.cs" Link="Utilities\StringBuilderCache.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="..\..\Avalonia.Base\Compatibility\TrimmingAttributes.cs" Link="TrimmingAttributes.cs" Visible="False" />
+    <Compile Include="..\..\Shared\IsExternalInit.cs" Link="Compatibility\IsExternalInit.cs" />
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
     <PackageReference Include="System.Reflection.Emit" Version="4.3.0" />
     <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 indexerClosureType = _sreBuilder.DefineType("IndexerClosure_" + Guid.NewGuid().ToString("N"));
             var trampolineBuilder = _sreBuilder.DefineType("Trampolines_" + 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,
             var compiler = new AvaloniaXamlIlCompiler(new AvaloniaXamlIlCompilerConfiguration(_sreTypeSystem, asm,
                     _sreMappings, _sreXmlns, AvaloniaXamlIlLanguage.CustomValueConverter,
                     _sreMappings, _sreXmlns, AvaloniaXamlIlLanguage.CustomValueConverter,
                     new XamlIlClrPropertyInfoEmitter(_sreTypeSystem.CreateTypeBuilder(clrPropertyBuilder)),
                     new XamlIlClrPropertyInfoEmitter(_sreTypeSystem.CreateTypeBuilder(clrPropertyBuilder)),
                     new XamlIlPropertyInfoAccessorFactoryEmitter(_sreTypeSystem.CreateTypeBuilder(indexerClosureType)),
                     new XamlIlPropertyInfoAccessorFactoryEmitter(_sreTypeSystem.CreateTypeBuilder(indexerClosureType)),
-                    new XamlIlTrampolineBuilder(_sreTypeSystem.CreateTypeBuilder(trampolineBuilder))),
+                    new XamlIlTrampolineBuilder(_sreTypeSystem.CreateTypeBuilder(trampolineBuilder)),
+                    null,
+                    diagnosticsHandler),
                 _sreEmitMappings,
                 _sreEmitMappings,
                 _sreContextType)
                 _sreContextType)
             {
             {
@@ -236,8 +259,9 @@ namespace Avalonia.Markup.Xaml.XamlIl
                 {
                 {
                     overrideType = _sreTypeSystem.GetType(document.RootInstance.GetType());
                     overrideType = _sreTypeSystem.GetType(document.RootInstance.GetType());
                 }
                 }
-                
+
                 var parsed = compiler.Parse(xaml, overrideType);
                 var parsed = compiler.Parse(xaml, overrideType);
+                parsed.Document = "runtimexaml:" + parsedDocuments.Count;
                 compiler.Transform(parsed);
                 compiler.Transform(parsed);
 
 
                 var xamlName = GetSafeUriIdentifier(document.BaseUri)
                 var xamlName = GetSafeUriIdentifier(document.BaseUri)
@@ -263,6 +287,8 @@ namespace Avalonia.Markup.Xaml.XamlIl
 
 
             compiler.TransformGroup(parsedDocuments);
             compiler.TransformGroup(parsedDocuments);
 
 
+            diagnostics.ThrowExceptionIfAnyError();
+
             var createdTypes = parsedDocuments.Select(document =>
             var createdTypes = parsedDocuments.Select(document =>
             {
             {
                 compiler.Compile(document.XamlDocument, document.TypeBuilderProvider, document.Uri, document.FileSource);
                 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;
             _values = values;
             
             
             Type = new XamlAstClrTypeReference(lineInfo, arrayType, false);
             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; }
         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 AvaloniaXamlIlPropertyPathTransformer(),
                 new AvaloniaXamlIlSetterTargetTypeMetadataTransformer(),
                 new AvaloniaXamlIlSetterTargetTypeMetadataTransformer(),
                 new AvaloniaXamlIlSetterTransformer(),
                 new AvaloniaXamlIlSetterTransformer(),
+                new AvaloniaXamlIlStyleValidatorTransformer(),
                 new AvaloniaXamlIlConstructorServiceProviderTransformer(),
                 new AvaloniaXamlIlConstructorServiceProviderTransformer(),
                 new AvaloniaXamlIlTransitionsTypeMetadataTransformer(),
                 new AvaloniaXamlIlTransitionsTypeMetadataTransformer(),
                 new AvaloniaXamlIlResolveByNameMarkupExtensionReplacer(),
                 new AvaloniaXamlIlResolveByNameMarkupExtensionReplacer(),
@@ -126,9 +127,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
 
 
         public List<IXamlAstGroupTransformer> GroupTransformers { get; }
         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 transformer in GroupTransformers)
             {
             {
                 foreach (var doc in documents)
                 foreach (var doc in documents)
@@ -166,8 +167,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
                     new XamlAstClrTypeReference(classDirective,
                     new XamlAstClrTypeReference(classDirective,
                         _configuration.TypeSystem.GetType(((XamlAstTextNode)classDirective.Values[0]).Text),
                         _configuration.TypeSystem.GetType(((XamlAstTextNode)classDirective.Values[0]).Text),
                         false) :
                         false) :
-                    TypeReferenceResolver.ResolveType(CreateTransformationContext(parsed, true),
-                        (XamlAstXmlTypeReference)rootObject.Type, true);
+                    TypeReferenceResolver.ResolveType(CreateTransformationContext(parsed),
+                        (XamlAstXmlTypeReference)rootObject.Type);
 
 
 
 
             if (overrideRootType != null)
             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,
             XamlIlClrPropertyInfoEmitter clrPropertyEmitter,
             XamlIlPropertyInfoAccessorFactoryEmitter accessorFactoryEmitter,
             XamlIlPropertyInfoAccessorFactoryEmitter accessorFactoryEmitter,
             XamlIlTrampolineBuilder trampolineBuilder,
             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;
             ClrPropertyEmitter = clrPropertyEmitter;
             AccessorFactoryEmitter = accessorFactoryEmitter;
             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.AstNodes;
 using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
 using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
 using Avalonia.Media;
 using Avalonia.Media;
+using XamlX;
 using XamlX.Ast;
 using XamlX.Ast;
 using XamlX.Transform;
 using XamlX.Transform;
 using XamlX.TypeSystem;
 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)
         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")
             if (type.FullName == "System.TimeSpan")
             {
             {
                 var tsText = text.Trim();
                 var tsText = text.Trim();
@@ -24,11 +40,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
                 {
                 {
                     // // shorthand seconds format (ie. "0.25")
                     // // shorthand seconds format (ie. "0.25")
                     if (!tsText.Contains(":") && double.TryParse(tsText,
                     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);
                         timeSpan = TimeSpan.FromSeconds(seconds);
                     else
                     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,
                 result = new XamlStaticOrTargetedReturnMethodCallNode(node,
@@ -56,7 +74,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
                 }
                 }
                 catch
                 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
                 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
                 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
                 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
                 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
                 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))
                 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,
                 result = new XamlStaticOrTargetedReturnMethodCallNode(node,
@@ -179,7 +197,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
                 }
                 }
                 catch
                 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
                 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
                 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 _))
                 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
                 result = new XamlAstNewClrObjectNode(node
                     , new(node, types.Uri, false)
                     , new(node, types.Uri, false)
@@ -341,7 +359,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
                     }
                     }
                     else
                     else
                     {
                     {
-                        throw new XamlX.XamlLoadException($"Invalid PointsList.", node);
+                        return ReturnOnParseError($"Unable to parse text \"{text}\" as a Points list", out result);
                     }
                     }
                 }
                 }
                 else
                 else
@@ -388,6 +406,14 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
                     nodes[index] = itemNode;
                     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))
                 if (types.AvaloniaList.MakeGenericType(elementType).IsAssignableFrom(type))
                 {
                 {
                     result = new AvaloniaXamlIlAvaloniaListConstantAstNode(node, types, type, elementType, nodes);
                     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
 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;
         Documents = documents;
     }
     }
 
 
+    public override string Document => CurrentDocument?.FileSource?.FilePath ?? "{unknown document}";
     public IXamlDocumentResource CurrentDocument { get; set; }
     public IXamlDocumentResource CurrentDocument { get; set; }
     
     
     public IReadOnlyCollection<IXamlDocumentResource> Documents { get; }
     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;
         private readonly IXamlAstGroupTransformer _transformer;
 
 
-        public Visitor(AstGroupTransformationContext context, IXamlAstGroupTransformer transformer)
+        public Visitor(AstGroupTransformationContext context, IXamlAstGroupTransformer transformer) : base(context)
         {
         {
-            _context = context;
             _transformer = transformer;
             _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)
     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)
         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>();
         var additionalProperties = new List<IXamlAstManipulationNode>();
@@ -56,8 +55,9 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer
             }
             }
             else
             else
             {
             {
-                throw new XamlDocumentParseException(context.CurrentDocument,
+                context.ReportTransformError(
                     $"Source property must be set on the \"{nodeTypeName}\" node.", valueNode);
                     $"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 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.",
                 $"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 resource wasn't found in the current assembly, search in the others.
         if (context.Configuration.TypeSystem.FindAssembly(assembly) is not { } assetAssembly)
         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");
         var avaResType = assetAssembly.FindType("CompiledAvaloniaXaml.!AvaloniaResources");
         if (avaResType is null)
         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);
         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 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.",
             $"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,
         IXamlType expectedLoadedType, IXamlAstNode fallbackNode, string assetPathUri, string assembly,
         IEnumerable<IXamlAstManipulationNode> manipulationNodes)
         IEnumerable<IXamlAstManipulationNode> manipulationNodes)
     {
     {
         if (!expectedLoadedType.IsAssignableFrom(type))
         if (!expectedLoadedType.IsAssignableFrom(type))
         {
         {
-            return context.ParseError(
+            context.ReportTransformError(
                 $"Resource \"{assetPathUri}\" is defined as \"{type}\" type in the \"{assembly}\" assembly, but expected \"{expectedLoadedType}\".",
                 $"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));
         IXamlAstNode newObjNode = new XamlAstObjectNode(li, new XamlAstClrTypeReference(li, type, false));
@@ -141,15 +146,16 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer
         return new NewObjectTransformer().Transform(context, newObjNode);
         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,
         IXamlType expectedLoadedType, IXamlAstNode fallbackNode, string assetPathUri, string assembly,
         IEnumerable<IXamlAstManipulationNode> manipulationNodes)
         IEnumerable<IXamlAstManipulationNode> manipulationNodes)
     {
     {
         if (!expectedLoadedType.IsAssignableFrom(method.ReturnType))
         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}\".",
                 $"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;
         var sp = context.Configuration.TypeMappings.ServiceProvider;
@@ -164,6 +170,13 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer
         AstGroupTransformationContext context, string nodeTypeName, XamlPropertyAssignmentNode sourceProperty,
         AstGroupTransformationContext context, string nodeTypeName, XamlPropertyAssignmentNode sourceProperty,
         bool strictSourceValueType)
         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)`.
         // 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
         if (sourceProperty.Values.OfType<XamlAstNewClrObjectNode>().FirstOrDefault() is not { } sourceUriNode
             || sourceUriNode.Type.GetClrType() != context.GetAvaloniaTypes().Uri
             || 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.
             // 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();
             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);
             return (null, anyPropValue);
         }
         }
 
 
@@ -193,9 +197,7 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer
         }
         }
         else if (!uriPath.Scheme.Equals("avares", StringComparison.CurrentCultureIgnoreCase))
         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);
             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.Collections.Generic;
 using System.Linq;
 using System.Linq;
 using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
 using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
+using XamlX;
 using XamlX.Ast;
 using XamlX.Ast;
 using XamlX.TypeSystem;
 using XamlX.TypeSystem;
 
 
@@ -22,6 +23,7 @@ internal class XamlMergeResourceGroupTransformer : IXamlAstGroupTransformer
 
 
         var mergeResourceIncludeType = context.GetAvaloniaTypes().MergeResourceInclude;
         var mergeResourceIncludeType = context.GetAvaloniaTypes().MergeResourceInclude;
         var mergeSourceNodes = new List<XamlPropertyAssignmentNode>();
         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())
         foreach (var manipulationNode in resourceDictionaryManipulation.Children.ToArray())
         {
         {
             void ProcessXamlPropertyAssignmentNode(XamlManipulationGroupNode parent, XamlPropertyAssignmentNode assignmentNode)
             void ProcessXamlPropertyAssignmentNode(XamlManipulationGroupNode parent, XamlPropertyAssignmentNode assignmentNode)
@@ -39,14 +41,16 @@ internal class XamlMergeResourceGroupTransformer : IXamlAstGroupTransformer
                         }
                         }
                         else
                         else
                         {
                         {
-                            throw new XamlDocumentParseException(context.CurrentDocument,
+                            shouldExit = true;
+                            context.ReportTransformError(
                                 "Invalid MergeResourceInclude node found. Make sure that Source property is set.",
                                 "Invalid MergeResourceInclude node found. Make sure that Source property is set.",
                                 valueNode);
                                 valueNode);
                         }
                         }
                     }
                     }
                     else if (mergeSourceNodes.Any())
                     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.",
                             "MergeResourceInclude should always be included last when mixing with other dictionaries inside of the ResourceDictionary.MergedDictionaries.",
                             valueNode);
                             valueNode);
                     }
                     }
@@ -66,7 +70,7 @@ internal class XamlMergeResourceGroupTransformer : IXamlAstGroupTransformer
             }
             }
         }
         }
 
 
-        if (!mergeSourceNodes.Any())
+        if (shouldExit || !mergeSourceNodes.Any())
         {
         {
             return node;
             return node;
         }
         }
@@ -78,7 +82,7 @@ internal class XamlMergeResourceGroupTransformer : IXamlAstGroupTransformer
                 AvaloniaXamlIncludeTransformer.ResolveSourceFromXamlInclude(context, "MergeResourceInclude", sourceNode, true);
                 AvaloniaXamlIncludeTransformer.ResolveSourceFromXamlInclude(context, "MergeResourceInclude", sourceNode, true);
             if (originalAssetPath is null)
             if (originalAssetPath is null)
             {
             {
-                return context.ParseError(
+                return context.ReportTransformError(
                     $"Node MergeResourceInclude is unable to resolve \"{originalAssetPath}\" path.", propertyNode, node);
                     $"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));
                 string.Equals(d.Uri, originalAssetPath, StringComparison.InvariantCultureIgnoreCase));
             if (targetDocument?.XamlDocument.Root is not XamlValueWithManipulationNode targetDocumentRoot)
             if (targetDocument?.XamlDocument.Root is not XamlValueWithManipulationNode targetDocumentRoot)
             {
             {
-                return context.ParseError(
+                return context.ReportTransformError(
                     $"Node MergeResourceInclude is unable to resolve \"{originalAssetPath}\" path.", propertyNode, node);
                     $"Node MergeResourceInclude is unable to resolve \"{originalAssetPath}\" path.", propertyNode, node);
             }
             }
 
 
@@ -94,7 +98,7 @@ internal class XamlMergeResourceGroupTransformer : IXamlAstGroupTransformer
                 .Children.OfType<XamlObjectInitializationNode>().Single();
                 .Children.OfType<XamlObjectInitializationNode>().Single();
             if (singleRootObject.Type != resourceDictionaryType)
             if (singleRootObject.Type != resourceDictionaryType)
             {
             {
-                return context.ParseError(
+                return context.ReportTransformError(
                     "MergeResourceInclude can only include another ResourceDictionary", propertyNode, node);
                     "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 System.Linq;
 using XamlX;
 using XamlX;
 using XamlX.Ast;
 using XamlX.Ast;
@@ -8,6 +9,14 @@ using XamlParseException = XamlX.XamlParseException;
 
 
 namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
 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
     class AvaloniaBindingExtensionTransformer : IXamlAstTransformer
     {
     {
         public bool CompileBindingsByDefault { get; set; }
         public bool CompileBindingsByDefault { get; set; }
@@ -32,7 +41,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                             if (!(directive.Values[0] is XamlAstTextNode text
                             if (!(directive.Values[0] is XamlAstTextNode text
                                 && bool.TryParse(text.Text, out var compileBindings)))
                                 && 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);
                             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)
             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)
             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(
             if (GetRelativeSourceObjectFromAssignment(
@@ -119,7 +119,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
             {
             {
                 if (convertedNode != null)
                 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
                 var modeProperty = relativeSourceObject.Children
@@ -159,14 +159,14 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                                 true).GetClrType(),
                                 true).GetClrType(),
                             XamlTypeExtensionNode typeExtensionNode => typeExtensionNode.Value.GetClrType(),
                             XamlTypeExtensionNode typeExtensionNode => typeExtensionNode.Value.GetClrType(),
                             null => null,
                             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 (ancestorType is null)
                     {
                     {
                         if (treeType == "Visual")
                         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")
                         else if (treeType == "Logical")
                         {
                         {
@@ -180,7 +180,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
 
 
                             if (ancestorType is null)
                             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
                     else
                     {
                     {
-                        throw new XamlParseException($"Unknown tree type '{treeType}'.", binding);
+                        throw new XamlBindingsTransformException($"Unknown tree type '{treeType}'.", binding);
                     }
                     }
                 }
                 }
                 else if (mode == "DataContext")
                 else if (mode == "DataContext")
@@ -221,20 +221,20 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                             x.ScopeType == AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.ControlTemplate);
                             x.ScopeType == AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.ControlTemplate);
                     if (contentTemplateNode is null)
                     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();
                     var parentType = contentTemplateNode.TargetType.GetClrType();
                     if (parentType is null)
                     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 };
                     convertedNode = new TemplatedParentBindingExpressionNode { Type = parentType };
                 }
                 }
                 else
                 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)
                 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;
                 relativeSourceObject = (XamlAstObjectNode)me.Value;
@@ -276,7 +276,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
             {
             {
                 if (on.Type.GetClrType() != context.GetAvaloniaTypes().RelativeSource)
                 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;
                 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();
                     var parentDataContextNode = context.ParentNodes().OfType<AvaloniaXamlIlDataContextTypeMetadataNode>().FirstOrDefault();
                     if (parentDataContextNode is null)
                     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;
                     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>()
             var targetTypeNode = on.Children.OfType<XamlAstXamlPropertyValueNode>()
                 .FirstOrDefault(p => p.Property.GetClrProperty().Name == "TargetType") ??
                 .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;
             IXamlType targetType;
 
 
@@ -30,7 +30,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
             else if (targetTypeNode.Values[0] is XamlAstTextNode text)
             else if (targetTypeNode.Values[0] is XamlAstTextNode text)
                 targetType = TypeReferenceResolver.ResolveType(context, text.Text, false, text, true).GetClrType();
                 targetType = TypeReferenceResolver.ResolveType(context, text.Text, false, text, true).GetClrType();
             else
             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,
             return new AvaloniaXamlIlTargetTypeMetadataNode(on,
                 new XamlAstClrTypeReference(targetTypeNode, targetType, false),
                 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
 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
     class AvaloniaXamlIlDataContextTypeTransformer : IXamlAstTransformer
     {
     {
         public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
         public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
@@ -43,7 +51,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                             }
                             }
                             else
                             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();
                     var parentItemsDataContext = context.ParentNodes().SkipWhile(n => n != parentObject).OfType<AvaloniaXamlIlDataContextTypeMetadataNode>().FirstOrDefault();
                     if (parentItemsDataContext != null)
                     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();
                     var parentDataContextNode = context.ParentNodes().OfType<AvaloniaXamlIlDataContextTypeMetadataNode>().FirstOrDefault();
                     if (parentDataContextNode is null)
                     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;
                     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);
                 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))
             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)
                     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);
                     defaultValue = TransformNode(new[] { argument }, typeArgument, objectNode);
@@ -68,20 +68,20 @@ internal class AvaloniaXamlIlOptionMarkupExtensionTransformer : IXamlAstTransfor
                             ?? Array.Empty<string>();
                             ?? Array.Empty<string>();
                         if (options.Length == 0)
                         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>()
                         var content = onObj.Children.OfType<XamlAstXamlPropertyValueNode>()
                             .SingleOrDefault(v => v.Property.GetClrProperty().Name == "Content");
                             .SingleOrDefault(v => v.Property.GetClrProperty().Name == "Content");
                         if (content is null)
                         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
                         var propertiesSet = options
                             .Select(o => type.GetAllProperties()
                             .Select(o => type.GetAllProperties()
                                 .FirstOrDefault(p => o.Equals(p.Name, StringComparison.Ordinal))
                                 .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();
                             .ToArray();
                         foreach (var propertySet in propertiesSet)
                         foreach (var propertySet in propertiesSet)
                         {
                         {
@@ -102,7 +102,7 @@ internal class AvaloniaXamlIlOptionMarkupExtensionTransformer : IXamlAstTransfor
 
 
             if (defaultValue is null && !values.Any())
             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(
             return new OptionsMarkupExtensionNode(
@@ -125,7 +125,7 @@ internal class AvaloniaXamlIlOptionMarkupExtensionTransformer : IXamlAstTransfor
                     var option = optAttr.Parameters.Single();
                     var option = optAttr.Parameters.Single();
                     if (option is null)
                     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();
                     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;
                 return false;
@@ -198,7 +198,7 @@ internal class AvaloniaXamlIlOptionMarkupExtensionTransformer : IXamlAstTransfor
 
 
             if (values.Count > 1)
             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();
             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
 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
     class AvaloniaXamlIlPropertyPathTransformer : IXamlAstTransformer
     {
     {
         public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
         public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
@@ -26,9 +34,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                 var parentScope = context.ParentNodes().OfType<AvaloniaXamlIlTargetTypeMetadataNode>()
                 var parentScope = context.ParentNodes().OfType<AvaloniaXamlIlTargetTypeMetadataNode>()
                     .FirstOrDefault();
                     .FirstOrDefault();
                 if(parentScope == null)
                 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)
                 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;
                 IEnumerable<PropertyPathGrammar.ISyntax> parsed;
@@ -38,7 +46,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                 }
                 }
                 catch (Exception e)
                 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>();
                 var elements = new List<IXamlIlPropertyPathElementNode>();
@@ -59,7 +67,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                 void HandleProperty(string name, string typeNamespace, string typeName)
                 void HandleProperty(string name, string typeNamespace, string typeName)
                 {
                 {
                     if(!expectProperty || currentType == null)
                     if(!expectProperty || currentType == null)
-                        throw new XamlX.XamlParseException("Unexpected property node", text);
+                        throw new XamlPropertyPathException("Unexpected property node", text);
 
 
                     var propertySearchType =
                     var propertySearchType =
                         typeName != null ? GetType(typeNamespace, typeName) : currentType;
                         typeName != null ? GetType(typeNamespace, typeName) : currentType;
@@ -80,7 +88,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                     }
                     }
 
 
                     if (prop == null)
                     if (prop == null)
-                        throw new XamlX.XamlParseException(
+                        throw new XamlPropertyPathException(
                             $"Unable to resolve property {name} on type {propertySearchType.GetFqn()}",
                             $"Unable to resolve property {name} on type {propertySearchType.GetFqn()}",
                             text);
                             text);
                     
                     
@@ -95,7 +103,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                     if (ge is PropertyPathGrammar.ChildTraversalSyntax)
                     if (ge is PropertyPathGrammar.ChildTraversalSyntax)
                     {
                     {
                         if (!expectTraversal)
                         if (!expectTraversal)
-                            throw new XamlX.XamlParseException("Unexpected child traversal .", text);
+                            throw new XamlPropertyPathException("Unexpected child traversal .", text);
                         elements.Add(new XamlIlChildTraversalPropertyPathElementNode());
                         elements.Add(new XamlIlChildTraversalPropertyPathElementNode());
                         expectTraversal = expectCast = false;
                         expectTraversal = expectCast = false;
                         expectProperty = true;
                         expectProperty = true;
@@ -103,7 +111,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                     else if (ge is PropertyPathGrammar.EnsureTypeSyntax ets)
                     else if (ge is PropertyPathGrammar.EnsureTypeSyntax ets)
                     {
                     {
                         if(!expectCast)
                         if(!expectCast)
-                            throw new XamlX.XamlParseException("Unexpected cast node", text);
+                            throw new XamlPropertyPathException("Unexpected cast node", text);
                         currentType = GetType(ets.TypeNamespace, ets.TypeName);
                         currentType = GetType(ets.TypeNamespace, ets.TypeName);
                         elements.Add(new XamlIlCastPropertyPathElementNode(currentType, true));
                         elements.Add(new XamlIlCastPropertyPathElementNode(currentType, true));
                         expectProperty = false;
                         expectProperty = false;
@@ -112,7 +120,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                     else if (ge is PropertyPathGrammar.CastTypeSyntax cts)
                     else if (ge is PropertyPathGrammar.CastTypeSyntax cts)
                     {
                     {
                         if(!expectCast)
                         if(!expectCast)
-                            throw new XamlX.XamlParseException("Unexpected cast node", text);
+                            throw new XamlPropertyPathException("Unexpected cast node", text);
                         //TODO: Check if cast can be done
                         //TODO: Check if cast can be done
                         currentType = GetType(cts.TypeNamespace, cts.TypeName);
                         currentType = GetType(cts.TypeNamespace, cts.TypeName);
                         elements.Add(new XamlIlCastPropertyPathElementNode(currentType, false));
                         elements.Add(new XamlIlCastPropertyPathElementNode(currentType, false));
@@ -128,12 +136,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                         HandleProperty(tqps.Name, tqps.TypeNamespace, tqps.TypeName);
                         HandleProperty(tqps.Name, tqps.TypeNamespace, tqps.TypeName);
                     }
                     }
                     else
                     else
-                        throw new XamlX.XamlParseException("Unexpected node " + ge, text);
+                        throw new XamlPropertyPathException("Unexpected node " + ge, text);
                     
                     
                 }
                 }
                 var propertyPathNode = new XamlIlPropertyPathNode(text, elements, types);
                 var propertyPathNode = new XamlIlPropertyPathNode(text, elements, types);
                 if (propertyPathNode.Type == null)
                 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;
                 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
 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
     class AvaloniaXamlIlSelectorTransformer : IXamlAstTransformer
     {
     {
         public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
         public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
@@ -30,14 +36,15 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                 return node;
                 return node;
 
 
             if (pn.Values.Count != 1)
             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)
             if (pn.Values[0] is XamlIlSelectorNode)
                 //Deja vu. I've just been in this place before
                 //Deja vu. I've just been in this place before
                 return node;
                 return node;
             
             
             if (!(pn.Values[0] is XamlAstTextNode tn))
             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 selectorType = pn.Property.GetClrProperty().Getter.ReturnType;
             var initialNode = new XamlIlSelectorInitialNode(node, selectorType);
             var initialNode = new XamlIlSelectorInitialNode(node, selectorType);
@@ -69,18 +76,18 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                             var type = result?.TargetType;
                             var type = result?.TargetType;
 
 
                             if (type == null)
                             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 =
                             var targetProperty =
                                 type.GetAllProperties().FirstOrDefault(p => p.Name == property.Property);
                                 type.GetAllProperties().FirstOrDefault(p => p.Name == property.Property);
 
 
                             if (targetProperty == null)
                             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,
                             if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context,
                                 new XamlAstTextNode(node, property.Value, type: context.Configuration.WellKnownTypes.String),
                                 new XamlAstTextNode(node, property.Value, type: context.Configuration.WellKnownTypes.String),
                                 targetProperty.PropertyType, out var typedValue))
                                 targetProperty.PropertyType, out var typedValue))
-                                throw new XamlParseException(
+                                throw new XamlTransformException(
                                     $"Cannot convert '{property.Value}' to '{targetProperty.PropertyType.GetFqn()}",
                                     $"Cannot convert '{property.Value}' to '{targetProperty.PropertyType.GetFqn()}",
                                     node);
                                     node);
 
 
@@ -92,13 +99,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                                 var targetType = result?.TargetType;
                                 var targetType = result?.TargetType;
                                 if (targetType == null)
                                 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;
                                 var attachedPropertyOwnerType = typeResolver(attachedProperty.Xmlns, attachedProperty.TypeName).Type;
 
 
                                 if (attachedPropertyOwnerType is null)
                                 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";
                                 var attachedPropertyName = attachedProperty.Property + "Property";
@@ -112,7 +119,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
 
 
                                 if (targetPropertyField is null)
                                 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
                                 var targetPropertyType = XamlIlAvaloniaPropertyHelper
@@ -121,7 +128,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                                 if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context,
                                 if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context,
                                     new XamlAstTextNode(node, attachedProperty.Value, type: context.Configuration.WellKnownTypes.String),
                                     new XamlAstTextNode(node, attachedProperty.Value, type: context.Configuration.WellKnownTypes.String),
                                     targetPropertyType, out var typedValue))
                                     targetPropertyType, out var typedValue))
-                                        throw new XamlParseException(
+                                        throw new XamlTransformException(
                                             $"Cannot convert '{attachedProperty.Value}' to '{targetPropertyType.GetFqn()}",
                                             $"Cannot convert '{attachedProperty.Value}' to '{targetPropertyType.GetFqn()}",
                                             node);
                                             node);
 
 
@@ -156,12 +163,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                             var parentTargetType = context.ParentNodes().OfType<AvaloniaXamlIlTargetTypeMetadataNode>().FirstOrDefault();
                             var parentTargetType = context.ParentNodes().OfType<AvaloniaXamlIlTargetTypeMetadataNode>().FirstOrDefault();
 
 
                             if (parentTargetType is null)
                             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());
                             result = new XamlIlNestingSelector(result, parentTargetType.TargetType.GetClrType());
                             break;
                             break;
                         default:
                         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)
             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) 
             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;
             pn.Values[0] = selector;
 
 
             var templateType = GetLastTemplateTypeFromSelector(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)
         protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
         {
         {
             if (!XamlIlAvaloniaPropertyHelper.Emit(context, codeGen, Property))
             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",
                     $"{Property.Name} of {(Property.Setter ?? Property.Getter).DeclaringType.GetFqn()} doesn't seem to be an AvaloniaProperty",
                     this);
                     this);
             context.Emit(Value, codeGen, context.Configuration.WellKnownTypes.Object);
             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)
         protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
         {
         {
             if (_selectors.Count == 0)
             if (_selectors.Count == 0)
-                throw new XamlLoadException("Invalid selector count", this);
+                throw new XamlX.XamlLoadException("Invalid selector count", this);
             if (_selectors.Count == 1)
             if (_selectors.Count == 1)
             {
             {
                 _selectors[0].Emit(context, codeGen);
                 _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)
             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);
             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.Collections.Generic;
 using System.Linq;
 using System.Linq;
 using XamlX;
 using XamlX;
@@ -9,6 +10,14 @@ using XamlX.TypeSystem;
 
 
 namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
 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
     class AvaloniaXamlIlSetterTransformer : IXamlAstTransformer
     {
     {
         public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
         public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
@@ -27,13 +36,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
             if (styleParent != null)
             if (styleParent != null)
             {
             {
                 targetType = styleParent.TargetType.GetClrType()
                 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;
                 lineInfo = on;
             }
             }
 
 
             if (targetType == null)
             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;
             IXamlType propType = null;
@@ -43,7 +52,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
             {
             {
                 var propertyName = property.Values.OfType<XamlAstTextNode>().FirstOrDefault()?.Text;
                 var propertyName = property.Values.OfType<XamlAstTextNode>().FirstOrDefault()?.Text;
                 if (propertyName == null)
                 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,
                 var avaloniaPropertyNode = XamlIlAvaloniaPropertyHelper.CreateNode(context, propertyName,
                     new XamlAstClrTypeReference(lineInfo, targetType, false), property.Values[0]);
                     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>()
                 var propertyPath = on.Children.OfType<XamlAstXamlPropertyValueNode>()
                     .FirstOrDefault(x => x.Property.GetClrProperty().Name == "PropertyPath");
                     .FirstOrDefault(x => x.Property.GetClrProperty().Name == "PropertyPath");
                 if (propertyPath == null)
                 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
                 if (propertyPath.Values[0] is IXamlIlPropertyPathNode ppn
                     && ppn.PropertyType != null)
                     && ppn.PropertyType != null)
                     propType = ppn.PropertyType;
                     propType = ppn.PropertyType;
                 else
                 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
             var valueProperty = on.Children
@@ -69,7 +78,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
             {
             {
                 if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context, valueProperty.Values[0],
                 if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context, valueProperty.Values[0],
                         propType, out var converted))
                         propType, out var converted))
-                    throw new XamlParseException(
+                    throw new XamlStyleTransformException(
                         $"Unable to convert property value to {propType.GetFqn()}",
                         $"Unable to convert property value to {propType.GetFqn()}",
                         valueProperty.Values[0]);
                         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()
                 var found = tref.Type.GetAllFields()
                     .FirstOrDefault(f => f.IsStatic && f.IsPublic && f.Name == propertyFieldName);
                     .FirstOrDefault(f => f.IsStatic && f.IsPublic && f.Name == propertyFieldName);
                 if (found == null)
                 if (found == null)
-                    throw new XamlX.XamlParseException(
+                    throw new XamlX.XamlTransformException(
                         $"Unable to find {propertyFieldName} field on type {tref.Type.GetFullName()}", lineInfo);
                         $"Unable to find {propertyFieldName} field on type {tref.Type.GetFullName()}", lineInfo);
                 return new XamlIlAvaloniaPropertyFieldNode(context.GetAvaloniaTypes(), lineInfo, found);
                 return new XamlIlAvaloniaPropertyFieldNode(context.GetAvaloniaTypes(), lineInfo, found);
             }
             }
@@ -110,7 +110,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
                 avaloniaPropertyType = avaloniaPropertyType.BaseType;
                 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",
                 $"{field.Name}'s type {field.FieldType} doesn't inherit from  AvaloniaProperty<T>, make sure to use typed properties",
                 lineInfo);
                 lineInfo);
 
 

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

@@ -87,8 +87,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
                 return transformed;
                 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)
             if (parentNode.Property?.Getter?.ReturnType == context.GetAvaloniaTypes().ICommand && lastElement is XamlIlClrMethodPathElementNode methodPathElement)
             {
             {
@@ -158,7 +157,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
                             {
                             {
                                 break;
                                 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:
                     case BindingExpressionGrammar.PropertyNameNode propName:
                         {
                         {
@@ -182,7 +181,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
                             }
                             }
                             else
                             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;
                             break;
                         }
                         }
@@ -207,7 +206,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
                             }
                             }
                             if (property is null)
                             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;
                             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);
                                 var textNode = new XamlAstTextNode(lineInfo, indexer.Arguments[currentParamIndex], type: context.Configuration.WellKnownTypes.String);
                                 if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context, textNode,
                                 if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context, textNode,
                                         param, out var converted))
                                         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()}",
                                         $"Unable to convert indexer parameter value of '{indexer.Arguments[currentParamIndex]}' to {param.GetFqn()}",
                                         textNode);
                                         textNode);
 
 
@@ -267,7 +266,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
 
 
                             if (ancestorType is null)
                             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));
                             nodes.Add(new FindAncestorPathElementNode(ancestorType, ancestor.Level));
@@ -294,7 +293,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
 
 
                         if (elementType is null)
                         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));
                         nodes.Add(new ElementNamePathElementNode(elementName.Name, elementType));
                         break;
                         break;
@@ -306,7 +305,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
 
 
                         if (castType is null)
                         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));
                         nodes.Add(new TypeCastPathElementNode(castType));
@@ -785,7 +784,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
                 {
                 {
                     if (!int.TryParse(item, out var index))
                     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);
                     _values.Add(index);
                 }
                 }
@@ -866,10 +865,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
                 Elements = elements;
                 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; }
             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;
 using System.Reflection;
 
 
 namespace Avalonia.Markup.Xaml;
 namespace Avalonia.Markup.Xaml;
@@ -20,4 +21,50 @@ public class RuntimeXamlLoaderConfiguration
     /// Default is 'false'.
     /// Default is 'false'.
     /// </summary>
     /// </summary>
     public bool DesignMode { get; set; } = false;
     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="..\..\..\src\Markup\Avalonia.Markup.Xaml.Loader\CompilerDynamicDependencies.cs" />
     <Compile Include="..\..\Avalonia.Base\Utilities\StringBuilderCache.cs" Link="Utilities\StringBuilderCache.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="..\..\Avalonia.Base\Compatibility\TrimmingAttributes.cs" Link="TrimmingAttributes.cs" Visible="False" />
+    <Compile Include="..\..\Shared\IsExternalInit.cs" Link="Compatibility\IsExternalInit.cs" />
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
     <PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.22045.20230930" />
     <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;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
+using XamlX;
 using XamlX.Compiler;
 using XamlX.Compiler;
 using XamlX.Emit;
 using XamlX.Emit;
 using XamlX.Transform;
 using XamlX.Transform;
@@ -19,10 +20,13 @@ internal sealed class MiniCompiler : XamlCompiler<object, IXamlEmitResult>
         foreach (var additionalType in additionalTypes)
         foreach (var additionalType in additionalTypes)
             mappings.XmlnsAttributes.Add(typeSystem.GetType(additionalType));
             mappings.XmlnsAttributes.Add(typeSystem.GetType(additionalType));
 
 
+        var diagnosticsHandler = new XamlDiagnosticsHandler();
+        
         var configuration = new TransformerConfiguration(
         var configuration = new TransformerConfiguration(
             typeSystem,
             typeSystem,
             typeSystem.Assemblies.First(),
             typeSystem.Assemblies.First(),
-            mappings);
+            mappings,
+            diagnosticsHandler: diagnosticsHandler);
         return new MiniCompiler(configuration);
         return new MiniCompiler(configuration);
     }
     }
         
         
@@ -47,4 +51,4 @@ internal sealed class MiniCompiler : XamlCompiler<object, IXamlEmitResult>
         XamlRuntimeContext<object, IXamlEmitResult> context,
         XamlRuntimeContext<object, IXamlEmitResult> context,
         bool needContextLocal) =>
         bool needContextLocal) =>
         throw new NotSupportedException();
         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 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 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' />";
     private const string MiniValidXaml = "<Valid xmlns='clr-namespace:Example;assembly=Example' />";
 
 
     [Fact]
     [Fact]
@@ -27,16 +26,6 @@ public class MiniCompilerTests
         Assert.NotNull(xaml.Root);
         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]
     [Fact]
     public void Should_Resolve_Types_From_Simple_Avalonia_Markup()
     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(ISupportInitialize).Assembly.Location))
             .AddReferences(MetadataReference.CreateFromFile(typeof(TypeConverterAttribute).Assembly.Location))
             .AddReferences(MetadataReference.CreateFromFile(typeof(TypeConverterAttribute).Assembly.Location))
             .AddSyntaxTrees(CSharpSyntaxTree.ParseText(source));
             .AddSyntaxTrees(CSharpSyntaxTree.ParseText(source));
-}
+}

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

@@ -8,7 +8,8 @@
         <TextBox x:Name="PasswordTextBox"
         <TextBox x:Name="PasswordTextBox"
                  Watermark="Password input"
                  Watermark="Password input"
                  UseFloatingWatermark="True" />
                  UseFloatingWatermark="True" />
+        <!-- Name generator should still generate members, even if XAML is invalid -->
         <Button x:Name="SignUpButton"
         <Button x:Name="SignUpButton"
-                Content="Sign up" />
+                Content="{x:Static NonExistent.Member}" />
     </StackPanel>
     </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.Reactive.Subjects;
 using System.Runtime.CompilerServices;
 using System.Runtime.CompilerServices;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
+using System.Xml;
 using Avalonia.Collections;
 using Avalonia.Collections;
 using Avalonia.Controls;
 using Avalonia.Controls;
 using Avalonia.Controls.Presenters;
 using Avalonia.Controls.Presenters;
@@ -496,7 +497,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
     </Window.DataTemplates>
     </Window.DataTemplates>
     <ContentControl Name='target' Content='{CompiledBinding}' />
     <ContentControl Name='target' Content='{CompiledBinding}' />
 </Window>";
 </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' />
         <TextBlock Text='{CompiledBinding StringProperty}' Name='textBlock' />
     </ContentControl>
     </ContentControl>
 </Window>";
 </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.DataTemplates>
     </ItemsControl>
     </ItemsControl>
 </Window>";
 </Window>";
-                ThrowsXamlTransformException(() => AvaloniaRuntimeXamlLoader.Load(xaml));
+                Assert.ThrowsAny<XmlException>(() => AvaloniaRuntimeXamlLoader.Load(xaml));
             }
             }
         }
         }
         
         
@@ -1148,7 +1171,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
         x:CompileBindings='true'>
         x:CompileBindings='true'>
     <TextBlock Text='{Binding InvalidPath}' Name='textBlock' />
     <TextBlock Text='{Binding InvalidPath}' Name='textBlock' />
 </Window>";
 </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:DataType='local:TestDataContext'
         x:CompileBindings='notabool'>
         x:CompileBindings='notabool'>
 </Window>";
 </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));
                     , 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)
         static void PerformClick(Button button)
         {
         {
             button.RaiseEvent(new KeyEventArgs
             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[0]);
         Assert.IsType<SimpleTheme>(control.Styles[1]);
         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]
     [Fact]
     public void StyleInclude_From_CodeBehind_Resolves_Compiled()
     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);
                 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]
         [Fact]
         public void Style_Parser_Throws_For_Duplicate_Setter()
         public void Style_Parser_Throws_For_Duplicate_Setter()
         {
         {
+            using var _ = UnitTestApplication.Start(TestServices.StyledWindow);
+
             var xaml = @"
             var xaml = @"
 <Window xmlns='https://github.com/avaloniaui'
 <Window xmlns='https://github.com/avaloniaui'
         xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
         xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
@@ -340,13 +342,27 @@ namespace Avalonia.Markup.Xaml.UnitTests
     </Window.Styles>
     </Window.Styles>
     <TextBlock/>
     <TextBlock/>
 </Window>";
 </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]
         [Fact]
         public void Control_Theme_Parser_Throws_For_Duplicate_Setter()
         public void Control_Theme_Parser_Throws_For_Duplicate_Setter()
         {
         {
+            using var _ = UnitTestApplication.Start(TestServices.StyledWindow);
+
             var xaml = @"
             var xaml = @"
 <Window xmlns='https://github.com/avaloniaui'
 <Window xmlns='https://github.com/avaloniaui'
         xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
         xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
@@ -361,8 +377,20 @@ namespace Avalonia.Markup.Xaml.UnitTests
 
 
     <u:TestTemplatedControl Theme='{StaticResource MyTheme}'/>
     <u:TestTemplatedControl Theme='{StaticResource MyTheme}'/>
 </Window>";
 </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)
                 if(e is XmlException)
                     return;
                     return;
+                throw new Exception("Expected to throw xaml exception", e);
             }
             }
             throw new Exception("Expected to throw xaml exception");
             throw new Exception("Expected to throw xaml exception");
         }
         }