| 
					
				 | 
			
			
				@@ -0,0 +1,555 @@ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+using System; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+using System.Collections.Concurrent; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+using System.Collections.Generic; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+using System.Collections.Immutable; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+using System.Linq; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+using System.Text.RegularExpressions; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+using System.Threading; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+using System.Threading.Tasks; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+using Microsoft.CodeAnalysis; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+using Microsoft.CodeAnalysis.CSharp; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+using Microsoft.CodeAnalysis.Diagnostics; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+using Microsoft.CodeAnalysis.Operations; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+namespace Avalonia.Analyzers; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+public partial class AvaloniaPropertyAnalyzer 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+{ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    public class CompileAnalyzer 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        /// <summary> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        /// A dictionary that maps field/property symbols to the AvaloniaProperty objects assigned to them. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        /// </summary> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        private readonly ConcurrentDictionary<ISymbol, AvaloniaPropertyDescription> _avaloniaProperyDescriptions = new(SymbolEqualityComparer.Default); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        private readonly ConcurrentDictionary<IPropertySymbol, ImmutableArray<AvaloniaPropertyDescription>> _clrPropertyToAvaloniaProperties = new(SymbolEqualityComparer.Default); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        private readonly INamedTypeSymbol _avaloniaObjectType; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        private readonly ImmutableHashSet<IMethodSymbol> _getValueMethods; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        private readonly ImmutableHashSet<IMethodSymbol> _setValueMethods; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        private readonly INamedTypeSymbol _avaloniaPropertyType; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        private readonly INamedTypeSymbol _styledPropertyType; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        private readonly INamedTypeSymbol _attachedPropertyType; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        private readonly INamedTypeSymbol _directPropertyType; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        private readonly ImmutableHashSet<IMethodSymbol> _avaloniaPropertyRegisterMethods; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        private readonly ImmutableHashSet<IMethodSymbol> _avaloniaPropertyAddOwnerMethods; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        public CompileAnalyzer(CompilationStartAnalysisContext context) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            _avaloniaObjectType = GetTypeOrThrow("Avalonia.AvaloniaObject"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            _getValueMethods = _avaloniaObjectType.GetMembers("GetValue").OfType<IMethodSymbol>().ToImmutableHashSet<IMethodSymbol>(SymbolEqualityComparer.Default); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            _setValueMethods = _avaloniaObjectType.GetMembers("SetValue").OfType<IMethodSymbol>().ToImmutableHashSet<IMethodSymbol>(SymbolEqualityComparer.Default); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            _avaloniaPropertyType = GetTypeOrThrow("Avalonia.AvaloniaProperty"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            _styledPropertyType = GetTypeOrThrow("Avalonia.StyledProperty`1"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            _attachedPropertyType = GetTypeOrThrow("Avalonia.AttachedProperty`1"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            _directPropertyType = GetTypeOrThrow("Avalonia.DirectProperty`2"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            _avaloniaPropertyRegisterMethods = _avaloniaPropertyType.GetMembers() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                .OfType<IMethodSymbol>().Where(m => m.Name.StartsWith("Register")).ToImmutableHashSet<IMethodSymbol>(SymbolEqualityComparer.Default); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            _avaloniaPropertyAddOwnerMethods = new[] { _styledPropertyType, _attachedPropertyType, _directPropertyType } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                .SelectMany(t => t.GetMembers("AddOwner").OfType<IMethodSymbol>()).ToImmutableHashSet<IMethodSymbol>(SymbolEqualityComparer.Default); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            FindAvaloniaPropertySymbols(context.Compilation, context.CancellationToken); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            context.RegisterOperationAction(AnalyzeFieldInitializer, OperationKind.FieldInitializer); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            context.RegisterOperationAction(AnalyzePropertyInitializer, OperationKind.PropertyInitializer); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            context.RegisterSymbolStartAction(StartPropertySymbolAnalysis, SymbolKind.Property); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if (context.Compilation.Language == LanguageNames.CSharp) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                context.RegisterCodeBlockAction(AnalyzePropertyMethods); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            INamedTypeSymbol GetTypeOrThrow(string name) => context.Compilation.GetTypeByMetadataName(name) ?? throw new KeyNotFoundException($"Could not locate {name} in the compilation context."); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        private void FindAvaloniaPropertySymbols(Compilation compilation, CancellationToken cancellationToken) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            var namespaceStack = new Stack<INamespaceSymbol>(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            namespaceStack.Push(compilation.GlobalNamespace); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            var types = new List<INamedTypeSymbol>(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            while (namespaceStack.Count > 0) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                var current = namespaceStack.Pop(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                foreach (var type in current.GetTypeMembers()) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    if (DerivesFrom(type, _avaloniaObjectType)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        types.Add(type); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                foreach (var child in current.GetNamespaceMembers()) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    namespaceStack.Push(child); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            var references = new ConcurrentBag<(ISymbol symbol, Func<ISymbolInitializerOperation, IEnumerable<ISymbol>> getInits)>(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            var parallelOptions = new ParallelOptions() { CancellationToken = cancellationToken }; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            Parallel.ForEach(types, parallelOptions, type => 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                foreach (var member in type.GetMembers()) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    switch (member) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        case IFieldSymbol fieldSymbol when IsValidAvaloniaPropertyStorage(fieldSymbol): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            references.Add((fieldSymbol, so => ((IFieldInitializerOperation)so).InitializedFields)); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            break; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        case IPropertySymbol propertySymbol when IsValidAvaloniaPropertyStorage(propertySymbol): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            references.Add((propertySymbol, so => ((IPropertyInitializerOperation)so).InitializedProperties)); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            break; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            // key initializes value 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            var fieldInitializations = new ConcurrentDictionary<ISymbol, ISymbol>(SymbolEqualityComparer.Default); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            Parallel.ForEach(references, parallelOptions, tuple => 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                foreach (var syntaxRef in tuple.symbol.DeclaringSyntaxReferences) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    var node = syntaxRef.GetSyntax(cancellationToken); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    if (!compilation.ContainsSyntaxTree(node.SyntaxTree)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        continue; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    var model = compilation.GetSemanticModel(node.SyntaxTree); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    var operation = node.ChildNodes().Select(n => model.GetOperation(n, cancellationToken)).OfType<ISymbolInitializerOperation>().FirstOrDefault(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    if (operation == null) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        return; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    var operationValue = operation.Value; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    while (operationValue is IConversionOperation conversion) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        operationValue = conversion.Operand; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    switch (operationValue) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        case IInvocationOperation invocation: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            RegisterInitializer_Invocation(tuple.getInits(operation), invocation, tuple.symbol); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            break; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        case IFieldReferenceOperation fieldRef when IsValidAvaloniaPropertyStorage(fieldRef.Field): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            fieldInitializations[fieldRef.Field] = tuple.symbol; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            break; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        case IPropertyReferenceOperation propRef when IsValidAvaloniaPropertyStorage(propRef.Property): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            fieldInitializations[propRef.Property] = tuple.symbol; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            break; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            // we have recorded every Register and AddOwner call. Now follow assignment chains. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            foreach (var root in fieldInitializations.Keys.Intersect(_avaloniaProperyDescriptions.Keys, SymbolEqualityComparer.Default).ToArray()) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                var propertyDescription = _avaloniaProperyDescriptions[root]; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                var owner = propertyDescription.AssignedTo[root]; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                var current = root; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                do 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    var target = fieldInitializations[current]; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    propertyDescription.AssignedTo[target] = owner; // This loop handles simple assignment operations, so do NOT change the owner 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    _avaloniaProperyDescriptions[target] = propertyDescription; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    fieldInitializations.TryGetValue(target, out current); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                while(current != null); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        private void RegisterInitializer_Invocation(IEnumerable<ISymbol> initializedSymbols, IInvocationOperation invocation, ISymbol target) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            try 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                if (invocation.TargetMethod.ReturnType is not INamedTypeSymbol propertyType) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    return; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                if (_avaloniaPropertyRegisterMethods.Contains(invocation.TargetMethod.OriginalDefinition)) // This is a call to one of the AvaloniaProperty.Register* methods 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    if (!invocation.TargetMethod.IsGenericMethod) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        return; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    var typeParamLookup = invocation.TargetMethod.TypeParameters.Select((s, i) => (param: s, index: i)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        .ToDictionary(t => t.param.Name, t => (INamedTypeSymbol)invocation.TargetMethod.TypeArguments[t.index]); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    if (!typeParamLookup.TryGetValue("TOwner", out var ownerType) && // if it's NOT a generic parameter, try to work out the runtime value 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        invocation.TargetMethod.Parameters.FirstOrDefault(p => p.Name == "ownerType") is INamedTypeSymbol ownerTypeParam && 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        invocation.Arguments.FirstOrDefault(a => SymbolEquals(a.Parameter, ownerTypeParam)) is IArgumentOperation argument) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        switch (argument.Value) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            case ITypeOfOperation typeOf: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                                ownerType = (INamedTypeSymbol)typeOf.Type!; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                                break; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    if (ownerType == null || !typeParamLookup.TryGetValue("TValue", out var propertyValueType)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        return; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    foreach (var symbol in initializedSymbols) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        string name; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        switch (invocation.Arguments[0].Value) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            case INameOfOperation nameof when nameof.Argument is IPropertyReferenceOperation propertyReference: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                                name = propertyReference.Property.Name; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                                break; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            case IAssignmentOperation assignment when assignment.ConstantValue is { HasValue: true } stringLiteral: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                                name = (string)stringLiteral.Value!; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                                break; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            default: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                                return; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        var description = _avaloniaProperyDescriptions.GetOrAdd(symbol, s => new AvaloniaPropertyDescription(name, propertyType, propertyValueType)); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        description.Name = name; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        description.AssignedTo[symbol] = ownerType; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        description.OwnerTypes.Add(ownerType); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                else if (_avaloniaPropertyAddOwnerMethods.Contains(invocation.TargetMethod.OriginalDefinition)) // This is a call to one of the AddOwner methods 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    if (invocation.TargetMethod.TypeArguments[0] is not INamedTypeSymbol ownerType) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        return; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    ISymbol sourceSymbol; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    switch (invocation.Instance) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        case IFieldReferenceOperation fieldReference: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            sourceSymbol = fieldReference.Field; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            break; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        case IPropertyReferenceOperation propertyReference: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            sourceSymbol = propertyReference.Property; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            break; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        default: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            return; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    var propertyValueType = AvaloniaPropertyType_GetValueType(propertyType); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    foreach (var symbol in initializedSymbols) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        var description = _avaloniaProperyDescriptions.GetOrAdd(symbol, s => 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            string inferredName = target.Name; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            var match = Regex.Match(target.Name, "(?<name>.*)Property$"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            if (match.Success) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                                inferredName = match.Groups["name"].Value; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            return new AvaloniaPropertyDescription(inferredName, (INamedTypeSymbol)invocation.TargetMethod.ReturnType, propertyValueType); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        description.AssignedTo[symbol] = ownerType; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        description.OwnerTypes.Add(ownerType); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            catch (Exception ex) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                throw new AvaloniaAnalysisException($"Failed to register the initializer of '{target}'.", ex); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        private void AnalyzeFieldInitializer(OperationAnalysisContext context) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            var operation = (IFieldInitializerOperation)context.Operation; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            foreach (var field in operation.InitializedFields) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                try 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    if (!_avaloniaProperyDescriptions.TryGetValue(field, out var description)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        continue; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    if (!IsValidAvaloniaPropertyStorage(field)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        context.ReportDiagnostic(Diagnostic.Create(InappropriatePropertyAssignment, field.Locations[0], field)); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    AnalyzeInitializer_Shared(context, field, description); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                catch (Exception ex) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    throw new AvaloniaAnalysisException($"Failed to process initialization of field '{field}'.", ex); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        private void AnalyzePropertyInitializer(OperationAnalysisContext context) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            var operation = (IPropertyInitializerOperation)context.Operation; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            foreach (var property in operation.InitializedProperties) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                try 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    if (!_avaloniaProperyDescriptions.TryGetValue(property, out var description)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        continue; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    if (!IsValidAvaloniaPropertyStorage(property)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        context.ReportDiagnostic(Diagnostic.Create(InappropriatePropertyAssignment, property.Locations[0], property)); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    AnalyzeInitializer_Shared(context, property, description); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                catch (Exception ex) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    throw new AvaloniaAnalysisException($"Failed to process initialization of property '{property}'.", ex); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        private void AnalyzeInitializer_Shared(OperationAnalysisContext context, ISymbol assignmentSymbol, AvaloniaPropertyDescription description) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if (!assignmentSymbol.Name.Contains(description.Name)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                context.ReportDiagnostic(Diagnostic.Create(PropertyNameMismatch, assignmentSymbol.Locations[0], 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    description.Name, assignmentSymbol)); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            try 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                var ownerType = description.AssignedTo[assignmentSymbol]; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                if (!IsAvaloniaPropertyType(description.PropertyType, _attachedPropertyType) && !SymbolEquals(ownerType, assignmentSymbol.ContainingType)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    context.ReportDiagnostic(Diagnostic.Create(OwnerDoesNotMatchOuterType, assignmentSymbol.Locations[0], ownerType)); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            catch (KeyNotFoundException) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                return; // WIP 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                throw new KeyNotFoundException($"Assignment operation for {assignmentSymbol} was not recorded."); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        private void StartPropertySymbolAnalysis(SymbolStartAnalysisContext context) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            var property = (IPropertySymbol)context.Symbol; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            try 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                var avaloniaPropertyDescriptions = GetAvaloniaPropertiesForType(property.ContainingType).ToLookup(d => d.Name); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                var candidateTargetProperties = avaloniaPropertyDescriptions[property.Name].ToImmutableArray(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                switch (candidateTargetProperties.Length) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    case 0: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        return; // does not refer to an AvaloniaProperty 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    case 1: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        candidateTargetProperties[0].PropertyWrappers.Add(property); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        break; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                _clrPropertyToAvaloniaProperties[property] = candidateTargetProperties; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                context.RegisterSymbolEndAction(context => 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    if (candidateTargetProperties.Length > 1) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        var candidateSymbols = candidateTargetProperties.Select(d => d.ClosestAssignmentFor(property.ContainingType)).Where(s => s != null); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        context.ReportDiagnostic(Diagnostic.Create(AmbiguousPropertyName, property.Locations[0], candidateSymbols.SelectMany(s => s!.Locations), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            property.ContainingType, property.Name, $"\n\t{string.Join("\n\t", candidateSymbols)}")); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        return; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    var avaloniaPropertyDescription = candidateTargetProperties[0]; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    var avaloniaPropertyStorage = avaloniaPropertyDescription.ClosestAssignmentFor(property.ContainingType); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    if (avaloniaPropertyStorage == null) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        return; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    context.ReportDiagnostic(Diagnostic.Create(AssociatedAvaloniaProperty, property.Locations[0], new[] { avaloniaPropertyStorage.Locations[0] }, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        avaloniaPropertyDescription.PropertyType.Name, avaloniaPropertyStorage)); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    if (!SymbolEquals(property.Type, avaloniaPropertyDescription.ValueType, includeNullability: true)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        context.ReportDiagnostic(Diagnostic.Create(PropertyTypeMismatch, property.Locations[0], 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            avaloniaPropertyStorage, $"\t\n{string.Join("\t\n", avaloniaPropertyDescription.ValueType, property.Type)}")); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    if (property.DeclaredAccessibility != avaloniaPropertyStorage.DeclaredAccessibility) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        context.ReportDiagnostic(Diagnostic.Create(InconsistentAccessibility, property.Locations[0], "property", avaloniaPropertyStorage)); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    VerifyAccessor(property.GetMethod, "readable", "get"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    if (!IsAvaloniaPropertyType(avaloniaPropertyDescription.PropertyType, _directPropertyType)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        VerifyAccessor(property.SetMethod, "writeable", "set"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    void VerifyAccessor(IMethodSymbol? method, string verb, string methodName) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        if (method == null) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            context.ReportDiagnostic(Diagnostic.Create(MissingAccessor, property.Locations[0], avaloniaPropertyStorage, verb, methodName)); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        else if (method.DeclaredAccessibility != avaloniaPropertyStorage.DeclaredAccessibility && method.DeclaredAccessibility != property.DeclaredAccessibility) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            context.ReportDiagnostic(Diagnostic.Create(InconsistentAccessibility, method.Locations[0], "property accessor", avaloniaPropertyStorage)); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            catch (Exception ex) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                throw new AvaloniaAnalysisException($"Failed to analyse property '{property}'.", ex); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        private void AnalyzePropertyMethods(CodeBlockAnalysisContext context) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if (context.OwningSymbol is not IMethodSymbol { AssociatedSymbol: IPropertySymbol property } method) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                return; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            try 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                if (!_clrPropertyToAvaloniaProperties.TryGetValue(property, out var candidateTargetProperties) ||  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    candidateTargetProperties.Length != 1) // a diagnostic about multiple candidates will have already been reported 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    return; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                var avaloniaPropertyDescription = candidateTargetProperties.Single(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                if (IsAvaloniaPropertyType(avaloniaPropertyDescription.PropertyType, _directPropertyType)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    return; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                if (!SymbolEquals(property.Type, avaloniaPropertyDescription.ValueType)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    return; // a diagnostic about this will have already been reported, and if the cast is implicit then this message would be confusing anyway 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                var bodyNode = context.CodeBlock.ChildNodes().Single(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                var operation = bodyNode.DescendantNodes() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    .Where(n => n.IsKind(SyntaxKind.InvocationExpression)) // this line is specific to C# 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    .Select(n => (IInvocationOperation)context.SemanticModel.GetOperation(n)!) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    .FirstOrDefault(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                var isGetMethod = method.MethodKind == MethodKind.PropertyGet; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                var expectedInvocations = isGetMethod ? _getValueMethods : _setValueMethods; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                if (operation == null || bodyNode.ChildNodes().Count() != 1 || !expectedInvocations.Contains(operation.TargetMethod.OriginalDefinition)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    ReportSideEffects(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    return; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                if (operation.Arguments.Length != 0) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    var argumentValue = operation.Arguments[0].Value; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    if (argumentValue is IConversionOperation conversion) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        argumentValue = conversion.Operand; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    switch (argumentValue) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        case IFieldReferenceOperation fieldRef when avaloniaPropertyDescription.AssignedTo.ContainsKey(fieldRef.Field): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        case IPropertyReferenceOperation propertyRef when avaloniaPropertyDescription.AssignedTo.ContainsKey(propertyRef.Property): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            break; // the argument is a reference to the correct AvaloniaProperty object 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        default: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            ReportSideEffects(argumentValue.Syntax.GetLocation()); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            return; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                if (!isGetMethod && 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    operation.Arguments.Length >= 2 && 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    operation.Arguments[1].Value.Kind != OperationKind.ParameterReference) // passing something other than `value` to SetValue 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    ReportSideEffects(operation.Arguments[1].Syntax.GetLocation()); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                void ReportSideEffects(Location? locationOverride = null) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    var propertySourceName = avaloniaPropertyDescription.ClosestAssignmentFor(method.ContainingType)?.Name ?? "[unknown]"; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    context.ReportDiagnostic(Diagnostic.Create(AccessorSideEffects, locationOverride ?? context.CodeBlock.GetLocation(), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        avaloniaPropertyDescription.Name, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        isGetMethod ? "read" : "written to", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        isGetMethod ? "get" : "set", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        isGetMethod ? $"GetValue({propertySourceName})" : $"SetValue({propertySourceName}, value)")); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            catch (Exception ex) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                throw new AvaloniaAnalysisException($"Failed to process property accessor '{method}'.", ex); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        private INamedTypeSymbol AvaloniaPropertyType_GetValueType(INamedTypeSymbol type) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            var compareType = type.IsGenericType ? type.ConstructUnboundGenericType().OriginalDefinition : type; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if (SymbolEquals(compareType, _styledPropertyType) || SymbolEquals(compareType, _attachedPropertyType)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                return (INamedTypeSymbol)type.TypeArguments[0]; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            else if (SymbolEquals(compareType, _directPropertyType)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                return (INamedTypeSymbol)type.TypeArguments[1]; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            throw new ArgumentException($"{type} is not a recognised AvaloniaProperty ({_styledPropertyType}, {_attachedPropertyType}, {_directPropertyType}).", nameof(type)); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        private ImmutableHashSet<AvaloniaPropertyDescription> GetAvaloniaPropertiesForType(ITypeSymbol type) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            var properties = new List<AvaloniaPropertyDescription>(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            var current = type; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            while (current != null) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                properties.AddRange(current.GetMembers().Intersect(_avaloniaProperyDescriptions.Keys, SymbolEqualityComparer.Default).Select(s => _avaloniaProperyDescriptions[s])); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                current = current.BaseType; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            return properties.ToImmutableHashSet(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+} 
			 |