Skip to content

Commit 0d366ea

Browse files
committed
fix(hr): Fix invalid x:Bind event's target resolution
1 parent a312e3f commit 0d366ea

File tree

2 files changed

+70
-79
lines changed

2 files changed

+70
-79
lines changed

src/SourceGenerators/Uno.UI.SourceGenerators/Content/Uno.UI.SourceGenerators.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@
211211
<XamlGeneratorAnalyzerSuppressions Include="csharp-105 // Ignore duplicate namespaces" />
212212
<XamlGeneratorAnalyzerSuppressions Include="csharp-1591 // Ignore missing XML comment warnings" />
213213
<XamlGeneratorAnalyzerSuppressions Include="csharp-CS8669 // Ignore annotation for nullable reference types" />
214+
<XamlGeneratorAnalyzerSuppressions Include="csharp-CS9113 // Parameter is unread" />
214215
</ItemGroup>
215216

216217
<!--Default UI automation member mappings-->

src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlFileGenerator.cs

Lines changed: 69 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -3582,17 +3582,18 @@ private void GenerateInlineEvent(string? closureName, IIndentedStringBuilder wri
35823582
// If a binding is inside a DataTemplate, the binding root in the case of an x:Bind is
35833583
// the DataContext, not the control's instance.
35843584
var template = IsMemberInsideFrameworkTemplate(member.Owner);
3585-
var eventSource = (template.isInside, _xClassName) switch
3585+
var targetInstance = (template.isInside, _xClassName) switch
35863586
{
35873587
(false, _) => "this",
35883588
(_, not null) => CurrentResourceOwnerName,
35893589
_ => null
35903590
};
3591-
if (eventSource is null)
3591+
if (targetInstance is null)
35923592
{
35933593
GenerateError(writer, $"Unable to use event {member.Member.Name} without a backing class (use x:Class)");
35943594
return;
35953595
}
3596+
EnsureXClassName();
35963597

35973598
var parentApply = (writer as XamlLazyApplyBlockIIndentedStringBuilder)?.MethodName;
35983599
var parametersWithType = delegateSymbol
@@ -3615,100 +3616,48 @@ private void GenerateInlineEvent(string? closureName, IIndentedStringBuilder wri
36153616

36163617
CurrentScope.XBindExpressions.Add(bind);
36173618

3618-
var eventTarget = XBindExpressionParser.RestoreSinglePath(bind.Members.First().Value?.ToString());
3619-
3620-
if (eventTarget == null)
3619+
var path = XBindExpressionParser.RestoreSinglePath(bind.Members.First().Value?.ToString());
3620+
if (path is null)
36213621
{
36223622
throw new InvalidOperationException("x:Bind event path cannot by empty");
36233623
}
36243624

3625-
var parts = eventTarget.Split('.').ToList();
3626-
var isStaticTarget = parts.FirstOrDefault()?.Contains(":") ?? false;
3627-
3628-
eventTarget = RewriteNamespaces(eventTarget);
3629-
3630-
// x:Bind to second-level method generates invalid code
3631-
// sanitizing member.Member.Name so that "ViewModel.SearchBreeds" becomes "ViewModel_SearchBreeds"
3632-
var sanitizedEventTarget = SanitizeResourceName(eventTarget);
3633-
3634-
(string target, string weakReference, IMethodSymbol targetMethod) buildTargetContext()
3625+
INamedTypeSymbol GetTargetType()
36353626
{
3636-
IMethodSymbol FindTargetMethodSymbol(INamedTypeSymbol? sourceType)
3637-
{
3638-
if (eventTarget.Contains("."))
3639-
{
3640-
ITypeSymbol? currentType = sourceType;
3641-
3642-
if (isStaticTarget)
3643-
{
3644-
// First part is a type for static method binding and should
3645-
// overide the original source type
3646-
currentType = GetType(RewriteNamespaces(parts[0]));
3647-
parts.RemoveAt(0);
3648-
}
3649-
3650-
for (var i = 0; i < parts.Count - 1; i++)
3651-
{
3652-
var next = currentType.GetAllMembersWithName(RewriteNamespaces(parts[i])).FirstOrDefault();
3653-
3654-
currentType = next switch
3655-
{
3656-
IFieldSymbol fs => fs.Type,
3657-
IPropertySymbol ps => ps.Type,
3658-
null => throw new InvalidOperationException($"Unable to find member {parts[i]} on type {currentType}"),
3659-
_ => throw new InvalidOperationException($"The field {next.Name} is not supported for x:Bind event binding")
3660-
};
3661-
}
3662-
3663-
var method = currentType?.GetFirstMethodWithName(parts.Last(), includeBaseTypes: true)
3664-
?? throw new InvalidOperationException($"Failed to find {parts.Last()} on {currentType}");
3665-
3666-
return method;
3667-
}
3668-
else
3669-
{
3670-
return sourceType?.GetFirstMethodWithName(eventTarget, includeBaseTypes: true)
3671-
?? throw new InvalidOperationException($"Failed to find {eventTarget} on {sourceType}");
3672-
}
3673-
}
3674-
36753627
if (template.isInside)
36763628
{
36773629
var dataTypeObject = FindMember(template.xamlObject!, "DataType", XamlConstants.XamlXmlNamespace);
36783630
if (dataTypeObject?.Value == null)
36793631
{
3680-
throw new Exception($"Unable to find x:DataType in enclosing DataTemplate for x:Bind event");
3632+
throw new Exception("Unable to find x:DataType in enclosing DataTemplate for x:Bind event");
36813633
}
36823634

3683-
var dataTypeSymbol = GetType(dataTypeObject.Value.ToString() ?? "");
3684-
3685-
return (
3686-
$"({member.Member.Name}_{sanitizedEventTarget}_That.Target as {XamlConstants.Types.FrameworkElement})?.DataContext as {dataTypeSymbol.GetFullyQualifiedTypeIncludingGlobal()}",
3687-
3688-
// Use of __rootInstance is required to get the top-level DataContext, as it may be changed
3689-
// in the current visual tree by the user.
3690-
$"(__rootInstance as global::Uno.UI.DataBinding.IWeakReferenceProvider).WeakReference",
3691-
FindTargetMethodSymbol(dataTypeSymbol)
3692-
);
3635+
return GetType(dataTypeObject.Value.ToString() ?? "");
36933636
}
3694-
else if (_xClassName?.Symbol != null)
3637+
else if (_xClassName?.Symbol is not null)
36953638
{
3696-
return (
3697-
$"{member.Member.Name}_{sanitizedEventTarget}_That.Target as {_xClassName}",
3698-
$"({eventSource} as global::Uno.UI.DataBinding.IWeakReferenceProvider).WeakReference",
3699-
FindTargetMethodSymbol(_xClassName.Symbol)
3700-
);
3639+
return _xClassName.Symbol;
37013640
}
37023641
else
37033642
{
37043643
throw new Exception($"Unable to find the type {_xClassName?.Namespace}.{_xClassName?.ClassName}");
37053644
}
37063645
}
37073646

3708-
var targetContext = buildTargetContext();
3709-
var targetMethodHasParameters = targetContext.targetMethod?.Parameters.Any() ?? false;
3647+
var targetType = GetTargetType(); // The type of the target object onto which the x:Bind path should be resolved
3648+
var targetInstanceWeakRef = template.isInside
3649+
// Use of __rootInstance is required to get the top-level DataContext, as it may be changed in the current visual tree by the user.
3650+
? "(__rootInstance as global::Uno.UI.DataBinding.IWeakReferenceProvider).WeakReference"
3651+
: $"({targetInstance} as global::Uno.UI.DataBinding.IWeakReferenceProvider).WeakReference";
37103652

3711-
EnsureXClassName();
3653+
var method = ResolveXBindMethod(targetType, path);
3654+
var invokeTarget = (method.isStatic, template.isInside) switch
3655+
{
3656+
(true, _) => method.declaringType.GetFullyQualifiedTypeIncludingGlobal(), // If the method is static, the target of the method is the type itself
3657+
(_, true) => $"((target.Target as {XamlConstants.Types.FrameworkElement})?.DataContext as {targetType.GetFullyQualifiedTypeIncludingGlobal()})",
3658+
_ => $"(target.Target as {targetType.GetFullyQualifiedTypeIncludingGlobal()})"
3659+
};
3660+
var invoke = $"{invokeTarget}?.{path}({(method.symbol.Parameters.Any() ? parameters.JoinBy(", ") : "")});";
37123661

37133662
var handler = RegisterChildSubclass(
37143663
$"{parentApply}_{member.Member.Name}_Handler",
@@ -3717,7 +3666,7 @@ public class {{name}}(global::Uno.UI.DataBinding.ManagedWeakReference target)
37173666
{
37183667
public void Invoke({{parametersWithType.JoinBy(", ")}})
37193668
{
3720-
(target.Target as {{_xClassName}})?.{{eventTarget}}({{(targetMethodHasParameters ? parameters.JoinBy(", ") : "")}});
3669+
{{invoke}}
37213670
}
37223671
}
37233672
"""));
@@ -3736,15 +3685,13 @@ public void Invoke({{parametersWithType.JoinBy(", ")}})
37363685
return;
37373686
}
37383687
3739-
{{componentDefinition.MemberName}}.{{member.Member.Name}} += new {{handler}}({{targetContext.weakReference}}).Invoke;
3688+
{{componentDefinition.MemberName}}.{{member.Member.Name}} += new {{handler}}({{targetInstanceWeakRef}}).Invoke;
37403689
__is{{name}}d = true;
37413690
}
37423691
"""));
37433692
}
37443693
else
37453694
{
3746-
EnsureXClassName();
3747-
37483695
//
37493696
// Generate a sub-class that uses a weak ref, so the owner is not being held onto by the delegate.
37503697
// 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(", ")}})
37623709
}
37633710
}
37643711
"""));
3765-
writer.AppendLineIndented($"var {member.Member.Name}_Handler = new {subClass}(({eventSource} as global::Uno.UI.DataBinding.IWeakReferenceProvider).WeakReference);");
3712+
writer.AppendLineIndented($"var {member.Member.Name}_Handler = new {subClass}(({targetInstance} as global::Uno.UI.DataBinding.IWeakReferenceProvider).WeakReference);");
37663713
writer.AppendLineIndented($"/* second level */ {closureName}.{member.Member.Name} += {member.Member.Name}_Handler.Invoke;");
37673714
}
37683715
}
37693716

3717+
private (ITypeSymbol declaringType, IMethodSymbol symbol, bool isStatic) ResolveXBindMethod(INamedTypeSymbol contextType, string path)
3718+
{
3719+
if (path.Contains("."))
3720+
{
3721+
ITypeSymbol currentType = contextType;
3722+
3723+
var parts = path.Split('.').ToList();
3724+
var isStatic = parts.FirstOrDefault()?.Contains(":") ?? false;
3725+
if (isStatic)
3726+
{
3727+
// First part is a type for static method binding and should
3728+
// overide the original source type
3729+
currentType = contextType = GetType(RewriteNamespaces(parts[0]));
3730+
parts.RemoveAt(0);
3731+
}
3732+
3733+
for (var i = 0; i < parts.Count - 1; i++)
3734+
{
3735+
var next = currentType.GetAllMembersWithName(RewriteNamespaces(parts[i])).FirstOrDefault();
3736+
3737+
currentType = next switch
3738+
{
3739+
IFieldSymbol fs => fs.Type,
3740+
IPropertySymbol ps => ps.Type,
3741+
null => throw new InvalidOperationException($"Unable to find member {parts[i]} on type {currentType}"),
3742+
_ => throw new InvalidOperationException($"The field {next.Name} is not supported for x:Bind event binding")
3743+
};
3744+
}
3745+
3746+
var method = currentType.GetFirstMethodWithName(parts.Last(), includeBaseTypes: true)
3747+
?? throw new InvalidOperationException($"Failed to find {parts.Last()} on {currentType}");
3748+
3749+
return (contextType, method, isStatic);
3750+
}
3751+
else
3752+
{
3753+
var method = contextType?.GetFirstMethodWithName(path, includeBaseTypes: true)
3754+
?? throw new InvalidOperationException($"Failed to find {path} on {contextType}");
3755+
3756+
return (contextType, method, false);
3757+
}
3758+
}
3759+
37703760
/// <summary>
37713761
/// Build localized properties which have not been set in the xaml.
37723762
/// </summary>

0 commit comments

Comments
 (0)