microsoft / microsoft/microsoft-ui-xaml
[Compiler Error][C++/WinRT] Bug: x:Name on custom control in another namespace makes .xaml.g.h omit required includes
- Dominant language
- C++
- Stars
- 8.4k
- Forks
- 942
- Avg merge
- 2d 7h
- Merged PRs (30d)
- 105
Description
### Describe the bug
If a user control/custom control lives in a child namespace (e.g., `struct winrt::MyApp::MyCustomControls::MyControl`) different from the page’s x:Class/RootNamespace, and that control is referenced in XAML with `x:Name`, the generated `*.xaml.g.h` emits strong-typed accessors using the fully-qualified type but does not include the header that declares that type.If the user’s translation unit hasn’t already included a header that declares the type before the *.xaml.g.h is included, the compile fails with errors like:
```text
error C3083: 'MyCustomControls': the left operand of '::' must be a type
... '' is not a member of 'winrt::'
```
This causes compile errors unless the developer manually includes the control’s header before the generated `.xaml.g.h`. Removing `x:Name` (or avoiding child namespaces) may hides the problem.
**Notes**
Forward declarations do not fix this: the generated *.xaml.g.h uses the concrete type in inline members, so a full definition is required.
The only reliable workaround is to include the control’s header before including the page’s generated header:
```cpp
// BlankPage.xaml.h
#include "pch.h"
#include "MyCustomControls\MyControls.h" // must come first
#include "MyPages\BlankPage.g.h"
```
Using **MSBuild Log Viewer** to trace and diagnose, we’ve further determined that the issue is likely related to the Xaml Compiler, specifically the implementation in **Microsoft.UI.Xaml.Markup.Compiler.dll**.
### Why is this important?
- **Gets in the way of a clean project structure.** Using x:Name on a custom control from another namespace fails to compile unless we manually pre-include that control’s header before the generated .xaml.g.h. Teams must either micromanage include order or abandon sub-namespaces (letting files pile up under one namespace/path).
- **Brittle builds.** Outcomes depend on TU include order and PCH contents; small refactors can suddenly break unrelated pages. Forward declarations don’t help because the generated header requires the full definition.
### Steps to reproduce the bug
1. Create a Blank WinUI3 Desktop App using c++ project templates.
2. Modify project configuration and prohibit cppwinrt from using prefixes with dots on the path.(use `false`).
3. Create a child directory and create a user custom control class in it.
4. Create another child directory for BlankPages.
5. Use our user custom control in XAML with `x:Name` markup.
6. rebuild the project, and goes on...
### Reproducible example
```cpp
// === Code ===
// Root: G:\WinUIProg\IssuesRepo\BugWinRTXamlSubNSError
// Note that if using a sub namespace, it is necessary to set false for the configuration of cppwinrt
// Files (12):
// 1. MainWindow.idl
// 2. MainWindow.xaml
// 3. MainWindow.xaml.cpp
// 4. MainWindow.xaml.h
// 5. MyCustomControls\MyControl.cpp
// 6. MyCustomControls\MyControl.h
// 7. MyCustomControls\MyControl.idl
// 8. MyPages\BlankPage.idl
// 9. MyPages\BlankPage.xaml
// 10. MyPages\BlankPage.xaml.cpp
// 11. MyPages\BlankPage.xaml.h
// 12. Themes\Generic.xaml
// ====================
// ================================================================================
// FILE: MainWindow.idl
// ================================================================================
namespace BugWinRTXamlSubNSError
{
[default_interface]
runtimeclass MainWindow : Microsoft.UI.Xaml.Window
{
MainWindow();
}
}
// ================================================================================
// FILE: MainWindow.xaml
// ================================================================================
// ================================================================================
// FILE: MainWindow.xaml.cpp
// ================================================================================
#include "pch.h"
#include "MainWindow.xaml.h"
#if __has_include("MainWindow.g.cpp")
#include "MainWindow.g.cpp"
#endif
namespace winrt::BugWinRTXamlSubNSError::implementation
{
}
// ================================================================================
// FILE: MainWindow.xaml.h
// ================================================================================
#pragma once
#include "MainWindow.g.h"
namespace winrt::BugWinRTXamlSubNSError::implementation
{
struct MainWindow : MainWindowT
{
MainWindow() = default;
};
}
namespace winrt::BugWinRTXamlSubNSError::factory_implementation
{
struct MainWindow : MainWindowT
{
};
}
// ================================================================================
// FILE: MyCustomControls\MyControl.cpp
// ================================================================================
#include "pch.h"
#include "MyCustomControls\MyControl.h"
#if __has_include("MyCustomControls\MyControl.g.cpp")
#include "MyCustomControls\MyControl.g.cpp"
#endif
using namespace winrt;
using namespace Microsoft::UI::Xaml;
namespace winrt::BugWinRTXamlSubNSError::MyCustomControls::implementation
{
MyControl::MyControl()
{
DefaultStyleKey(winrt::box_value(L"BugWinRTXamlSubNSError.MyCustomControls.MyControl"));
}
int32_t MyControl::MyProperty()
{
throw hresult_not_implemented();
}
void MyControl::MyProperty(int32_t /* value */)
{
throw hresult_not_implemented();
}
}
// ================================================================================
// FILE: MyCustomControls\MyControl.h
// ================================================================================
#pragma once
#include "MyCustomControls\MyControl.g.h"
namespace winrt::BugWinRTXamlSubNSError::MyCustomControls::implementation
{
struct MyControl : MyControlT
{
MyControl();
int32_t MyProperty();
void MyProperty(int32_t value);
};
}
namespace winrt::BugWinRTXamlSubNSError::MyCustomControls::factory_implementation
{
struct MyControl : MyControlT
{
};
}
// ================================================================================
// FILE: MyCustomControls\MyControl.idl
// ================================================================================
namespace BugWinRTXamlSubNSError.MyCustomControls
{
[default_interface]
runtimeclass MyControl : Microsoft.UI.Xaml.Controls.Control
{
MyControl();
Int32 MyProperty;
}
}
// ================================================================================
// FILE: MyPages\BlankPage.idl
// ================================================================================
namespace BugWinRTXamlSubNSError.MyPages
{
[default_interface]
runtimeclass BlankPage : Microsoft.UI.Xaml.Controls.Page
{
BlankPage();
}
}
// ================================================================================
// FILE: MyPages\BlankPage.xaml
// ================================================================================
// ================================================================================
// FILE: MyPages\BlankPage.xaml.cpp
// ================================================================================
#include "pch.h"
#include "MyPages\BlankPage.xaml.h"
#if __has_include("MyPages\BlankPage.g.cpp")
#include "MyPages\BlankPage.g.cpp"
#endif
using namespace winrt;
using namespace Microsoft::UI::Xaml;
namespace winrt::BugWinRTXamlSubNSError::MyPages::implementation
{
BlankPage::BlankPage()
{
}
}
// ================================================================================
// FILE: MyPages\BlankPage.xaml.h
// ================================================================================
#pragma once
#include "MyPages\BlankPage.g.h"
namespace winrt::BugWinRTXamlSubNSError::MyPages::implementation
{
struct BlankPage : BlankPageT
{
BlankPage();
};
}
namespace winrt::BugWinRTXamlSubNSError::MyPages::factory_implementation
{
struct BlankPage : BlankPageT
{
};
}
// ================================================================================
// FILE: Themes\Generic.xaml
// ================================================================================
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:MyControl">
<Border
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
```
### Actual behavior
`*.xaml.g.h` references concrete types from `x:Name` but does not ensure they are declared/defined, leading to compile failures unless users add manual pre-includes.
### Expected behavior
Generated code should compile without developers micro-managing include order when `x:Name` references types from child namespaces.
### Screenshots
_No response_
### NuGet package version
WinUI 3 - Windows App SDK 1.8 Experimental 4: 1.8.250702007-experimental4
### Windows version
Windows 11 (24H2): Build 26100
### Additional context
Environment:
- cppwinrt version: 2.0.250303.1.
- WinUI3: WASDK 1.8 experimental4.
- VisualStudio: Enterprise 2022 17.4.3.
Please consider one of the following:
- Emit necessary includes for all `x:Name` element types used in the XAML file (preferred). I think this information should be visible and recognized by the compiler when processing winmd and xaml.
- Or provide a documented MSBuild option to inject user-specified headers before the generated .xaml.g.h.
- Or generate a small companion header (e.g., `BlankPage.xaml.includes.g.h`) listing required includes and have .xaml.g.h include it first.
This reproduces both in app projects and when the control is supplied from a C++/WinRT WinRT component. I can attach a minimal repro if helpful.
Contributor guide
Research direction
Build the provided WinUI 3 C++ reproduction using the listed MyCustomControls\MyControl and MyPages\BlankPage files, then inspect the generated BlankPage.xaml.g.h. Start by tracing the XAML compiler path identified as Microsoft.UI.Xaml.Markup.Compiler.dll. Done means the generated header compiles when x:Name references a control in a child namespace without manual pre-includes.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- compilers, desktop
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 38/100