diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/Content/Uno.UI.SourceGenerators.props b/src/SourceGenerators/Uno.UI.SourceGenerators/Content/Uno.UI.SourceGenerators.props index c999051962a4..28f271772675 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/Content/Uno.UI.SourceGenerators.props +++ b/src/SourceGenerators/Uno.UI.SourceGenerators/Content/Uno.UI.SourceGenerators.props @@ -211,6 +211,7 @@ + diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlFileGenerator.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlFileGenerator.cs index c639cb0aefb2..265fd8df4824 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlFileGenerator.cs +++ b/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlFileGenerator.cs @@ -3582,17 +3582,18 @@ private void GenerateInlineEvent(string? closureName, IIndentedStringBuilder wri // If a binding is inside a DataTemplate, the binding root in the case of an x:Bind is // the DataContext, not the control's instance. var template = IsMemberInsideFrameworkTemplate(member.Owner); - var eventSource = (template.isInside, _xClassName) switch + var targetInstance = (template.isInside, _xClassName) switch { (false, _) => "this", (_, not null) => CurrentResourceOwnerName, _ => null }; - if (eventSource is null) + if (targetInstance is null) { GenerateError(writer, $"Unable to use event {member.Member.Name} without a backing class (use x:Class)"); return; } + EnsureXClassName(); var parentApply = (writer as XamlLazyApplyBlockIIndentedStringBuilder)?.MethodName; var parametersWithType = delegateSymbol @@ -3615,89 +3616,27 @@ private void GenerateInlineEvent(string? closureName, IIndentedStringBuilder wri CurrentScope.XBindExpressions.Add(bind); - var eventTarget = XBindExpressionParser.RestoreSinglePath(bind.Members.First().Value?.ToString()); - - if (eventTarget == null) + var path = XBindExpressionParser.RestoreSinglePath(bind.Members.First().Value?.ToString()); + if (path is null) { throw new InvalidOperationException("x:Bind event path cannot by empty"); } - var parts = eventTarget.Split('.').ToList(); - var isStaticTarget = parts.FirstOrDefault()?.Contains(":") ?? false; - - eventTarget = RewriteNamespaces(eventTarget); - - // x:Bind to second-level method generates invalid code - // sanitizing member.Member.Name so that "ViewModel.SearchBreeds" becomes "ViewModel_SearchBreeds" - var sanitizedEventTarget = SanitizeResourceName(eventTarget); - - (string target, string weakReference, IMethodSymbol targetMethod) buildTargetContext() + INamedTypeSymbol GetTargetType() { - IMethodSymbol FindTargetMethodSymbol(INamedTypeSymbol? sourceType) - { - if (eventTarget.Contains(".")) - { - ITypeSymbol? currentType = sourceType; - - if (isStaticTarget) - { - // First part is a type for static method binding and should - // overide the original source type - currentType = GetType(RewriteNamespaces(parts[0])); - parts.RemoveAt(0); - } - - for (var i = 0; i < parts.Count - 1; i++) - { - var next = currentType.GetAllMembersWithName(RewriteNamespaces(parts[i])).FirstOrDefault(); - - currentType = next switch - { - IFieldSymbol fs => fs.Type, - IPropertySymbol ps => ps.Type, - null => throw new InvalidOperationException($"Unable to find member {parts[i]} on type {currentType}"), - _ => throw new InvalidOperationException($"The field {next.Name} is not supported for x:Bind event binding") - }; - } - - var method = currentType?.GetFirstMethodWithName(parts.Last(), includeBaseTypes: true) - ?? throw new InvalidOperationException($"Failed to find {parts.Last()} on {currentType}"); - - return method; - } - else - { - return sourceType?.GetFirstMethodWithName(eventTarget, includeBaseTypes: true) - ?? throw new InvalidOperationException($"Failed to find {eventTarget} on {sourceType}"); - } - } - if (template.isInside) { var dataTypeObject = FindMember(template.xamlObject!, "DataType", XamlConstants.XamlXmlNamespace); if (dataTypeObject?.Value == null) { - throw new Exception($"Unable to find x:DataType in enclosing DataTemplate for x:Bind event"); + throw new Exception("Unable to find x:DataType in enclosing DataTemplate for x:Bind event"); } - var dataTypeSymbol = GetType(dataTypeObject.Value.ToString() ?? ""); - - return ( - $"({member.Member.Name}_{sanitizedEventTarget}_That.Target as {XamlConstants.Types.FrameworkElement})?.DataContext as {dataTypeSymbol.GetFullyQualifiedTypeIncludingGlobal()}", - - // Use of __rootInstance is required to get the top-level DataContext, as it may be changed - // in the current visual tree by the user. - $"(__rootInstance as global::Uno.UI.DataBinding.IWeakReferenceProvider).WeakReference", - FindTargetMethodSymbol(dataTypeSymbol) - ); + return GetType(dataTypeObject.Value.ToString() ?? ""); } - else if (_xClassName?.Symbol != null) + else if (_xClassName?.Symbol is not null) { - return ( - $"{member.Member.Name}_{sanitizedEventTarget}_That.Target as {_xClassName}", - $"({eventSource} as global::Uno.UI.DataBinding.IWeakReferenceProvider).WeakReference", - FindTargetMethodSymbol(_xClassName.Symbol) - ); + return _xClassName.Symbol; } else { @@ -3705,10 +3644,20 @@ IMethodSymbol FindTargetMethodSymbol(INamedTypeSymbol? sourceType) } } - var targetContext = buildTargetContext(); - var targetMethodHasParameters = targetContext.targetMethod?.Parameters.Any() ?? false; + var targetType = GetTargetType(); // The type of the target object onto which the x:Bind path should be resolved + var targetInstanceWeakRef = template.isInside + // Use of __rootInstance is required to get the top-level DataContext, as it may be changed in the current visual tree by the user. + ? "(__rootInstance as global::Uno.UI.DataBinding.IWeakReferenceProvider).WeakReference" + : $"({targetInstance} as global::Uno.UI.DataBinding.IWeakReferenceProvider).WeakReference"; - EnsureXClassName(); + var method = ResolveXBindMethod(targetType, path); + var invokeTarget = (method.isStatic, template.isInside) switch + { + (true, _) => method.declaringType.GetFullyQualifiedTypeIncludingGlobal(), // If the method is static, the target of the method is the type itself + (_, true) => $"((target.Target as {XamlConstants.Types.FrameworkElement})?.DataContext as {targetType.GetFullyQualifiedTypeIncludingGlobal()})", + _ => $"(target.Target as {targetType.GetFullyQualifiedTypeIncludingGlobal()})" + }; + var invoke = $"{invokeTarget}?.{path}({(method.symbol.Parameters.Any() ? parameters.JoinBy(", ") : "")});"; var handler = RegisterChildSubclass( $"{parentApply}_{member.Member.Name}_Handler", @@ -3717,7 +3666,7 @@ public class {{name}}(global::Uno.UI.DataBinding.ManagedWeakReference target) { public void Invoke({{parametersWithType.JoinBy(", ")}}) { - (target.Target as {{_xClassName}})?.{{eventTarget}}({{(targetMethodHasParameters ? parameters.JoinBy(", ") : "")}}); + {{invoke}} } } """)); @@ -3736,15 +3685,13 @@ public void Invoke({{parametersWithType.JoinBy(", ")}}) return; } - {{componentDefinition.MemberName}}.{{member.Member.Name}} += new {{handler}}({{targetContext.weakReference}}).Invoke; + {{componentDefinition.MemberName}}.{{member.Member.Name}} += new {{handler}}({{targetInstanceWeakRef}}).Invoke; __is{{name}}d = true; } """)); } else { - EnsureXClassName(); - // // Generate a sub-class that uses a weak ref, so the owner is not being held onto by the delegate. // We can use the WeakReferenceProvider to get a self reference to avoid adding the cost of the @@ -3762,11 +3709,54 @@ public void Invoke({{parametersWithType.JoinBy(", ")}}) } } """)); - writer.AppendLineIndented($"var {member.Member.Name}_Handler = new {subClass}(({eventSource} as global::Uno.UI.DataBinding.IWeakReferenceProvider).WeakReference);"); + writer.AppendLineIndented($"var {member.Member.Name}_Handler = new {subClass}(({targetInstance} as global::Uno.UI.DataBinding.IWeakReferenceProvider).WeakReference);"); writer.AppendLineIndented($"/* second level */ {closureName}.{member.Member.Name} += {member.Member.Name}_Handler.Invoke;"); } } + private (ITypeSymbol declaringType, IMethodSymbol symbol, bool isStatic) ResolveXBindMethod(INamedTypeSymbol contextType, string path) + { + if (path.Contains(".")) + { + ITypeSymbol currentType = contextType; + + var parts = path.Split('.').ToList(); + var isStatic = parts.FirstOrDefault()?.Contains(":") ?? false; + if (isStatic) + { + // First part is a type for static method binding and should + // overide the original source type + currentType = contextType = GetType(RewriteNamespaces(parts[0])); + parts.RemoveAt(0); + } + + for (var i = 0; i < parts.Count - 1; i++) + { + var next = currentType.GetAllMembersWithName(RewriteNamespaces(parts[i])).FirstOrDefault(); + + currentType = next switch + { + IFieldSymbol fs => fs.Type, + IPropertySymbol ps => ps.Type, + null => throw new InvalidOperationException($"Unable to find member {parts[i]} on type {currentType}"), + _ => throw new InvalidOperationException($"The field {next.Name} is not supported for x:Bind event binding") + }; + } + + var method = currentType.GetFirstMethodWithName(parts.Last(), includeBaseTypes: true) + ?? throw new InvalidOperationException($"Failed to find {parts.Last()} on {currentType}"); + + return (contextType, method, isStatic); + } + else + { + var method = contextType?.GetFirstMethodWithName(path, includeBaseTypes: true) + ?? throw new InvalidOperationException($"Failed to find {path} on {contextType}"); + + return (contextType, method, false); + } + } + /// /// Build localized properties which have not been set in the xaml. ///