microsoft / microsoft/microsoft-ui-xaml
C++/WinRT XAML compiler generates a call to a missing projected accessor for a named x element (x:Name) referenced by x:Bind
- Dominant language
- C++
- Stars
- 8.4k
- Forks
- 942
- Avg merge
- 2d 7h
- Merged PRs (30d)
- 105
Description
### Describe the bug
In a WinUI 3 C++/WinRT XAML runtime class, the XAML compiler generates code that accesses an element declared with `x:Name` through the projected data-root type when the named element is controlled by `x:Load` and referenced by another compiled `x:Bind`.
This has been reproduced with both `UserControl` and `Page` roots.
For the following named element:
```xml
```
the generated `.xaml.g.hpp` contains:
```cpp
Update_ImageTest(GetDataRoot().ImageTest(), NOT_PHASED);
```
`GetDataRoot()` correctly returns the projected runtime type, for example:
```cpp
winrt::WinUI3cppWorkTest::TestControl
```
However, an element declared only with:
```xml
x:Name="ImageTest"
```
is not automatically exposed as a public property on that projected runtime type. If `ImageTest` is not also declared in the runtime class's IDL, the projected type has no `ImageTest()` accessor and the generated code does not compile.
The following additional IDL declaration works around the compilation failure:
```idl
Microsoft.UI.Xaml.Controls.Image ImageTest{ get; };
```
However, requiring every named XAML element used by this unloadable-binding path to be exposed as part of the runtime class's public WinRT contract appears unintended.
### Why is this important?
This prevents a WinUI 3 C++/WinRT `Page`, `UserControl`, or other XAML-backed runtime class from compiling when all of the following conditions are present:
1. An element is declared with `x:Name`.
2. The element is controlled by an `x:Load` binding.
3. Another compiled `x:Bind` references that named element.
The application needs to conditionally load an image and pass the image to a custom image source through a strongly typed compiled binding.
A named XAML element is normally an implementation detail of the XAML class. It should not have to be added to the runtime class's public IDL contract merely so that compiler-generated binding code can access it.
Adding the element to IDL changes the public Windows Runtime API of the class and unnecessarily exposes an internal UI implementation detail to external consumers.
The generated `.xaml.g.hpp` file also cannot be edited as a solution because it is regenerated whenever XAML code generation runs.
### Steps to reproduce the bug
1. Create a WinUI 3 desktop application using C++/WinRT.
2. Add a runtime class derived from `UserControl`.
For example, declare the runtime class in IDL without an `ImageTest` property:
```idl
[default_interface]
runtimeclass TestControl : Microsoft.UI.Xaml.Controls.UserControl
{
TestControl();
}
```
The same behavior can also be reproduced with a runtime class derived from `Microsoft.UI.Xaml.Controls.Page`.
3. Use the runtime class as the root element of a XAML file:
```xml
```
`SvgImageSource.BindSizeTo` is a dependency property whose type accepts the outer `Image` or `FrameworkElement`.
4. Build the project.
5. Observe that compilation of `Generated Files/XamlTypeInfo.g.cpp` fails.
6. Inspect the generated `TestControl.xaml.g.hpp`.
The generated binding connection code contains:
```cpp
case 2: // TestControl.xaml line 27
{
auto targetElement =
target.as<::winrt::Microsoft::UI::Xaml::Controls::Image>();
obj2 = targetElement;
this->UnloadableBindingSourcesToUpdate.push_back([this]()
{
Update_ImageTest(GetDataRoot().ImageTest(), NOT_PHASED);
});
}
break;
```
7. MSVC reports that `ImageTest` is not a member of the projected runtime class.
8. Add the following property to the runtime class's IDL:
```idl
[default_interface]
runtimeclass TestControl : Microsoft.UI.Xaml.Controls.UserControl
{
TestControl();
Microsoft.UI.Xaml.Controls.Image ImageTest{ get; };
}
```
9. Rebuild the project.
The generated expression can now compile because the IDL declaration causes an `ImageTest()` accessor to be generated on the projected runtime type.
Deleting the generated and intermediate files and performing a clean rebuild without the additional IDL property generates the same failing code.
### Actual behavior
The XAML compiler generates:
```cpp
Update_ImageTest(GetDataRoot().ImageTest(), NOT_PHASED);
```
`GetDataRoot()` returns the projected runtime class:
```cpp
winrt::WinUI3cppWorkTest::TestControl
```
When `ImageTest` is declared only through XAML:
```xml
x:Name="ImageTest"
```
the projected runtime class does not contain a public `ImageTest()` accessor.
MSVC therefore reports:
```text
error C2039: 'ImageTest': is not a member of
'winrt::WinUI3cppWorkTest::TestControl'
```
The diagnostic points to the generated `TestControl.xaml.g.hpp` file while `Generated Files/XamlTypeInfo.g.cpp` is being compiled.
As a result, the application cannot be built unless the named XAML element is additionally exposed as a public property in IDL or the XAML binding is restructured.
### Expected behavior
The XAML compiler should generate valid C++/WinRT code for a named element controlled by `x:Load` and referenced by another compiled `x:Bind`.
The generated binding code may use `GetDataRoot()` if appropriate, but it must not call an accessor that does not exist on the projected runtime type.
The compiler should either:
* access the named unloadable element through the internal storage generated for the XAML class or binding connection;
* generate an appropriate internal accessor that is accessible from the binding code; or
* otherwise resolve the named element without requiring it to be declared as a public property in IDL.
Conceptually, the generated update might use the stored reference assigned in the binding connection:
```cpp
obj2 = targetElement;
```
However, the exact implementation is up to the XAML compiler.
A private XAML element declared with `x:Name` should not need to be exposed as part of the runtime class's public Windows Runtime contract merely to make compiler-generated binding code compile.
### Screenshots
1. Generated `TestControl.xaml.g.hpp` code containing:
```cpp
Update_ImageTest(GetDataRoot().ImageTest(), NOT_PHASED);
```
2. Original `UserControl` XAML and MSVC error C2039:
```text
'ImageTest': is not a member of
'winrt::WinUI3cppWorkTest::TestControl'
```
3. The same generated-code problem reproduced with another XAML root/control type:
### NuGet package version
2.3.1
### Windows version
_No response_
### Additional context
The issue occurs in the unloadable-binding update registered for the element controlled by `x:Load`:
```cpp
this->UnloadableBindingSourcesToUpdate.push_back([this]()
{
Update_ImageTest(GetDataRoot().ImageTest(), NOT_PHASED);
});
```
The `ImageTest` element is originally declared only through `x:Name`. It is not declared as a property in `TestControl.idl`.
The following IDL declaration is a confirmed workaround:
```idl
[default_interface]
runtimeclass TestControl : Microsoft.UI.Xaml.Controls.UserControl
{
TestControl();
Microsoft.UI.Xaml.Controls.Image ImageTest{ get; };
}
```
This works because the C++/WinRT projection then contains an accessor equivalent to:
```cpp
Microsoft::UI::Xaml::Controls::Image ImageTest() const;
```
and the generated expression becomes a valid projected-runtime call:
```cpp
GetDataRoot().ImageTest()
```
However, this workaround exposes an internal named XAML element as part of the runtime class's public Windows Runtime API.
Other possible workarounds include replacing the compiled element reference:
```xml
BindSizeTo="{x:Bind ImageTest}"
```
with a runtime element-name binding:
```xml
BindSizeTo="{Binding ElementName=ImageTest}"
```
or removing `x:Load`.
This may be related to #7579 because it concerns generated C++/WinRT accessors for named XAML elements, although this issue specifically occurs in the unloadable-binding update generated for the combination of `x:Load` and an element-reference `x:Bind`.
A separate failure has also been observed in a compiled `DataTemplate`, where a similar binding scenario generated the syntactically invalid expression:
```cpp
Update_PeerFlagImage(.obj63(), NOT_PHASED);
```
That generated expression is a different failure mode and should be tracked separately.
Contributor guide
Research direction
Reproduce the failure with the provided UserControl or Page XAML using x:Load and an element-reference x:Bind. Inspect the generated TestControl.xaml.g.hpp and Generated Files/XamlTypeInfo.g.cpp, especially the unloadable-binding update that calls GetDataRoot().ImageTest(). Done means the generated code builds without requiring ImageTest in the runtime class's IDL, with a regression test covering this scenario.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- compilers, desktop
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100