Support nullable stream binding operator (?^) - #22069
Conversation
Previously {Binding Header^} raised 'Value is null.' when the bound
IObservable<T>/Task was null. Add support for the '?^' operator so
that a null source is propagated as a null value instead of an error.
- BindingExpressionGrammar: parse '?^' and set StreamNode.AcceptsNull
- StreamNode/DynamicPluginStreamNode: handle null source when
acceptsNull is set
- CompiledBindingPathBuilder: add acceptsNull overloads for
StreamTask/StreamObservable
- XamlIlBindingPathHelper: emit acceptsNull argument for compiled
bindings
- Add grammar, reflection and compiled binding tests
|
You can test this PR using the following package version. |
|
Before I review this, are we sure that this is something we want? C# has no equivalent - for example you can't await a null task or observable. C# does have the We also don't support Could we get an example of some use-cases for this feature? (Note that I'm not against this, just trying to understand) |
|
My stream binding requests mostly from the real-world use case with Lingua https://github.com/irihitech/Irihi.Lingua. We discovered this when building ursa demos. This is merged to main branch, you can try to build, check AutoCompleteBox page and navigate away.
This happens when I navigate away from this page, the data context is uninstalled firstly before binding evaluation stops. so it triggers an null error. we don't have |
|
Hmm, OK, I need to investigate that. I'm a little rusty on exactly how the binding system works, but I don't think we should be logging errors when the data context changes. The nullable operator should in theory only be needed when the actual property is null (again, need to re-familiarize myself with it all so I may be mistaken). |
|
I get your point, but maybe skipping null task/observable should just be the default behavior? |
But your observable/task properties don't actually return Assuming that those properties don't return As far as I can see, this is a bug caused by |
C# null-conditional await may be coming in .NET 12. |
Can you point me to where you have these in your code? I couldn't find them in the link you gave previously. |
Yes but the proposal itself says:
Our existing |
Oops it's in ursa demo https://github.com/irihitech/Ursa.Avalonia/blob/main/demo/Ursa.Demo/Controls/DemoSectionView.axaml |
…niaUI#22082) * Add failing test for issue described in AvaloniaUI#22069. AvaloniaUI#22069 (comment) * Add failing test for AvaloniaUI#18949. * Add failing tests for null conditional on attached property. * Short-circuit the binding chain on a null-conditional operator. The null-conditional operator in a binding path was only applied to the node it was attached to: a null source produced a null value which was then passed to the next node in the chain, which raised "Value is null.". C# instead short-circuits the remainder of the expression, so `a?.b.c` evaluates to null when `a` is null. Do the same for binding paths. When a null-conditional node has a null source it now sets its own value and that of all subsequent nodes to null, and the binding publishes null rather than an error. Publishing null rather than UnsetValue means TargetNullValue still applies. Attached properties in reflection bindings never honoured the operator at all: the grammar parses `?.(Foo.Bar)` and sets AttachedPropertyNameNode.AcceptsNull, but ExpressionNodeFactory discarded the flag and AvaloniaPropertyAccessorNode had no way to accept it. Pass it through. Compiled bindings were unaffected as they route attached properties through PropertyAccessorNode. Fixes AvaloniaUI#18949. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Atuuu4jtp14QXXoCzkp2A6 * Add TargetNullValue tests for short-circuited chains. Covers `A?.B.C` where A is null, for both CLR and Avalonia properties. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014aahFHMmgxZtZ5EH3H5Xcc --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
|
@rabbitism I've taken a look at the code, and honestly - I don't think this serves as a good argument for adding By exposing a nullable But the main issue with the code is that it's mixing two different notification systems:
tagChanges
.Select(tag => tag switch
{
DemoSectionTag.Function => LanguageManager.Instance.DemoSection_Tag_Function,
DemoSectionTag.Style => LanguageManager.Instance.DemoSection_Tag_Style,
DemoSectionTag.Others => LanguageManager.Instance.DemoSection_Tag_Others,
_ => Observable.Return<string?>(null),
})
.Switch();
You'd have a problem trying to do this even with C#: Rx won't work all the time because the property is nullable, and What I'd say is: |
…niaUI#22082) * Add failing test for issue described in AvaloniaUI#22069. AvaloniaUI#22069 (comment) * Add failing test for AvaloniaUI#18949. * Add failing tests for null conditional on attached property. * Short-circuit the binding chain on a null-conditional operator. The null-conditional operator in a binding path was only applied to the node it was attached to: a null source produced a null value which was then passed to the next node in the chain, which raised "Value is null.". C# instead short-circuits the remainder of the expression, so `a?.b.c` evaluates to null when `a` is null. Do the same for binding paths. When a null-conditional node has a null source it now sets its own value and that of all subsequent nodes to null, and the binding publishes null rather than an error. Publishing null rather than UnsetValue means TargetNullValue still applies. Attached properties in reflection bindings never honoured the operator at all: the grammar parses `?.(Foo.Bar)` and sets AttachedPropertyNameNode.AcceptsNull, but ExpressionNodeFactory discarded the flag and AvaloniaPropertyAccessorNode had no way to accept it. Pass it through. Compiled bindings were unaffected as they route attached properties through PropertyAccessorNode. Fixes AvaloniaUI#18949. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Atuuu4jtp14QXXoCzkp2A6 * Add TargetNullValue tests for short-circuited chains. Covers `A?.B.C` where A is null, for both CLR and Avalonia properties. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014aahFHMmgxZtZ5EH3H5Xcc --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
|
I'm in full agreement with @grokys here: this is an anti-pattern, and we should avoid adding a feature that enables it - without blocking it. Especially since the "main" case is now fixed with Thus, I'm going to close this PR. |

Previously {Binding Header^} raised 'Value is null.' when the bound IObservable/Task was null. Add support for the '?^' operator so that a null source is propagated as a null value instead of an error.
What does the pull request do?
What is the current behavior?
What is the updated/expected behavior with this PR?
How was the solution implemented (if it's not obvious)?
Checklist
Breaking changes
Obsoletions / Deprecations
Fixed issues