Skip to content

Comments

[XSG] Add heuristic to support bindable properties generated by other source generators#32597

Merged
StephaneDelcroix merged 15 commits intomainfrom
copilot/use-heuristic-to-identify-bindable-properties
Nov 28, 2025
Merged

[XSG] Add heuristic to support bindable properties generated by other source generators#32597
StephaneDelcroix merged 15 commits intomainfrom
copilot/use-heuristic-to-identify-bindable-properties

Conversation

Copy link
Contributor

Copilot AI commented Nov 13, 2025

Note

Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!

Description of Change

The XAML source generator fails with error MAUIX2002 when trying to bind to properties that have their BindableProperty field generated by another source generator (e.g., CommunityToolkit.Maui's [BindableProperty] attribute), because source generators run independently and cannot see each other's output.

This PR implements a heuristic to detect and support such properties, allowing bindings to work correctly.

Implementation Details:

The heuristic detects properties/fields with recognized bindable property attributes and generates appropriate code:

  • For BindingBase values: generates SetBinding call
  • For literal values: generates SetValue call (not property setter)
  • Supports optional PropertyName parameter on attributes for explicit property name specification
  • Integrated into CanSetBinding and CanSetValue methods, keeping SetPropertyValue structure unchanged

Recognized Attributes:

The heuristic recognizes these specific attribute types by their full names:

  • CommunityToolkit.Maui.BindablePropertyAttribute
  • SQuan.Helpers.Maui.Mvvm.BindablePropertyAttribute
  • Maui.BindableProperty.Generator.Core.AutoBindableAttribute

The list of recognized attributes is easily extensible by adding to the RecognizedBindablePropertyAttributes array. Only exact full name matches are supported to ensure specificity and prevent false positives.

Name Matching Strategy:

The heuristic checks these name patterns:

  • Exact match (properties): Balance → property Balance
  • Underscore prefix (fields): Balance → field _balance
  • Lowercase first letter (fields): Balance → field balance
  • Uppercase first letter (fields): Balance → field Balance

Files Changed (3 files):

  1. src/Controls/src/SourceGen/ITypeSymbolExtensions.Maui.cs - Heuristic detection logic with helper methods and recognized attribute list
  2. src/Controls/src/SourceGen/SetPropertyHelpers.cs - Integration into CanSetBinding and CanSetValue methods
  3. src/Controls/tests/SourceGen.UnitTests/InitializeComponent/BindablePropertyHeuristic.cs - Comprehensive test coverage with real-world attribute namespaces (14 new tests)

Code Structure:

The heuristic is cleanly integrated into the existing code structure:

  • CanSetBinding checks the heuristic when BP is null and value is BindingBase
  • CanSetValue checks the heuristic when BP is null and value is not BindingBase
  • SetBinding and SetValue handle both normal BP case and heuristic case with minimal duplication
  • SetPropertyValue structure unchanged - continues calling Can* methods in order

This ensures:

  • Real BindableProperties are always used first
  • Source-generated BPs (detected via heuristic) use SetBinding/SetValue
  • Only properties without BPs use property setters
  • Clean separation of concerns

Test Results:

  • ✅ All 14 BindablePropertyHeuristic tests pass
  • ✅ All 100 SourceGen.UnitTests pass
  • ✅ All 1746 Xaml.UnitTests pass

Example:

// User code with source-generated BindableProperty
public class BalanceView : Label
{
    [CommunityToolkit.Maui.BindableProperty]  // Generates BalanceProperty field at runtime
    public double Balance { get; set; }
}
<!-- XAML that previously failed with MAUIX2002, now works -->
<local:BalanceView Balance="{Binding Value}" />

Generated code:

// For binding:
balanceView.SetBinding(BalanceView.BalanceProperty, binding);

// For literal value:
balanceView.SetValue(BalanceView.BalanceProperty, 42.0);

Issues Fixed

Fixes #32596

Original prompt

This section details on the original issue you should resolve

<issue_title>[XSG] Use heuristic to identify bindable properties generated by another source generator</issue_title>
<issue_description>There are libraries which generate the BindableProperty boilerplate based on a partial property or on a field annoated by some attribute. It can look something like this:

[BindableProperty]
public partial string Title { get; set; }

or

[BindableProperty]
private string _title;

The source generator creates the long declaration of the bindable property field:

public static BindableProperty <name>Property = BindableProperty.Create(...);

Since Roslyn source generators run independently of each other, our XAML SourceGenerator cannot see these generated bindable property static fields. This breaks apps which rely on this mechansim. For example:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
    x:Class="BalancePage"
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:local="clr-namespace:App"
    x:DataType="local:BalancePage">

    <VerticalStackLayout>
        <Slider x:Name="BalanceSlider" />
        <local:BalanceView Balance="{Binding Source={x:Reference BalanceSlider}, x:DataType='Slider', Path=Value}" />
    </VerticalStackLayout>
</ContentPage>
namespace App;

public partial class BalanceView : Label
{
	[BindableProperty] public partial double Balance { get; set; } = 0.0;

	public BalanceView()
	{
            // ...
	}
}

This currently fails with the following error:

error MAUIX2002: No accessible property, BindableProperty, or event found for "Balance", or mismatching type between value and property.

I imagine we implement a heuristic which will detect that we are trying to assign a BindingExtension node to a property or a field which has "similar" name to the XAML property name ("Balance", "_balance", "balance") and the property or field has an attribute which is called "BindablePropertyAttribute".

I would start by creating a PoC with SourceGen.UnitTests coverage. Let's see if this is feasible or not before we commit to this feature.

Later, we might allow more different attribute names to meet the expectations of the community and we might allow more distance between the XAML property name and the property/field name.</issue_description>

Comments on the Issue (you are @copilot in this section)


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI changed the title [WIP] Use heuristic to identify bindable properties from source generator [XSG] Add heuristic to support bindable properties generated by other source generators Nov 13, 2025
Copilot AI requested a review from simonrozsival November 13, 2025 18:32
@rmarinho
Copy link
Member

@copilot Seems alot of code changes here not related with fixes, can you just commit the changes for the issue in question.

Copy link
Contributor Author

Copilot AI commented Nov 13, 2025

@copilot Seems alot of code changes here not related with fixes, can you just commit the changes for the issue in question.

Removed the formatting commit (bc7ccd0). The PR now contains only the 3 files directly related to the fix:

  • ITypeSymbolExtensions.Maui.cs (heuristic detection)
  • SetPropertyHelpers.cs (integration)
  • BindablePropertyHeuristic.cs (tests)

Commit 52d2097

Copy link
Member

@simonrozsival simonrozsival left a comment

Choose a reason for hiding this comment

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

@copilot you only added tests for AutoPropertyAttribute. I would also like to see tests for BindablePropertyAttribute. Change those tests into [Theory], ideally just one, and add parameters to test all the relevant inputs and their expected outputs.

Copy link
Contributor Author

Copilot AI commented Nov 14, 2025

@copilot you only added tests for AutoPropertyAttribute. I would also like to see tests for BindablePropertyAttribute. Change those tests into [Theory], ideally just one, and add parameters to t...

Consolidated tests into a Theory with 6 InlineData test cases covering all combinations of BindablePropertyAttribute/AutoPropertyAttribute with properties/fields and with/without PropertyName parameter. Added 3 additional Fact tests for edge cases (missing attribute, mismatched names, literal values). All tests verify the correct SetBinding code is generated. Commit 7d9969c

Copy link
Member

@simonrozsival simonrozsival left a comment

Choose a reason for hiding this comment

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

@copilot please make one more iteration

Copilot AI requested a review from simonrozsival November 14, 2025 11:05
@simonrozsival simonrozsival marked this pull request as ready for review November 14, 2025 11:25
Copilot AI review requested due to automatic review settings November 14, 2025 11:25
Copilot AI and others added 11 commits November 28, 2025 10:59
- Split Theory test into separate tests for properties and fields for better clarity
- Merge duplicate if statements for BindablePropertyAttribute and AutoPropertyAttribute checks
- All 64 tests passing (53 original + 11 new)

Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>
- Always declare PropertyName as string? in attribute definitions
- Add parameter to test field name variations (_balance, balance)
- All 66 tests passing (53 original + 13 new)

Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>
- Add null/empty checks to DerivePotentialPropertyNames and DerivePotentialFieldNames to prevent IndexOutOfRangeException
- Support field names with uppercase first letter (uncommon but now supported)
- All 66 tests passing

Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>
…erties

- Modified heuristic logic to handle both BindingBase and literal values
- Added SetValueWithHeuristic method to generate SetValue calls
- Updated test to verify SetValue is used instead of property setter
- Added test to ensure property setter is still used when no attribute present
- All 67 tests passing (53 original + 14 heuristic tests)

Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>
- Heuristic now evaluated after BP checks but before property setter
- Ensures source-generated BPs use SetBinding/SetValue instead of setter
- Prevents heuristic from being applied too early
- All 67 tests passing

Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>
- Replace suffix matching with full name checking
- Support recognized attributes: CommunityToolkit.Maui, SQuan.Helpers.Maui.Mvvm, Maui.BindableProperty.Generator.Core
- Add flexible suffix matching for other libraries (e.g., Test.BindablePropertyAttribute)
- All 67 tests passing (53 original + 14 heuristic tests)

Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>
- Only check exact full attribute names from the recognized list
- Add Test.BindablePropertyAttribute and Test.AutoPropertyAttribute for unit tests
- All 67 tests passing (53 original + 14 heuristic tests)

Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>
- Move heuristic logic from separate block in SetPropertyValue into Can* methods
- CanSetBinding now checks heuristic when BP is null and value is BindingBase
- CanSetValue now checks heuristic when BP is null and value is not BindingBase
- SetPropertyValue structure unchanged - still just calls Can* methods
- SetBinding and SetValue methods updated to handle heuristic case
- All existing tests passing

Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>
… in tests

- Remove IsBindingBaseNode method - inline the check in CanSetBinding
- Simplify SetBinding and SetValue to reduce duplication using ternary expressions
- Replace Test.BindablePropertyAttribute with CommunityToolkit.Maui.BindablePropertyAttribute in tests
- Replace Test.AutoPropertyAttribute with Maui.BindableProperty.Generator.Core.AutoBindableAttribute in tests
- Update recognized attributes list to remove Test.* entries
- All 67 tests passing

Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>
- Fix null reference error on line 320 (use bpName instead of bpFieldSymbol.ToFQDisplayString())
- Move heuristic check in CanSetValue from inside if(bpFieldSymbol != null) to else block
- Add null/empty check for localName in CanSetBinding heuristic
- Update tests to use real namespace attributes (CommunityToolkit.Maui, Maui.BindableProperty.Generator.Core)

Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>
- Changed tests to use block-style namespace syntax instead of file-scoped
- Changed BalanceView from partial class with partial properties to regular class
- Removed partial keyword from properties since that requires C# 13 implementation
- All 14 BindablePropertyHeuristic tests pass
- All 100 SourceGen.UnitTests pass
- All 1746 Xaml.UnitTests pass

Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>
@StephaneDelcroix StephaneDelcroix force-pushed the copilot/use-heuristic-to-identify-bindable-properties branch from 10b568d to 1cce733 Compare November 28, 2025 10:28
@simonrozsival
Copy link
Member

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 3 pipeline(s).

@StephaneDelcroix
Copy link
Contributor

/azp run MAUI-UITests-public

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@StephaneDelcroix StephaneDelcroix merged commit f944dab into main Nov 28, 2025
161 of 164 checks passed
@StephaneDelcroix StephaneDelcroix deleted the copilot/use-heuristic-to-identify-bindable-properties branch November 28, 2025 16:17
@PureWeen PureWeen added the area-xaml XAML, CSS, Triggers, Behaviors label Dec 17, 2025
PureWeen pushed a commit that referenced this pull request Jan 14, 2026
…blePropertyAttribute (#33028)

> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Description of Change

Binding source generators now support properties generated by
CommunityToolkit.Mvvm's `[ObservableProperty]` attribute. Previously,
bindings failed because MAUI generators cannot see properties generated
by other source generators.

**Implementation** (following PR #32954 pattern for RelayCommand):

- **BindingSourceGen/ITypeSymbolExtensions.cs**: Added
`TryGetObservablePropertyType()` to detect fields with
`[ObservableProperty]` and infer property types. Supports all three
documented field naming patterns: `camelCase`, `_camelCase`, and
`m_camelCase`. Includes validation for empty property names.

- **BindingSourceGen/PathParser.cs**: Enhanced `TryHandleSpecialCases()`
to handle ObservableProperty patterns as fallback when symbol resolution
fails.

- **BindingSourceGen/BindingSourceGenerator.cs**: Updated
`GetLambdaReturnType()` to infer types from ObservableProperty fields,
enabling C# lambda bindings.

- **SourceGen/ITypeSymbolExtensions.cs**: Updated `TryGetProperty()` to
check ObservableProperty-inferred properties, enabling XAML string-based
bindings.

- **Tests**: Added comprehensive unit tests in
`BindingSourceGen.UnitTests` (8 tests) to validate ObservableProperty
binding support across all field naming patterns and scenarios. Tests
verify the generated binding code by checking:
- Handler patterns: `new(static source => source, "PropertyName")` -
validates property path in generated code
- Setter expressions: `source.PropertyName = value;` - validates
property assignment

**Field Naming Pattern Support:**

All three CommunityToolkit.Mvvm documented naming conventions are
supported:
- `name` → `Name`
- `_name` → `Name`
- `m_name` → `Name`

**Works for both XAML and C# bindings:**

```csharp
public class MyViewModel
{
    [ObservableProperty]
    private string? name;  // Generates Name property
    
    [ObservableProperty]
    private ObservableCollection<Tag> _tags = new();  // Generates Tags property
    
    [ObservableProperty]
    private int m_count;  // Generates Count property
}
```

```xaml
<Label Text="{Binding Name}" />
<ListView ItemsSource="{Binding Tags}" />
```

```csharp
label.SetBinding(Label.TextProperty, static (MyViewModel vm) => vm.Name);
listView.SetBinding(ListView.ItemsSourceProperty, static (MyViewModel vm) => vm.Tags);
```

### Issues Fixed

Fixes #32597

<!-- START COPILOT ORIGINAL PROMPT -->



<details>

<summary>Original prompt</summary>

> 
> ----
> 
> *This section details on the original issue you should resolve*
> 
> <issue_title>[XSG][BindingSourceGen] Add support for
CommunityToolkit.Mvvm ObservablePropertyAttribute source
generator</issue_title>
> <issue_description>Similar to #32597
> Related to #32953
> 
> The CommunityToolkit.Mvvm package contains a source generator which
generates bindable property bolierplate code based on the
`[ObservableProperty]` attribute:
https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/generators/observableproperty
> 
> Scenarios:
> ```c#
> // applied to a field
> [ObservableProperty]
> private string? name;
> 
> // generated code:
> /// <inheritdoc cref="_tags"/>
>
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator",
"8.3.0.0")]
> [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
> public
global::System.Collections.ObjectModel.ObservableCollection<global::TestSampleContentBindings.Models.Tag>
Tags
> {
>     get => _tags;
>     [global::System.Diagnostics.CodeAnalysis.MemberNotNull("_tags")]
>     set
>     {
> if
(!global::System.Collections.Generic.EqualityComparer<global::System.Collections.ObjectModel.ObservableCollection<global::TestSampleContentBindings.Models.Tag>>.Default.Equals(_tags,
value))
>         {
>             OnTagsChanging(value);
>             OnTagsChanging(default, value);
>
OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Tags);
>             _tags = value;
>             OnTagsChanged(value);
>             OnTagsChanged(default, value);
>
OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Tags);
>         }
>     }
> }
> ```
> 
> Our source generators cannot see properties generated by other source
generators, so the binding compilation will fail.
> 
> Our sample content template uses this source generator and we are
currently failing over to reflection-based bindings.</issue_description>
> 
> ## Comments on the Issue (you are @copilot in this section)
> 
> <comments>
> </comments>
> 


</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes #32955

<!-- START COPILOT CODING AGENT TIPS -->
---

💬 We'd love your input! Share your thoughts on Copilot coding agent in
our [2 minute survey](https://gh.io/copilot-coding-agent-survey).

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>
Co-authored-by: Simon Rozsival <simon@rozsival.com>
github-actions bot pushed a commit that referenced this pull request Jan 16, 2026
…blePropertyAttribute (#33028)

> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Description of Change

Binding source generators now support properties generated by
CommunityToolkit.Mvvm's `[ObservableProperty]` attribute. Previously,
bindings failed because MAUI generators cannot see properties generated
by other source generators.

**Implementation** (following PR #32954 pattern for RelayCommand):

- **BindingSourceGen/ITypeSymbolExtensions.cs**: Added
`TryGetObservablePropertyType()` to detect fields with
`[ObservableProperty]` and infer property types. Supports all three
documented field naming patterns: `camelCase`, `_camelCase`, and
`m_camelCase`. Includes validation for empty property names.

- **BindingSourceGen/PathParser.cs**: Enhanced `TryHandleSpecialCases()`
to handle ObservableProperty patterns as fallback when symbol resolution
fails.

- **BindingSourceGen/BindingSourceGenerator.cs**: Updated
`GetLambdaReturnType()` to infer types from ObservableProperty fields,
enabling C# lambda bindings.

- **SourceGen/ITypeSymbolExtensions.cs**: Updated `TryGetProperty()` to
check ObservableProperty-inferred properties, enabling XAML string-based
bindings.

- **Tests**: Added comprehensive unit tests in
`BindingSourceGen.UnitTests` (8 tests) to validate ObservableProperty
binding support across all field naming patterns and scenarios. Tests
verify the generated binding code by checking:
- Handler patterns: `new(static source => source, "PropertyName")` -
validates property path in generated code
- Setter expressions: `source.PropertyName = value;` - validates
property assignment

**Field Naming Pattern Support:**

All three CommunityToolkit.Mvvm documented naming conventions are
supported:
- `name` → `Name`
- `_name` → `Name`
- `m_name` → `Name`

**Works for both XAML and C# bindings:**

```csharp
public class MyViewModel
{
    [ObservableProperty]
    private string? name;  // Generates Name property
    
    [ObservableProperty]
    private ObservableCollection<Tag> _tags = new();  // Generates Tags property
    
    [ObservableProperty]
    private int m_count;  // Generates Count property
}
```

```xaml
<Label Text="{Binding Name}" />
<ListView ItemsSource="{Binding Tags}" />
```

```csharp
label.SetBinding(Label.TextProperty, static (MyViewModel vm) => vm.Name);
listView.SetBinding(ListView.ItemsSourceProperty, static (MyViewModel vm) => vm.Tags);
```

### Issues Fixed

Fixes #32597

<!-- START COPILOT ORIGINAL PROMPT -->



<details>

<summary>Original prompt</summary>

> 
> ----
> 
> *This section details on the original issue you should resolve*
> 
> <issue_title>[XSG][BindingSourceGen] Add support for
CommunityToolkit.Mvvm ObservablePropertyAttribute source
generator</issue_title>
> <issue_description>Similar to #32597
> Related to #32953
> 
> The CommunityToolkit.Mvvm package contains a source generator which
generates bindable property bolierplate code based on the
`[ObservableProperty]` attribute:
https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/generators/observableproperty
> 
> Scenarios:
> ```c#
> // applied to a field
> [ObservableProperty]
> private string? name;
> 
> // generated code:
> /// <inheritdoc cref="_tags"/>
>
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator",
"8.3.0.0")]
> [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
> public
global::System.Collections.ObjectModel.ObservableCollection<global::TestSampleContentBindings.Models.Tag>
Tags
> {
>     get => _tags;
>     [global::System.Diagnostics.CodeAnalysis.MemberNotNull("_tags")]
>     set
>     {
> if
(!global::System.Collections.Generic.EqualityComparer<global::System.Collections.ObjectModel.ObservableCollection<global::TestSampleContentBindings.Models.Tag>>.Default.Equals(_tags,
value))
>         {
>             OnTagsChanging(value);
>             OnTagsChanging(default, value);
>
OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Tags);
>             _tags = value;
>             OnTagsChanged(value);
>             OnTagsChanged(default, value);
>
OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Tags);
>         }
>     }
> }
> ```
> 
> Our source generators cannot see properties generated by other source
generators, so the binding compilation will fail.
> 
> Our sample content template uses this source generator and we are
currently failing over to reflection-based bindings.</issue_description>
> 
> ## Comments on the Issue (you are @copilot in this section)
> 
> <comments>
> </comments>
> 


</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes #32955

<!-- START COPILOT CODING AGENT TIPS -->
---

💬 We'd love your input! Share your thoughts on Copilot coding agent in
our [2 minute survey](https://gh.io/copilot-coding-agent-survey).

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>
Co-authored-by: Simon Rozsival <simon@rozsival.com>
@github-actions github-actions bot locked and limited conversation to collaborators Jan 17, 2026
github-actions bot pushed a commit that referenced this pull request Jan 20, 2026
…blePropertyAttribute (#33028)

> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Description of Change

Binding source generators now support properties generated by
CommunityToolkit.Mvvm's `[ObservableProperty]` attribute. Previously,
bindings failed because MAUI generators cannot see properties generated
by other source generators.

**Implementation** (following PR #32954 pattern for RelayCommand):

- **BindingSourceGen/ITypeSymbolExtensions.cs**: Added
`TryGetObservablePropertyType()` to detect fields with
`[ObservableProperty]` and infer property types. Supports all three
documented field naming patterns: `camelCase`, `_camelCase`, and
`m_camelCase`. Includes validation for empty property names.

- **BindingSourceGen/PathParser.cs**: Enhanced `TryHandleSpecialCases()`
to handle ObservableProperty patterns as fallback when symbol resolution
fails.

- **BindingSourceGen/BindingSourceGenerator.cs**: Updated
`GetLambdaReturnType()` to infer types from ObservableProperty fields,
enabling C# lambda bindings.

- **SourceGen/ITypeSymbolExtensions.cs**: Updated `TryGetProperty()` to
check ObservableProperty-inferred properties, enabling XAML string-based
bindings.

- **Tests**: Added comprehensive unit tests in
`BindingSourceGen.UnitTests` (8 tests) to validate ObservableProperty
binding support across all field naming patterns and scenarios. Tests
verify the generated binding code by checking:
- Handler patterns: `new(static source => source, "PropertyName")` -
validates property path in generated code
- Setter expressions: `source.PropertyName = value;` - validates
property assignment

**Field Naming Pattern Support:**

All three CommunityToolkit.Mvvm documented naming conventions are
supported:
- `name` → `Name`
- `_name` → `Name`
- `m_name` → `Name`

**Works for both XAML and C# bindings:**

```csharp
public class MyViewModel
{
    [ObservableProperty]
    private string? name;  // Generates Name property
    
    [ObservableProperty]
    private ObservableCollection<Tag> _tags = new();  // Generates Tags property
    
    [ObservableProperty]
    private int m_count;  // Generates Count property
}
```

```xaml
<Label Text="{Binding Name}" />
<ListView ItemsSource="{Binding Tags}" />
```

```csharp
label.SetBinding(Label.TextProperty, static (MyViewModel vm) => vm.Name);
listView.SetBinding(ListView.ItemsSourceProperty, static (MyViewModel vm) => vm.Tags);
```

### Issues Fixed

Fixes #32597

<!-- START COPILOT ORIGINAL PROMPT -->



<details>

<summary>Original prompt</summary>

> 
> ----
> 
> *This section details on the original issue you should resolve*
> 
> <issue_title>[XSG][BindingSourceGen] Add support for
CommunityToolkit.Mvvm ObservablePropertyAttribute source
generator</issue_title>
> <issue_description>Similar to #32597
> Related to #32953
> 
> The CommunityToolkit.Mvvm package contains a source generator which
generates bindable property bolierplate code based on the
`[ObservableProperty]` attribute:
https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/generators/observableproperty
> 
> Scenarios:
> ```c#
> // applied to a field
> [ObservableProperty]
> private string? name;
> 
> // generated code:
> /// <inheritdoc cref="_tags"/>
>
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator",
"8.3.0.0")]
> [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
> public
global::System.Collections.ObjectModel.ObservableCollection<global::TestSampleContentBindings.Models.Tag>
Tags
> {
>     get => _tags;
>     [global::System.Diagnostics.CodeAnalysis.MemberNotNull("_tags")]
>     set
>     {
> if
(!global::System.Collections.Generic.EqualityComparer<global::System.Collections.ObjectModel.ObservableCollection<global::TestSampleContentBindings.Models.Tag>>.Default.Equals(_tags,
value))
>         {
>             OnTagsChanging(value);
>             OnTagsChanging(default, value);
>
OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Tags);
>             _tags = value;
>             OnTagsChanged(value);
>             OnTagsChanged(default, value);
>
OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Tags);
>         }
>     }
> }
> ```
> 
> Our source generators cannot see properties generated by other source
generators, so the binding compilation will fail.
> 
> Our sample content template uses this source generator and we are
currently failing over to reflection-based bindings.</issue_description>
> 
> ## Comments on the Issue (you are @copilot in this section)
> 
> <comments>
> </comments>
> 


</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes #32955

<!-- START COPILOT CODING AGENT TIPS -->
---

💬 We'd love your input! Share your thoughts on Copilot coding agent in
our [2 minute survey](https://gh.io/copilot-coding-agent-survey).

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>
Co-authored-by: Simon Rozsival <simon@rozsival.com>
github-actions bot pushed a commit that referenced this pull request Jan 21, 2026
…blePropertyAttribute (#33028)

> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Description of Change

Binding source generators now support properties generated by
CommunityToolkit.Mvvm's `[ObservableProperty]` attribute. Previously,
bindings failed because MAUI generators cannot see properties generated
by other source generators.

**Implementation** (following PR #32954 pattern for RelayCommand):

- **BindingSourceGen/ITypeSymbolExtensions.cs**: Added
`TryGetObservablePropertyType()` to detect fields with
`[ObservableProperty]` and infer property types. Supports all three
documented field naming patterns: `camelCase`, `_camelCase`, and
`m_camelCase`. Includes validation for empty property names.

- **BindingSourceGen/PathParser.cs**: Enhanced `TryHandleSpecialCases()`
to handle ObservableProperty patterns as fallback when symbol resolution
fails.

- **BindingSourceGen/BindingSourceGenerator.cs**: Updated
`GetLambdaReturnType()` to infer types from ObservableProperty fields,
enabling C# lambda bindings.

- **SourceGen/ITypeSymbolExtensions.cs**: Updated `TryGetProperty()` to
check ObservableProperty-inferred properties, enabling XAML string-based
bindings.

- **Tests**: Added comprehensive unit tests in
`BindingSourceGen.UnitTests` (8 tests) to validate ObservableProperty
binding support across all field naming patterns and scenarios. Tests
verify the generated binding code by checking:
- Handler patterns: `new(static source => source, "PropertyName")` -
validates property path in generated code
- Setter expressions: `source.PropertyName = value;` - validates
property assignment

**Field Naming Pattern Support:**

All three CommunityToolkit.Mvvm documented naming conventions are
supported:
- `name` → `Name`
- `_name` → `Name`
- `m_name` → `Name`

**Works for both XAML and C# bindings:**

```csharp
public class MyViewModel
{
    [ObservableProperty]
    private string? name;  // Generates Name property
    
    [ObservableProperty]
    private ObservableCollection<Tag> _tags = new();  // Generates Tags property
    
    [ObservableProperty]
    private int m_count;  // Generates Count property
}
```

```xaml
<Label Text="{Binding Name}" />
<ListView ItemsSource="{Binding Tags}" />
```

```csharp
label.SetBinding(Label.TextProperty, static (MyViewModel vm) => vm.Name);
listView.SetBinding(ListView.ItemsSourceProperty, static (MyViewModel vm) => vm.Tags);
```

### Issues Fixed

Fixes #32597

<!-- START COPILOT ORIGINAL PROMPT -->



<details>

<summary>Original prompt</summary>

> 
> ----
> 
> *This section details on the original issue you should resolve*
> 
> <issue_title>[XSG][BindingSourceGen] Add support for
CommunityToolkit.Mvvm ObservablePropertyAttribute source
generator</issue_title>
> <issue_description>Similar to #32597
> Related to #32953
> 
> The CommunityToolkit.Mvvm package contains a source generator which
generates bindable property bolierplate code based on the
`[ObservableProperty]` attribute:
https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/generators/observableproperty
> 
> Scenarios:
> ```c#
> // applied to a field
> [ObservableProperty]
> private string? name;
> 
> // generated code:
> /// <inheritdoc cref="_tags"/>
>
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator",
"8.3.0.0")]
> [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
> public
global::System.Collections.ObjectModel.ObservableCollection<global::TestSampleContentBindings.Models.Tag>
Tags
> {
>     get => _tags;
>     [global::System.Diagnostics.CodeAnalysis.MemberNotNull("_tags")]
>     set
>     {
> if
(!global::System.Collections.Generic.EqualityComparer<global::System.Collections.ObjectModel.ObservableCollection<global::TestSampleContentBindings.Models.Tag>>.Default.Equals(_tags,
value))
>         {
>             OnTagsChanging(value);
>             OnTagsChanging(default, value);
>
OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Tags);
>             _tags = value;
>             OnTagsChanged(value);
>             OnTagsChanged(default, value);
>
OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Tags);
>         }
>     }
> }
> ```
> 
> Our source generators cannot see properties generated by other source
generators, so the binding compilation will fail.
> 
> Our sample content template uses this source generator and we are
currently failing over to reflection-based bindings.</issue_description>
> 
> ## Comments on the Issue (you are @copilot in this section)
> 
> <comments>
> </comments>
> 


</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes #32955

<!-- START COPILOT CODING AGENT TIPS -->
---

💬 We'd love your input! Share your thoughts on Copilot coding agent in
our [2 minute survey](https://gh.io/copilot-coding-agent-survey).

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>
Co-authored-by: Simon Rozsival <simon@rozsival.com>
github-actions bot pushed a commit that referenced this pull request Jan 23, 2026
…blePropertyAttribute (#33028)

> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Description of Change

Binding source generators now support properties generated by
CommunityToolkit.Mvvm's `[ObservableProperty]` attribute. Previously,
bindings failed because MAUI generators cannot see properties generated
by other source generators.

**Implementation** (following PR #32954 pattern for RelayCommand):

- **BindingSourceGen/ITypeSymbolExtensions.cs**: Added
`TryGetObservablePropertyType()` to detect fields with
`[ObservableProperty]` and infer property types. Supports all three
documented field naming patterns: `camelCase`, `_camelCase`, and
`m_camelCase`. Includes validation for empty property names.

- **BindingSourceGen/PathParser.cs**: Enhanced `TryHandleSpecialCases()`
to handle ObservableProperty patterns as fallback when symbol resolution
fails.

- **BindingSourceGen/BindingSourceGenerator.cs**: Updated
`GetLambdaReturnType()` to infer types from ObservableProperty fields,
enabling C# lambda bindings.

- **SourceGen/ITypeSymbolExtensions.cs**: Updated `TryGetProperty()` to
check ObservableProperty-inferred properties, enabling XAML string-based
bindings.

- **Tests**: Added comprehensive unit tests in
`BindingSourceGen.UnitTests` (8 tests) to validate ObservableProperty
binding support across all field naming patterns and scenarios. Tests
verify the generated binding code by checking:
- Handler patterns: `new(static source => source, "PropertyName")` -
validates property path in generated code
- Setter expressions: `source.PropertyName = value;` - validates
property assignment

**Field Naming Pattern Support:**

All three CommunityToolkit.Mvvm documented naming conventions are
supported:
- `name` → `Name`
- `_name` → `Name`
- `m_name` → `Name`

**Works for both XAML and C# bindings:**

```csharp
public class MyViewModel
{
    [ObservableProperty]
    private string? name;  // Generates Name property
    
    [ObservableProperty]
    private ObservableCollection<Tag> _tags = new();  // Generates Tags property
    
    [ObservableProperty]
    private int m_count;  // Generates Count property
}
```

```xaml
<Label Text="{Binding Name}" />
<ListView ItemsSource="{Binding Tags}" />
```

```csharp
label.SetBinding(Label.TextProperty, static (MyViewModel vm) => vm.Name);
listView.SetBinding(ListView.ItemsSourceProperty, static (MyViewModel vm) => vm.Tags);
```

### Issues Fixed

Fixes #32597

<!-- START COPILOT ORIGINAL PROMPT -->



<details>

<summary>Original prompt</summary>

> 
> ----
> 
> *This section details on the original issue you should resolve*
> 
> <issue_title>[XSG][BindingSourceGen] Add support for
CommunityToolkit.Mvvm ObservablePropertyAttribute source
generator</issue_title>
> <issue_description>Similar to #32597
> Related to #32953
> 
> The CommunityToolkit.Mvvm package contains a source generator which
generates bindable property bolierplate code based on the
`[ObservableProperty]` attribute:
https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/generators/observableproperty
> 
> Scenarios:
> ```c#
> // applied to a field
> [ObservableProperty]
> private string? name;
> 
> // generated code:
> /// <inheritdoc cref="_tags"/>
>
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator",
"8.3.0.0")]
> [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
> public
global::System.Collections.ObjectModel.ObservableCollection<global::TestSampleContentBindings.Models.Tag>
Tags
> {
>     get => _tags;
>     [global::System.Diagnostics.CodeAnalysis.MemberNotNull("_tags")]
>     set
>     {
> if
(!global::System.Collections.Generic.EqualityComparer<global::System.Collections.ObjectModel.ObservableCollection<global::TestSampleContentBindings.Models.Tag>>.Default.Equals(_tags,
value))
>         {
>             OnTagsChanging(value);
>             OnTagsChanging(default, value);
>
OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Tags);
>             _tags = value;
>             OnTagsChanged(value);
>             OnTagsChanged(default, value);
>
OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Tags);
>         }
>     }
> }
> ```
> 
> Our source generators cannot see properties generated by other source
generators, so the binding compilation will fail.
> 
> Our sample content template uses this source generator and we are
currently failing over to reflection-based bindings.</issue_description>
> 
> ## Comments on the Issue (you are @copilot in this section)
> 
> <comments>
> </comments>
> 


</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes #32955

<!-- START COPILOT CODING AGENT TIPS -->
---

💬 We'd love your input! Share your thoughts on Copilot coding agent in
our [2 minute survey](https://gh.io/copilot-coding-agent-survey).

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>
Co-authored-by: Simon Rozsival <simon@rozsival.com>
PureWeen pushed a commit that referenced this pull request Jan 23, 2026
…blePropertyAttribute (#33028)

> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Description of Change

Binding source generators now support properties generated by
CommunityToolkit.Mvvm's `[ObservableProperty]` attribute. Previously,
bindings failed because MAUI generators cannot see properties generated
by other source generators.

**Implementation** (following PR #32954 pattern for RelayCommand):

- **BindingSourceGen/ITypeSymbolExtensions.cs**: Added
`TryGetObservablePropertyType()` to detect fields with
`[ObservableProperty]` and infer property types. Supports all three
documented field naming patterns: `camelCase`, `_camelCase`, and
`m_camelCase`. Includes validation for empty property names.

- **BindingSourceGen/PathParser.cs**: Enhanced `TryHandleSpecialCases()`
to handle ObservableProperty patterns as fallback when symbol resolution
fails.

- **BindingSourceGen/BindingSourceGenerator.cs**: Updated
`GetLambdaReturnType()` to infer types from ObservableProperty fields,
enabling C# lambda bindings.

- **SourceGen/ITypeSymbolExtensions.cs**: Updated `TryGetProperty()` to
check ObservableProperty-inferred properties, enabling XAML string-based
bindings.

- **Tests**: Added comprehensive unit tests in
`BindingSourceGen.UnitTests` (8 tests) to validate ObservableProperty
binding support across all field naming patterns and scenarios. Tests
verify the generated binding code by checking:
- Handler patterns: `new(static source => source, "PropertyName")` -
validates property path in generated code
- Setter expressions: `source.PropertyName = value;` - validates
property assignment

**Field Naming Pattern Support:**

All three CommunityToolkit.Mvvm documented naming conventions are
supported:
- `name` → `Name`
- `_name` → `Name`
- `m_name` → `Name`

**Works for both XAML and C# bindings:**

```csharp
public class MyViewModel
{
    [ObservableProperty]
    private string? name;  // Generates Name property
    
    [ObservableProperty]
    private ObservableCollection<Tag> _tags = new();  // Generates Tags property
    
    [ObservableProperty]
    private int m_count;  // Generates Count property
}
```

```xaml
<Label Text="{Binding Name}" />
<ListView ItemsSource="{Binding Tags}" />
```

```csharp
label.SetBinding(Label.TextProperty, static (MyViewModel vm) => vm.Name);
listView.SetBinding(ListView.ItemsSourceProperty, static (MyViewModel vm) => vm.Tags);
```

### Issues Fixed

Fixes #32597

<!-- START COPILOT ORIGINAL PROMPT -->



<details>

<summary>Original prompt</summary>

> 
> ----
> 
> *This section details on the original issue you should resolve*
> 
> <issue_title>[XSG][BindingSourceGen] Add support for
CommunityToolkit.Mvvm ObservablePropertyAttribute source
generator</issue_title>
> <issue_description>Similar to #32597
> Related to #32953
> 
> The CommunityToolkit.Mvvm package contains a source generator which
generates bindable property bolierplate code based on the
`[ObservableProperty]` attribute:
https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/generators/observableproperty
> 
> Scenarios:
> ```c#
> // applied to a field
> [ObservableProperty]
> private string? name;
> 
> // generated code:
> /// <inheritdoc cref="_tags"/>
>
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator",
"8.3.0.0")]
> [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
> public
global::System.Collections.ObjectModel.ObservableCollection<global::TestSampleContentBindings.Models.Tag>
Tags
> {
>     get => _tags;
>     [global::System.Diagnostics.CodeAnalysis.MemberNotNull("_tags")]
>     set
>     {
> if
(!global::System.Collections.Generic.EqualityComparer<global::System.Collections.ObjectModel.ObservableCollection<global::TestSampleContentBindings.Models.Tag>>.Default.Equals(_tags,
value))
>         {
>             OnTagsChanging(value);
>             OnTagsChanging(default, value);
>
OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Tags);
>             _tags = value;
>             OnTagsChanged(value);
>             OnTagsChanged(default, value);
>
OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Tags);
>         }
>     }
> }
> ```
> 
> Our source generators cannot see properties generated by other source
generators, so the binding compilation will fail.
> 
> Our sample content template uses this source generator and we are
currently failing over to reflection-based bindings.</issue_description>
> 
> ## Comments on the Issue (you are @copilot in this section)
> 
> <comments>
> </comments>
> 


</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes #32955

<!-- START COPILOT CODING AGENT TIPS -->
---

💬 We'd love your input! Share your thoughts on Copilot coding agent in
our [2 minute survey](https://gh.io/copilot-coding-agent-survey).

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>
Co-authored-by: Simon Rozsival <simon@rozsival.com>
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-xaml XAML, CSS, Triggers, Behaviors

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Proposal][XSG] Use heuristic to identify bindable properties generated by another source generator

5 participants