diff --git a/packages/Avalonia/AvaloniaBuildTasks.targets b/packages/Avalonia/AvaloniaBuildTasks.targets
index 8fe77a095d..d3750935f9 100644
--- a/packages/Avalonia/AvaloniaBuildTasks.targets
+++ b/packages/Avalonia/AvaloniaBuildTasks.targets
@@ -134,6 +134,8 @@
false
false
false
+ true
+ false
@@ -162,6 +164,7 @@
DelaySign="$(DelaySign)"
SkipXamlCompilation="$(_AvaloniaSkipXamlCompilation)"
DebuggerLaunch="$(AvaloniaXamlIlDebuggerLaunch)"
+ CreateSourceInfo="$(AvaloniaXamlCreateSourceInfo)"
DefaultCompileBindings="$(AvaloniaUseCompiledBindingsByDefault)"
VerboseExceptions="$(AvaloniaXamlVerboseExceptions)"
AnalyzerConfigFiles="@(EditorConfigFiles)"/>
diff --git a/packages/Avalonia/AvaloniaRules.Project.xml b/packages/Avalonia/AvaloniaRules.Project.xml
index b69ea6de17..0a5c1b8243 100644
--- a/packages/Avalonia/AvaloniaRules.Project.xml
+++ b/packages/Avalonia/AvaloniaRules.Project.xml
@@ -31,6 +31,11 @@
Description="Allow debug XAML compilation"
Category="Debug" />
+
+
();
@@ -363,7 +364,7 @@ namespace Avalonia.Markup.Xaml.XamlIl
}
var parsed = compiler.Parse(xaml, overrideType);
- parsed.Document = "runtimexaml:" + parsedDocuments.Count;
+ parsed.Document = document.Document ?? ("runtimexaml" + parsedDocuments.Count);
compiler.Transform(parsed);
var xamlName = GetSafeUriIdentifier(document.BaseUri)
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
index e0225a24f7..5b00ea0bea 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
+using Avalonia.Markup.Xaml.Loader.CompilerExtensions.Transformers;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.GroupTransformers;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
using XamlX;
@@ -20,6 +21,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
private readonly IXamlType _contextType = null!;
private readonly AvaloniaXamlIlDesignPropertiesTransformer _designTransformer;
private readonly AvaloniaBindingExtensionTransformer _bindingTransformer;
+ private readonly AvaloniaXamlIlAddSourceInfoTransformer _addSourceInfoTransformer;
+ private readonly AvaloniaXamlResourceTransformer _resourceTransformer;
private AvaloniaXamlIlCompiler(TransformerConfiguration configuration, XamlLanguageEmitMappings emitMappings)
: base(configuration, emitMappings, true)
@@ -47,6 +50,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
new AvaloniaXamlIlResolveClassesPropertiesTransformer(),
new AvaloniaXamlIlTransformInstanceAttachedProperties(),
new AvaloniaXamlIlTransformSyntheticCompiledBindingMembers());
+
+
InsertAfter(
new AvaloniaXamlIlAvaloniaPropertyResolver(),
new AvaloniaXamlIlReorderClassesPropertiesTransformer(),
@@ -85,7 +90,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
);
InsertBeforeMany(new [] { typeof(DeferredContentTransformer), typeof(AvaloniaXamlIlCompiledBindingsMetadataRemover) },
- new AvaloniaXamlIlDeferredResourceTransformer());
+ _resourceTransformer = new AvaloniaXamlResourceTransformer());
InsertBefore(new AvaloniaXamlIlTransformRoutedEvent());
@@ -94,6 +99,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
Transformers.Add(new AvaloniaXamlIlEnsureResourceDictionaryCapacityTransformer());
Transformers.Add(new AvaloniaXamlIlRootObjectScope());
+ Transformers.Add(_addSourceInfoTransformer = new AvaloniaXamlIlAddSourceInfoTransformer());
+
Emitters.Add(new AvaloniaNameScopeRegistrationXamlIlNodeEmitter());
Emitters.Add(new AvaloniaXamlIlRootObjectScope.Emitter());
@@ -122,6 +129,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
public const string PopulateName = "__AvaloniaXamlIlPopulate";
public const string BuildName = "__AvaloniaXamlIlBuild";
+ public bool CreateSourceInfo
+ {
+ get => _addSourceInfoTransformer.CreateSourceInfo || _resourceTransformer.CreateSourceInfo;
+ set => _addSourceInfoTransformer.CreateSourceInfo = _resourceTransformer.CreateSourceInfo = value;
+ }
+
public bool IsDesignMode
{
get => _designTransformer.IsDesignMode;
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs
index 766c6d9e02..9ee893e0d4 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs
@@ -20,8 +20,12 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer
{
public IXamlAstNode Transform(AstGroupTransformationContext context, IXamlAstNode node)
{
- if (node is not XamlValueWithManipulationNode valueNode
- || valueNode.Value is not XamlAstNewClrObjectNode objectNode
+ // Filter object initialization nodes like:
+ // > XamlValueWithManipulationNode
+ // > > XamlAstNewClrObjectNode // StyleInclude or ResourceInclude, can be nested in another XamlValueWithManipulationNode
+ // > > XamlObjectInitializationNode
+ if (node is not XamlValueWithManipulationNode { Manipulation: XamlObjectInitializationNode initializationNode } valueNode
+ || valueNode.UnwrapValue() is not { } objectNode
|| (objectNode.Type.GetClrType() != context.GetAvaloniaTypes().StyleInclude
&& objectNode.Type.GetClrType() != context.GetAvaloniaTypes().ResourceInclude))
{
@@ -36,11 +40,6 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer
throw new InvalidOperationException($"\"{nodeTypeName}\".Loaded property is expected to be defined");
}
- if (valueNode.Manipulation is not XamlObjectInitializationNode initializationNode)
- {
- throw new InvalidOperationException($"Invalid \"{nodeTypeName}\" node initialization.");
- }
-
var additionalProperties = new List();
if (initializationNode.Manipulation is not XamlPropertyAssignmentNode { Property: { Name: "Source" } } sourceProperty)
{
@@ -176,9 +175,25 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer
strictSourceValueType ? XamlDiagnosticSeverity.Error : XamlDiagnosticSeverity.Warning,
$"\"{nodeTypeName}.Source\" supports only \"avares://\" absolute or relative uri. This {nodeTypeName} will be resolved in runtime instead.",
node);
-
+
// We expect that AvaloniaXamlIlLanguageParseIntrinsics has already parsed the Uri and created node like: `new Uri(assetPath, uriKind)`.
- if (sourceProperty.Values.OfType().FirstOrDefault() is not { } sourceUriNode
+ if (sourceProperty.Values.Count != 1)
+ {
+ OnInvalidSource(sourceProperty);
+ return (null, null);
+ }
+
+ // `new Uri` can be wrapped in manipulation node if source info or another manipulation was applied.
+ var sourceUriNodeWrapped = sourceProperty.Values.Single();
+ var sourceUriNode = sourceUriNodeWrapped switch
+ {
+ XamlAstNewClrObjectNode newObj => newObj,
+ XamlValueWithManipulationNode manipulation => manipulation.UnwrapValue(),
+ _ => null
+ };
+
+ // Validate Uri type and constant arguments.
+ if (sourceUriNode is null
|| sourceUriNode.Type.GetClrType() != context.GetAvaloniaTypes().Uri
|| sourceUriNode.Arguments.FirstOrDefault() is not XamlConstantNode { Constant: string originalAssetPath }
|| sourceUriNode.Arguments.Skip(1).FirstOrDefault() is not XamlConstantNode { Constant: int uriKind })
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAddSourceInfoTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAddSourceInfoTransformer.cs
new file mode 100644
index 0000000000..1ce6e456d6
--- /dev/null
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAddSourceInfoTransformer.cs
@@ -0,0 +1,69 @@
+using System.Linq;
+using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
+using XamlX.Ast;
+using XamlX.Emit;
+using XamlX.IL;
+using XamlX.Transform;
+
+namespace Avalonia.Markup.Xaml.Loader.CompilerExtensions.Transformers
+{
+ ///
+ /// An XAMLIL AST transformer that injects metadata into the generated XAML code.
+ ///
+ ///
+ /// This transformer wraps object creation nodes with a manipulation node that adds source information.
+ /// This source information includes line number, position, and document name, which can be useful for debugging and diagnostics.
+ /// Note: ResourceDictionary source info is handled separately in .
+ ///
+ internal class AvaloniaXamlIlAddSourceInfoTransformer : IXamlAstTransformer
+ {
+ ///
+ /// Gets or sets a value indicating whether source information should be generated
+ /// and injected into the compiled XAML output.
+ ///
+ public bool CreateSourceInfo { get; set; }
+
+ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
+ {
+ if (CreateSourceInfo
+ && node is XamlAstNewClrObjectNode objNode
+ && context.ParentNodes().FirstOrDefault() is not XamlValueWithManipulationNode { Manipulation: XamlSourceInfoValueManipulation }
+ && !objNode.Type.GetClrType().IsValueType)
+ {
+ var avaloniaTypes = context.GetAvaloniaTypes();
+
+ return new XamlValueWithManipulationNode(
+ objNode, objNode,
+ new XamlSourceInfoValueManipulation(avaloniaTypes, objNode, context.Document));
+ }
+
+ return node;
+ }
+
+ private class XamlSourceInfoValueManipulation(
+ AvaloniaXamlIlWellKnownTypes avaloniaTypes,
+ XamlAstNewClrObjectNode objNode, string? document)
+ : XamlAstNode(objNode), IXamlAstManipulationNode, IXamlAstILEmitableNode
+ {
+ public XamlILNodeEmitResult Emit(XamlEmitContext context, IXamlILEmitter codeGen)
+ {
+ // Target object is already on stack.
+
+ // var info = new XamlSourceInfo(Line, Position, Document);
+ codeGen.Ldc_I4(Line);
+ codeGen.Ldc_I4(Position);
+ if (document is not null)
+ codeGen.Ldstr(document);
+ else
+ codeGen.Ldnull();
+ codeGen.Newobj(avaloniaTypes.XamlSourceInfoConstructor);
+
+ // Set the XamlSourceInfo property on the current object
+ // XamlSourceInfo.SetValue(@this, info);
+ codeGen.EmitCall(avaloniaTypes.XamlSourceInfoSetter);
+
+ return XamlILNodeEmitResult.Void(1);
+ }
+ }
+ }
+}
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs
deleted file mode 100644
index 81a174c6e2..0000000000
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs
+++ /dev/null
@@ -1,191 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Visitors;
-using XamlX;
-using XamlX.Ast;
-using XamlX.Emit;
-using XamlX.IL;
-using XamlX.Transform;
-using XamlX.TypeSystem;
-
-namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
-{
- internal class AvaloniaXamlIlDeferredResourceTransformer : IXamlAstTransformer
- {
- public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
- {
- if (!(node is XamlPropertyAssignmentNode pa) || pa.Values.Count != 2)
- return node;
-
- var types = context.GetAvaloniaTypes();
-
- if (pa.Property.DeclaringType == types.ResourceDictionary && pa.Property.Name == "Content"
- && ShouldBeDeferred(pa.Values[1]))
- {
- IXamlMethod addMethod = TryGetSharedValue(pa.Values[1], out var isShared) && !isShared
- ? types.ResourceDictionaryNotSharedDeferredAdd
- : types.ResourceDictionaryDeferredAdd;
-
- pa.Values[1] = new XamlDeferredContentNode(pa.Values[1], types.XamlIlTypes.Object, context.Configuration);
- pa.PossibleSetters = new List
- {
- new XamlDirectCallPropertySetter(addMethod),
- };
- }
- else if (pa.Property.Name == "Resources" && pa.Property.Getter?.ReturnType.Equals(types.IResourceDictionary) == true
- && ShouldBeDeferred(pa.Values[1]))
- {
- IXamlMethod addMethod = TryGetSharedValue(pa.Values[1], out var isShared) && !isShared
- ? types.ResourceDictionaryNotSharedDeferredAdd
- : types.ResourceDictionaryDeferredAdd;
-
- pa.Values[1] = new XamlDeferredContentNode(pa.Values[1], types.XamlIlTypes.Object, context.Configuration);
- pa.PossibleSetters = new List
- {
- new AdderSetter(pa.Property.Getter, addMethod),
- };
- }
-
- return node;
-
- bool TryGetSharedValue(IXamlAstValueNode valueNode, out bool value)
- {
- value = default;
- if (valueNode is XamlAstConstructableObjectNode co)
- {
- // Try find x:Share directive
- if (co.Children.Find(d => d is XamlAstXmlDirective { Namespace: XamlNamespaces.Xaml2006, Name: "Shared" }) is XamlAstXmlDirective sharedDirective)
- {
- if (sharedDirective.Values.Count == 1 && sharedDirective.Values[0] is XamlAstTextNode text)
- {
- if (bool.TryParse(text.Text, out var parseValue))
- {
- // If the parser succeeds, remove the x:Share directive
- co.Children.Remove(sharedDirective);
- return true;
- }
- else
- {
- context.ReportTransformError("Invalid argument type for x:Shared directive.", node);
- }
- }
- else
- {
- context.ReportTransformError("Invalid number of arguments for x:Shared directive.", node);
- }
- }
- }
- return false;
- }
- }
-
- private static bool ShouldBeDeferred(IXamlAstValueNode node)
- {
- var clrType = node.Type.GetClrType();
-
- // XAML compiler is currently strict about value types, allowing them to be created only through converters.
- // At the moment it should be safe to not defer structs.
- if (clrType.IsValueType)
- {
- return false;
- }
-
- // Never defer strings.
- if (clrType.FullName == "System.String")
- {
- return false;
- }
-
- // Do not defer resources, if it has any x:Name registration, as it cannot be delayed.
- // This visitor will count x:Name registrations, ignoring nested NestedScopeMetadataNode scopes.
- // We set target scope level to 0, assuming that this resource node is a scope of itself.
- var nameRegistrationsVisitor = new NameScopeRegistrationVisitor(
- targetMetadataScopeLevel: 0);
- node.Visit(nameRegistrationsVisitor);
- if (nameRegistrationsVisitor.Count > 0)
- {
- return false;
- }
-
- return true;
- }
-
- class AdderSetter : IXamlILOptimizedEmitablePropertySetter, IEquatable
- {
- private readonly IXamlMethod _getter;
- private readonly IXamlMethod _adder;
-
- public AdderSetter(IXamlMethod getter, IXamlMethod adder)
- {
- _getter = getter;
- _adder = adder;
- TargetType = getter.DeclaringType;
- Parameters = adder.ParametersWithThis().Skip(1).ToList();
-
- bool allowNull = Parameters.Last().AcceptsNull();
- BinderParameters = new PropertySetterBinderParameters
- {
- AllowMultiple = true,
- AllowXNull = allowNull,
- AllowRuntimeNull = allowNull,
- AllowAttributeSyntax = false,
- };
- }
-
- public IXamlType TargetType { get; }
-
- public PropertySetterBinderParameters BinderParameters { get; }
-
- public IReadOnlyList Parameters { get; }
- public IReadOnlyList CustomAttributes => _adder.CustomAttributes;
-
- public void Emit(IXamlILEmitter emitter)
- {
- var locals = new Stack();
- // Save all "setter" parameters
- for (var c = Parameters.Count - 1; c >= 0; c--)
- {
- var loc = emitter.LocalsPool.GetLocal(Parameters[c]);
- locals.Push(loc);
- emitter.Stloc(loc.Local);
- }
-
- emitter.EmitCall(_getter);
- while (locals.Count>0)
- using (var loc = locals.Pop())
- emitter.Ldloc(loc.Local);
- emitter.EmitCall(_adder, true);
- }
-
- public void EmitWithArguments(
- XamlEmitContextWithLocals context,
- IXamlILEmitter emitter,
- IReadOnlyList arguments)
- {
- emitter.EmitCall(_getter);
-
- for (var i = 0; i < arguments.Count; ++i)
- context.Emit(arguments[i], emitter, Parameters[i]);
-
- emitter.EmitCall(_adder, true);
- }
-
- public bool Equals(AdderSetter? other)
- {
- if (ReferenceEquals(null, other))
- return false;
- if (ReferenceEquals(this, other))
- return true;
-
- return _getter.Equals(other._getter) && _adder.Equals(other._adder);
- }
-
- public override bool Equals(object? obj)
- => Equals(obj as AdderSetter);
-
- public override int GetHashCode()
- => (_getter.GetHashCode() * 397) ^ _adder.GetHashCode();
- }
- }
-}
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlResourceTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlResourceTransformer.cs
new file mode 100644
index 0000000000..4e2458696e
--- /dev/null
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlResourceTransformer.cs
@@ -0,0 +1,343 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Visitors;
+using XamlX;
+using XamlX.Ast;
+using XamlX.Emit;
+using XamlX.IL;
+using XamlX.Transform;
+using XamlX.TypeSystem;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
+{
+ ///
+ /// Transforms ResourceDictionary and IResourceDictionary property assignments
+ /// to use Add method calls with deferred content where applicable.
+ /// Additionally, handles x:Shared on assignments and injects XamlSourceInfo.
+ ///
+ internal class AvaloniaXamlResourceTransformer : IXamlAstTransformer
+ {
+ ///
+ /// Gets or sets a value indicating whether source information should be generated
+ /// and injected into the compiled XAML output.
+ ///
+ public bool CreateSourceInfo { get; set; } = true;
+
+ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
+ {
+ if (!(node is XamlPropertyAssignmentNode pa) || pa.Values.Count != 2)
+ return node;
+
+ var types = context.GetAvaloniaTypes();
+ var document = context.Document;
+
+ if (pa.Property.DeclaringType == types.ResourceDictionary && pa.Property.Name == "Content")
+ {
+ var value = pa.Values[1];
+ (var adder, value) = ResolveAdderAndValue(value);
+
+ pa.Values[1] = value;
+ pa.PossibleSetters = new List
+ {
+ new AdderSetter(adder, CreateSourceInfo, types, value.Line, value.Position, document),
+ };
+ }
+ else if (pa.Property.Name == "Resources" && pa.Property.Getter?.ReturnType.Equals(types.IResourceDictionary) == true)
+ {
+ var value = pa.Values[1];
+ (var adder, value) = ResolveAdderAndValue(value);
+
+ pa.Values[1] = value;
+ pa.PossibleSetters = new List
+ {
+ new AdderSetter(pa.Property.Getter, adder, CreateSourceInfo, types, value.Line, value.Position, document),
+ };
+ }
+
+ return node;
+
+ (IXamlMethod adder, IXamlAstValueNode newValue) ResolveAdderAndValue(IXamlAstValueNode valueNode)
+ {
+ if (ShouldBeDeferred(valueNode))
+ {
+ var adder = TryGetSharedValue(valueNode, out var isShared) && !isShared
+ ? types.ResourceDictionaryNotSharedDeferredAdd
+ : types.ResourceDictionaryDeferredAdd;
+ var deferredNode = new XamlDeferredContentNode(valueNode, types.XamlIlTypes.Object, context.Configuration);
+ return (adder, deferredNode);
+ }
+ else
+ {
+ var adder = XamlTransformHelpers.FindPossibleAdders(context, types.IResourceDictionary)
+ .FirstOrDefault() ?? throw new XamlTransformException("No suitable Add method found for IResourceDictionary.", node);
+ return (adder, valueNode);
+ }
+ }
+
+ bool TryGetSharedValue(IXamlAstValueNode valueNode, out bool value)
+ {
+ value = default;
+ if (valueNode is XamlAstConstructableObjectNode co)
+ {
+ // Try find x:Share directive
+ if (co.Children.Find(d => d is XamlAstXmlDirective { Namespace: XamlNamespaces.Xaml2006, Name: "Shared" }) is XamlAstXmlDirective sharedDirective)
+ {
+ if (sharedDirective.Values.Count == 1 && sharedDirective.Values[0] is XamlAstTextNode text)
+ {
+ if (bool.TryParse(text.Text, out var parseValue))
+ {
+ // If the parser succeeds, remove the x:Share directive
+ co.Children.Remove(sharedDirective);
+ return true;
+ }
+ else
+ {
+ context.ReportTransformError("Invalid argument type for x:Shared directive.", node);
+ }
+ }
+ else
+ {
+ context.ReportTransformError("Invalid number of arguments for x:Shared directive.", node);
+ }
+ }
+ }
+ return false;
+ }
+ }
+
+ private static bool ShouldBeDeferred(IXamlAstValueNode node)
+ {
+ var clrType = node.Type.GetClrType();
+
+ // XAML compiler is currently strict about value types, allowing them to be created only through converters.
+ // At the moment it should be safe to not defer structs.
+ if (clrType.IsValueType)
+ {
+ return false;
+ }
+
+ // Never defer strings.
+ if (clrType.FullName == "System.String")
+ {
+ return false;
+ }
+
+ // Do not defer resources, if it has any x:Name registration, as it cannot be delayed.
+ // This visitor will count x:Name registrations, ignoring nested NestedScopeMetadataNode scopes.
+ // We set target scope level to 0, assuming that this resource node is a scope of itself.
+ var nameRegistrationsVisitor = new NameScopeRegistrationVisitor(
+ targetMetadataScopeLevel: 0);
+ node.Visit(nameRegistrationsVisitor);
+ if (nameRegistrationsVisitor.Count > 0)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ class AdderSetter : IXamlILOptimizedEmitablePropertySetter, IEquatable
+ {
+ private readonly IXamlMethod? _getter;
+ private readonly IXamlMethod _adder;
+ private readonly bool _emitSourceInfo;
+ private readonly AvaloniaXamlIlWellKnownTypes _avaloniaTypes;
+ private readonly string? _document;
+ private readonly int _line, _position;
+
+ ///
+ /// Creates an adder-only setter. Target is assumed to be already on the stack before emit.
+ /// For example:
+ /// var resourceDictionary = ...
+ /// resourceDictionary.Add(key, value);
+ /// resourceDictionary.Add(key2, value2);
+ ///
+ public AdderSetter(
+ IXamlMethod adder,
+ bool emitSourceInfo,
+ AvaloniaXamlIlWellKnownTypes avaloniaTypes,
+ int line, int position, string? document)
+ {
+ _adder = adder;
+ _emitSourceInfo = emitSourceInfo;
+ _avaloniaTypes = avaloniaTypes;
+ _line = line;
+ _position = position;
+ _document = document;
+
+ TargetType = adder.ThisOrFirstParameter();
+ Parameters = adder.ParametersWithThis().Skip(1).ToList();
+ bool allowNull = Parameters.Last().AcceptsNull();
+ BinderParameters = new PropertySetterBinderParameters
+ {
+ AllowMultiple = true,
+ AllowXNull = allowNull,
+ AllowRuntimeNull = allowNull
+ };
+ }
+
+ ///
+ /// Explicit target getter - target will be obtained by calling the getter first.
+ ///
+ ///
+ public AdderSetter(
+ IXamlMethod getter, IXamlMethod adder,
+ bool emitSourceInfo,
+ AvaloniaXamlIlWellKnownTypes avaloniaTypes,
+ int line, int position, string? document)
+ : this(adder, emitSourceInfo, avaloniaTypes, line, position, document)
+ {
+ _getter = getter;
+ TargetType = getter.DeclaringType;
+ BinderParameters.AllowMultiple = false;
+ BinderParameters.AllowAttributeSyntax = false;
+ }
+
+ public IXamlType TargetType { get; }
+
+ public PropertySetterBinderParameters BinderParameters { get; }
+
+ public IReadOnlyList Parameters { get; }
+ public IReadOnlyList CustomAttributes => _adder.CustomAttributes;
+
+ ///
+ /// Emits the setter with arguments already on the stack.
+ ///
+ ///
+ /// If _getter is null - assume target is already on the stack.
+ /// In this case, we can just call Emit. Unless _emitSourceInfo is true.
+ ///
+ /// If _emitSourceInfo is true - we need to make sure that target and key are on the stack for XamlSourceInfo setting,
+ /// so we need to store parameters to locals first regardless.
+ ///
+ public void Emit(IXamlILEmitter emitter)
+ {
+ using var keyLocal = emitter.LocalsPool.GetLocal(Parameters[0]);
+
+ if (_getter is not null || _emitSourceInfo)
+ {
+ var locals = new Stack();
+ // Save all "setter" parameters
+ for (var c = Parameters.Count - 1; c >= 0; c--)
+ {
+ var loc = emitter.LocalsPool.GetLocal(Parameters[c]);
+ locals.Push(loc);
+ emitter.Stloc(loc.Local);
+
+ if (c == 0 && _emitSourceInfo)
+ {
+ // Store the key argument for XamlSourceInfo later
+ emitter.Ldloc(loc.Local);
+ emitter.Stloc(keyLocal.Local);
+ }
+ }
+
+ if (_getter is not null)
+ {
+ emitter.EmitCall(_getter);
+ }
+
+ // Duplicate the target object on stack for setting XamlSourceInfo later
+ emitter.Dup();
+
+ while (locals.Count > 0)
+ using (var loc = locals.Pop())
+ emitter.Ldloc(loc.Local);
+ }
+
+ emitter.EmitCall(_adder, true);
+
+ if (_emitSourceInfo)
+ {
+ // Target is already on stack (dup)
+ // Load the key argument from local
+ emitter.Ldloc(keyLocal.Local);
+ EmitSetSourceInfo(emitter);
+ }
+ }
+
+ ///
+ /// Emits the setter with provided arguments that are not yet on the stack.
+ ///
+ ///
+ /// If _getter is null - assume target is already on the stack.
+ /// If _emitSourceInfo is true - we need to make sure that target and key are on the stack for XamlSourceInfo setting.
+ ///
+ public void EmitWithArguments(
+ XamlEmitContextWithLocals context,
+ IXamlILEmitter emitter,
+ IReadOnlyList arguments)
+ {
+ using var keyLocal = _emitSourceInfo ? emitter.LocalsPool.GetLocal(Parameters[0]) : null;
+
+ if (_getter is not null)
+ {
+ emitter.EmitCall(_getter);
+ }
+
+ if (_emitSourceInfo)
+ {
+ // Duplicate the target object on stack for setting XamlSourceInfo later
+ emitter.Dup();
+ }
+
+ for (var i = 0; i < arguments.Count; ++i)
+ {
+ context.Emit(arguments[i], emitter, Parameters[i]);
+
+ // Store the key argument for XamlSourceInfo later
+ if (i == 0 && _emitSourceInfo)
+ {
+ emitter.Stloc(keyLocal!.Local);
+ emitter.Ldloc(keyLocal.Local);
+ }
+ }
+
+ emitter.EmitCall(_adder, true);
+
+ if (_emitSourceInfo)
+ {
+ // Target is already on stack (dub)
+ // Load the key argument from local
+ emitter.Ldloc(keyLocal!.Local);
+
+ EmitSetSourceInfo(emitter);
+ }
+ }
+
+ private void EmitSetSourceInfo(IXamlILEmitter emitter)
+ {
+ // Assumes the target object and key are already on the stack
+
+ emitter.Ldc_I4(_line);
+ emitter.Ldc_I4(_position);
+ if (_document is not null)
+ emitter.Ldstr(_document);
+ else
+ emitter.Ldnull();
+ emitter.Newobj(_avaloniaTypes.XamlSourceInfoConstructor);
+
+ // Set the XamlSourceInfo property on the current object
+ // XamlSourceInfo.SetXamlSourceInfo(@this, key, info);
+ emitter.EmitCall(_avaloniaTypes.XamlSourceInfoDictionarySetter);
+ }
+
+ public bool Equals(AdderSetter? other)
+ {
+ if (ReferenceEquals(null, other))
+ return false;
+ if (ReferenceEquals(this, other))
+ return true;
+
+ return _getter?.Equals(other._getter) == true && _adder.Equals(other._adder);
+ }
+
+ public override bool Equals(object? obj)
+ => Equals(obj as AdderSetter);
+
+ public override int GetHashCode()
+ => (_getter, _adder).GetHashCode();
+ }
+ }
+}
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
index 8659eb1299..df54e71108 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
@@ -134,6 +134,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlType ControlTemplate { get; }
public IXamlType EventHandlerT { get; }
public IXamlMethod GetClassProperty { get; }
+ public IXamlConstructor XamlSourceInfoConstructor { get; }
+ public IXamlMethod XamlSourceInfoSetter { get; }
+ public IXamlMethod XamlSourceInfoDictionarySetter { get; }
sealed internal class InteractivityWellKnownTypes
{
@@ -343,6 +346,15 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
allowDowncast:false,
cfg.WellKnownTypes.String
);
+
+ var xamlSourceInfo = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Diagnostics.XamlSourceInfo");
+ XamlSourceInfoConstructor = xamlSourceInfo.GetConstructor([
+ XamlIlTypes.Int32, XamlIlTypes.Int32, XamlIlTypes.String
+ ]);
+ XamlSourceInfoSetter =
+ xamlSourceInfo.GetMethod("SetXamlSourceInfo", XamlIlTypes.Void, false, XamlIlTypes.Object, xamlSourceInfo);
+ XamlSourceInfoDictionarySetter =
+ xamlSourceInfo.GetMethod("SetXamlSourceInfo", XamlIlTypes.Void, false, IResourceDictionary, XamlIlTypes.Object, xamlSourceInfo);
}
}
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlAstNewClrObjectHelper.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlAstNewClrObjectHelper.cs
new file mode 100644
index 0000000000..612f675124
--- /dev/null
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlAstNewClrObjectHelper.cs
@@ -0,0 +1,26 @@
+using XamlX.Ast;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions;
+
+internal static class XamlAstNewClrObjectHelper
+{
+ ///
+ /// Tries to resolve the underlying value of a ,
+ /// unwrapping any nested instances.
+ ///
+ public static TXamlAstValueNode? UnwrapValue(this XamlValueWithManipulationNode node)
+ where TXamlAstValueNode : class, IXamlAstValueNode
+ {
+ var current = node.Value;
+ while (current is XamlValueWithManipulationNode valueWithManipulation)
+ {
+ current = valueWithManipulation.Value;
+ if (current is TXamlAstValueNode typedValue)
+ {
+ return typedValue;
+ }
+ }
+
+ return current as TXamlAstValueNode;
+ }
+}
diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
index e6186bbea6..6dd2690414 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
+++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
@@ -38,6 +38,7 @@
+
diff --git a/src/Markup/Avalonia.Markup.Xaml/Diagnostics/XamlSourceInfo.cs b/src/Markup/Avalonia.Markup.Xaml/Diagnostics/XamlSourceInfo.cs
new file mode 100644
index 0000000000..1bb5f1c98c
--- /dev/null
+++ b/src/Markup/Avalonia.Markup.Xaml/Diagnostics/XamlSourceInfo.cs
@@ -0,0 +1,151 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using Avalonia.Controls;
+
+namespace Avalonia.Markup.Xaml.Diagnostics
+{
+ ///
+ /// Represents source location information for an element within a XAML or code file.
+ ///
+ // ReSharper disable once ClassNeverInstantiated.Global //This class is instantiated through the XAML compiler.
+ public record XamlSourceInfo
+ {
+ private static readonly AttachedProperty s_xamlSourceInfo =
+ AvaloniaProperty.RegisterAttached(
+ "XamlSourceInfo", typeof(XamlSourceInfo));
+
+ private static readonly ConditionalWeakTable