Skip to content

Commit e7a306b

Browse files
jevansaksCopilot
andcommitted
Update proposal doc with __attribute__((flag_enum)) approach
Replace the [[clang::annotate('w32m:flags')]] and DEFINE_ENUM_FLAG_OPERATORS references with the validated __attribute__((flag_enum)) approach that flows through the AST via Win32MetadataScraper detection. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent bf57235 commit e7a306b

1 file changed

Lines changed: 49 additions & 27 deletions

File tree

docs/copilot/plans/enum-remapping-proposal.md

Lines changed: 49 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,11 @@ enums.
5858
```cpp
5959
#ifdef _WIN32METADATA_
6060

61-
enum SET_THEME_APP_PROPERTIES_FLAGS : unsigned int {
61+
enum __attribute__((flag_enum)) SET_THEME_APP_PROPERTIES_FLAGS : unsigned int {
6262
ALLOW_NONCLIENT = STAP_ALLOW_NONCLIENT,
6363
ALLOW_CONTROLS = STAP_ALLOW_CONTROLS,
6464
ALLOW_WEBCONTENT = STAP_ALLOW_WEBCONTENT,
6565
};
66-
DEFINE_ENUM_FLAG_OPERATORS(SET_THEME_APP_PROPERTIES_FLAGS)
6766

6867
#else
6968
#define SET_THEME_APP_PROPERTIES_FLAGS DWORD
@@ -91,8 +90,11 @@ Key points:
9190
- `#else #define NAME DWORD` — the enum name becomes a DWORD macro for normal
9291
compilation, allowing unconditional use in function signatures
9392
- Modern C++ syntax — `enum NAME : base_type`, no `typedef` needed
94-
- `DEFINE_ENUM_FLAG_OPERATORS(NAME)` — existing SDK convention for flags enums,
95-
already detected by the ConstantsScraper (no new annotation needed)
93+
- `__attribute__((flag_enum))` — Clang's standard attribute for bitmask enums
94+
(see [Clang Attribute Reference](https://clang.llvm.org/docs/AttributeReference.html#flag-enum)).
95+
The Win32MetadataScraper detects this attribute in the AST and injects
96+
`[Flags]` via PInvokeGenerator's `WithAttributes` mechanism — fully through the
97+
AST, no text parsing or sidecar RSP entries needed
9698
- Member values reference the original `#define` macros directly — the preprocessor
9799
computes the actual values
98100
@@ -104,11 +106,15 @@ All scenarios were tested with ClangSharp v17 and v18 (test files in
104106
| Scenario | ClangSharp v17 | ClangSharp v18 |
105107
|---|---|---|
106108
| Enum declarations | ✅ | ✅ |
107-
| `[[clang::annotate("w32m:flags")]]` | ❌ (dropped) | ✅ `[NativeAnnotation]` |
109+
| `__attribute__((flag_enum))` → `[Flags]` | ✅ (via tooling) | ✅ (via tooling) |
108110
| C++11 enum base types (`: unsigned char`, etc.) | ✅ `enum X : byte` | ✅ |
109111
| Signed overflow (`(int)0xFFFFFFFF`) | ✅ auto `unchecked()` | ✅ |
110112
| Function params get enum type | ✅ | ✅ |
111-
| `#pragma clang attribute push` for flags | ❌ (dropped) | ✅ |
113+
114+
Note: `__attribute__((flag_enum))` is present in the Clang AST on both v17 and v18
115+
(as `CX_AttrKind_FlagEnum`), but ClangSharp's PInvokeGenerator doesn't handle it
116+
natively. The Win32MetadataScraper detects it during the AST walk and passes it to
117+
PInvokeGenerator via `WithAttributes`, which works on all versions.
112118
113119
### Three member patterns
114120
@@ -134,7 +140,8 @@ Use `push_macro`/`undef`/`pop_macro` to suppress macro expansion inside the enum
134140
#undef FILE_ATTRIBUTE_READONLY
135141
#undef FILE_ATTRIBUTE_HIDDEN
136142

137-
enum [[clang::annotate("w32m:flags")]] FILE_ATTRIBUTE_FLAGS : unsigned int {
143+
```cpp
144+
enum __attribute__((flag_enum)) FILE_ATTRIBUTE_FLAGS : unsigned int {
138145
FILE_ATTRIBUTE_READONLY = 0x00000001,
139146
FILE_ATTRIBUTE_HIDDEN = 0x00000002,
140147
};
@@ -159,28 +166,44 @@ enum OBJECT_IDENTIFIER : int {
159166

160167
### Flags annotation
161168

162-
There is no C++ standard `[[bitmask]]` attribute. We define a SAL-like macro:
169+
Clang provides `__attribute__((flag_enum))` to mark enums as bitmask types (see
170+
[Clang Attribute Reference](https://clang.llvm.org/docs/AttributeReference.html#flag-enum)).
171+
There is no C++ standard equivalent — no `[[bitmask]]` attribute exists even in C++26.
163172

164-
```cpp
165-
// In win32metadata.h
166-
#ifdef _WIN32METADATA_
167-
#define _Enum_flags_ [[clang::annotate("w32m:flags")]]
168-
#else
169-
#define _Enum_flags_
170-
#endif
173+
The Win32MetadataScraper detects `flag_enum` during the AST walk by checking
174+
`EnumDecl.Attrs` for `CX_AttrKind_FlagEnum`. Discovered flag enums are injected into
175+
PInvokeGenerator's `WithAttributes` as `[Flags]`:
176+
177+
```csharp
178+
// In RemapDiscovery.WalkDecl:
179+
if (decl is EnumDecl enumDecl && enumDecl.HasAttrs)
180+
{
181+
foreach (var attr in enumDecl.Attrs)
182+
{
183+
if (attr.Kind == CX_AttrKind_FlagEnum)
184+
result.FlagEnumNames.Add(enumDecl.Name);
185+
}
186+
}
187+
188+
// In Program.cs, before creating PInvokeGenerator config:
189+
foreach (var flagEnum in discovery.FlagEnumNames)
190+
withAttributes[flagEnum] = new List<string> { "Flags" };
171191
```
172192

173-
Usage:
193+
Usage in headers:
174194
```cpp
175-
enum _Enum_flags_ FILE_ATTRIBUTE_FLAGS : unsigned int { ... };
195+
enum __attribute__((flag_enum)) FILE_ATTRIBUTE_FLAGS : unsigned int { ... };
176196
```
177197
178-
On v18+, this emits `[NativeAnnotation("w32m:flags")]` on the enum, which the emitter
179-
converts to `[Flags]`.
198+
This approach:
199+
- Flows entirely through the Clang AST — no text parsing
200+
- Works on both ClangSharp v17 and v18
201+
- Requires no sidecar RSP entries
202+
- Uses a standard Clang attribute rather than a custom annotation
180203
181-
As a fallback, the existing `DEFINE_ENUM_FLAG_OPERATORS(FOO)` macro is already detected
182-
by the ConstantsScraper (text-level regex) and written to `enumsMakeFlags.generated.rsp`.
183-
Both mechanisms can coexist.
204+
For SDK headers that already use `DEFINE_ENUM_FLAG_OPERATORS(FOO)`, the existing
205+
ConstantsScraper text-detection path (`enumsMakeFlags.generated.rsp`) continues to
206+
work as a fallback.
184207
185208
### Underlying types
186209
@@ -307,7 +330,7 @@ Every field in enums.json maps to a header construct:
307330
| enums.json field | Header equivalent |
308331
|---|---|
309332
| `name` | `enum NAME` |
310-
| `flags` | `_Enum_flags_` / `[[clang::annotate("w32m:flags")]]` |
333+
| `flags` | `__attribute__((flag_enum))` on enum |
311334
| `type` | C++ enum base (`: unsigned int`, `: unsigned char`, etc.) |
312335
| `members[].name` | Enum member identifier |
313336
| `members[].value` | Literal value or `#define` macro reference |
@@ -321,10 +344,9 @@ Every field in enums.json maps to a header construct:
321344

322345
## Implementation steps
323346

324-
1. **Upgrade ClangSharp to v18+** — required for `[[clang::annotate]]` on enums
325-
2. **Build a migration tool** — converts each `enums.json` entry into a conditional
326-
enum declaration block
327-
3. **Create overlay headers** with the generated declarations
347+
1. **Build a migration tool** — converts each `enums.json` entry into a conditional
348+
enum declaration block with `__attribute__((flag_enum))` where appropriate
349+
2. **Create overlay headers** with the generated declarations
328350
4. **Add `_Enum_type_()` usage annotations** — from `enums.json` `uses` array
329351
5. **Validate winmd equivalence** — compare winmd output before/after
330352
6. **Remove `enums.json`** and simplify ConstantsScraper

0 commit comments

Comments
 (0)