Skip to content

Commit fa0f614

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

File tree

2 files changed

+75
-79
lines changed

2 files changed

+75
-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: 74 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,47 @@ 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 onto which the method should be invoked is the declaringType itself
3657+
(_, true) => $"((target.Target as {XamlConstants.Types.FrameworkElement})?.DataContext as {targetType.GetFullyQualifiedTypeIncludingGlobal()})?",
3658+
_ => $"(target.Target as {targetType.GetFullyQualifiedTypeIncludingGlobal()})?"
3659+
};
37123660

37133661
var handler = RegisterChildSubclass(
37143662
$"{parentApply}_{member.Member.Name}_Handler",
@@ -3717,7 +3665,7 @@ public class {{name}}(global::Uno.UI.DataBinding.ManagedWeakReference target)
37173665
{
37183666
public void Invoke({{parametersWithType.JoinBy(", ")}})
37193667
{
3720-
(target.Target as {{_xClassName}})?.{{eventTarget}}({{(targetMethodHasParameters ? parameters.JoinBy(", ") : "")}});
3668+
{{invokeTarget}}.{{method.path}}({{(method.symbol.Parameters.Any() ? parameters.JoinBy(", ") : "")}});
37213669
}
37223670
}
37233671
"""));
@@ -3736,15 +3684,13 @@ public void Invoke({{parametersWithType.JoinBy(", ")}})
37363684
return;
37373685
}
37383686
3739-
{{componentDefinition.MemberName}}.{{member.Member.Name}} += new {{handler}}({{targetContext.weakReference}}).Invoke;
3687+
{{componentDefinition.MemberName}}.{{member.Member.Name}} += new {{handler}}({{targetInstanceWeakRef}}).Invoke;
37403688
__is{{name}}d = true;
37413689
}
37423690
"""));
37433691
}
37443692
else
37453693
{
3746-
EnsureXClassName();
3747-
37483694
//
37493695
// Generate a sub-class that uses a weak ref, so the owner is not being held onto by the delegate.
37503696
// We can use the WeakReferenceProvider to get a self reference to avoid adding the cost of the
@@ -3762,11 +3708,60 @@ public void Invoke({{parametersWithType.JoinBy(", ")}})
37623708
}
37633709
}
37643710
"""));
3765-
writer.AppendLineIndented($"var {member.Member.Name}_Handler = new {subClass}(({eventSource} as global::Uno.UI.DataBinding.IWeakReferenceProvider).WeakReference);");
3711+
writer.AppendLineIndented($"var {member.Member.Name}_Handler = new {subClass}(({targetInstance} as global::Uno.UI.DataBinding.IWeakReferenceProvider).WeakReference);");
37663712
writer.AppendLineIndented($"/* second level */ {closureName}.{member.Member.Name} += {member.Member.Name}_Handler.Invoke;");
37673713
}
37683714
}
37693715

3716+
private (string path, ITypeSymbol declaringType, IMethodSymbol symbol, bool isStatic) ResolveXBindMethod(INamedTypeSymbol contextType, string path)
3717+
{
3718+
if (path.Contains("."))
3719+
{
3720+
var rewrittenPath = new StringBuilder();
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 override the original source type
3728+
currentType = contextType = GetType(RewriteNamespaces(parts[0]));
3729+
parts.RemoveAt(0);
3730+
}
3731+
3732+
for (var i = 0; i < parts.Count - 1; i++)
3733+
{
3734+
var memberName = RewriteNamespaces(parts[i]);
3735+
var next = currentType.GetAllMembersWithName(memberName).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+
rewrittenPath.Append(memberName);
3746+
rewrittenPath.Append("?.");
3747+
}
3748+
3749+
var method = currentType.GetFirstMethodWithName(parts.Last(), includeBaseTypes: true)
3750+
?? throw new InvalidOperationException($"Failed to find {parts.Last()} on {currentType}");
3751+
3752+
rewrittenPath.Append(method.Name);
3753+
3754+
return (rewrittenPath.ToString(), contextType, method, isStatic);
3755+
}
3756+
else
3757+
{
3758+
var method = contextType.GetFirstMethodWithName(path, includeBaseTypes: true)
3759+
?? throw new InvalidOperationException($"Failed to find {path} on {contextType}");
3760+
3761+
return (method.Name, contextType, method, false);
3762+
}
3763+
}
3764+
37703765
/// <summary>
37713766
/// Build localized properties which have not been set in the xaml.
37723767
/// </summary>

0 commit comments

Comments
 (0)