* Reminder for future me.
* Move compiled bindings to Avalonia base.
* Update suppressions.
* Tweaked `BindingExpressionVisitor`.
And documented public (internal) API.
* Fix ncrunch config.
* Add comprehensive unit tests for BindingExpressionVisitor.
Tests cover all supported features including property access, indexers,
AvaloniaProperty access, logical NOT, stream bindings, and type operators.
Also includes tests for unsupported operations that should throw exceptions.
Discovered bug: IsAssignableFrom check at line 139 is backwards, causing
upcasts to incorrectly throw and downcasts to be incorrectly ignored.
Bug is documented with skipped tests showing expected behavior and passing
tests documenting current broken behavior.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* Fix backwards IsAssignableFrom check in BindingExpressionVisitor.
Fixed the inheritance cast check which was inverted, causing:
- Upcasts (derived→base) to incorrectly throw exceptions
- Downcasts (base→derived) to be incorrectly ignored
Changed line 139 from:
node.Operand.Type.IsAssignableFrom(node.Type)
to:
node.Type.IsAssignableFrom(node.Operand.Type)
This correctly identifies safe upcasts (which are ignored) vs unsafe
downcasts (which throw exceptions).
Updated tests to remove skip attributes and removed the temporary tests
that documented the broken behavior. All 33 tests now pass.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* Allow downcasts and all reference type casts in binding expressions.
Changed the cast handling to allow both upcasts and downcasts for reference
types. This makes casts actually useful in binding expressions while still
rejecting value type conversions that would require actual conversion logic.
The logic now checks:
- Both types must be reference types (not value types)
- One type must be assignable to/from the other (either direction)
This allows practical scenarios like:
- Upcasts: (BaseClass)derived
- Downcasts: (DerivedClass)baseInstance
- Casting through object: (TargetType)(object)source
The binding system will gracefully handle any runtime type mismatches.
Updated tests to verify downcasts are allowed and added test for casting
through object pattern.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* Add clarifying test for cast transparency in binding expressions.
Added test showing that casts are transparent in property chains.
For example, ((TestClass)x).Property produces just one node for the
property access - the cast doesn't create additional nodes.
This clarifies that empty nodes for (TestClass)x is correct behavior:
- Empty nodes = bind to source directly
- The cast is just a type annotation, transparent to the binding path
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* Fix: Casts should create ReflectionTypeCastNode, not be transparent.
Casts were incorrectly being treated as transparent (not creating nodes),
but CompiledBindingPath uses TypeCastPathElement which creates FuncTransformNode.
For consistency and correctness, BindingExpressionVisitor should also create
cast nodes using ReflectionTypeCastNode.
Changes:
- Convert expressions now create ReflectionTypeCastNode
- TypeAs expressions now create ReflectionTypeCastNode
- Both upcasts and downcasts create nodes (runtime checks handle failures)
Examples:
- x => (TestClass)x → 1 node (cast)
- x => ((TestClass)x).Prop → 2 nodes (cast + property)
- x => x.Child as object → 2 nodes (property + cast)
This matches the behavior of CompiledBindingPathBuilder.TypeCast<T>().
Updated all related tests to verify cast nodes are created.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* Use compiled cast functions instead of reflection-based type checks.
Changed from ReflectionTypeCastNode (which uses Type.IsInstanceOfType) to
FuncTransformNode with a compiled cast function. This matches how
CompiledBindingPath handles TypeCastPathElement and provides better
performance by avoiding reflection.
The CreateCastFunc method compiles an expression `(object? obj) => obj as T`
which generates efficient IL similar to the 'is T' pattern used in
TypeCastPathElement<T>, rather than using reflection-based type checks.
Performance improvement:
- Before: Type.IsInstanceOfType() reflection call for each cast
- After: Compiled IL using 'as' operator (same as TypeCastPathElement<T>)
Updated tests to expect FuncTransformNode instead of ReflectionTypeCastNode.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* Reuse TypeCastPathElement<T> cast function directly.
Instead of compiling our own cast expression, extract the Cast delegate
directly from TypeCastPathElement<T>. This ensures we use the exact same
code path as CompiledBindingPath, avoiding any potential behavioral
differences and code duplication.
Benefits:
- Code reuse - single implementation of cast logic
- Consistency - same behavior as CompiledBindingPathBuilder.TypeCast<T>()
- No duplicate expression compilation logic
Implementation uses reflection to create the closed generic type and
extract the pre-compiled Cast delegate, which is still more efficient
than reflection-based type checks at runtime.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* Revert to lambda compilation approach for cast functions.
Reverted from using TypeCastPathElement<T> back to compiling lambda
expressions directly. The lambda approach is cleaner and more straightforward:
Benefits of lambda compilation:
- No Activator.CreateInstance call (avoids reflection for construction)
- More direct - creates exactly what we need
- No coupling to TypeCastPathElement internal implementation
- Simpler code flow
The compiled lambda generates the same efficient IL code (using 'as' operator)
as TypeCastPathElement<T> does, just without the indirection.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* Code cleanup: Fix XML docs and remove unused usings.
- Changed CreateCastFunc XML docs to use <remarks> tag for better formatting
- Removed unused 'using Avalonia.Data;' from tests
- Removed redundant '#nullable enable' from tests
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* Refactor BindingExpressionVisitor to use CompiledBindingPathBuilder.
Changes BindingExpressionVisitor to use CompiledBindingPathBuilder instead of
directly creating ExpressionNode instances, unifying the approach with
compile-time XAML bindings. Adds new BuildPath() method that returns
CompiledBindingPath, while maintaining backwards compatibility through the
existing BuildNodes() wrapper method.
Key changes:
- Replace internal List<ExpressionNode> with CompiledBindingPathBuilder
- Refactor all visitor methods to call builder methods
- Add accessor factory methods and implementations for property access
- Support AvaloniaProperty, CLR properties, arrays, indexers, streams, casts
- Update tests to expect PropertyAccessorNode, StreamNode, ArrayIndexerNode
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* Remove CompiledBindingPathFromExpressionBuilder in favor of BindingExpressionVisitor.
Now that BindingExpressionVisitor has been refactored to use
CompiledBindingPathBuilder and provides a BuildPath() method, the test-only
CompiledBindingPathFromExpressionBuilder class is redundant and can be removed.
Changes:
- Replace CompiledBindingPathFromExpressionBuilder.Build() with
BindingExpressionVisitor<TIn>.BuildPath() in BindingExpressionTests
- Delete CompiledBindingPathFromExpressionBuilder.cs test file
- All 122 tests in BindingExpressionTests.Compiled continue to pass
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* Move test-only methods from production code to test extensions.
Removes BindingExpression.Create and BindingExpressionVisitor.BuildNodes
from production code since they were only used by unit tests.
Changes:
- Remove BindingExpression.Create<TIn, TOut> method
- Remove BindingExpressionVisitor.BuildNodes method
- Add BindingExpressionVisitorExtensions in Base.UnitTests
- Add BindingExpressionExtensions in LeakTests
- Add static helper methods in test classes to reduce noise
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* Add public CompiledBinding.Create factory methods from LINQ expressions.
Adds two static factory methods to create CompiledBinding instances from
lambda expressions using BindingExpressionVisitor.BuildPath():
- Create<TIn, TOut>(expression, converter, mode)
Creates binding without explicit source (uses DataContext)
- Create<TIn, TOut>(source, expression, converter, mode)
Creates binding with explicit source
This provides a type-safe, ergonomic API for creating compiled bindings
from code without string-based paths.
Usage:
var binding = CompiledBinding.Create(viewModel, vm => vm.Title);
textBlock.Bind(TextBlock.TextProperty, binding);
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* Merge CompiledBinding.Create overloads and add all binding property parameters.
Consolidates the two Create method overloads into a single method with source
as an optional parameter. Adds optional parameters for all CompiledBinding
properties (priority, converterCulture, converterParameter, fallbackValue,
stringFormat, targetNullValue, updateSourceTrigger, delay) per PR feedback.
Properties that default to AvaloniaProperty.UnsetValue (source, fallbackValue,
targetNullValue) use null-coalescing to convert null parameter values.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* (Re-)update suppressions.
* Add missing using.
* Fix ncrunch build.
* Remove file I committed by accident.
* PR feedback.
* Store static members outside generic class.
Based on PR feedback.
---------
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Make LeafNode property nullable and return null when the binding expression
has no nodes (e.g., when using a constant source with a converter but no path).
This fixes an ArgumentOutOfRangeException that occurred when accessing
LeafNode or Description properties on such bindings.
Fixes#20441
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
* Update to xunit.v3
* Little more progress
* More fixes
* Keep VSTest supported
* Adjust Nuke
* Few fixes
* Fix for xunit 2
* Fix GetData override
* Adjust
* Use MTP for xunit 2
* Fix test
* Better fix
* --no-progress
* Few more fixes
* no progress
* Fix test
* Better fix
* TRX
* Move to Directory.Build.props
* Unify on MTP v2
* Update
* Update to stable
* 1.0.1
* 1.0.2
* Fix some warnings
* Fix more warnings
* Fix more warnings
* Update ncrunch config.
* Tidy up reflection and multi-binding APIs:
- Move `BindingBase` and `MultiBinding` into Avalonia.Base
- `BindingBase` becomes a true base class for all bindings, and contains only the `Instance` method
- Properties common between reflection and compiled bindings are moved into `StandardBindingBase`
- `Binding` is moved to Avalonia.Base and renamed to `ReflectionBinding`
- A compatibility shim for `Binding` remains in Avalonia.Markup
- Remove `IBinding` and `IBinding2`
- Remove `ITreeDataTemplate's usage of `InstancedBinding`
- Remove `NativeMenuBarPresenter`s usage of `InstancedBinding`
- Remove `InstancedBinding` as it is now unused
This required an update to the DataGrid submodule: cell data validation has been temporarily removed as this used `InstancedBinding`.
* `Instance()` => `CreateInstance()`.
The use of "Instance" as a verb is quite unusual apparently ;)
* Seal classes where appropriate.
* Seal classes where appropriate.
* Remove `StandardBindingBase`.
Simply duplicate the members in reflection and compiled binding classes.
* Delete deleted submodule directory.
* Add missing attribute.
Fixes compile error.
* Fix reference to removed class.
* Update suppressions.
* Enable nullability in UnitTests
* Enable nullability in Base.UnitTests
* Enable nullability in Markup.UnitTests
* Enable nullability in Markup.Xaml.UnitTests
* Update ncrunch config.
* Fix test naming.
* Add BindingExpressionGrammar tests.
We only had tests for the error states, and that was named incorrectly.
* Parse null-conditionals in bindings.
* Initial impl of null conditionals in binding path.
Fixes#17029.
* Ensure that nothing is logged.
* Make "?." work when binding to methods.
* Don't add a new public API.
And add a comment reminding us to make this class internal for 12.0.
* Use existing method.
* Add failing tests for OneTime and null data context bindings
* Made OneTime bindings update on DataContext changes
Also allows null as a valid value for bindings without path
* Remove now obsolete test
* Added failing test for #15201.
* Handle nested BindingNotifications.
When #13970 was written, [a check](https://github.com/AvaloniaUI/Avalonia/pull/13970/files#diff-cfb25a491b9452e1815aa2c0d71465aaf81e99792a88a04a1a2ed572fd1930ffR60) was added to ensure that nested `BindingNotification`s didn't happen, and the refactor was written with the assumption that they wouldn't happen.
The problem is that they _do_ happen: when a source object implements both `INotifyDataErrorInfo` and had data annotations, then the nested data validation plugins would each wrap the value coming from the previous plugin in a new `BindingNotification`, resulting in nested `BindingNotifications`.
This adds support for nested binding notifications back in - even though IMO nesting binding notifications is a bug, if we're doing it and we previously supported it then we should continue to support it.
Fixes#15201
* Added failing tests for #15081.
* Provide target property in BindingExpression ctor.
Usually it is not necessary to provide the target property when creating a `BindingExpression` because the property will be assigned when the binding expression is attached to the target in `BindingExpressionBase.Attach`.
This is however one case where `Attach` is not called: when the obsolete `binding.Initiate` method is called and then an observable is read from the `InstancedBinding` without the binding actually being attached to the target object. In this case, prior to the binding refactor in #13970 the value produced by the observable was still converted to the target type. After #13970, because the target property (and hence the target type) is not yet set, the conversion is to the target type is no longer done.
`DataGrid` uses this obsolete method when editing cells, causing #15081. Ideally we'd fix that in `DataGrid` but I'm not happy making this change so close to 11.1, so instead fix this use-case to behave as before.
Fixes#15081
* Added more OneWayToSource tests.
One of whom is failing.
* Don't public value for OneWayToSource bindings.
Looks to have been a brainfart. This allows `OneWayToSource` bindings to read-only properties.
* Update ncrunch config.
* WIP: Benchmarks
* Initial refactor of binding infrastructure.
- `ExpressionObserver` has been removed and its functionality merged with `BindingExpression`
- `BindingExpression` handles all types of `BindingMode` itself; doesn't require `BindingOperations.Apply` to set up a separate observable for `TwoWay/`OneWayToSource` bindings
- This allows us to fix some long-standing issues with `OneWayToSource` bindings
- Expression nodes have been refactored
- No longer split between `Avalonia.Base` and `Avalonia.Markup`
- Categorize them according to whether they use reflection or not
A few tests are failing around binding warnings: this is because the next step here is to fix binding warnings.
* Make default binding Source = UnsetProperty.
Null is a theoretically valid value for `Source`; setting it to null shouldn't mean "use the data context".
* Move logging to BindingExpression.
As `BindingExpression` now has enough information to decide when it's appropriate to log an error/warning or not.
Fixes#5762Fixes#9422
* Add compatibility hack for older compiled bindings.
Previously, `CompiledBindingPathBuilder` didn't have a `TemplatedParent` method and instead the XAML compiler rewrite templated parent bindings to be a `$self.TemplateParent` property binding. resulting in extraneous logs.
Add a constructor with an `apiVersion` to `CompiledBindingPathBuilder` which will be used by newer versions of the XAML compiler, and if a usage is detected using an `apiVersion` of 0, then upgrade `$self.TemplatedParent` to use a `TemplatedParentPathElement`.
* Log errors from property accessors.
* Don't log errors for named control bindings...
...on elements which aren't yet rooted.
* Log errors for failed conversions.
* Use consistent wording for binding warnings.
"Could not convert" instead of "Cannot convert".
* Log warnings for converter exceptions.
* Don't convert new TargetTypeConverters each time.
* Added failing test for implicit conversion.
* Support cast operators in compiled bindings.
A bit of a hack as we'd ideally not be using reflection when using compiled bindings.
* This shouldn't be a public API.
Should only be used for tests.
* Make enum/int conversion work.
* Check for SetValue equality after conversion.
And also use "identity equals" where value types and strings use `object.Equals` and reference types use `object.ReferenceEquals`.
* Added ConverterCulture back to bindings.
* Fix merge error.
Removed deleted files from csproj that were re-added due to indentation changes.
* Use BindingExpression directly in ValueStoe.
* Introduce BindingExpressionBase.
And `UntypedBindingExpressionBase`.
* Make TemplateBinding a BindingExpression.
* Make DynamicResource use a BindingExpression.
* WIP: Start exposing a BindingExpression API.
* Finish exposing a BindingExpression API.
* Fix OneTimeBinding.
* Remove unneeded classes/methods.
* Don't call obsolete API.
* Make BindingExpressionBase the public API.
This matches WPF's API.
* Added BindingExpressionBase.UpdateTarget.
* Initial implementation of UpdateSourceTrigger.
* Don't use weak references for values.
If they're boxed values, they can get collected.
* No need for virtual/generic methods here now.
* Reintroduce support for binding anchors.
Turns out these were needed by animations, just our animation system has no unit tests so I missed that fact earlier. Add a basic animation unit test that fails without anchor support, and add binding anchors back in. Currently a private API as I suspect this feature shouldn't be needed outside the framework.
* Include new property in clone.
And add real-life example of `UpdateSourceTrigger=LostFocus` to BindingDemo.
* Fix merge error.
* Updated BindingExpression tests.
- Make them run for both compiled and reflection bindings (found a bunch of tests that fail with compiled bindings)
- Make them not depend on converting the `BindingExpression` to an observable and instead test the end result of the binding on an `AvaloniaObject`
* Fix compiled binding indexer tests.
* Use data validation plugins in PropertyAccessorNode.
Added a warning suppression for now: we may need a separate `DataValidators` list for AOT-friendly plugins.
* Don't separate plugins by reflection.
`DataAnnotationsValidationPlugin` is public and so it can't be moved. No point in moving the others if this one will be in the wrong place.
* Remove unneeded methods.
* Make reflection binding tests use a string.
Convert the `System.Linq.Expression` to a string and then use this, as reflection bindings will always be instanced with a string path.
* Added TODO12 plan for IBinding2.
* Use more specific exception.
* Fix nits from code review.
* Make expression nodes sealed where possible.
* Unsubscribe on Stop, don't re-subscribe.
D'oh.
* Tweak ExpressionNode lists.
Saves a few K in benchmarks and it's a cleaner API.
* Add a pooled option in BindingExpressionGrammar.
Micro-optimization.
* Avoid allocations when enumerating binding plugins.
* Add IBinding2 support to observable bind overloads.
In the case of `TemplateBinding`, the `IObservable<object?>` bind overload is selected by C#. Add an explicit check for an `IBinding2` here to use the more performant code-path.
* Remove disposed binding from ImmediateBindingFrame.
* Added TemplateBinding benchmarks.
* Remove duplicate items.
Seems to have been caused by a merge error.
* Fix exception when closing color picker.
And add tests.
* Don't skip converter when binding to self.
* Don't pass UnsetValue to converters.
This follows WPF behavior.
* Log element name if present.
More useful than just logging the control hash code.
* Respect binding priority.
* Throw on mismatched binding priorities.
We don't want to respect the binding priority in this case as it breaks `TemplateBindings` when the default `LocalValue` priority is passed. Instead make sure that the priority parameter matches that of the expression.
This reverts commit a72765d705.
* Convert to target type in TemplateBinding.
* Short-circuit target type conversion for same types.
* Merge core libraries.
Everything below `Avalonia.Controls` into `Avalonia.Base`.
* Move new files to correct place.
* Removed unused dirs/projects,
* Removed outdated references from theme assemblies.
* Merge unit tests to match new assembly layout.
* Fixup test namespaces.
* Make directory match namespace.
* Move files to match namespace.
* Move files to match namespace.
* Fix up incorrect namespace.
`Avalonia.Visuals.Media.Imaging` -> `Avalonia.Media.Imaging`.
* Fix resource URL.
* Removed outdated dependencies.
* Added missing project reference.
* Update test namespaces.
* Fix merge error.
* Fix merge errors.
* Fix bad merge in WindowsInteropTest.csproj.
* Fix up merge errors in csprojs.
* Remove merged tests from nuke.
* Fix up namespace.
* Fix compile error.
* Fix failing tests.
Now that more unit tests are present in Avalonia.Base.UnitTests, general `AvaloniaObject` properties are getting registered. Ignore those.
Co-authored-by: Jumar Macato <16554748+jmacato@users.noreply.github.com>
`ExpressionNode`s were always single-subscriber and making them use `IObservable<>` meant that we had to have extra allocations in order to return `IDisposable`s. Instead of using `IObservable` use a simpler `Subscribe`/`Unsubscribe` pattern. This saves a bunch more memory.