Skip to content

Allow casting to a nested type in a binding path - #22173

Merged
grokys merged 1 commit into
AvaloniaUI:mainfrom
Evan260:fix-nested-type-cast-binding
Sep 9, 2026
Merged

grokys merged 1 commit into
AvaloniaUI:mainfrom
Evan260:fix-nested-type-cast-binding

Conversation

@Evan260

@Evan260 Evan260 commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

What does the pull request do?

Lets a binding path cast to a nested type, e.g. {Binding ((local:Outer+Nested)DataContext).SomeProperty}.

What is the current behavior?

It fails to compile:

Internal compiler error: Expected ')'. (AvaloniaXamlIlBindingPathParser)

+ already works for nested types elsewhere in XAML, for instance in x:DataType='{x:Type local:TestDataContext+NestedGeneric, ...}', which has a test in this repo. It just wasn't accepted in a binding path.

What is the updated/expected behavior with this PR?

The cast resolves and the binding works, for both compiled and reflection bindings.

How was the solution implemented (if it's not obvious)?

The binding path parser builds type names with ParseIdentifier, which doesn't allow +, so parsing stopped at the + and the parser then expected a ).

Added ParseTypeIdentifier, which is the same thing but also accepts +, and used it only where a type name is parsed. Ordinary identifiers are unchanged, so Foo+Bar in a normal property path is still invalid.

Nothing else needed changing. The compiled path passes the name to TypeReferenceResolver, and the reflection path to Assembly.GetType, and both already understand Outer+Nested.

One thing worth flagging

The issue also mentions . as a separator. I haven't allowed that, because . after a type name is how attached properties are written, as in (ns:Owner.Property), so accepting it there would break them. + is the CLR nested type separator anyway. Happy to revisit if you disagree.

Checklist

Four tests: two at the parser level and one each for compiled and reflection bindings. Two of them cover the non-nested case so the existing behaviour stays covered.

Breaking changes

None. This only accepts input that previously failed to compile.

Fixed issues

Fixes #22047

The binding path parser built type names with ParseIdentifier, which doesn't
accept '+', so a cast like ((local:Outer+Nested)DataContext) stopped parsing at
the '+' and then failed expecting a ')'. Parse type names with a variant that
accepts '+'. Ordinary identifiers are unchanged.
@avaloniaui-bot

Copy link
Copy Markdown

You can test this PR using the following package version. 12.2.999-cibuild0069598-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@MrJul MrJul added bug area-bindings backport-candidate-12.1.x Consider this PR for backporting to 12.1 branch labels Sep 8, 2026
@grokys grokys self-assigned this Sep 9, 2026

@grokys grokys left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Thanks.

{
if (IsValidIdentifierStart(r.Peek))
{
return r.TakeWhile(c => IsValidIdentifierChar(c) || c == '+');

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this will parse types such as Foo+ or Foo++Bar as valid. The types will then fail at the lookup stage so not a huge issue though, doesn't need to be fixed here.

/// Parses an identifier that may name a nested type, e.g. "Outer+Inner".
/// </summary>
public static ReadOnlySpan<char> ParseTypeIdentifier(this
#if NET7SDK

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is vestigial at this point, but it matches other methods in this file, so we can leave it and remove it later.

@grokys
grokys added this pull request to the merge queue Sep 9, 2026
Merged via the queue into AvaloniaUI:main with commit 8057428 Sep 9, 2026
11 checks passed
@Evan260
Evan260 deleted the fix-nested-type-cast-binding branch September 9, 2026 17:04
MrJul pushed a commit to MrJul/Avalonia that referenced this pull request Sep 22, 2026
The binding path parser built type names with ParseIdentifier, which doesn't
accept '+', so a cast like ((local:Outer+Nested)DataContext) stopped parsing at
the '+' and then failed expecting a ')'. Parse type names with a variant that
accepts '+'. Ordinary identifiers are unchanged.
@MrJul MrJul added backported-12.1.x and removed backport-candidate-12.1.x Consider this PR for backporting to 12.1 branch labels Sep 22, 2026
grokys pushed a commit to Evan260/Avalonia that referenced this pull request Oct 7, 2026
The binding path parser built type names with ParseIdentifier, which doesn't
accept '+', so a cast like ((local:Outer+Nested)DataContext) stopped parsing at
the '+' and then failed expecting a ')'. Parse type names with a variant that
accepts '+'. Ordinary identifiers are unchanged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Error when casting to nested type in binding syntax

4 participants