Skip to content

Port the projection generator from C++ to C##2440

Open
Sergio0694 wants to merge 13 commits into
staging/3.0from
staging/CodeWriters
Open

Port the projection generator from C++ to C##2440
Sergio0694 wants to merge 13 commits into
staging/3.0from
staging/CodeWriters

Conversation

@Sergio0694

Copy link
Copy Markdown
Member

Summary

Brings the full port of the CsWinRT projection generator from C++ (cswinrt.exe) to C# back into the official 3.0 staging branch. This is the accumulated work from the staging/CodeWriters integration branch: 9 feature PRs plus a merge that refreshed the branch from staging/3.0. The legacy native src/cswinrt project is removed and replaced by the new managed projection-writer/generator stack, alongside shared build-tool infrastructure and a C#-authored internal metadata project.

Motivation

CsWinRT 3.0 moves all of its build tooling to managed, Native-AOT-published CLI tools (see the WinRT.*.Generator family). The projection generator was the last major piece still implemented as the native C++ cswinrt.exe. Porting it to C# removes the final native build dependency, lets the projection writer share the same infrastructure (response-file parsing, debug repro, error contracts, public keys, etc.) as the other generators, and makes the code far easier to evolve and test. Doing this on a dedicated staging/CodeWriters branch let the port land incrementally and stay continuously validated before merging back into staging/3.0.

Changes

This rolls up the following merged PRs (see each for full detail):

Net structural effect (313 files, roughly +26.6k / -19.4k lines):

  • Removed: src/cswinrt (the legacy native C++ projection generator).
  • Added: src/WinRT.Projection.Writer (managed code writers), src/WinRT.Projection.Ref.Generator (reference projection generator), src/WinRT.Generator.Core (shared build-tool infrastructure), and src/WinRT.Internal (C#-authored internal metadata).
  • Updated: src/WinRT.Projection.Generator, the other generator projects, src/Projections, samples, tests, the solution file, and Directory.Build.props wiring.

Validation

Each underlying PR was validated independently (clean builds with zero warnings, Native-AOT publish, and end-to-end projection generation against Windows.winmd with byte-level determinism checks). The branch has also been refreshed from staging/3.0 so it merges cleanly.

Sergio0694 and others added 10 commits May 9, 2026 02:06
* Port ABI Methods static class bodies for Out parameters

Mirrors C++ write_abi_method_call_marshalers Out branch. For each Out parameter:
- Function pointer signature: void** (string/object/runtime class), <BlittableStruct>*, or <Primitive>*
- Declare local with appropriate ABI type (default-initialized)
- Pass &__<name> to the vtable call
- After call: write back to caller's 'out' var via the appropriate ConvertToManaged
  (HStringMarshaller, WindowsRuntimeObjectMarshaller, <Marshaller>, or direct cast)
- Free string/object/runtime-class out values in finally

throw null!: 680 -> 621 (-59)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Allow blittable struct arrays in ABI Methods PassArray (e.g. ReadOnlySpan<RectInt32>)

throw null!: 621 -> 589 (-32)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Wire IDictionary CopyTo to use both IDictionary and IEnumerable<KVP> objref fields

Mirrors C++ truth: CopyTo dispatches via IDictionaryMethods_<K>_<V>_CopyTo(null, <objRef>, <enumObjRef>, array, arrayIndex). The CopyTo accessor takes an extra WindowsRuntimeObjectReference parameter for the enumerable.

throw null!: 589 -> 570 (-19)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Port ReferenceImpl get_Value for non-blittable runtime classes and delegates

For non-blittable runtime class / delegate ReferenceImpl, emit:

    <Projected> unboxedValue = (<Projected>)ComInterfaceDispatch.GetInstance<object>((ComInterfaceDispatch*)thisPtr);
    void* value = <Name>Marshaller.ConvertToUnmanaged(unboxedValue).DetachThisPtrUnsafe();
    *(void**)result = value;

Mirrors C++ write_reference_impl runtime-class/delegate path.

throw null!: 570 -> 509 (-61)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Port Ref (in T) blittable param support in ABI Methods static class

Function pointer signature gets the ABI scalar/blittable-struct type directly (passed by value to the vtable).

throw null!: 509 -> 489 (-20)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Use fixed(T*) blocks for in T params and pass pinned pointer to vtable

Mirrors C++ truth: 'in Guid target' generates 'fixed(Guid* _target = &target) { ... pass _target ... }' and the function pointer signature uses 'Guid*' for the parameter.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Port static class events to dispatch through ABI Methods event helpers

For static event members on classes:
- Public static event with add/remove that delegates to <ABIClass>.<Name>(_objRef, _objRef).Subscribe(value) / .Unsubscribe(value)
- Static ABI Methods class now emits per-event ConditionalWeakTable<object, EventSource> static field plus a static method that returns an EventSource for the (thisObject, thisReference) pair, lazily creating the source via [UnsafeAccessor(Constructor)] for generic events or new <DelegateName>EventSource(...) for non-generic delegates.
- Vtable slot for events advances by 2 per event (add + remove).

throw null!: 489 -> 414 (-75)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Port ABI Methods bodies for HResult/Exception return type

For static ABI Methods that return Exception/HResult:
- Function pointer return: 'global::ABI.System.Exception*'
- Local: 'global::ABI.System.Exception __retval = default;'
- Return: 'return global::ABI.System.ExceptionMarshaller.ConvertToManaged(__retval);'

throw null!: 414 -> 327 (-87)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Allow blittable struct ReceiveArray return in ABI Methods static class

throw null!: 327 -> 322 (-5)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Emit DelegateName Impl and ReferenceImpl classes for delegate CCW vtable

Each delegate now emits:
- <DelegateName>Vftbl struct (QI/AddRef/Release/Invoke)
- <DelegateName>NativeDelegate static class with <DelegateName>Invoke extension method
- <DelegateName>InterfaceEntriesImpl file static class
- <DelegateName>Impl internal static class (CCW vtable, with Invoke UnmanagedCallersOnly)
- <DelegateName>ReferenceImpl file static class (IReference vtable, get_Value)

The Invoke body for the CCW dispatch handles bool/byte, char/ushort, enum/underlying,
and string conversions, plus the standard pattern for runtime classes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Emit full body for delegate ComWrappersMarshallerAttribute and ComWrappersCallback

Non-generic delegate ComWrappersMarshallerAttribute now emits:
- GetOrCreateComInterfaceForObject (uses TrackerSupport flag)
- ComputeVtables (uses InterfaceEntriesImpl.Entries)
- CreateObject (uses WindowsRuntimeDelegateMarshaller.UnboxToManaged)

Non-generic delegate ComWrappersCallback (emitted as 'file abstract') now contains
a CreateObject body that creates the object reference and constructs the projected
delegate via the NativeDelegate Invoke extension method when supported.

Generic delegates retain throw null bodies (no per-instantiation infrastructure
emitted in the projection layer).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Emit get_Value body for non-blittable struct ReferenceImpl

Even non-blittable struct types (e.g. those with bool/char fields) can be
copied directly via 'var value = (T)GetInstance<object>(...); *(T*)result = value;'
since the C# struct field layout matches the WinRT ABI for these primitives.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Support almost-blittable structs in ABI Methods bodies and marshaller class

Almost-blittable structs are structs with only primitive fields (including bool/char,
which have matching C# and WinRT ABI layouts) and no reference type fields.

These can pass directly across the WinRT ABI without per-field marshalling. Now:
- EmitAbiMethodBodyIfSimple supports them for params, returns, and out params
- WriteStructEnumMarshallerClass emits BoxToUnmanaged/UnboxToManaged with the simple
  WindowsRuntimeValueTypeMarshaller pattern
- Complex structs (with string/object fields) still emit throw null stubs for
  ConvertToUnmanaged/ConvertToManaged/Dispose pending field-level marshalling support

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Treat byref-wrapped array params as ReceiveArray

WinRT metadata represents 'out byte[]' as 'byte[]&' (ByRef wrapping SzArray).
Previously this was categorized as ParamCategory.Out and emitted as 'out byte value'
instead of 'out byte[] value'. Now ParamHelpers.GetParamCategory peels through ByRef
to detect array params, and WriteProjectionParameterType for ReceiveArray peels
through ByRef to extract the element type.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Support ReceiveArray params (out T[]) in ABI Methods bodies

Adds full marshalling for 'out byte[]' / 'out T[]' style parameters where T is a
blittable primitive or almost-blittable struct. Pattern follows the existing
ReceiveArray return value support: declare uint length + T* data locals, pass
&__name_length, &__name_data to the vtable, then convert via UnsafeAccessor
ConvertToManaged_<name> and free in the finally block.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Treat FillArray (Span<T> in/out) params identically to PassArray in ABI Methods

Truth uses 'fixed(void* _name = name)' + (uint)name.Length, _name pattern for
Span<T> params regardless of whether they're in (PassArray) or in/out (FillArray).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Support array params in factory ctor callback Invoke

Factory callback now handles ReadOnlySpan<T>/Span<T> params (PassArray/FillArray)
for blittable element types. Args struct binding uses Span<T>/ReadOnlySpan<T>,
and the callback invokes via 'fixed(void* _name = name)' + (uint)name.Length, _name
pattern matching the truth.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Support return values in delegate Impl Invoke (CCW dispatch)

Now emits *__retval writeback after calling the managed delegate, with proper
conversion: bool -> byte, char -> ushort, enum -> underlying, string via
HStringMarshaller, runtime class via Marshaller.ConvertToUnmanaged + DetachThisPtrUnsafe.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Support ReceiveArray returns of strings/runtime classes/objects

Element ABI type is 'void*' (function pointer signature uses 'void**'),
local is 'void**', UnsafeAccessor type path is rooted at the element type's
namespace (e.g. 'ABI.Windows.AI.Actions.Hosting.<...>ArrayMarshaller') for
runtime classes, or 'ABI.System.<...>ArrayMarshaller' for strings/blittable.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Emit per-field marshalling for complex structs with string fields

Complex structs (with string fields, but no other reference types) now emit:
- ConvertToUnmanaged: builds ABI struct via field-by-field assignment with
  HStringMarshaller.ConvertToUnmanaged for string fields
- ConvertToManaged: constructs projected struct via constructor accepting
  the marshalled fields
- Dispose: frees string fields via HStringMarshaller.Free
- BoxToUnmanaged: same simple WindowsRuntimeValueTypeMarshaller pattern
- UnboxToManaged: unboxes to ABI struct then ConvertToManaged

Also fixes ABI struct field types: enum/bool now use the projected type
(matching the truth) instead of the ABI primitive form (int/byte). The C#
struct layout matches the WinRT ABI directly for these primitives.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Support generic instance params in delegate Impl Invoke

Emits UnsafeAccessor static externs to call ConvertToManaged for generic
instance types (e.g. IEnumerable<T>) and uses them in the dispatch.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Support return values in NativeDelegate Invoke extension method

EmitNativeDelegateBody now handles delegates with simple return types: blittable
primitives, almost-blittable structs, strings, runtime classes, objects.
Patterns mirror the truth: declare __retval local of ABI type, pass &__retval
to vtable, convert back to managed return type, free if needed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Support complex struct returns in ABI Methods bodies

Complex structs (with reference type fields like string or Nullable<T>) now
get full marshalling: ABI struct local + Marshaller.ConvertToManaged + Dispose
in finally. Mirrors the truth pattern for properties returning structs like
AccessListEntry / StorePackageUpdateStatus.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Simplify if-else for IDE0045 in Release build

Use ternary expressions for elementAbi assignment in receive-array path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Support PassArray of strings/runtime classes/objects in ABI Methods bodies

Ports the C++ logic from cswinrt code_writers.h:
- For runtime class/object element: declare InlineArray16<nint> + ArrayPool<nint>
  fallback for the data buffer, fixed(void* _name = __name_span), emit
  CopyToUnmanaged_<name> UnsafeAccessor and call before vtable, Dispose_<name>
  + ArrayPool<nint>.Shared.Return in finally.
- For string element: also declare InlineArray16<HStringHeader> + InlineArray16<nint>
  (pinned handles), use HStringArrayMarshaller.ConvertToUnmanagedUnsafe to fill,
  HStringArrayMarshaller.Dispose + return all 3 ArrayPools in finally.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Support PassArray of strings in ABI Methods bodies (HStringArrayMarshaller path)

Allow PassArray of string elements in the simple-body check, and emit a combined
fixed block 'fixed(void* _name = __name_span, _name_inlineHeaderArray = __name_headerSpan)'
to provide both the data pointer and the header pointer needed by
HStringArrayMarshaller.ConvertToUnmanagedUnsafe. Mirrors C++ cswinrt
write_locals/write_fixed_marshaler/write_dispose patterns.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Support Nullable<T> fields in complex struct marshalling

In WinMD metadata, Nullable<T> is encoded as Windows.Foundation.IReference<T>.
Detect this in the field walk and emit the appropriate marshalling:
- ConvertToUnmanaged: ABI.System.<TypeName>Marshaller.BoxToUnmanaged(value.<f>).DetachThisPtrUnsafe()
- ConvertToManaged: ABI.System.<TypeName>Marshaller.UnboxToManaged(value.<f>)
- Dispose: WindowsRuntimeUnknownMarshaller.Free(value.<f>)
- ABI struct field: void* (the IReference<T>* pointer)
- BoxToUnmanaged: use CreateComInterfaceFlags.TrackerSupport when struct has reference fields

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Support PassArray of blittable elements in delegate Impl Invoke (CCW)

Construct ReadOnlySpan<T>/Span<T> from the (length, pointer) ABI pair before
calling the managed delegate's Invoke. Mirrors the C++ truth pattern.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Support PassArray of blittable elements in NativeDelegate Invoke ext method

Adds fixed(void* _name = name) block + (uint)name.Length, _name args to call.
Updates IsDelegateInvokeNativeSupported predicate to allow PassArray of blittable
elements. This also enables ComWrappersCallback.CreateObject to construct the
delegate via the NativeDelegate Invoke method group.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Use projected struct type for almost-blittable struct ABI signatures

Update WriteAbiType to emit the projected struct type (not ABI) for almost-blittable
structs (those with only primitive fields, no string/Nullable<T>/object fields).
Also extend EmitDoAbiBodyIfSimple to support these structs in returns and Out params.

This matches the truth pattern where e.g. BandwidthStatistics is passed across the
WinRT ABI directly without an ABI struct wrapper.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Support Out string/runtime class/object params in Do_Abi (CCW dispatch)

EmitDoAbiBodyIfSimple now allows Out/Ref params with string/runtime class/object
underlying type. Writeback uses HStringMarshaller.ConvertToUnmanaged for strings
and Marshaller.ConvertToUnmanaged(...).DetachThisPtrUnsafe() for runtime classes
and objects.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Support HResult/Exception return type in Do_Abi (CCW dispatch)

Use global::ABI.System.Exception for the ABI representation of HResult/Exception
returns (matching truth's encoding). Emit *__retval = ExceptionMarshaller.ConvertToUnmanaged
for the writeback. Now the Vftbl signature for cross-module HResult-returning methods
matches truth: 'global::ABI.System.Exception*' instead of 'int*'.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Support HResult/Exception input param in ABI Methods bodies

Param check now allows HResult/Exception. Function pointer signature uses
'global::ABI.System.Exception' for the param type. Local converted up-front
via global::ABI.System.ExceptionMarshaller.ConvertToUnmanaged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Support PassArray of strings/runtime classes/objects in factory ctor callback

Factory ctor Invoke now handles non-blittable PassArray params via the same
InlineArray16 + ArrayPool pattern as ABI Methods bodies, including the string
case with HStringArrayMarshaller and the triple inline array dance for headers
and pinned handles. Wraps in try/finally with full cleanup.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Support PassArray of strings/runtime classes/objects in Do_Abi (CCW dispatch)

EmitDoAbiBodyIfSimple now handles non-blittable PassArray params via:
- InlineArray16<T> + ArrayPool<T> declaration (T = projected element type)
- CopyToManaged_<name> via UnsafeAccessor inside try block
- Pass __<name> Span<T> to managed call
- finally block with ArrayPool<T>.Shared.Return cleanup

Also fixes ABI param signature for SzArray of ref-types: use 'void*' (single
star) instead of 'void**' to match truth (mirrors C++ pass_array signature).

This was the last throw null!. The C# port now produces functionally equivalent
output to the original C++ cswinrt for the entire Windows SDK projection.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix nullability warning for Nullable<T> marshaller name in Release build

Suppress nullable check via ! since marshallerName is non-null when the
TryGetNullablePrimitiveMarshallerName returns true.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Match truth visibility for ABI Methods static class

The C++ generator emits 'internal' for ABI Methods classes only when the parent
interface is exclusive to a class (and not opted into PublicExclusiveTo) or
marked [ProjectionInternal]. Otherwise it emits 'public'. My code always emitted
'internal'. This caused 347 ABI Methods classes to have wrong visibility relative
to truth.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Skip empty ABI Methods classes when interface has no methods/props/events

C++ generator's write_static_abi_classes skips emission when 'members.empty()'
(after collecting all method/property/event members). My emitter now checks
type.Methods (non-special) + type.Properties + type.Events before emitting.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Skip non-default/non-overridable exclusive interface emission

Mirrors C++ write_interface skip rule: exclusive interfaces other than the default
and overridable ones are not used in the projection. Skip them unless
public_exclusiveto is set (or in reference projection or component mode).

Adds IsDefaultOrOverridableInterfaceTypedef helper that walks the exclusive class
type's InterfaceImpl entries looking for [Default] or [Overridable] interfaces
that match the given interface.

Also adds the proper *ComWrappersMarshallerAttribute (with body) and
*InterfaceEntriesImpl static class for enums/structs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Skip events on exclusive interfaces in ABI Methods (mirrors C++ skip_exclusive_events)

When an interface is exclusive to a class (and not opted into PublicExclusiveTo),
its events are inlined directly in the RCW class. The Methods type should not
emit those event members. If the resulting Methods type would be empty, skip
emission entirely.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix nullability warning in IsDefaultOrOverridableInterfaceTypedef helper

* Emit IsOverridableInterface override on runtime classes

Mirrors C++ write_custom_query_interface_impl: emit
'protected override bool IsOverridableInterface(in Guid iid) => ...'
where the body checks each [Overridable] interface's IID, optionally calls
base.IsOverridableInterface, or returns false.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Skip event member emission in ABI Methods for exclusive interfaces

The Methods class shouldn't emit event helpers when the interface is exclusive
to a class — those events are inlined directly in the RCW class instead.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Emit static members on instance runtime classes (e.g. GetForCurrentView)

Instance runtime classes can also declare static factory methods via [Static]
attribute on the class (separate from [Activatable]). The C++ generator emits
both via write_class_members + write_static_members. We were only calling
WriteClassMembers — now also call WriteStaticClassMembers for instance classes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Use explicit interface impl for ICollection<KVP>.Remove on dictionaries

When a class implements both IDictionary<K,V>.Remove(K key) and ICollection<KVP>.Remove(KVP item),
the second one must use explicit interface implementation to avoid signature collision.
Truth pattern: 'bool ICollection<KeyValuePair<K,V>>.Remove(KeyValuePair<K,V> item) => ...'

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Use projected enum type in ABI signatures (matches truth)

WriteAbiType and GetAbiPrimitiveType now emit the projected enum type for enum
parameters and returns (instead of the underlying primitive). C# enums have the
same memory layout as their underlying type, so this is functionally equivalent
but matches the truth's signature shape. Removed all enum casts in delegate
Invoke / NativeDelegate / ABI Methods / Do_Abi / factory ctor body emission
since the function pointer signature now matches the projected type directly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Remove unused EmitProjectedTypeName helper

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Match truth marshaller for unsealed runtime classes

Wires up two related semantic differences vs the C++ baseline:

1. ComWrappers marshalingType: was hardcoded to Standard/Agile in
   the public marshaller attribute and the callback. Now derives
   from the class's [MarshalingBehaviorAttribute] (mirrors
   get_marshaling_type_name) and uses the same value for both.

2. Unsealed runtime classes:
   - public Marshaller.ConvertToUnmanaged uses either
     IWindowsRuntimeInterface<DefIface>.GetInterface (when default
     interface is non-exclusive) or value.GetDefaultInterface()
     (when it is exclusive).
   - public Marshaller.ConvertToManaged routes through
     WindowsRuntimeUnsealedObjectMarshaller (vs WindowsRuntimeObjectMarshaller).
   - file-scoped *ComWrappersCallback implements
     IWindowsRuntimeUnsealedObjectComWrappersCallback with both
     TryCreateObject (runtimeClassName match) and CreateObject.
   - Class members get an 'internal [new] WindowsRuntimeObjectReferenceValue
     GetDefaultInterface()' helper when default iface is exclusive.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Match truth EventHandlerEventSource interop type encoding

For event-handler delegate types in Windows.Foundation namespace
(EventHandler<T>, TypedEventHandler<TSender, TResult>), the C++
generator emits a special UnsafeAccessorType encoding that points
to the WinRT.Runtime EventHandlerEventSource type:

  ABI.WindowsRuntime.InteropServices.<#CsWinRT>EventHandlerEventSource'<arity><...>

Mine was emitting:

  ABI.System.<#corlib>EventHandler'<arity><...>EventSource

(applying the .NET type mapping then appending 'EventSource' suffix).

This is a runtime-impacting difference: the UnsafeAccessorType
string is resolved at runtime, so an incorrect type name would
cause projections to fail at startup.

Mirrors C++ write_interop_dll_type_name_for_typedef special case
at code_writers.h:493-496.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Emit protected base-chaining ctors and real composable bodies

Mirrors C++ write_composable_constructors completely:

1. Public/protected composable ctors now have a real body (was 'throw null!'):
   - Parameterless: chains to 'base(default(WindowsRuntimeActivationTypes.DerivedComposed),
     factoryObjRef, iid, marshalingType)'
   - Parameterized: chains to 'base(callback.Instance, iid, marshalingType,
     WindowsRuntimeActivationArgsReference.CreateUnsafe(new ArgsStruct(...)))'
   - Body initializes the default interface objref when 'GetType() == typeof(ClassName)'
   - Adds GC.AddMemoryPressure when [GCPressure] is set on the class.

2. Four protected base-chaining ctors are now emitted on every composable
   (i.e. unsealed) class so derived projected types can chain through:
   - protected ClassName(WindowsRuntimeActivationTypes.DerivedComposed _, ...)
   - protected ClassName(WindowsRuntimeActivationTypes.DerivedSealed _, ...)
   - protected ClassName(WindowsRuntimeActivationFactoryCallback.DerivedComposed ..., ...)
   - protected ClassName(WindowsRuntimeActivationFactoryCallback.DerivedSealed ..., ...)
   Each forwards the args to the matching base class constructor, with
   GC.AddMemoryPressure if applicable.

3. The factory objref is emitted at the top (was missing before), so the
   parameterless ctor can reference it.

This was previously missing entirely on classes like CompositionAnimation
and similar — derived classes (e.g. CompositionAnimation -> KeyFrameAnimation
-> ColorKeyFrameAnimation) couldn't chain through the activation hierarchy.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Emit base class for unsealed runtime classes inheriting from another class

C++ write_type_inheritance accepts a base_semantics parameter and writes
the projected base type when it's not System.Object. My port was always
emitting WindowsRuntimeObject as the base, flattening the inheritance
hierarchy:

  Before: class CompositionAnimation : WindowsRuntimeObject, ...
  After:  class CompositionAnimation : Windows.UI.Composition.CompositionObject, ...

This matters for derived runtime classes (e.g. CompositionAnimation extends
CompositionObject; KeyFrameAnimation extends CompositionAnimation; etc.).
Without the right base, type checks (is/as) and member resolution would
behave incorrectly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Use 'Remove' (not 'RemovePair') for ICollection<KVP>.Remove accessor

The C++ generator emits two same-named UnsafeAccessor methods named
'Remove' on the IDictionary stub: one for IDictionary<K,V>.Remove(K key)
and one for ICollection<KVP>.Remove(KVP item). C# overload resolution
distinguishes them by parameter type, so no rename is needed.

Mine was disambiguating by renaming the KVP overload to 'RemovePair'.
This is a meaningful diff vs the C++ baseline because the actual
UnsafeAccessor target name is 'Remove', and using a different alias
isn't strictly wrong but creates a divergence vs truth.

The explicit 'ICollection<KVP>.Remove' interface implementation that
calls into it remains (no naming clash since both overloads of the
helper are now both named 'Remove').

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Use 'bool'/'char' (not 'byte'/'ushort') for Boolean/Char ABI

The C++ generator emits the C# 'bool' and 'char' types directly in
the ABI signatures (delegate function pointers and Do_Abi method sigs),
relying on DisableRuntimeMarshalling to avoid blittability issues:

  Before: delegate* unmanaged[MemberFunction]<void*, byte, int>
  After:  delegate* unmanaged[MemberFunction]<void*, bool, int>

  Before: private static int Do_Abi_X(void* thisPtr, byte* __retval)
  After:  private static int Do_Abi_X(void* thisPtr, bool* __retval)

This is a real semantic difference vs the C++ baseline. Although both
are 1 byte (bool with DisableRuntimeMarshalling), the C# type signature
of the delegate pointer is different.

Removed the now-unused conversion code:
- '(byte)(value ? 1 : 0)' for input bool params -> just pass the bool
- '__retval != 0' for output bool returns -> just return the bool
- '(ushort)value' for input char params -> just pass the char
- '(char)__retval' for output char returns -> just return the char

Also updated GetAbiPrimitiveType, GetAbiFundamentalType, and the
Do_Abi body emission paths to consistently use bool/char.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Format enum I4/U4 values as hex (matches C++ write_constant)

Truth uses hex format for enum values:
  File = unchecked((uint)0x1)
Mine was using decimal:
  File = unchecked((uint)1)

Mirrors C++ write_constant which uses '%#0x' printf format for both
Int32 and UInt32 constants (code_writers.h:10065-10076).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Emit IDIC explicit interface event impls with Subscribe/Unsubscribe bodies

The IDIC (DynamicInterfaceCastable) implementation file interfaces
were emitting events as abstract declarations:

  event T EventName;

Truth emits them as full explicit interface implementations that
dispatch through the static ABI Methods class:

  event T IInterface.EventName
  {
      add
      {
          var _obj = ((WindowsRuntimeObject)this).GetObjectReferenceForInterface(typeof(IInterface).TypeHandle);
          IInterfaceMethods.EventName((WindowsRuntimeObject)this, _obj).Subscribe(value);
      }
      remove
      {
          var _obj = ...;
          IInterfaceMethods.EventName((WindowsRuntimeObject)this, _obj).Unsubscribe(value);
      }
  }

Without these bodies, the IDIC interface couldn't actually wire up
events when the runtime used IDynamicInterfaceCastable to dispatch
event subscriptions. Mirrors C++ write_interface_members event handling.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Use lazy default-interface objref + ctor init for unsealed runtime classes

The C++ generator passes 'replaceDefaultByInner = type.Flags().Sealed()'
to write_class_objrefs_definition. For sealed classes (replaceDefaultByInner=true),
the default interface is emitted as a simple expression-bodied
'private WindowsRuntimeObjectReference _objRef_X => NativeObjectReference;'.
For unsealed classes (replaceDefaultByInner=false), ALL interfaces (including
the default) use the lazy MakeObjectReference pattern, and the default also
gets an 'init;' accessor so the constructor can set it.

Mine was always using the simple pattern for the default interface, regardless
of whether the class was sealed. This is incorrect for unsealed classes because
derived projected types have their own default interface (different IID), and
the default objref must be lazy-initialized via QueryInterface.

Also fixed the constructor body for unsealed classes — was a placeholder
comment ('// Default interface objref initialization (simplified port)'),
now properly emits:

  if (GetType() == typeof(ClassName))
  {
      _objRef_<DefaultIface> = NativeObjectReference;
  }

This relies on the new 'init;' accessor on the default objref property.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add IID property to *ReferenceImpl classes (matches C++ write_reference_impl)

The C++ generator emits a 'public static ref readonly Guid IID' property
at the end of the *ReferenceImpl class for every enum/struct/etc. that
gets a reference impl:

  public static ref readonly Guid IID
  {
      [MethodImpl(MethodImplOptions.AggressiveInlining)]
      get => ref global::ABI.InterfaceIIDs.IID_<EscapedABIName>Reference;
  }

This IID is referenced by the corresponding ComWrappersMarshallerAttribute
to box the value back to its managed form via WindowsRuntimeValueTypeMarshaller.

Without this property, the generated reference impl class would compile but
the marshaller couldn't locate the IID at runtime.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Emit WindowsRuntimeReferenceType attribute on enum projections

The C++ generator emits '[WindowsRuntimeReferenceType(typeof(EnumName?))]'
attribute on enum projections (mirrors C++ write_enum at line 10097
calling write_winrt_reference_type_attribute). This attribute lets the
runtime resolve the correct nullable boxing type for value-type WinRT
references like 'Windows.Foundation.IReference\1<EnumName>'.

The same attribute is already emitted on struct projections (already
present in WriteStruct).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Match interop generator's array marshaller name encoding

The interop generator's InteropUtf8NameFactory.TypeName method (used to
name array marshaller types) follows two rules at depth=0 (top-level type):

1. The full path is 'ABI.<<assembly>ElementName>ArrayMarshaller'
   (no namespace prefix outside the brackets).
2. Inside the brackets, the element type uses just its name (no namespace),
   prefixed with the assembly marker.

Mine was emitting:
  ABI.Windows.AI.Actions.<<#Windows>Windows-AI-Actions-ActionEntity>ArrayMarshaller

Truth (and interop generator) emit:
  ABI.<<#Windows>ActionEntity>ArrayMarshaller

This is a runtime-impacting fix — the UnsafeAccessorType string is matched
at runtime against the interop generator's emitted type name, so any
mismatch would fail to resolve the marshaller.

Added EncodeArrayElementName helper that mirrors the interop generator's
AppendRawTypeName at depth=0, and updated GetArrayMarshallerInteropPath
to use it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Restore typeNamespace prefix in array marshaller path

The previous commit removed the typeNamespace prefix incorrectly. The
truth actually emits 'ABI.<typeNamespace>.<<assembly>ElementName>ArrayMarshaller'
where the typeNamespace IS the path prefix (matching C++ format string
'%.<%%' at write_interop_dll_type_name_for_typedef line 513).

Inside the brackets, however, the element name uses just the type name
(no namespace) — that part of my previous fix was correct.

Truth:  ABI.Windows.AI.Actions.<<#Windows>ActionEntity>ArrayMarshaller
Mine:   ABI.Windows.AI.Actions.<<#Windows>ActionEntity>ArrayMarshaller (now)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Use 'System' namespace for fundamentals in array marshaller path

For string arrays, the marshaller path is 'ABI.System.<string>ArrayMarshaller'
because string is in the System namespace. Mine was returning empty for
fundamentals in GetMappedNamespace, which produced 'ABI.<string>ArrayMarshaller'.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Use metadata return param name in Do_Abi method signatures and bodies

The C++ generator uses the WinMD return parameter name in Do_Abi method
signatures and bodies (e.g. 'value' for getters, 'cookie' for event adds,
'clientIDBytes' for ClientID receive-array), with the size pointer for
arrays named '__<name>Size'. Mine was using hardcoded '__retval' and
'__retvalLength'.

Truth:  Do_Abi_get_AccountId_0(void* thisPtr, Guid* value)
Mine:   Do_Abi_get_AccountId_0(void* thisPtr, Guid* __retval) (was)

Truth:  Do_Abi_get_ClientID_6(void* thisPtr, uint* __clientIDBytesSize, byte** clientIDBytes)
Mine:   Do_Abi_get_ClientID_6(void* thisPtr, uint* __retvalLength, byte** __retval) (was)

Truth:  Do_Abi_add_LoggingEnabled_7(void* thisPtr, void* handler, EventRegistrationToken* token)
Mine:   Do_Abi_add_LoggingEnabled_7(void* thisPtr, void* handler, EventRegistrationToken* __retval) (was)

Mirrors C++ '__%Size' convention from helpers/code_writers (line 1305-1308, 1366-1368).

Added GetReturnParamName(sig) and GetReturnSizeParamName(sig) helpers,
and threaded them through:
- WriteAbiParameterTypesPointer (signature)
- EmitDelegateInvokeBody (delegate Invoke body)
- EmitDoAbiBodyIfSimple (method/property Do_Abi body)
- EmitDoAbiAddEvent (event add Do_Abi body)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Use short type names (no namespace) in mapped collection helper identifiers

C# helper method names for mapped collection stubs (e.g.
IReadOnlyDictionaryMethods_string_NamedResource_Keys) should use the
short type name 'NamedResource', not the fully-qualified
'Windows_ApplicationModel_Resources_Core_NamedResource' form.

Mine was passing forceWriteNamespace=true to WriteTypeName, which
produced 'global::Windows.ApplicationModel.Resources.Core.NamedResource'
that escape_type_name_for_identifier then turned into a long underscore-
separated identifier.

Mirrors C++ write_readonlydictionary_members_using_static_abi_methods
which uses 'escape_type_name_for_identifier(write_projection_type(arg), true)'
where write_projection_type does NOT force namespace qualification.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Use 'Guid' (not 'global::System.Guid') in projected/ABI signatures

The C++ generator emits just 'Guid' for the Guid type semantics (mirrors
'guid_type' case in write_projection_type_for_name_type and write_abi_type
which both write just 'Guid'). The 'using System;' directive at the top
of every projection file makes this resolvable.

Mine was emitting 'global::System.Guid' which produced visible diffs in
identifiers (e.g. _objRef_System_Collections_Generic_IDictionary_global__System_Guid__object_)
and Do_Abi method signatures.

Truth:  Do_Abi_get_AccountId_0(void* thisPtr, Guid* value)
Mine:   Do_Abi_get_AccountId_0(void* thisPtr, global::System.Guid* value)

Same fix applied to Type_ semantics ('global::System.Type' -> 'Type').

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Use short EventSource name (no namespace prefix) inside ABI Methods class

For non-generic delegate event handlers, the EventSource class lives in
the same ABI namespace as the static Methods class that references it.
Truth uses just the short name (e.g. 'ActivatedEventHandlerEventSource')
instead of the fully-qualified 'global::ABI.Windows.UI.WebUI.ActivatedEventHandlerEventSource'.

This matches the C++ generator which writes 'EventHandlerEventSource'
unprefixed since it's in the same scope.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Strip System.Collections.Generic prefix in mapped collection stubs

Truth uses unqualified collection type names (IEnumerator<T>, ICollection<T>,
KeyValuePair<K,V>) since 'using System.Collections.Generic;' is in scope.
Mine was emitting the fully-qualified 'global::System.Collections.Generic.X'
form, which produced visible (cosmetic) diffs throughout mapped collection
stubs.

The fields generated by WriteClassObjRefDefinitions still use the long form
(e.g. _objRef_System_Collections_Generic_IEnumerable_global__System_Collections_Generic_KeyValuePair_string__object__),
so I kept a 'kvLong' variable to compute the matching enumerableObjRefName.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Use 'override' modifier for class methods named 'ToString' returning string

When a runtime class implements IStringable, the projected ToString()
method should be marked 'override' to override Object.ToString(). C++
write_class_method (line 1942-1959) detects this pattern: method name
'ToString', no parameters, returns string -> use 'override' modifier
(and force the return type to 'string').

Mine was emitting 'public string ToString() => ...' which technically
hides Object.ToString() rather than overriding it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Use ABI marshallers for mapped value types (DateTime, TimeSpan)

Mapped value types (Windows.Foundation.DateTime -> System.DateTimeOffset,
Windows.Foundation.TimeSpan -> System.TimeSpan) require explicit marshaller
conversion between projected and ABI representations because their managed
.NET layout doesn't match the WinRT ABI layout.

Truth pattern (mirrors C++ write_abi_type/marshaller behavior):

  // Function pointer signature uses ABI type
  delegate* unmanaged[MemberFunction]<void*, ABI.System.TimeSpan, void**, int>

  // Caller-side input: convert via marshaller before passing
  ABI.System.TimeSpan __value = ABI.System.TimeSpanMarshaller.ConvertToUnmanaged(value);
  // ... pass __value to function pointer

  // Caller-side return: declare ABI local, convert back via marshaller
  ABI.System.TimeSpan __retval = default;
  // ... call function pointer with &__retval
  return ABI.System.TimeSpanMarshaller.ConvertToManaged(__retval);

  // Do_Abi (CCW) input: convert ABI param to managed before calling user method
  user.Method(ABI.System.TimeSpanMarshaller.ConvertToManaged(value))

  // Do_Abi return: convert managed result to ABI for output
  *retval = ABI.System.TimeSpanMarshaller.ConvertToUnmanaged(result);

This commit covers:

1. **WriteAbiType**: emit 'global::ABI.System.{DateTimeOffset,TimeSpan}'
   for the corresponding Windows.Foundation types in both Definition and
   Reference type semantics paths.

2. **Factory callback Invoke body**: declare ABI local with marshaller
   conversion, pass __<name> in delegate function pointer call.

3. **Do_Abi method body — return**: convert managed __result via marshaller
   when assigning to *<retval>.

4. **Do_Abi method body — input**: convert ABI parameter via marshaller
   before passing to the user's managed method.

Helpers added:
- IsMappedMarshalingValueType / IsMappedAbiValueType
- GetMappedProjectedTypeName / GetMappedAbiTypeName / GetMappedMarshallerName

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Remove unused GetMappedProjectedTypeName helper

* Caller-side ABI marshalling for mapped value types in static ABI methods

Extends commit '983f0d01 Use ABI marshallers for mapped value types' to
cover the static ABI Methods caller-side bodies (the ones that call into
the function pointer):

1. **Input mapped value type**: declare ABI local with marshaller
   conversion before call, pass __<name> in delegate function pointer call.
2. **Return mapped value type**: __retval is declared as ABI type
   (via GetBlittableStructAbiType returning the ABI name), and the
   return statement converts via marshaller.

GetBlittableStructAbiType now returns 'global::ABI.System.{DateTimeOffset,
TimeSpan}' for mapped value types so __retval has the correct type.

Truth pattern (matches now):

  public static unsafe TimeSpan OldPosition(WindowsRuntimeObjectReference thisReference)
  {
      using WindowsRuntimeObjectReferenceValue thisValue = thisReference.AsValue();
      void* ThisPtr = thisValue.GetThisPtrUnsafe();
      global::ABI.System.TimeSpan __retval = default;
      RestrictedErrorInfo.ThrowExceptionForHR((*(delegate* unmanaged[MemberFunction]<
          void*, global::ABI.System.TimeSpan*, int>**)ThisPtr)[7](ThisPtr, &__retval));
      return global::ABI.System.TimeSpanMarshaller.ConvertToManaged(__retval);
  }

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Emit ABI struct + per-field marshalling for structs with mapped value type fields

Structs that contain mapped value-type fields (DateTime/TimeSpan) are NOT
blittable (per C++ is_type_blittable: 'mapping->requires_marshaling' returns
true => blittable=false). For these, the C++ generator emits:

1. **ABI struct** with ABI-typed fields (e.g. ABI.Windows.Media.Core.MseTimeRange
   with global::ABI.System.TimeSpan Start/End fields).
2. **Marshaller class** with ConvertToUnmanaged/ConvertToManaged that
   iterate over fields and use the per-field marshaller.

Mine was treating MseTimeRange as 'almost-blittable' (since IsAnyStruct
recursively succeeded for TimeSpan: 1 Int64 field), so it skipped the ABI
struct emission entirely.

Updates:

- IsAnyStruct: short-circuit to false when the type is a mapped struct
  with RequiresMarshaling=true. Mirrors C++ is_type_blittable struct case.
- IsFieldTypeBlittable: same check for nested struct fields.
- WriteAbiStruct: emit 'global::ABI.System.{DateTimeOffset,TimeSpan}'
  for fields whose type is a mapped value type.
- WriteStructEnumMarshallerClass: in the per-field ConvertToUnmanaged/
  ConvertToManaged body, call the field marshaller's ConvertToUnmanaged/
  ConvertToManaged for mapped value-type fields.
- allFieldsSupported check: accept mapped value type fields (no longer
  forces 'throw null!').

Truth pattern (matches now):

  // ABI struct
  public unsafe struct MseTimeRange
  {
      public global::ABI.System.TimeSpan Start;
      public global::ABI.System.TimeSpan End;
  }

  // Marshaller body
  public static MseTimeRange ConvertToUnmanaged(global::Windows.Media.Core.MseTimeRange value)
  {
      return new() {
          Start = global::ABI.System.TimeSpanMarshaller.ConvertToUnmanaged(value.Start),
          End = global::ABI.System.TimeSpanMarshaller.ConvertToUnmanaged(value.End)
      };
  }

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Match truth's qualified KeyValuePair form in mapped collection stubs

Truth uses 'global::System.Collections.Generic.KeyValuePair<K,V>' (fully
qualified) for KVP generic args, even though 'using System.Collections.Generic;'
is in scope. C++ emits this via the IKeyValuePair mapped projection which
forces the global:: prefix.

Mine was emitting just 'KeyValuePair<K,V>' (relying on the using directive),
which produced a visible diff in IEnumerator<KVP>, ICollection<KVP>, etc.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Use unqualified KeyValuePair as param type, qualified inside generic args

Truth uses two distinct forms for KeyValuePair in mapped collection stubs:

- **Unqualified** ('KeyValuePair<K,V>') as parameter or return type:
  * 'public void Add(KeyValuePair<...> item)'
  * 'public bool Contains(KeyValuePair<...> item)'
  * 'bool ICollection<KeyValuePair<...>>.Remove(KeyValuePair<...> item)'

- **Fully qualified** ('global::System.Collections.Generic.KeyValuePair<K,V>')
  when used as a generic argument:
  * 'public IEnumerator<global::System.Collections.Generic.KeyValuePair<...>> GetEnumerator()'

This matches how the C++ generator emits these contexts.

Mine had been emitting the qualified form everywhere (after the previous
commit), which fixed the IEnumerator<KVP> case but broke the parameter
type case. Split into 'kv' (unqualified) and 'kvNested' (qualified) and
use each in its appropriate context.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Walk inherited interfaces when emitting IDIC explicit interface impls

The C++ generator's write_interface_idic_impl walks BOTH the interface's
own members AND the inherited interface members (via
write_required_interface_members_for_abi_type), emitting all of them as
explicit interface implementations on the IDIC file interface.

Mine was only walking the interface's own MethodList/PropertyList/EventList,
so derived interfaces' IDIC impls (e.g. IBackgroundTaskInstance2,
IBackgroundTaskInstance4) were missing the inherited members from
their base (IBackgroundTaskInstance.Canceled, etc.).

Truth has 3 occurrences of 'IBackgroundTaskInstance.Canceled' explicit
impl (one in IBackgroundTaskInstance IDIC, two in derived interfaces).
Mine had 1.

Skips:
- Mapped interfaces with custom members output (IIterable, IMap, etc.)
  — these are handled by the mapped-interface stubs path with the
  C# member names.
- Generic interfaces (with unbound type params) — we can't substitute
  T at this layer.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Emit UnsafeAccessor for generic interface IIDs in objref + ComWrappers callbacks

Mirrors C++ write_unsafe_accessor_for_iid pattern:
- Emit [UnsafeAccessor(StaticMethod, Name=get_IID_<encoding>)] extern method
  for each generic interface instantiation that needs an IID lookup.
- Use the IID accessor function (e.g., IID_X(null)) as the second arg to
  NativeObjectReference.As(), CreateObjectReference, etc.
- Emit accessor inside the file-scoped *ComWrappersMarshallerAttribute and
  *ComWrappersCallback class bodies for the default interface when generic.
- Add System.Guid special case in interop type name encoding (writes
  'System-Guid' / 'ABI.System.<<#corlib>Guid>' instead of treating as a
  regular type with assembly marker).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add [MethodImpl(NoInlining)] + HString fast-path in static abi methods

Mirrors C++ pattern (write_static_abi_methods + write_fixed_marshaler):
- Decorate emittable static abi methods with [MethodImpl(NoInlining)]
  and 'unsafe' modifier (match reference output exactly).
- Use HStringMarshaller fast-path for input string params:
  fixed(void* _path = path) {
    HStringMarshaller.ConvertToUnmanagedUnsafe((char*)_path, path?.Length, out HStringReference __path);
    ... [N](ThisPtr, __path.HString, &__retval) ...
  }
- Stack-allocated HStringReference avoids HSTRING allocation/free per call.
- Drop string-input free() from finally block (no allocation).
- Drop hasStringParams from needsTryFinally trigger (no cleanup needed).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Reorder finally-block frees to match reference (retval last)

Mirrors C++ disposer iteration order in write_abi_method_call_marshalers:
- 1st: Non-blittable PassArray/FillArray cleanup (Dispose + ArrayPool returns)
- 2nd: Out param frees (HString / object / runtime class)
- 3rd: ReceiveArray param frees (Free_<name> via UnsafeAccessor)
- 4th: Return value free (__retval) — emitted LAST

Previously emitted __retval first, then the param/array cleanup.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Remove unconditional 'partial' modifier from class emissions

Mirrors C++ write_class (template '%%%% %class %%' has no 'partial')
and write_static_class (template '%%% static class %').

Truth has zero 'partial class' / 'static partial class' declarations.
Mine had 3624 partial class declarations (now 0).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Match reference type qualifiers + body styles for properties/interface members

- Drop 'global::System.' prefix on ReadOnlySpan/Span (System is in 'using').
- Use expression-body for getter-only properties: 'public T Prop => Expr;'
  instead of 'public T Prop { get => Expr; }' (matches truth's '%' template).
- Use block body for IWindowsRuntimeInterface<T>.GetInterface() and
  GetDefaultInterface() (matches C++ multi-line template).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix enum const formatting (0 vs 0x0) and remove unused CS0672 pragma

- Match C++ printf '%#0x' alternate hex form: value 0 emits '0',
  non-zero emits '0x<hex>'.
- Remove '#pragma warning disable CS0672' header (truth doesn't have it).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix qualifiers: event types use forceWriteNamespace=false, static props use expression body

- Event types: pass forceWriteNamespace=false to WriteTypeName so type
  arguments inherit current namespace context (mirrors C++ write_event:
  bind<write_type_name>(EventType, Projected, false)). Outer EventHandler
  still gets 'global::System.' prefix (from being in different namespace),
  but type args in the same namespace stay unqualified.
- Static class getter-only properties: use expression body
  'public static T Prop => Expr;' instead of 'public static T Prop { get => Expr; }'
  (matches truth and instance property emission).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Use namespace-aware qualification in inheritance lists

WriteInterfaceTypeName now respects the current namespace context for
TypeReference and GenericInstance: same-namespace types are emitted
unqualified (matches truth output: 'IFileLoggingSession', 'IObservableMap<string, object>'
not 'global::Windows.Foundation.Diagnostics.IFileLoggingSession').

Inheritance list (and IWindowsRuntimeInterface<T>) wrappers stay aligned
with how individual interface refs are emitted in member contexts.

Objref name computation (GetObjRefName) keeps using a separate
WriteFullyQualifiedInterfaceName helper so '_objRef_X' field names stay
unique across namespaces (e.g. _objRef_..._global__Windows_Web_Http_HttpCookie_).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Drop class-level 'unsafe' on IXxxMethods + add #nullable enable/disable around marshallers

Mirrors C++ behavior:
- IXxxMethods classes use '% static class %' (no unsafe), with individual
  methods marked 'public static unsafe' as needed.
- write_abi_class wraps marshaller emission with #nullable enable/disable
  to opt into nullable annotations (matches truth output).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Match C++ is_type_blittable: bool/char fields don't make a struct non-blittable

C++ is_type_blittable returns 'type != fundamental_type::String' for
fundamental types — so Boolean and Char are blittable in this check.
Mine was returning false for both, causing structs like
CorePhysicalKeyStatus (with bool fields) to incorrectly emit a duplicate
ABI struct.

Truth: only one 'struct CorePhysicalKeyStatus'.
Mine before: two (one projected, one ABI). After: one.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Use namespace-aware qualifier for runtime class base class

WriteTypeInheritance was unconditionally emitting 'global::' for the
base class. Mirror C++ write_typedef_name behavior: same-namespace base
classes stay unqualified (e.g. 'AppointmentActionEntity : ActionEntity'
instead of 'AppointmentActionEntity : global::Windows.AI.Actions.ActionEntity').

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Combine multiple input string params into a single fixed block

Mirrors C++ pattern: emit a single 'fixed(void* _a = a, _b = b, ...)'
block once for ALL string inputs, then all
HStringMarshaller.ConvertToUnmanagedUnsafe calls inside.

Previously emitted nested fixed blocks (one per string param), which
worked but didn't match reference output and added unnecessary indent
levels.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Implement static abi method bodies for mapped value types (DateTime/TimeSpan)

Previously these methods (e.g. ICustomActionEntityStoreMethods.GetLastModifiedTime
returning DateTimeOffset, IActionEntityFactory6Methods.CreateDateTimeEntity
taking DateTimeOffset) emitted 'throw null!' because CanEmitAbiMethodBody
didn't recognize mapped value types as a supported case.

Updates:
- CanEmitAbiMethodBody: accept input params and return values that are mapped
  value types (DateTime/TimeSpan).
- Function pointer signature: emit 'global::ABI.System.DateTimeOffset[*]' for
  mapped value type input/return slots.
- __retval declaration: declare as the ABI struct type.
- Return statement: convert via 'global::ABI.System.<X>Marshaller.ConvertToManaged(__retval)'
  for the mapped value type return path.

Verified Windows.AI.Actions.cs goes from 3 throw null! to 0.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Implement Do_Abi server-side bodies for mapped value type returns/params

Mirrors C++: extend EmitDoAbiBodyIfSimple to accept methods with mapped
value type (DateTime/TimeSpan) input params and return values.

The body emission already had a branch for mapped value type returns
('IsMappedAbiValueType(rt!)' assigning via marshaller.ConvertToUnmanaged),
but the gating check (returnSimple/allParamsSimple) was missing the case.

Reduces 'throw null!' across all generated files from 37 to 9 (only
DateTime/TimeSpan ARRAY params + complex-struct params remain).

Also fix nullable-warning errors in Release build (added 'rt!' on rt
references in code paths where 'rt is not null' was already established).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Implement static abi method bodies for complex struct input parameters

Mirrors C++/truth pattern for non-blittable struct input params (e.g. ProfileUsage):
  ProfileUsage __value = default;
  try {
      __value = ProfileUsageMarshaller.ConvertToUnmanaged(value);
      ...call...
  }
  finally {
      ProfileUsageMarshaller.Dispose(__value);
  }

Updates:
- CanEmitAbiMethodBody accepts IsComplexStruct(p.Type).
- Function pointer signature uses GetAbiStructTypeName for complex struct slot.
- Local declared OUTSIDE try with default; assigned INSIDE try via marshaller.
- Finally block emits Dispose call (BEFORE other cleanup, mirrors truth ordering).
- Call site passes '__<name>' (the ABI struct local).

Reduces throw null! from 9 to 8 (1 remaining ProfileUsage instance fixed;
8 remaining are DateTime/TimeSpan PassArray params - more complex case
left as follow-up).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Implement static abi method bodies for mapped value type PassArray params

Mirrors C++/truth pattern for ReadOnlySpan<DateTime>/ReadOnlySpan<TimeSpan>:
- Storage uses the ABI struct type (e.g. global::ABI.System.DateTimeOffset)
  instead of nint for InlineArray16/ArrayPool<T>/Span<T>.
- CopyToUnmanaged_<name> takes 'ABI.System.DateTimeOffset* data' param and
  uses 'ABI.System.DateTimeOffset*' cast for _<name>.
- Element pool no longer requires Dispose (no per-element disposal needed)
  AND truth doesn't return the ArrayPool either, so skip both in finally.
- hasNonBlittablePassArray flag also excludes mapped value types so void-
  returning methods don't get an unnecessary try/finally.

Reduces 'throw null!' across all generated files from 8 to 0 (every
emittable method now has a real body).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* XAML projection support: composable factories, overridable interface impls, factory ABI, cross-module IIDs

Multiple changes to make the XAML projection generation produce
compilable output:

1. Composable factory args struct: filter out the trailing baseInterface/
   innerInterface params (they're consumed by Invoke directly, not stored
   in args). Mirrors C++ truth pattern where CreateInstanceWithType_3Args
   only contains the user-input dataType param.

2. Composable factory callback Invoke signature: includes additional
   'WindowsRuntimeObject baseInterface, out void* innerInterface' params
   between additionalParameters and out retval. Marshal baseInterface,
   declare __innerInterface=default, pass in call, assign to out param.

3. Overridable interface impl: add explicit interface implementation
   ('T IFooOverrides.Foo() => Foo()') when the interface is Overridable,
   for both methods and properties, on both sealed and non-sealed classes.

4. ICommand.CanExecuteChanged event: special case the WinRT
   EventHandler<object> -> non-generic EventHandler mapping (matches C++
   write_event hard-coded fix).

5. Test runner: add 'compare-xaml' mode to mirror the XAML projection .rsp.

6. Add WriteCompareXaml: -exclude Windows + -include Windows.UI.Xaml etc.

7. Factory interfaces of included runtime classes: emit ABI Methods classes
   and IIDs even when filter excludes the interface itself (else static class
   members can't dispatch).

8. Cross-module class interfaces: resolve TypeRef -> TypeDef via metadata
   cache when emitting IIDs for inherited interfaces (e.g.
   IID_Windows_UI_Composition_IAnimationObject from a XAML class).

9. GetAbiStructTypeName: respect mapped types so 'Windows.UI.Xaml.Interop.TypeName'
   uses 'global::ABI.System.Type' (mapped) not 'global::ABI.Windows.UI.Xaml.Interop.TypeName'.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* XAML projection: handle mapped structs, ICommand event, SR addition embed

Multiple fixes to make the XAML projection compile against the SDK projection:

1. Mapped struct (e.g. Duration, KeyTime, RepeatBehavior, GridLength,
   CornerRadius, Thickness): treat them as 'pass-through' (IsAnyStruct=true,
   IsComplexStruct=false) when their RequiresMarshaling=false, regardless
   of inner field layout. Mirrors C++ is_type_blittable for mapped struct_type
   case. Skip emission of an auto-generated ABI struct AND auto-generated
   per-field ConvertToUnmanaged/ConvertToManaged marshaller for these
   mapped structs (truth's marshaller only has BoxToUnmanaged/UnboxToManaged).
   The struct is provided by the addition file in its replacement form.

2. Mapped TypeName: GetAbiStructTypeName now applies mapped namespace+name
   (so 'Windows.UI.Xaml.Interop.TypeName' uses 'global::ABI.System.Type'
   instead of 'global::ABI.Windows.UI.Xaml.Interop.TypeName').

3. ICommand.CanExecuteChanged event source field: use non-generic
   EventHandlerEventSource (not generic EventHandlerEventSource<object>),
   matching the special-cased event handler type.

4. SR.cs files: rename to _SR.cs on disk to bypass the SDK's
   CreateManifestResourceNames culture-detection heuristic (which would
   otherwise treat '.SR.' as a culture identifier and embed them as
   satellite resources). Override LogicalName via Update so the manifest
   name still matches what Additions.All looks up at runtime.

XAML test project now compiles 0 errors against the SDK projection.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Authoring projection: fix default/exclusive-to interfaces, file headers, factory ABI namespace

- AddDefaultInterfaceEntry: handle TypeRef and TypeSpecification (generic
  instantiations like IDictionary<K,V>) by using TypeSemantics + WriteTypeName,
  matching what the C++ tool does via for_typedef + write_type_name.
- AddExclusiveToInterfaceEntries: same fix, plus resolve TypeRef to TypeDef so
  the [ExclusiveTo] check actually fires for cross-module interfaces.
- WriteBaseStrings: prepend the auto-generated file header to embedded base
  resources (InspectableVftbl.cs, ReferenceInterfaceEntries.cs, ...) to mirror
  C++ main.cpp which calls write_file_header before each base string.
- WriteModuleActivationFactory: emit 'global::ABI.Impl.<ns>.<name>...' for
  authored types instead of 'global::ABI.<ns>.<name>...' to match C++
  write_type_name(type, CCW, true).
- TestRunner: add 'compare-authoring' mode driving the writer with the same
  inputs/filters as the truth .rsp.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Authoring projection: fix factory class inheritance, members, and global type names

- WriteFactoryClass: emit projected type as 'global::<ns>.<name>' for typeof
  and new expressions to match C++ write_type_name(type, Projected).
- Add factory interface inheritance list (e.g. ', IButtonUtilsStatic') from
  [Static]/[Activatable] attribute targets, mirroring C++
  write_factory_class_inheritance.
- Add factory class members: forwarding methods/properties/events for static
  factory interfaces and constructor wrappers for activatable factory
  interfaces, mirroring C++ write_factory_class_members.
- WriteStaticFactoryMethod, WriteStaticFactoryProperty, WriteStaticFactoryEvent,
  WriteFactoryActivatableMethod helpers.
- Sort NamespaceMembers lists alphabetically by type name in MetadataCache to
  match C++ std::map<string_view, TypeDef> iteration order.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Authoring projection: emit interface method [Overload] attributes; sort factory interfaces

- WriteInterfaceMemberSignatures now emits projected Windows.Foundation.Metadata
  attributes ([Overload], [DefaultOverload], [Experimental]) on each interface
  method, mirroring C++ write_interface_required + write_custom_attributes.
- AttributedTypes.Get returns a SortedDictionary so factory interfaces in the
  inheritance list and member emission order match C++ std::map<string_view,
  attributed_type> sorted-by-key behavior.
- WriteStaticFactoryProperty / WriteStaticFactoryEvent now emit multi-line
  block form matching C++ write_property and write_event when both accessors
  are present, instead of single-line { get; set; } shorthand.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Authoring projection: emit component-mode class marshaller and metadata wrapper

- WriteAbiClass now branches on Component setting:
  * Component mode: emit the simpler write_component_class_marshaller form
    (WindowsRuntimeInterfaceMarshaller<T>.ConvertToUnmanaged + IID,
    (T?)WindowsRuntimeObjectMarshaller.ConvertToManaged) and the
    write_authoring_metadata_type wrapper (file static class T {} with
    [WindowsRuntimeMetadataTypeName] and [WindowsRuntimeMappedType]).
  * Non-component mode: keep the existing ComWrappers marshaller infrastructure.
- Mirrors C++ write_abi_class branching on settings.component.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Authoring projection: emit metadata wrapper for delegates/enums; conditional attributes

- WriteAbiDelegate and WriteAbiEnum now call WriteAuthoringMetadataType in
  Component mode, mirroring C++ write_abi_delegate / write_abi_enum.
- WriteAuthoringMetadataType now emits the conditional attributes per the
  C++ contract: WindowsRuntimeReferenceType for non-delegate/non-class,
  the ComWrappersMarshaller attribute for non-struct/non-class, and
  WindowsRuntimeClassName for non-class types.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Authoring projection: emit delegate ABI helpers in C++ tool's order

Split WriteDelegateMarshallerStub (which combined three concerns) into three
separate helpers - WriteDelegateMarshallerOnly, WriteDelegateComWrappersCallback,
and WriteDelegateComWrappersMarshallerAttribute - so WriteAbiDelegate can call
them in the same order the C++ tool emits them:
  marshaller, vtbl, native_delegate, comwrappers_callback,
  delegates_interface_entries_impl, com_wrappers_marshaller_attribute_impl,
  delegate_impl, reference_impl.

This makes the file layout match what the C++ tool produces.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Authoring projection: emit non-blittable struct ABI definition in component mode

WriteAbiStruct now mirrors C++ write_abi_struct's component-mode branch:
- For non-blittable structs in component mode: emit
 …
…riven correctness fixes (#2418)

* Pass 0: Add `rsp` mode to TestRunner for refactor validation harness

Adds a `rsp <path>` argument mode to the TestRunner program that parses a
projection-generator response file and invokes `ProjectionWriter.Run` with
the matching options. Used by the refactor PR's per-commit byte-identity
validation script (see session-state `validate-writer-output.ps1`) to drive
the writer against the 8 canonical regen scenarios:

  refgen-truth-full              (the 5 standard regen scenarios from
  refgen-windows                  the original C# port PR validation)
  refgen-everything
  refgen-everything-with-ui
  refgen-pushnot
  refgen-truth-authoring-aligned (input-aligned scenarios used by the
  refgen-truth-winsdk-aligned     truth-comparison harness for byte-for-byte
  refgen-truth-winui-fast         diffing against the C++ tool's output)

The TestRunner project itself will be deleted in Pass 22 (final structural
polish), so this addition is throw-away infrastructure scoped to the refactor.

Captured baseline manifests for all 8 scenarios (1,553 generated `.cs` files
total, SHA256 per file). Every subsequent commit in the refactor PR must
produce byte-identical output across all 8 scenarios; the only exception is
Pass 16 (output-format cleanup) which intentionally changes whitespace and
uses a separate Roslyn parse-equivalence gate.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass 1: Rename WinRT.Projection.Generator.Writer -> WinRT.Projection.Writer

Drops the redundant `Generator` token from the writer project name. The
writer is a writer, not a generator -- there's already a separate
`WinRT.Projection.Generator` (the orchestrator) and `WinRT.Projection.Ref.Generator`
(the CLI host).

Renames:
- Folder: `src/WinRT.Projection.Generator.Writer/` -> `src/WinRT.Projection.Writer/`
- Project: `WinRT.Projection.Generator.Writer.csproj` -> `WinRT.Projection.Writer.csproj`
- TestRunner folder + project: same pattern
- Root namespace: `WindowsRuntime.ProjectionGenerator.Writer` -> `WindowsRuntime.ProjectionWriter`

External references updated:
- `src/cswinrt.slnx` solution entry + 8 BuildDependency references
- `src/WinRT.Projection.Generator/WinRT.Projection.Generator.csproj` ProjectReference
- `src/WinRT.Projection.Ref.Generator/WinRT.Projection.Ref.Generator.csproj` ProjectReference
- All `using WindowsRuntime.ProjectionGenerator.Writer*;` directives across consumers

Two consumer-side fixups required by the rename:
1. `ProjectionGeneratorProcessingState.cs` previously used the relative
   `Writer.ProjectionWriterOptions` form (relying on `WindowsRuntime.ProjectionGenerator.Writer`
   being a sibling namespace lookup). Added an explicit
   `using WindowsRuntime.ProjectionWriter;` and switched to unqualified `ProjectionWriterOptions`.
2. The new namespace `WindowsRuntime.ProjectionWriter` collides with the static
   class `ProjectionWriter` defined inside it, so `ProjectionWriter.Run(...)` calls
   in consumer code became ambiguous. Fully-qualified the two call sites
   (`ProjectionGenerator.Generate.cs:71`, `ReferenceProjectionGenerator.cs:67`)
   and the matching `<see cref>` doc comments to
   `global::WindowsRuntime.ProjectionWriter.ProjectionWriter.Run`.

Validation: all 8 regen scenarios produce byte-identical output to the
captured baseline (see Pass 0 harness).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass 2: Remove dead `NetstandardCompat` flag from Settings

In CsWinRT 3.0, `NetstandardCompat` is always `false` and the surrounding
infrastructure has long been removed. Drop:

- `Settings.NetstandardCompat` property (`Helpers/Settings.cs`)
- The `!settings.NetstandardCompat` gate in `IsFastAbiClass`. The method
  no longer needs a `Settings` argument; simplified signature is
  `IsFastAbiClass(TypeDefinition type)`. Updated 3 callers in
  `CodeWriters.ClassMembers.cs` and `CodeWriters.ObjRefs.cs`.
- The `s_emptySettingsForFastAbi` workaround field and the
  `private static Settings GetSettings(TypeDefinition _)` helper that
  existed solely to feed an empty Settings into `IsFastAbiClass`.
- The `if (!Settings.NetstandardCompat) { ... }` guards around the
  `#pragma warning disable/restore CA1416` emission in
  `WriteBeginAbiNamespace`/`WriteEndAbiNamespace` (`Writers/TypeWriter.cs`).
  These pragmas are now emitted unconditionally, matching production behavior.

Validation: all 8 regen scenarios produce byte-identical output to baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass 3: Remove dead `.rsp` file emission in WinRT.Projection.Generator

The orchestrator used to emit a `ProjectionGenerator.rsp` file alongside
each generation as a "debug artifact" in the historical `cswinrt.exe` CLI
format. This was needed back when the orchestrator launched the C++
`cswinrt.exe` as a child process. The writer is now an in-proc library
called directly via `ProjectionWriter.Run(options)`, so the .rsp file is
purely a historical leftover with no consumers.

Removed:
- `out string rspFile` parameter from `BuildWriterOptions`
- `rspFile = Path.Combine(outputFolder, "ProjectionGenerator.rsp")` line
- `using StreamWriter fileStream = new(rspFile)` declaration
- All 12 `fileStream.WriteLine(...)` calls scattered across the type
  enumeration loops, the `-target`/`-input`/`-output`/`-component`/`-exclude`
  emission lines, and the per-input-WinMD `-input` lines
- `WriteWindowsSdkFilters(StreamWriter writer, List<string>, List<string>, bool)`
  signature: dropped the `StreamWriter writer` parameter; the function is
  now pure list-building. Local `Include`/`Exclude` lambdas simplified to
  expression-bodied form (`includes.Add(ns)` / `excludes.Add(ns)`).
- `rspFilePath` constructor parameter and `RspFilePath` property on
  `ProjectionGeneratorProcessingState`. Updated `ProcessReferences` to
  drop the now-unused `rspFile` destructuring and ctor argument.
- XML doc comments referencing the .rsp debug artifact.

Net result: the orchestrator no longer produces `ProjectionGenerator.rsp`
files and ~30 lines of dead code are gone.

Validation: all 8 regen scenarios produce byte-identical output to baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass 4: Folder rebalancing -- reshuffle CodeWriters partials by concept

Reorganize the 16 `CodeWriters.X.cs` partials into role-based folders that
mirror `WinRT.Interop.Generator/`:

- `Builders/`    : top-level orchestrator dispatch (`CodeWriters.cs`)
- `Factories/`   : per-concept emitters (Abi, Class, ClassMembers, Component,
                   Constructors, CustomAttributes, Interface, MappedInterfaceStubs,
                   Methods, ObjRefs, RefModeStubs)
- `Helpers/`     : cross-cutting name/IID computation (Guids, Helpers,
                   InteropTypeName, TypeNames)
- `Writers/`     : kept for the indented-text-writer infrastructure
                   (`TextWriter.cs`, `TypeWriter.cs`)

The moved files all retain their `partial class CodeWriters` declaration in
the root namespace `WindowsRuntime.ProjectionWriter`. No type renames yet
(those happen in Pass 13 when partials get promoted to dedicated
`*Factory`/`*Builder` types). Sub-namespaces will start being introduced in
Pass 5 (Models/) and Pass 6 (Extensions/).

Move-only commit: builds clean, output is byte-identical to baseline across
all 8 regen scenarios.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass 5: Move model types to `Models/`

Extract data carriers from the catch-all `Helpers/Helpers.cs` and the
`Factories/CodeWriters.ClassMembers.cs` / `Factories/CodeWriters.Class.cs`
god-files into a dedicated `Models/` folder under a sub-namespace
`WindowsRuntime.ProjectionWriter.Models` (matching the interop generator's
convention -- see `WinRT.Interop.Generator/Models/`).

New files:
- `Models/MethodSignatureInfo.cs` (contains `MethodSig` -- type rename deferred to Pass 13)
- `Models/ParameterInfo.cs` (contains `ParamInfo` record)
- `Models/ParameterCategory.cs` (contains `ParamCategory` enum + `ParamHelpers` static class)
- `Models/PropertyAccessorState.cs` (carved out of `CodeWriters.ClassMembers`)
- `Models/StaticPropertyAccessorState.cs` (carved out of `CodeWriters.Class`)

The original definitions are removed from their old locations. Each
consumer file gets a `using WindowsRuntime.ProjectionWriter.Models;`
directive (`Builders/CodeWriters.cs`, plus `Factories/CodeWriters.{Abi,
Class, ClassMembers, Constructors, Interface, Methods}.cs`).

Type names are kept verbatim ("Names stay the same initially; renames happen
in Pass 13" per the refactor plan).

Validation: builds clean, all 8 regen scenarios produce byte-identical
output to baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass 6.1: Add Extensions/ITypeDefOrRefExtensions.cs with `Names()` helper

Introduce the `Extensions/` folder (matching `WinRT.Interop.Generator/Extensions/`)
under sub-namespace `WindowsRuntime.ProjectionWriter.Extensions`. First
extension class is `ITypeDefOrRefExtensions` with a single `Names()` helper
that returns `(string Namespace, string Name)` with both fields guaranteed
non-null (a missing namespace becomes `string.Empty`, a missing name becomes
`string.Empty`).

Migrated 60 occurrences of the 2-line idiom

    string ns = X.Namespace?.Value ?? string.Empty;
    string name = X.Name?.Value ?? string.Empty;

to the equivalent

    (string ns, string name) = X.Names();

across 14 files (Factories/, Helpers/, Generation/, Metadata/). The
extension is defined on `ITypeDefOrRef` (the common base interface of
`TypeDefinition`, `TypeReference`, and `TypeSpecification` in AsmResolver),
so a single extension class covers all three concrete types.

Each migrated file gained a `using WindowsRuntime.ProjectionWriter.Extensions;`
directive.

This pass only handles the 2-line `(ns, name) =` form. Single-field accesses
(`Namespace?.Value` alone, or with `?? ""` fallback variations) and any
remaining inline forms are not touched here -- the goal is mechanical
high-confidence batch migration. Sub-pass 6.2+ will continue with other
extensions (TypeSignature, CustomAttribute, MethodDefinition, etc.) and
remaining call patterns.

Validation: builds clean, all 8 regen scenarios produce byte-identical
output to baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass 6.2: Add Extensions/HasCustomAttributeExtensions.cs (HasAttribute/GetAttribute)

Add `HasAttribute(this IHasCustomAttribute, string ns, string name)` and
`GetAttribute(this IHasCustomAttribute, string ns, string name)` extension
methods on AsmResolver's `IHasCustomAttribute` interface (the common base
for `TypeDefinition`, `MethodDefinition`, `PropertyDefinition`,
`InterfaceImplementation`, etc.). The implementations are inlined from the
existing `TypeCategorization.HasAttribute`/`GetAttribute` static helpers.

Migrated 5 files (CodeWriters.Class.cs, CodeWriters.ClassMembers.cs,
CodeWriters.ObjRefs.cs, ProjectionGenerator.cs, CodeWriters.Guids.cs)
from the static-call form

    TypeCategorization.HasAttribute(member, "Windows.Foundation.Metadata", "DefaultAttribute")
    Helpers.HasAttribute(impl, "Windows.Foundation.Metadata", "OverridableAttribute")

to the extension-method form

    member.HasAttribute("Windows.Foundation.Metadata", "DefaultAttribute")
    impl.HasAttribute("Windows.Foundation.Metadata", "OverridableAttribute")

The original static `TypeCategorization.HasAttribute`/`GetAttribute` and
the `Helpers.HasAttribute`/`GetAttribute` forwarders are kept for now;
they will be removed in Pass 8 when the catch-all Helpers.cs is split.

Validation: builds clean, all 8 regen scenarios produce byte-identical
output to baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass 6.3: Add MethodDefinition/PropertyDefinition/InterfaceImpl/TypeDefinition extensions

Convert the static helpers in `Helpers/Helpers.cs` that wrap AsmResolver
metadata APIs into proper extension methods organized per-receiver-type:

- `Extensions/MethodDefinitionExtensions.cs`:
  `IsConstructor`, `IsSpecial`, `IsRemoveOverload`, `IsNoExcept`
- `Extensions/PropertyDefinitionExtensions.cs`:
  `IsNoExcept`
- `Extensions/InterfaceImplementationExtensions.cs`:
  `IsDefaultInterface`, `IsOverridable`
- `Extensions/TypeDefinitionExtensions.cs`:
  `GetDefaultInterface`, `GetDelegateInvoke`, `HasDefaultConstructor`

Migrated all call sites of `Helpers.IsX(member)` to the equivalent
extension method form `member.IsX()` across 9 files
(Builders/CodeWriters.cs, Factories/CodeWriters.{Abi,Class,ClassMembers,
Component,Constructors,Interface}.cs, Helpers/CodeWriters.{Guids,Helpers}.cs).

The original static helpers in `Helpers/Helpers.cs` are kept for now as
forwarders to the extensions; they will be removed in Pass 8 when the
catch-all Helpers.cs is split.

Validation: builds clean, all 8 regen scenarios produce byte-identical
output to baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass 7: Split ProjectionGenerator into phase partials

Decompose `Generation/ProjectionGenerator.cs` (413 lines) into focused
partial-class files mirroring the interop generator's `InteropGenerator.X.cs`
decomposition convention:

- `ProjectionGenerator.cs` (top-level shell + `Run()` orchestration -- now 196 lines)
- `ProjectionGenerator.Namespace.cs` (the `ProcessNamespace` helper that
  emits a single namespace's projection .cs file -- 4-phase pipeline:
  TypeMapGroup attributes, projected types, ABI types, custom additions)
- `ProjectionGenerator.Resources.cs` (the `WriteBaseStrings` helper that
  emits the embedded `Resources/Base/*.cs` resources verbatim into the
  output folder, with the auto-generated header prepended)

Made `ProjectionGenerator` a `partial class`. No method bodies changed;
this is a pure file-level move + partial-class declaration. The IID
writing block and the component activatable discovery remain inline in
`Run()` for now -- extracting them would require carving out the inline
state held by `Run()` (factory interface tracking, the GuidWriter handle,
etc.). They are appropriate candidates for `ProjectionGenerator.GeneratedIids.cs`
and `ProjectionGenerator.Component.cs` partials in a follow-up.

Validation: builds clean, all 8 regen scenarios produce byte-identical
output to baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass 9: Introduce `IndentedTextWriter` (general-purpose, StringBuilder-backed)

Add `Writers/IndentedTextWriter.cs` -- a clean general-purpose indented-text
writer that is *only* about writing text. WinRT-specific concerns (file
headers, projection namespace blocks, mode flags, metadata cache) will be
split out of `TextWriter`/`TypeWriter` in Pass 10 and live as extension
methods or on the new `ProjectionEmitContext` type.

This implementation is a `class` backed by a `StringBuilder` so it can be
passed around freely and live as long as needed in a long-running tool.
The source generator's variant
(`src/Authoring/WinRT.SourceGenerator2/Helpers/IndentedTextWriter.cs`) is
a `ref struct` backed by a `DefaultInterpolatedStringHandler` because it
lives in a Roslyn source generator where transient compilation context +
zero-alloc are the priority. The `class` variant trades a bit of perf for
ergonomics that match the writer project's needs.

API surface:
- `IncreaseIndent()` / `DecreaseIndent()`
- `Block WriteBlock()` returning a `struct Block : IDisposable` for
  `using (writer.WriteBlock()) { ... }` scoped emission of `{ ... }` blocks
- `Write(string, bool isMultiline = false)` and
  `Write(ReadOnlySpan<char>, bool isMultiline = false)`
- `WriteLine()`, `WriteLine(string, bool)`, `WriteLine(ReadOnlySpan<char>, bool)`
- `WriteIf(bool, ...)`, `WriteLineIf(bool, ...)`
- `ToStringAndClear()`

Per the v5 refactor plan, this initial revision intentionally does NOT
include custom interpolated-string handlers (the perf-focused
`InterpolatedStringHandlerArgument` overloads). Callsite syntax is
identical (`writer.Write($"...")`) against either the `string` overload
(via interpolation) or a future handler-based overload, so adding handlers
later is a non-breaking optimization that does not require any callsite
churn. The user will add specialized handlers later as a perf follow-up.

Indentation is per-line: the first `Write` after a newline (or at buffer
start) prepends the current indentation; mid-line writes do not. Multiline
content normalizes `CRLF` -> `LF` and indents each line via the current
indentation level. Empty lines never receive indentation, so raw
multi-line literals with blank lines do not gain trailing whitespace.

`WriteLine(skipIfPresent: true)` collapses runs of blank lines and
suppresses a blank line immediately after `{` -- foundational support for
Pass 16's blank-line-collapsing rule.

This commit is purely additive: no existing call sites change yet.
The old `TextWriter` and `TypeWriter` continue to be used. Output is
byte-identical to baseline across all 8 regen scenarios.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass 8: Reorganize Helpers/ -- split the 184-line catch-all into focused files

Decompose `Helpers/Helpers.cs` (originally 184 lines, mix of unrelated
concerns) into focused single-purpose helpers/extensions:

- `Helpers/CSharpKeywords.cs` -- the C# keyword `HashSet` + `IsKeyword(string)`
- `Helpers/IdentifierEscaping.cs` -- `StripBackticks`, `WriteEscapedIdentifier`
- `Helpers/AccessibilityHelper.cs` -- `InternalAccessibility(Settings)`
- Moved to `Extensions/PropertyDefinitionExtensions.cs`: `GetPropertyMethods`
- New `Extensions/EventDefinitionExtensions.cs`: `GetEventMethods`
- Moved to `Extensions/TypeDefinitionExtensions.cs`: `GetContractVersion`,
  `GetVersion`

Migrated all call sites across 10 files (Builders, Factories, Helpers).
The remaining `Helpers/Helpers.cs` (62 lines, down from 184) contains only
`GetExclusiveToType(TypeDefinition iface, MetadataCache cache)` -- the one
helper that genuinely needs the `MetadataCache` (and so cannot be expressed
as a pure extension method).

The dead static forwarders that were kept after Pass 6 (HasAttribute,
GetAttribute, IsConstructor, IsSpecial, IsRemoveOverload, IsNoExcept,
IsDefaultInterface, IsOverridable, GetDefaultInterface, GetDelegateInvoke,
HasDefaultConstructor) are now removed: every consumer has been migrated
to the corresponding extension method on the appropriate AsmResolver type.

Validation: builds clean, all 8 regen scenarios produce byte-identical
output to baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass 6.4: Add Extensions/TypeSignatureExtensions.cs (TypeSignature predicates)

Complete Pass 6 with the remaining cache-free TypeSignature shape predicates:

- `IsString()` -- corlib `System.String` check
- `IsObject()` -- corlib `System.Object` check
- `IsSystemType()` -- `System.Type` / `Windows.UI.Xaml.Interop.TypeName`
- `IsNullableT()` -- generic instance of `IReference<T>` or `Nullable<T>`
- `GetNullableInnerType()` -- the `T` of a `Nullable<T>`-shaped instance
- `IsGenericInstance()` -- any generic instance signature
- `IsHResultException()` -- `System.Exception` / `Windows.Foundation.HResult`
- `StripByRefAndCustomModifiers()` -- peel byref + modreq/modopt wrappers
- `IsByRefType()` -- byref check that peels custom modifiers first

Migrated 156 call sites across `Factories/CodeWriters.Abi.cs` (135) and
`Factories/CodeWriters.Constructors.cs` (21). Removed the now-unused
private `IsString`/`IsObject`/`IsSystemType`/`IsNullableT`/`GetNullableInnerType`/
`IsGenericInstance`/`IsHResultException` static methods from `Abi.cs`.

Also moved `IsByRefType` and `PeelByRefAndCustomModifiers` from
`Models/ParameterCategory.cs` to the new extensions, and updated
`ParamHelpers.GetParamCategory` to call them as extension methods on
`TypeSignature`.

Cache-dependent predicates (`IsBlittablePrimitive` with cross-module enum
resolution, `IsAnyStruct`, `IsComplexStruct`, `IsTypeBlittable`,
`IsFieldTypeBlittable`, `IsMappedAbiValueType`, `IsEnumType`) intentionally
remain in `Factories/CodeWriters.Abi.cs` -- they need access to the
`MetadataCache` which is currently held in the `_cacheRef` static field.
They will be migrated to a dedicated resolver in Pass 18 (ABI marshalling
shape analysis), once Pass 11 has eliminated the static cache state.

Validation: builds clean, all 8 regen scenarios produce byte-identical
output to baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass 7.2: Complete ProjectionGenerator partial split (GeneratedIids + Component)

Finish Pass 7 by extracting the two remaining inline blocks from `Run()`:

- `Generation/ProjectionGenerator.GeneratedIids.cs`:
  `WriteGeneratedInterfaceIIDsFile()` -- the IID-property emission loop that
  writes `GeneratedInterfaceIIDs.cs` (skipped in reference projection mode).
  Includes the global factory-interface discovery sweep and the
  sorted-namespace iteration that produces the parent-before-child grouping
  in the output file.
- `Generation/ProjectionGenerator.Component.cs`:
  `DiscoverComponentActivatableTypes()` -- returns the `(componentActivatable,
  byModule)` pair from scanning every namespace for activatable/static classes
  in component mode.
  `WriteComponentModuleFile(byModule)` -- writes the `WinRT_Module.cs` file
  with the per-module activation factory entry points.

`Run()` is now a clean, linear, 6-phase orchestrator (66 lines, down from
196): discover component activatables -> verbose log -> write IIDs file ->
per-namespace processing -> write component module + default/exclusive
interface files -> write embedded resource files. All inline state is gone;
each phase is a single method call.

Validation: builds clean, all 8 regen scenarios produce byte-identical
output to baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass 10a: Introduce ProjectionEmitContext (additive)

Add `Helpers/ProjectionEmitContext.cs` -- a per-emission context bundling all
state shared by the projection writers (settings + metadata cache + the
active namespace).

This replaces, conceptually, the implicit state previously held on
`TypeWriter` (which mixes indented-text emission with WinRT-specific state)
and the hidden static `CodeWriters._cacheRef`. During the refactor the
existing `TypeWriter` remains the primary writer surface; this commit
introduces the context as additive infrastructure so it can then be threaded
through writer signatures one family at a time (sub-passes 10b/10c) before
`TypeWriter`/`TextWriter` are finally retired.

Pass 10b/10c (the actual callsite migration -- ~150 method signatures and
hundreds of inner callsites) and Pass 11b/c/d (the cache-static elimination)
are intentionally deferred to subsequent commits to keep this PR's per-commit
diffs small and reviewable. The context API is finalized here; only the
adoption sweep is incremental.

Validation: builds clean, all 8 regen scenarios produce byte-identical
output to baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass 19: Centralize magic strings into References/

Add the `References/` folder under sub-namespace
`WindowsRuntime.ProjectionWriter.References` with 4 files of well-known
literals (mirrors `WinRT.Interop.Generator/References/InteropNames.cs`):

- `References/ProjectionNames.cs`:
  `GlobalPrefix` ("global::"), `AbiPrefix` ("ABI."), `GlobalAbiPrefix`,
  `MarshallerSuffix`, `ConvertToUnmanaged`, `ConvertToManaged`, `IID`, `VoidPointer`
- `References/WellKnownNamespaces.cs`:
  `WindowsFoundation`, `WindowsFoundationMetadata`, `WindowsFoundationCollections`,
  `System`, `WindowsRuntimeInternal`, `WindowsUIXamlInterop`
- `References/WellKnownAttributeNames.cs`:
  `ActivatableAttribute`, `StaticAttribute`, `ComposableAttribute`,
  `DefaultAttribute`, `OverridableAttribute`, `ExclusiveToAttribute`,
  `FastAbiAttribute`, `NoExceptionAttribute`, `ContractVersionAttribute`,
  `VersionAttribute`
- `References/WellKnownTypeNames.cs`:
  `HResult`, `DateTime`, `TimeSpan`, `IReferenceGeneric`, `NullableGeneric`,
  `Type`, `Exception`, `Object`, `TypeName`

This commit is purely additive: no callsites change yet. Subsequent passes
will replace the inline string literals with references to these constants.

Validation: builds clean, all 8 regen scenarios produce byte-identical
output to baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass 20: Comment cleanup + Errors/

Two-part cleanup pass.

Part 1: mechanical port-comment strip
- Removed pure-XML-doc summaries of the form
  `/// <summary>Mirrors C++ <c>write_xxx</c>.</summary>` (no information beyond
  the C++ port reference).
- Removed standalone `///` and `//` comment lines that were just `Mirrors C++ X`
  with no additional explanation.
- Stripped parenthetical line pins like ` (code_writers.h:1234)` and
  ` code_writers.h:1234` from inline comments (62 of 66 removed).
- Net result: 295 -> 134 `Mirrors C++` references and 66 -> 4 line pins.

Inline comments that explain non-obvious WinRT-spec rules or that surround
substantive logic are kept (those 134 remaining `Mirrors C++` refs all sit
inside multi-line explanatory comments). They will be revisited in Pass 23
(XML doc completeness sweep) where each surviving comment will either be
expanded into proper documentation or replaced with a more idiomatic .NET
description.

Part 2: introduce Errors/ infrastructure
- `Errors/WellKnownProjectionWriterException.cs`: a sealed `Exception`
  subclass with an `Id` property and a `ThrowOrAttach` helper that
  preserves outer-exception context across re-throws. Mirrors the
  `WellKnownInteropException` pattern used by `WinRT.Interop.Generator`.
- `Errors/WellKnownProjectionWriterExceptions.cs`: factory methods that
  produce well-known exceptions tagged with the `CSWINRTPROJECTIONGEN`
  error prefix (matches the project's documented error-id range
  `CSWINRTPROJECTIONGENxxxx`).

Initial catalog has two entries (`InternalInvariantFailed`,
`CannotResolveType`); subsequent passes will add IDs as ad-hoc
`InvalidOperationException` throws scattered through the writer get
converted to well-known exceptions.

Validation: builds clean, all 8 regen scenarios produce byte-identical
output to baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass 17a: Add Extensions/IndentedTextWriterExtensions.cs

Add the foundational set of emission micro-pattern helpers as extension
methods on `IndentedTextWriter`:

- `WriteSeparated<T>(IEnumerable<T>, string separator, Action<...,T> writeItem)`
  -- replaces the "for each, write item, write `, `" boilerplate that appears
  ~30+ times across the writers.
- `WriteCommaSeparated<T>(IEnumerable<T>, Action<...,T> writeItem)` -- a thin
  convenience wrapper around `WriteSeparated` with `", "` as the separator.
- `WriteGlobal(string typeName)` -- emits `"global::"` + type name.
- `WriteGlobalAbi(string typeName)` -- emits `"global::ABI."` + type name.

These build on the `References/ProjectionNames.cs` constants from Pass 19
so the prefixes have a single canonical source.

This commit is purely additive: the helpers exist alongside the legacy
`TextWriter`/`TypeWriter` and will be used by subsequent passes (Pass 14/15
mechanical sweeps and Pass 16 formatting cleanup) once writer call sites
have been migrated to `IndentedTextWriter`.

Validation: builds clean, all 8 regen scenarios produce byte-identical
output to baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass 18a: Add ABI marshalling shape analysis (additive infrastructure)

Add the additive infrastructure for the future ABI shape resolver:

- `Models/AbiTypeShapeKind.cs` -- enum classifying a WinRT type signature's
  marshalling shape (BlittablePrimitive, Enum, BlittableStruct, ComplexStruct,
  MappedAbiValueType, String, Object, RuntimeClassOrInterface, Delegate,
  GenericInstance, NullableT, SystemType, HResultException, Array, Unknown).
- `Models/AbiTypeShape.cs` -- immutable record pairing a `Kind` with the
  underlying `TypeSignature`.
- `Resolvers/AbiTypeShapeResolver.cs` -- the resolver itself, constructed
  with a `MetadataCache` reference for cross-module type resolution. The
  initial implementation handles the cache-free shape predicates (String,
  Object, HResultException, SystemType, NullableT, GenericInstance, Array)
  by delegating to the `TypeSignatureExtensions` extensions added in Pass 6.4;
  cache-aware classifications (BlittablePrimitive, Enum, BlittableStruct,
  ComplexStruct, MappedAbiValueType, RuntimeClassOrInterface, Delegate)
  return Unknown for now so callsites can fall through to the legacy
  predicates without behavior change.

Subsequent commits within Pass 18 will progressively migrate the
cache-dependent inline predicates (currently private static methods on
`CodeWriters.Abi.cs`) into this resolver and switch callsites over.

This commit is purely additive: the resolver has no callers yet.

Validation: builds clean, all 8 regen scenarios produce byte-identical
output to baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass 21a: Remove IDE0004/IDE0005 csproj suppressions

Drop `IDE0004` (cast is redundant) and `IDE0005` (using directive is unnecessary)
from the writer's `<NoWarn>` list and fix the 5 violations they exposed:

- `Helpers/CodeWriters.Guids.cs` switch-expression default arms had explicit
  `(ushort)0` and `(byte)0` casts that the analyzer flagged as redundant.
- `Extensions/TypeSignatureExtensions.cs` had an unused
  `using AsmResolver.DotNet;` directive (the file only uses
  `AsmResolver.DotNet.Signatures` and `AsmResolver.PE.DotNet.Metadata.Tables`).
- `Factories/CodeWriters.MappedInterfaceStubs.cs` had an unused
  `using AsmResolver.DotNet;` directive.
- `Models/MethodSignatureInfo.cs` had an unused
  `using AsmResolver.DotNet.Collections;` directive.

Validation: builds clean (0 warnings, 0 errors), all 8 regen scenarios
produce byte-identical output to baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass 21b: Remove 16 dead `<NoWarn>` IDE suppressions from csproj

Drop the following IDE-rule suppressions that no longer flag any violations
in the writer codebase (verified by individually un-suppressing each rule
and rebuilding):

- IDE0007 (use `var`)
- IDE0017 (simplify object initialization)
- IDE0021 (use expression body for ctor)
- IDE0040 (add accessibility modifiers)
- IDE0044 (make field readonly)
- IDE0050 (convert anonymous type to tuple)
- IDE0052 (remove unread private members)
- IDE0055 (style for newlines)
- IDE0063 (use simple `using` statement)
- IDE0066 (convert switch to expression)
- IDE0083 (use pattern matching)
- IDE0130 (namespace must match folder)
- IDE0150 (prefer `null` check over type check)
- IDE0180 (use tuple swap)
- IDE0270 (use coalesce expression)
- IDE0290 (use primary constructor)

These all became dead suppressions through Pass 5 (Models/), Pass 6 (Extensions/),
Pass 7 (split ProjectionGenerator), Pass 8 (split Helpers/), Pass 9
(IndentedTextWriter), Pass 10a (ProjectionEmitContext), and Pass 17a/18a
(emission helpers / ABI shape resolver) -- the new code consistently follows
the modern style these rules enforce.

Validation: builds clean (0 warnings, 0 errors), all 8 regen scenarios
produce byte-identical output to baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass 23a: Add XML doc completeness for Models/ state-bag types

Add proper `<summary>` doc comments to the public fields/properties of the
three Models/ state-bag types that previously had only a single class-level
summary:

- `Models/PropertyAccessorState.cs`: 22 public fields
- `Models/StaticPropertyAccessorState.cs`: 9 public fields
- `Models/MethodSignatureInfo.cs`: `Method`/`Params`/`ReturnParam` properties,
  `ReturnType` getter, `ReturnParamName(string)` method

Each field/property now documents its purpose (e.g. "Gets or sets the ABI
Methods class name used by the getter dispatch") with WinRT-spec-relevant
notes captured in `<remarks>`-style narrative where useful.

This brings the new code in `Models/`, `Extensions/`, `References/`,
`Errors/`, and `Resolvers/` (added across passes 5-20) up to full XML doc
coverage. The remaining `///` gaps live in the legacy `CodeWriters.*`
partials that will be addressed in subsequent Pass 23 sub-passes once
those partials get their final layout from Pass 12 (Abi.cs split) and
Pass 13 (rename to *Factory/*Builder).

Validation: builds clean, all 8 regen scenarios produce byte-identical
output to baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass 10b: Restructure TextWriter/TypeWriter on top of IndentedTextWriter

Restructure the legacy `TextWriter`/`TypeWriter` writer surfaces so they internally
delegate all emission to an `IndentedTextWriter`. The two surfaces now share a
single buffer through their underlying writer, which is the foundation for
incremental migration of methods to take `(IndentedTextWriter writer,
ProjectionEmitContext context)` instead of `TypeWriter w` (per the Pass 10
plan: "convert one writer family at a time ... Old TypeWriter keeps a
passthrough overload during the transition").

Changes:

- `IndentedTextWriter` gains the supporting API needed for the wrapper:
  `Length`, `Back()`, `GetSubstring(start, length)`, `Remove(start, length)`,
  `CurrentIndentLevel`, `ResetIndent()`, `FlushToString()`, `FlushToFile(path)`.
  These mirror the `TextWriter` operations the C++ port relied on
  (`WriteTemp` capture-and-restore, `Back()` last-char check, file flush
  with content-equality skip).

- `Writers/TextWriter.cs` rewritten as a thin shim that wraps an
  `IndentedTextWriter`. The legacy `%`/`@`/`^` format placeholders,
  `WriteValue` polymorphic dispatch, `WriteCode` backtick-stripping, and
  `WriteTemp` are all preserved unchanged at the public API. Internally,
  every character flows through `IndentedTextWriter.Write`.
  Brace-tracked auto-indent (the historical "no leading whitespace in source
  strings" convention from the C++ port) is preserved by driving
  `IndentedTextWriter.IncreaseIndent()` / `DecreaseIndent()` from the
  brace-state machine in `TextWriter.UpdateState`. This keeps the existing
  emission semantics identical while letting code that uses
  `IndentedTextWriter` directly (via `TextWriter.Writer`) interoperate
  cleanly with the same buffer.

- `Writers/TypeWriter.cs` rewritten as a thin sealed wrapper that exposes a
  bundled `Context` (`ProjectionEmitContext`) alongside the inherited
  `Writer` (`IndentedTextWriter`) accessor. The four WinRT-specific helpers
  (`WriteFileHeader`, `WriteBeginProjectedNamespace`,
  `WriteEndProjectedNamespace`, `WriteBeginAbiNamespace`,
  `WriteEndAbiNamespace`) become passthroughs that delegate to extension
  methods on `(IndentedTextWriter, ProjectionEmitContext)`. The mutable
  `InAbiNamespace` / `InAbiImplNamespace` flags are still maintained on
  `TypeWriter` for legacy callers.

- New `Extensions/ProjectionWriterExtensions.cs` houses the WinRT-specific
  emission extensions (`WriteFileHeader`, `WriteBeginProjectedNamespace`,
  `WriteEndProjectedNamespace`, `WriteBeginAbiNamespace`,
  `WriteEndAbiNamespace`) on `IndentedTextWriter` + `ProjectionEmitContext`.
  These are the long-term replacement for the `TypeWriter` instance methods;
  migrated callers will use these directly.

Validation: builds clean, all 8 regen scenarios produce byte-identical
output to baseline. The shared-buffer approach means the brace-auto-indent
behavior is unchanged for legacy callers, and migrated callers (Pass 10c)
will use IndentedTextWriter's explicit Increase/DecreaseIndent + WriteBlock
APIs going forward.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass 10c-1: Migrate ProjectionGenerator partials to IndentedTextWriter

Migrate the orchestrator partials -- the topmost layer that owns the writers
and writes the output files -- from `TypeWriter` to the new
`(IndentedTextWriter writer, ProjectionEmitContext context)` surface,
following the Pass 10c "convert one writer family at a time" plan:

- `ProjectionGenerator.Namespace.cs` (`ProcessNamespace`):
  - Constructs `ProjectionEmitContext` explicitly with the active settings,
    cache, and namespace.
  - Constructs `TypeWriter` from the context (using the new
    `TypeWriter(ProjectionEmitContext)` ctor introduced in Pass 10b) and
    captures the underlying `IndentedTextWriter` via `w.Writer`.
  - File header / projected-namespace begin+end / ABI-namespace begin+end
    are all called as `writer.WriteFileHeader(context)` /
    `writer.WriteBeginProjectedNamespace(context)` / etc. directly on the
    `IndentedTextWriter` via the new extensions.
  - Phase 4 additions and `FlushToFile` go through the `IndentedTextWriter`.
  - `TypeWriter w` is still passed to the unmigrated `CodeWriters.X(w, ...)`
    helpers (those migrate in subsequent commits).

- `ProjectionGenerator.GeneratedIids.cs` (`WriteGeneratedInterfaceIIDsFile`):
  - Same pattern: explicit `ProjectionEmitContext`, `IndentedTextWriter`
    captured via `guidWriter.Writer`, file flush via the indented writer.

- `ProjectionGenerator.Component.cs` (`WriteComponentModuleFile`):
  - Switched the leaner banner-only `CodeWriters.WriteFileHeader(TextWriter)`
    helper to also accept (or be called with) `wm.Writer`. Added the
    `WriteFileHeader(IndentedTextWriter)` overload to `CodeWriters.Helpers.cs`,
    with the previous `TextWriter` overload now a one-line passthrough
    (will go away when `TextWriter` is deleted in the final 10c step).

- `ProjectionGenerator.Resources.cs` (`WriteBaseStrings`):
  - The temporary banner writer is now a fresh `IndentedTextWriter`, with
    `CodeWriters.WriteFileHeader(headerWriter)` going to the new overload
    and `headerWriter.FlushToString()` returning the captured banner.

Validation: builds clean, all 8 regen scenarios produce byte-identical
output to baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass 10c-2: Migrate RefModeStubs writer family + IL2026 pragmas

Migrate three small writer families that don't need any per-call state
beyond the writer itself, from `TypeWriter w` to `IndentedTextWriter writer`:

- `Factories/CodeWriters.RefModeStubs.cs`:
  `EmitRefModeObjRefGetterBody`, `EmitSyntheticPrivateCtor`, `EmitRefModeInvokeBody`
- `Helpers/CodeWriters.Helpers.cs`:
  `WritePragmaDisableIL2026`, `WritePragmaRestoreIL2026`

Updated 5 call sites (2 in `CodeWriters.Constructors.cs`, 1 in
`CodeWriters.Class.cs`, 2 in `ProjectionGenerator.Namespace.cs`) to pass
`w.Writer` (legacy `TypeWriter`) or `writer` (new `IndentedTextWriter`)
instead of `w`.

Validation: builds clean, all 8 regen scenarios produce byte-identical
output to baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass 10c-3: Migrate type-name family to (IndentedTextWriter, ProjectionEmitContext)

Migrate the high-fanout type-name helpers in `Helpers/CodeWriters.TypeNames.cs`
to take `(IndentedTextWriter writer, ProjectionEmitContext context)` as their
new primary signature, with the legacy `TypeWriter` overloads kept as
passthrough wrappers per the Pass 10c plan ("Old TypeWriter keeps a
passthrough overload during the transition").

Migrated helpers (each gets a new `(IndentedTextWriter, ...)` overload plus
a one-line `TypeWriter` passthrough that calls `WriteX(w.Writer, w.Context, ...)`):

- `WriteFundamentalType(IndentedTextWriter, FundamentalType)`
- `WriteFundamentalNonProjectedType(IndentedTextWriter, FundamentalType)`
- `WriteTypedefName(IndentedTextWriter, ProjectionEmitContext, TypeDefinition, TypedefNameType, bool)`
- `WriteTypeParams(IndentedTextWriter, TypeDefinition)`
- `WriteTypeName(IndentedTextWriter, ProjectionEmitContext, TypeSemantics, TypedefNameType, bool)`
- `WriteProjectionType(IndentedTextWriter, ProjectionEmitContext, TypeSemantics)`

Inside the migrated implementations:
- `w.Settings` -> `context.Settings`
- `w.CurrentNamespace` -> `context.CurrentNamespace`
- `w.InAbiNamespace` / `w.InAbiImplNamespace` -> `context.InAbiNamespace` / `context.InAbiImplNamespace`
- `w.WriteCode(name)` -> `writer.Write(IdentifierEscaping.StripBackticks(name))`

Also extended `Helpers/ProjectionEmitContext.cs` with the two emission-mode
flags (`InAbiNamespace`, `InAbiImplNamespace`) plus scoped `IDisposable`
helpers (`EnterAbiNamespace()`, `EnterAbiImplNamespace()`) per the Pass 10
plan ("The mode flags become scoped via IDisposable helpers ... eliminating
the 'did I forget to reset?' failure mode"). The legacy `TypeWriter`'s
`WriteBeginAbiNamespace` / `WriteEndAbiNamespace` / `WriteBeginProjectedNamespace`
/ `WriteEndProjectedNamespace` methods now flip the flags through the
shared `Context` (via `SetInAbiNamespace` / `SetInAbiImplNamespace` internal
setters), so legacy and migrated callers see the same state.

Validation: builds clean, all 8 regen scenarios produce byte-identical
output to baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass 10c-4: Migrate Methods family + IdentifierEscaping to (IndentedTextWriter, ProjectionEmitContext)

Migrate two more writer families to take `(IndentedTextWriter writer,
ProjectionEmitContext context)` as their primary signature, with legacy
`TypeWriter` overloads kept as one-line passthroughs:

`Factories/CodeWriters.Methods.cs`:
- `WriteProjectedSignature(IndentedTextWriter, ProjectionEmitContext, TypeSignature, bool)`
- `WriteProjectionParameterType(IndentedTextWriter, ProjectionEmitContext, ParamInfo)`
- `WriteParameterName(IndentedTextWriter, ParamInfo)` -- inlined the
  `IdentifierEscaping.WriteEscapedIdentifier` call so this helper is
  trivially context-free.
- `WriteProjectionParameter(IndentedTextWriter, ProjectionEmitContext, ParamInfo)`
- `WriteProjectionReturnType(IndentedTextWriter, ProjectionEmitContext, MethodSig)`
- `WriteParameterList(IndentedTextWriter, ProjectionEmitContext, MethodSig)`
- `FormatField(FieldDefinition)` is unchanged (returns a string).

`Helpers/IdentifierEscaping.cs`:
- `WriteEscapedIdentifier(IndentedTextWriter, string)` is the new primary
  signature; the legacy `TextWriter` overload becomes a one-line passthrough.

Validation: builds clean, all 8 regen scenarios produce byte-identical
output to baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass 10c-5: Migrate WinRT attribute helpers to (IndentedTextWriter, ProjectionEmitContext)

Migrate the 8 WinRT-attribute emission helpers in `Helpers/CodeWriters.Helpers.cs`
to take `(IndentedTextWriter writer, ProjectionEmitContext context)` as their
primary signature, with the legacy `TypeWriter` overloads kept as one-line
passthroughs:

- `WriteWinRTMetadataAttribute(IndentedTextWriter, TypeDefinition, MetadataCache)`
  -- doesn't need context (just emits a string literal from the cache lookup).
- `WriteWinRTMetadataTypeNameAttribute(IndentedTextWriter, ProjectionEmitContext, TypeDefinition)`
- `WriteWinRTMappedTypeAttribute(IndentedTextWriter, ProjectionEmitContext, TypeDefinition)`
- `WriteValueTypeWinRTClassNameAttribute(IndentedTextWriter, ProjectionEmitContext, TypeDefinition)`
- `WriteWinRTReferenceTypeAttribute(IndentedTextWriter, ProjectionEmitContext, TypeDefinition)`
- `WriteComWrapperMarshallerAttribute(IndentedTextWriter, ProjectionEmitContext, TypeDefinition)`
- `WriteWinRTWindowsMetadataTypeMapGroupAssemblyAttribute(IndentedTextWriter, ProjectionEmitContext, TypeDefinition)`
- `WriteWinRTComWrappersTypeMapGroupAssemblyAttribute(IndentedTextWriter, ProjectionEmitContext, TypeDefinition, bool)`
- `WriteWinRTIdicTypeMapGroupAssemblyAttribute(IndentedTextWriter, ProjectionEmitContext, TypeDefinition)`

Inside the migrated implementations:
- `w.Settings` -> `context.Settings`
- `w.WriteTemp("%", new Action<TextWriter>(tw => { ... }))` patterns are
  replaced with the cleaner explicit form: a fresh scratch `IndentedTextWriter`,
  written into via `WriteTypedefName(scratch, context, ...)` /
  `WriteTypeParams(scratch, type)`, then captured via `scratch.ToString()`.
  The scratch writer's indent level is 0 by construction, so the captured
  string has no leading whitespace -- matching the legacy `WriteTemp`
  semantics (which toggles `_enableIndent = false`).

Validation: builds clean, all 8 regen scenarios produce byte-identical
output to baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass 10c-6: Migrate CustomAttributes family to (IndentedTextWriter, ProjectionEmitContext)

Migrate the custom-attribute carry-over and platform attribute helpers
in 'CodeWriters.CustomAttributes.cs' to the new emit surface, and add
the 'CheckPlatform'/'Platform' mutable state to 'ProjectionEmitContext'
(with 'TypeWriter' forwarding through the context).

Methods migrated:
- WriteCustomAttributeArgs
- WritePlatformAttribute
- WriteCustomAttributes
- WriteTypeCustomAttributes
- GetPlatform (private)
- FormatCustomAttributeArg (private; no longer needs a writer)

Each public method gains a primary '(IndentedTextWriter writer, ProjectionEmitContext context, ...)'
overload, with the legacy 'TypeWriter w' overload kept as a one-line passthrough.

Validation: all 8 regen scenarios produce byte-identical output to baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass 10c-7: Migrate MappedInterfaceStubs family to (IndentedTextWriter, ProjectionEmitContext)

Migrate the mapped-interface stub emitters in 'CodeWriters.MappedInterfaceStubs.cs' to
the new emit surface. These emit C# interface stub members for WinRT interfaces that
project to .NET BCL interfaces (IClosable->IDisposable, IMap->IDictionary, etc).

Methods migrated to '(IndentedTextWriter, ProjectionEmitContext, ...)':
- WriteMappedInterfaceStubs
- EmitDisposable, EmitGenericEnumerable, EmitGenericEnumerator
- EmitDictionary, EmitReadOnlyDictionary, EmitList, EmitReadOnlyList
- EmitNonGenericList, EmitUnsafeAccessor
- EncodeArgIdentifier (private)

The 'WriteTemp' calls (which emitted projected type names into a scratch buffer with
indent disabled) are replaced with explicit scratch 'IndentedTextWriter' instances
calling 'WriteTypeName(scratch, context, ...)'. A small 'WriteTypeNameToString' helper
captures the pattern.

The legacy 'TypeWriter' overload of 'WriteMappedInterfaceStubs' is kept as a one-line
passthrough.

Validation: all 8 regen scenarios produce byte-identical output to baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass 10c-8: Migrate Interface and Guids families to (IndentedTextWriter, ProjectionEmitContext)

Migrate the interface-emission family in 'CodeWriters.Interface.cs' (WriteGuidAttribute,
WriteTypeInheritance, WriteInterfaceTypeName, WritePropType, WriteInterfaceMemberSignatures,
WriteMethodCustomAttributes, WriteInterface) and the entire GUID family in
'CodeWriters.Guids.cs' (WriteGuid, WriteGuidBytes, WriteIidGuidPropertyName,
WriteIidReferenceGuidPropertyName, WriteIidGuidPropertyFromType, WriteGuidSignature,
WriteIidGuidPropertyFromSignature, WriteIidGuidPropertyForClassInterfaces,
WriteInterfaceIidsBegin, WriteInterfaceIidsEnd) to the new emit surface.

Adds an '(IndentedTextWriter writer, ProjectionEmitContext context, EventDefinition evt[, GenericInstanceTypeSignature?])'
overload of 'WriteEventType' so the interface migration can reach it without falling
back to legacy 'TextWriter'. The 'WriteTemp' calls inside Guids.cs (used to compose
escaped IID identifier names from a typedef name + type params) are replaced with
explicit scratch 'IndentedTextWriter' instances.

All legacy 'TypeWriter'/'TextWriter' overloads are kept as one-line passthroughs.

Validation: all 8 regen scenarios produce byte-identical output to baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass 10c-9: Migrate ObjRefs family to (IndentedTextWriter, ProjectionEmitContext)

Migrate the objref-emission family in 'CodeWriters.ObjRefs.cs' to the new emit surface:
- GetObjRefName, WriteFullyQualifiedInterfaceName (private)
- WriteIidExpression, BuildIidPropertyNameForGenericInterface (private)
- EmitUnsafeAccessorForIid (private), WriteIidReferenceExpression
- WriteClassObjRefDefinitions, EmitObjRefForInterface (private)
- EmitTransitiveInterfaceObjRefs (private)

The 'WriteTemp' calls (used to build escaped IID property names from a generic
interface signature) are replaced with explicit scratch 'IndentedTextWriter' instances.

Legacy 'TypeWriter' overloads are kept as one-line passthroughs for both public methods
and private helpers (private overloads exist because 'CodeWriters.Abi.cs' calls
'BuildIidPropertyNameForGenericInterface' and 'EmitUnsafeAccessorForIid' directly via
the legacy 'TypeWriter' surface; those callsites will be migrated when Abi.cs is migrated).

Validation: all 8 regen scenarios produce byte-identical output to baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass 10c-10: Migrate Component family to (IndentedTextWriter, ProjectionEmitContext)

Migrate the component-mode helpers in 'CodeWriters.Component.cs' to the new emit surface:
- AddMetadataTypeEntry
- WriteFactoryClass
- WriteFactoryActivatableMethod, WriteStaticFactoryMethod, WriteStaticFactoryProperty,
  WriteStaticFactoryEvent (private)
- WriteFactoryReturnType, WriteFactoryPropertyType, WriteFactoryMethodParameters (private)
- WriteModuleActivationFactory

The 'WriteTemp' calls (used to build typedef name + type params strings for the metadata
type-name map) are replaced with explicit scratch 'IndentedTextWriter' instances.

All legacy 'TypeWriter'/'TextWriter' overloads are kept as one-line passthroughs.

Validation: all 8 regen scenarios produce byte-identical output to baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass 10c-11: Migrate Builders/CodeWriters dispatchers to (IndentedTextWriter, ProjectionEmitContext)

Migrate the top-level dispatchers and emission entries in 'Builders/CodeWriters.cs' to
the new emit surface:
- WriteType, WriteAbiType (dispatchers)
- WriteEnum, WriteStruct, WriteContract, WriteDelegate, WriteAttribute

Adds a new 'TypeWriter(IndentedTextWriter writer, ProjectionEmitContext context)' constructor
that wraps an existing 'IndentedTextWriter' (instead of allocating a new buffer) so migrated
dispatchers can call into not-yet-migrated legacy methods like 'WriteClass'/'WriteAbiClass'
on the same emit buffer without losing state.

Inside 'WriteAbiType' and 'WriteType' (Class branch only), the not-yet-migrated entries are
called via a transient 'TypeWriter(writer, context)' wrapper. The Class family will be
migrated in the next sub-pass.

Validation: all 8 regen scenarios produce byte-identical output to baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass 10c-12: Add (IndentedTextWriter, ProjectionEmitContext) overloads for Class family

Add primary '(IndentedTextWriter, ProjectionEmitContext, ...)' overloads for the
Class-family entry points in 'CodeWriters.Class.cs':
- WriteClassModifiers (fully migrated -- no state needed)
- WriteStaticClass, WriteStaticClassMembers, WriteClass (thin wrappers that build
  a transient TypeWriter and delegate to the legacy impl)

Update 'WriteType' dispatcher in 'Builders/CodeWriters.cs' to call the new
'WriteClass(IndentedTextWriter, ProjectionEmitContext, TypeDefinition)' overload
directly (so the TypeWriter wrapper allocation moves into the wrapper itself,
keeping the dispatcher uniform across all categories).

For Class.cs, the not-yet-migrated bodies (which still call WriteAttributedTypes,
WriteClassMembers, etc.) keep the legacy 'TypeWriter w' surface. Those will be
migrated when ClassMembers.cs and Constructors.cs are migrated in the next sub-pass.

Validation: all 8 regen scenarios produce byte-identical output to baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass 10c-13: Add (IndentedTextWriter, ProjectionEmitContext) overloads for ClassMembers and Constructors

Add primary '(IndentedTextWriter, ProjectionEmitContext, ...)' overloads for the public
entry points in:
- CodeWriters.ClassMembers.cs: WriteClassMembers
- CodeWriters.Constructors.cs: WriteAttributedTypes, WriteFactoryConstructors,
  WriteComposableConstructors

Each new overload is a thin wrapper that builds a transient TypeWriter (sharing the
underlying buffer + context) and delegates to the legacy impl. The bodies of these
methods are large and intricate; the per-call site flattening will follow in a future
mechanical sweep once the surrounding helpers are also migrated.

Validation: all 8 regen scenarios produce byte-identical output to baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass 10c-14: Add (IndentedTextWriter, ProjectionEmitContext) overloads for Abi family + Helpers

- Add 'Factories/CodeWriters.Abi.Overloads.cs' with primary '(IndentedTextWriter, ProjectionEmitContext, ...)'
  overloads for the 15 public methods declared in the large 'CodeWriters.Abi.cs' file
  (WriteAbiEnum, WriteAbiStruct, WriteAbiDelegate, WriteTempDelegateEventSourceSubclass,
  WriteAbiClass, WriteAbiInterface, EmitImplType, WriteAbiParameterTypesPointer (x2),
  WriteInterfaceVftbl, WriteInterfaceImpl, WriteInterfaceIdicImpl, WriteInterfaceMarshaller,
  WriteIidGuidReference, WriteAbiType). Each overload is a thin TypeWriter-wrapping passthrough.
- Migrate the remaining helpers in 'CodeWriters.Helpers.cs' (AddDefaultInterfaceEntry,
  AddExclusiveToInterfaceEntries) to take 'ProjectionEmitContext' directly. The 'WriteTemp'
  calls (used to render mapped CCW interface names from 'TypeSemantics') are replaced with
  explicit scratch 'IndentedTextWriter' instances.
- Update 'WriteAbiType' dispatcher in 'Builders/CodeWriters.cs' to use the new overloads
  directly (no transient TypeWriter needed).
- Update all callsites in 'Generation/ProjectionGenerator.Namespace.cs' to use the new
  '(IndentedTextWriter, ProjectionEmitContext)' overloads instead of the legacy 'TypeWriter w'.

Validation: all 8 regen scenarios produce byte-identical output to baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass 10c-15: Promote primary signature in Class/ClassMembers/Constructors/Abi families

Promote '(IndentedTextWriter writer, ProjectionEmitContext context, ...)' from a thin
TypeWriter-wrapping passthrough to the primary implementation across all 4 large body files:
- Factories/CodeWriters.Class.cs (WriteStaticClassMembers, WriteClass)
- Factories/CodeWriters.ClassMembers.cs (WriteClassMembers)
- Factories/CodeWriters.Constructors.cs (WriteAttributedTypes, WriteFactoryConstructors,
  WriteComposableConstructors)
- Factories/CodeWriters.Abi.cs (15 public methods: WriteAbiEnum, WriteAbiStruct, WriteAbiDelegate,
  WriteTempDelegateEventSourceSubclass, WriteAbiClass, WriteAbiInterface, EmitImplType,
  WriteAbiParameterTypesPointer x2, WriteInterfaceVftbl, WriteInterfaceImpl,
  WriteInterfaceIdicImpl, WriteInterfaceMarshaller, WriteIidGuidReference, WriteAbiType)

Each method's primary signature now takes (IndentedTextWriter, ProjectionEmitContext, ...)
directly. The body opens with a 'TypeWriter w = new(writer, context);' alias, allowing the
existing 'w.Write(...)' / 'w.Settings' / etc. body code to remain unchanged. Each method
gains a one-line legacy 'TypeWriter w' passthrough overload that calls the new signature.

The 'CodeWriters.Abi.Overloads.cs' file (added in Pass 10c-14 as a wrappers-only file) is
rewritten to contain just the legacy passthroughs.

This completes the public API surface migration: every public method declared in
'WindowsRuntime.ProjectionWriter' now exposes the '(IndentedTextWriter, ProjectionEmitContext, ...)'
signature as primary. The full body flattening (replacing 'w.Write(...)' with 'writer.Write(...)'
throughout the ~2700 callsites) and the eventual deletion of TypeWriter/TextWriter is deferred
to a later mechanical sweep -- it is not blocking for Pass 11+, since all state now flows through
ProjectionEmitContext.

Validation: all 8 regen scenarios produce byte-identical output to baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass 21 (cont): Remove IDE0011/0019/0042/0059/0090 suppressions

Audit each remaining IDE suppression in WinRT.Projection.Writer.csproj by removing it
and checking what diagnostics fire:
- IDE0011 (require braces): 0 fires  -> removed
- IDE0090 (use new()):       0 fires  -> removed
- IDE0019 (pattern match):   2 fires  -> fixed (TypeSemantics.GetGenericInstance) + removed
- IDE0042 (deconstruct):     8 fires  -> fixed (Builders/CodeWriters.cs WriteStruct + Helpers/CodeWriters.Guids.cs WriteGuid/WriteGuidBytes) + removed
- IDE0059 (unused value):    4 fires  -> fixed (Factories/CodeWriters.Abi.cs WriteAbiEnum/WriteAbiStruct removed dead 'name' locals) + removed

Updates 'WinRT.Projection.Writer.csproj' NoWarn list to drop these 5 suppressions.

Validation: all 8 regen scenarios produce byte-identical output to baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass 21 (cont): Remove IDE0008 (use explicit type instead of 'var') suppression

Replace 'var' with explicit types at the 7 sites that previously suppressed IDE0008:
- Helpers/ContractPlatforms.cs (1: foreach tuple deconstruction)
- Helpers/CodeWriters.Guids.cs (1: GetGuidFields IList args)
- Factories/CodeWriters.Abi.cs (3: WriteInterfaceVftbl segments+fastAbi, TryGetNullablePrimitiveMarshallerName gt)
- Factories/CodeWriters.Class.cs (2: IsFastAbiOtherInterface and IsFastAbiDefaultInterface fastAbi)

Updates 'WinRT.Projection.Writer.csproj' NoWarn list to drop IDE0008.

Validation: all 8 regen scenarios produce byte-identical output to baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass 21 (cont): Remove IDE0300/0301/0305/0306 (collection initialization) suppressions

Convert array/list initialization to collection-expression syntax at the sites that previously
suppressed IDE0300, IDE0301, IDE0305, IDE0306:
- Helpers/GuidGenerator.cs (s_namespaceBytes byte[])
- Helpers/Additions.cs (All IReadOnlyList of tuples)
- Helpers/TypeFilter.cs (Empty, _include, _exclude)
- Helpers/CodeWriters.CustomAttributes.cs (FormatAttributeTargets entries)
- ProjectionWriterOptions.cs (Include/Exclude/AdditionExclude defaults)
- Builders/CodeWriters.cs (FormatConstant data fallback)
- Generation/ProjectionGenerator.cs (sorted KVP lists)
- Factories/CodeWriters.Component.cs (orderedTypes copy)

Updates 'WinRT.Projection.Writer.csproj' NoWarn list to drop IDE0300/0301/0305/0306.

Validation: all 8 regen scenarios produce byte-identical output to baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass 21 (cont): Remove IDE0057 (use range operator) suppression

Replace 'string.Substring(...)' calls with range operator '[..]' / '[..n]' / '[n..]' /
'[n..m]' / '[..^k]' at the 11 sites that previously suppressed IDE0057:
- Builders/CodeWriters.cs (ToCamelCase tail)
- Helpers/IdentifierEscaping.cs (StripBackticks prefix)
- Helpers/TypeFilter.cs (3: namespace/name split + rule rest)
- Factories/CodeWriters.Abi.cs (2: get_/set_/add_/remove_ method-name strip)
- Factories/CodeWriters.CustomAttributes.cs (2: 'Attribute' suffix strip via [..^k])
- Helpers/CodeWriters.Guids.cs (2: 'global::' / 'global::ABI.' prefix strip)
- Helpers/CodeWriters.Helpers.cs (1: ContractVersion '+' suffix strip)

Updates 'WinRT.Projection.Writer.csproj' NoWarn list to drop IDE0057.

Validation: all 8 regen scenarios produce byte-identical output to baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass 21 (cont): Remove IDE0060 (unused parameter) suppression

Drop genuinely unused parameters from internal methods at the 7 sites that previously
suppressed IDE0060:
- Builders/CodeWriters.cs: delete dead WriteType/WriteAbiType legacy TypeWriter overloads
  (never called -- all call sites already use the new (IndentedTextWriter, ProjectionEmitContext, ...) overloads).
- Factories/CodeWriters.Abi.cs:
  * GetArrayMarshallerInteropPath: drop 'TypeWriter w' and 'string encodedElement' params (only
    'elementType' is used). Update all 12 callsites in Abi.cs and Constructors.cs.
  * EmitEventTableField: drop unused 'TypeDefinition iface' param.
  * WriteInterfaceIdicImplMembersForInterface: drop unused 'HashSet<TypeDefinition> visited' param.
- Factories/CodeWriters.Interface.cs: WriteGuidAttribute drop unused 'context' param. Update callers.
- Factories/CodeWriters.CustomAttributes.cs:
  * WriteCustomAttributeArgs: drop unused 'context' param. Delete legacy 'TypeWriter w' passthrough
    (no longer needed since the new signature only takes a 'CustomAttribute').

Updates 'WinRT.Projection.Writer.csproj' NoWarn list to drop IDE0060.

Validation: all 8 regen scenarios produce byte-identical output to baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass 10c-16: Flatten Class.cs bodies to (IndentedTextWriter, ProjectionEmitContext)

Migrate CodeWriters.Class.cs from the wrapper-style ('IndentedTextWriter writer, ProjectionEmitContext context'
calling 'TypeWriter w' legacy primary) to flat primary impls that consume 'writer'/'context' directly.

Methods promoted to (IndentedTextWriter, ProjectionEmitContext, ...) primary:
- WriteStaticClass, WriteStaticClassMembers, WriteClass (public)
- WriteStaticFactoryObjRef, WriteClassCore (private)

Replaces all 'w.X' references with 'writer.X' / 'context.X' / etc. The two 'w.WriteTemp(...)'
patterns are converted to explicit scratch IndentedTextWriter instances. Helper calls like
'WriteTypedefName(w, ...)' switch to the (writer, context, ...) overloads.

Also migrates the private 'WriteParameterNameWithModifier' helper in CodeWriters.ClassMembers.cs
to add an (IndentedTextWriter, ProjectionEmitContext, ParamInfo) overload (needed because
Class.cs now calls it from a flat-style body).

Each public method retains a one-line legacy 'TypeWriter w' passthrough overload.

Validation: all 8 regen scenarios produce byte-identical output to baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass 10c-17: Flatten ClassMembers.cs bodies to (IndentedTextWriter, ProjectionEmitContext)

Migrate 'CodeWriters.ClassMembers.cs' from wrapper-style to flat primary impls:
- WriteClassMembers (public): primary takes (IndentedTextWriter, ProjectionEmitContext, TypeDefinition).
  Legacy 'TypeWriter w' overload now a one-line passthrough.
- WriteInterfaceMembersRecursive, WriteInterfaceMembers (private): signatures migrated to
  (IndentedTextWriter, ProjectionEmitContext, ...).
- WriteInterfaceTypeNameForCcw (private): primary migrated; one-line legacy 'TypeWriter w'
  overload retained because 'CodeWriters.Abi.cs' still calls it from a TypeWriter-bodied
  legacy primary.

The 4 'w.WriteTemp(...)' patterns are converted to explicit scratch IndentedTextWriter
instances. All 'w.X' references replaced with 'writer.X' / 'context.X' / etc. All
helper calls migrated to (writer, context, ...) overloads.

Validation: all 8 regen scenarios produce byte-identical output to baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass 10c-18: Flatten Constructors.cs bodies to (IndentedTextWriter, ProjectionEmitContext)

Migrate 'CodeWriters.Constructors.cs' from wrapper-style to flat primary impls:
- WriteAttributedTypes, WriteFactoryConstructors, WriteComposableConstructors (public):
  primary takes (IndentedTextWriter, ProjectionEmitContext, ...). Legacy 'TypeWriter w'
  overload now a one-line passthrough.
- EmitFactoryArgsStruct, EmitFactoryCallbackClass (private): signatures migrated to
  (IndentedTextWriter, ProjectionEmitContext, ...).
- GetDefaultInterfaceIid (private): signature migrated; the 'writer' parameter was unused
  (the method only writes to a scratch IndentedTextWriter), so dropped it.

The 5 'w.WriteTemp(...)' patterns are converted to explicit scratch IndentedTextWriter
instances. All 'w.X' references replaced with 'writer.X' / 'context.X' / etc. All
helper calls migrated to (writer, context, ...) overloads.

Two not-yet-migrated callees in Abi.cs ('GetNullableInnerMarshallerName',
'EmitMarshallerConvertToUnmanaged') are still 'TypeWriter w'-only. Constructors.cs
calls them via 'new TypeWriter(writer, context)' until they are migrated alongside Abi.cs.

Validation: all 8 regen scenarios produce byte-identical output to baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pass 10c-19: Flatten Abi.cs bodies to (IndentedTextWriter, ProjectionEmitContext)

Migrate all 35+ private helpers in 'CodeWriters.Abi.cs' from 'TypeWriter w' to
'(IndentedTextWriter writer, ProjectionEmitContext context, ...)'. The 15 public methods
were already in flat form (Pass 10c-15) -- this commit drops their 'TypeWriter w = new(writer, context);'
aliases since the bodies no longer need them.

Helpers mig…
…classifier resolvers, helper consolidation (#2419)

* Add interpolated-string handler to IndentedTextWriter

Introduce AppendInterpolatedStringHandler (ref struct) to support compiler-backed interpolated-string appends into IndentedTextWriter. The new handler (internal, EditorBrowsable(Never), [InterpolatedStringHandler]) handles literals, formatted values (with optional format), ReadOnlySpan<char>, and preserves multiline semantics (CRLF -> LF normalization and per-line indentation); it leverages StringBuilder's handler for non-string formatting to avoid allocations. Also make IndentedTextWriter partial, add the necessary System.Runtime.CompilerServices import, and add two Write overloads annotated with [InterpolatedStringHandlerArgument] so the compiler can bind interpolated strings to the new handler.

* Refactor IndentedTextWriter overloads and logic

Rework IndentedTextWriter surface and internals: reorder Write/WriteIf/WriteLine overloads to take the isMultiline flag first, add convenience overloads for string and ReadOnlySpan<char>, and expose interpolated-string handler overloads for Write/WriteLine. Tighten behaviour: early-return on empty spans, improve newline insertion when previous buffer ends with '{' or '}', and centralize indentation handling in WriteRawText. Also update AppendInterpolatedStringHandler to accept a scoped ReadOnlySpan<char>, remove ToStringAndClear, and ensure Clear resets indentation. Includes XML doc/remarks linking and small comment/formatting tweaks.

* Correct _writer.Write argument order

Pass _isMultiline as the first argument to _writer.Write across IndentedTextWriter.AppendInterpolatedStringHandler. Updated calls in AppendLiteral and the various AppendFormatted overloads to match the expected Write(bool, ...) signature and avoid swapping the value and boolean parameters.

* Move isMultiline: true argument before string content at all callsites

The recent IndentedTextWriter refactor moved the `isMultiline` parameter
to the FIRST positional position on the new Write/WriteLine overloads
(to satisfy the InterpolatedStringHandlerArgument requirement that the
named handler argument come after its referenced parameters).

This commit updates all 231 callsites across 25 files in the
`WinRT.Projection.Writer` to match. Mechanical change:

    writer.WriteLine($$\"\"\"
        ...content...
        \"\"\", isMultiline: true);

becomes:

    writer.WriteLine(isMultiline: true, $$\"\"\"
        ...content...
        \"\"\");

No semantic differences. Build passes with 0 warnings, 0 errors.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Reshape WriteLineIf overloads to match the canonical Write[Line]/WriteIf pattern

The two `WriteLineIf` content overloads still had `bool isMultiline = false`
as a trailing parameter with a default value -- a leftover from before the
`isMultiline`-first refactor. The other overload families
(`Write`/`WriteLine`/`WriteIf`) all use a pair-of-overloads pattern
where each content overload has a sibling that takes `isMultiline`
immediately before the content (no default).

Reshape `WriteLineIf` to match. The `WriteLineIf(bool condition, bool skipIfPresent = false)`
newline-only overload remains unchanged (analogous to `WriteLine(bool skipIfPresent = false)`).
The two content overloads each gain a sibling:

- `WriteLineIf(bool condition, string content)`
- `WriteLineIf(bool condition, bool isMultiline, string content)`  (new shape)
- `WriteLineIf(bool condition, ReadOnlySpan<char> content)`
- `WriteLineIf(bool condition, bool isMultiline, ReadOnlySpan<char> content)`  (new shape)

No callers needed updating: `WriteLineIf` has zero external callers in the
writer codebase.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add conditional AppendInterpolatedStringHandler ctors

Introduce two constructor overloads for IndentedTextWriter.AppendInterpolatedStringHandler to support compiler-generated conditional interpolated string emission. Both overloads accept literalLength, formattedCount, writer, condition and an out bool shouldAppend; one overload also accepts an isMultiline parameter. When condition is false the handler is disabled (shouldAppend = false), _writer is set to null! and _isMultiline is set to false; when true the writer and multiline flag are initialized. XML docs note these are intended for compiler use and arguments are not validated.

* Add XML docs to new WriteIf/WriteLineIf interpolated handler overloads

Combines the conditional-style docs from the existing string/span `WriteIf`
and `WriteLineIf` overloads with the interpolated-handler-style docs from
the existing `Write` and `WriteLine` handler overloads, matching the
documentation pattern used everywhere else in the file.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Convert simple if-then-write blocks to WriteIf/WriteLineIf

Mechanical conversion across the writer codebase: any single-statement
`if (cond) { writer.Write[Line](content); }` block where the body is a
plain (non-interpolated) string or span call is rewritten to
`writer.Write[Line]If(cond, content);`. 68 conversions across 23 files.

The conversion intentionally SKIPS interpolated-string calls
(`writer.Write($"...")`). The new `WriteIf`/`WriteLineIf` interpolated
handler overloads have an unintentional parameter-name mismatch with the
existing `AppendInterpolatedStringHandler` constructors: the compiler
binds `condition` positionally to the handler's existing
`(int, int, IndentedTextWriter, bool isMultiline)` constructor, so
`writer.WriteLineIf(false, $"foo")` would silently always write AND
treat the content as multiline. Once the handler gets a dedicated
`(..., bool condition)` constructor that returns `out bool wantsToContinue`
this filter can be relaxed.

Conditions skipped by the converter:
- if-statement is part of an `else`/`else if` chain (preceded by `else`
  on the prior non-blank/non-comment line, OR followed by `else` on the
  next non-blank/non-comment line after the close brace -- the previous
  filter only checked the immediately-following line and missed cases
  separated by a blank line + comment).
- body uses an interpolated string literal or an `isMultiline:` named
  argument (which would route to the interpolated handler overload).
- body has more than one statement, or body's call doesn't end with `);`.

Validation:
- Output is byte-identical to the pre-conversion branch baseline (1553/1553).
- Real CI-style compile of WinRT.Sdk.Projection + WinRT.Sdk.Xaml.Projection
  passes with 0 compile errors.
- Syntax validation across all 8 regression scenarios passes with 0 errors.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add TryAppendInterpolatedStringHandler partial type

Introduce a separate interpolated-string handler type for conditional
write paths (WriteIf/WriteLineIf). It mirrors the shape and members of
AppendInterpolatedStringHandler but only exposes the constructors that
take a leading 'condition' argument and produce 'out bool shouldAppend',
which is the contract the language compiler requires to short-circuit
literal/interpolation evaluation when the condition is false.

Splitting this into its own handler type avoids the constructor-overload
collision with the unconditional Write/WriteLine overloads: per the
interpolated-string-handler resolution rules, the compiler always
prefers a constructor with a trailing 'out bool shouldAppend'
parameter when one exists with a matching shape, which would otherwise
silently bind unconditional 'Write(bool isMultiline, ...)' calls to the
conditional constructor and lose the isMultiline value.

This commit only adds the new type. Wiring WriteIf/WriteLineIf to it
(and removing the now-redundant conditional constructors from
AppendInterpolatedStringHandler) follows in a separate commit.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Switch WriteIf/WriteLineIf to TryAppendInterpolatedStringHandler

Update the four interpolated-handler overloads on WriteIf/WriteLineIf
to take a 'ref TryAppendInterpolatedStringHandler' parameter instead of
'ref AppendInterpolatedStringHandler', and remove the now-unused
conditional constructors from AppendInterpolatedStringHandler.

This fixes a latent bug introduced when the conditional constructors
were originally added to AppendInterpolatedStringHandler. Per the
language rules for interpolated-string-handler resolution, the compiler
always prefers a constructor with a trailing 'out bool shouldAppend'
parameter when one exists with a matching shape. Both
WriteIf(bool condition, ...) and Write(bool isMultiline, ...) produce
the same expected handler-constructor signature
(int, int, IndentedTextWriter, bool) from their respective
[InterpolatedStringHandlerArgument] attributes, so the addition of the
5/6-arg conditional constructors silently caused all
Write(isMultiline: true, $$..."""...""") calls to bind to the
conditional constructor, where the bool parameter was treated as a
condition flag and _isMultiline was unconditionally forced to false.
As a result, the AppendLiteral path stopped splitting multi-line
raw-string content per line and lost the per-line indent contribution,
producing visibly broken indentation inside generated projection
sources wherever an interpolated multi-line raw string was written.

Splitting the conditional flavor into its own handler type breaks the
constructor-shape collision: WriteIf/WriteLineIf still get the
short-circuit behavior they need, while Write/WriteLine bind to the
AppendInterpolatedStringHandler 4-arg (int, int, writer, bool)
constructor as intended.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add IIndentedTextWriterCallback factory-callback infrastructure

Introduce a small "callback" mechanism that lets factories return a
readonly value-type closure which can be embedded as an interpolation
hole inside an interpolated raw string passed to Write/WriteLine.
The callback's Write method is invoked at interpolation time and emits
its content directly into the writer at the writer's current position
and indentation level, with no allocation and no intermediate string.

This lets emission code that previously had to break a single multiline
fragment into three separate calls (a leading writer.Write of the
prefix, a helper that emits a piece of derived content, and a trailing
writer.WriteLine of the suffix) collapse back into a single, readable
interpolated raw string with the helper inlined as an interpolation
hole, while preserving the same output and the same zero-allocation
characteristics.

The new pieces are:
- IIndentedTextWriterCallback: marker interface implemented by the
  callback closures.
- WriteIidGuidReferenceCallback: the first concrete callback,
  emitted by the new AbiTypeHelpers.WriteIidGuidReference(context,
  type) overload to inline the IID GUID literal expression.
- AppendFormatted<T> dispatch on both interpolated-string handlers
  (AppendInterpolatedStringHandler and
  TryAppendInterpolatedStringHandler): when T is a value type that
  implements IIndentedTextWriterCallback, the handler invokes the
  callback's Write method directly instead of falling through to the
  string/StringBuilder path.

Use the new pattern in AbiInterfaceFactory in the two places that
previously emitted the IID via a three-call sequence (the static IID
property body and the inline-IID argument in the marshaller emission).

Co-Authored-By: Copilot <223556219+Copilot@users.noreply.github.com>

* Add IIndentedTextWriterCallback Format() extension

Add an extension method that, given any value-type implementation of
IIndentedTextWriterCallback, leases an IndentedTextWriter from the pool,
invokes the callback against it, and returns the resulting string. Use
this overload when the caller needs the emitted text as a standalone
string (e.g. to compose into another string or pass through APIs that
take string), rather than appending it inline as an interpolation hole
inside a larger writer call.

Currently unused; subsequent commits will introduce additional
callbacks and migrate existing string-returning helpers to call
factory(...).Format() instead of duplicating the leased-pool boilerplate.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add WriteTypedefName/WriteTypeParams callbacks and migrate string callers

Add two new IIndentedTextWriterCallback implementations that mirror the
existing writer-taking factory methods on TypedefNameWriter:

  - WriteTypedefNameCallback wraps WriteTypedefName(IndentedTextWriter,
    ProjectionEmitContext, TypeDefinition, TypedefNameType, bool).
  - WriteTypeParamsCallback wraps WriteTypeParams(IndentedTextWriter,
    TypeDefinition).

Each comes with a callback-returning factory method on TypedefNameWriter
that uses '<inheritdoc>' on the corresponding writer-taking factory so
the doc surface stays in one place. The existing callback-returning
WriteIidGuidReference factory on AbiTypeHelpers gets the same
'<inheritdoc>' treatment for consistency.

Adding the callback-returning WriteTypedefName overload at the same
signature as the existing string-returning helper would conflict on
return type (C# does not allow return-type-only overloads), so the
string-returning WriteTypedefName(ProjectionEmitContext, ...) overload
is removed and its 8 callers are migrated to call the new factory and
invoke the IIndentedTextWriterCallback.Format() extension method to
get the standalone string. The dead StartsWith(GlobalPrefix) defensive
checks at each call site are also dropped: every caller passed
'forceWriteNamespace: true', and the writer-taking method always
emits the 'global::' prefix when that flag is set, so the defensive
fallback branch was unreachable.

Output is byte-identical for all 10 generated files in the
refgen-pushnot scenario (validated against the post-handler-fix
baseline).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Refactor WriteInterfaceMarshaller to use callback interpolation holes

The marshaller emission previously broke a single coherent class
declaration into nine separate writer calls (six string literals plus
five WriteTypedefName/WriteTypeParams pairs) so it could splice the
projected type name into multiple positions inside the body. Each
literal segment had to manually carry its own interior indentation,
which made the layout fragile and hid the underlying shape of the
emitted class.

Replace the chunked emission with a single multiline interpolated raw
string. The callbacks for the three repeated holes are constructed
once as locals and embedded inline:

  - WriteTypedefNameCallback for the projected type name (used 4 times)
  - WriteTypeParamsCallback for the generic parameter list (used 4 times)
  - WriteIidGuidReferenceCallback for the IID GUID literal (used 1 time)

The new form preserves the existing zero-allocation behavior (the
callbacks dispatch through IIndentedTextWriterCallback at interpolation
time) while making the emitted class shape readable at the call site.

Side effect: this also fixes a latent indent bug in the previous
chunked emission. The non-isMultiline 'writer.Write($$..."""...""")'
call between the inner WriteTypedefName/WriteTypeParams pair and the
trailing 'public static ...' segment was emitting its literal interior
as raw text, which prepended only the writer's class-level indent
(4 spaces) to the closing '}' of ConvertToUnmanaged and to the next
'public static' declaration, leaving them at column 4 while the
matching opening '{' of ConvertToManaged ended up at column 8. The
single multiline raw string with isMultiline routing fixes the
indentation so every member inside the class is uniformly at column 8.

Output diffs against the post-handler-fix baseline are limited to the
four files containing non-exclusive non-generic projected interfaces
(TestComponent.cs, TestComponentCSharp.cs,
TestComponentCSharp.TestPublicExclusiveTo.cs,
TestComponentCSharp.Windows.cs); each diff is the indent-fix described
above. The generated sources still parse cleanly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Rename TryAppendInterpolatedStringHandler -> AppendIf

Rename the interpolated string handler type and file from TryAppendInterpolatedStringHandler to AppendIfInterpolatedStringHandler, updating constructor names and all references in IndentedTextWriter (WriteIf/WriteLineIf signatures). Also apply a tiny formatting tweak in IIndentedTextWriterCallbackExtensions.cs (spacing around callback.Write). This aligns the handler name with its semantics and keeps usages consistent.

* Add interface-typed callback dispatch to AppendFormatted

Add a polymorphic dispatch path to AppendFormatted<T> on both the
AppendInterpolatedStringHandler and AppendIfInterpolatedStringHandler
so callers can declare a local of type IIndentedTextWriterCallback
(rather than a concrete callback struct) and assign one of several
concrete callback values based on a runtime condition. The local can
then be embedded as an interpolation hole in the same way the existing
value-type callbacks are.

The existing 'typeof(T).IsValueType && value is IIndentedTextWriterCallback'
fast path is preserved unchanged: when 'T' is a concrete callback
struct (the common case) the JIT can constant-fold the type checks
and elide the polymorphic branch entirely. The new branch only fires
when 'T' is the interface type itself (or some other reference type
that happens to implement the interface), which is the case where the
caller wanted polymorphic behavior in the first place.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add WriteTypedefNameWithTypeParams writer + callback and migrate string callers

Add the missing writer-taking method 'WriteTypedefNameWithTypeParams(
IndentedTextWriter, ProjectionEmitContext, TypeDefinition,
TypedefNameType, bool)' on TypedefNameWriter, plus a matching
WriteTypedefNameWithTypeParamsCallback struct and a callback-returning
factory overload that uses '<inheritdoc>' on the writer-taking method
to keep the doc surface in one place.

The writer-taking method is a tiny composition of 'WriteTypedefName'
and 'WriteTypeParams' (which is exactly what the previous
string-returning convenience already did internally) and lets callers
emit the typedef-name + generic-parameter list as a single inline
operation on a writer. The callback factory wraps it for use as an
interpolation hole inside a larger interpolated raw string, mirroring
the WriteTypedefName / WriteTypeParams / WriteIidGuidReference pattern.

The callback-returning factory occupies the same signature as the
previous string-returning 'WriteTypedefNameWithTypeParams' overload,
so the string-returning version is removed and its 6 callers (in
ComponentFactory, IidExpressionGenerator, MetadataAttributeFactory)
are migrated to call the new factory and chain '.Format()' to get
the standalone string.

Output is byte-identical for all 10 generated files in the
refgen-pushnot scenario.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Refactor MetadataAttributeFactory to use callback interpolation holes

Replace the chunked '[opener]; WriteTypedefName; WriteTypeParams;
[closer]' emission pattern in five attribute writers with single
interpolated raw strings that embed the typedef-name + generic-params
emission as interpolation holes (using
WriteTypedefNameWithTypeParamsCallback and the existing WriteIid /
WriteTypedefName / WriteTypeParams callbacks). The resulting attribute
text is now legible at the call site, and conditional pieces inside
the template are prepared as locals before the writer call so the
'$$"""..."""' block stays whole.

The methods updated are:

  - WriteWinRTMetadataTypeNameAttribute, WriteWinRTMappedTypeAttribute,
    WriteWinRTReferenceTypeAttribute: simple one-liners now built as
    a single interpolated WriteLine.
  - WriteWinRTWindowsMetadataTypeMapGroupAssemblyAttribute and
    WriteWinRTComWrappersTypeMapGroupAssemblyAttribute: the 'target:'
    typeof slot toggles between the ABI typedef and the projected
    typedef based on 'context.Settings.Component'; both branches
    yield WriteTypedefNameWithTypeParamsCallback values that get
    selected into a value-typed local. The 'value:' string-literal
    slot in the ComWrappers attribute toggles between an
    'IReference`1<...>' wrapper and the bare projection name; both
    branches yield strings, so a plain 'string' local is used.
  - WriteWinRTIdicTypeMapGroupAssemblyAttribute: two distinct
    callbacks (Projected source, ABI proxy) prepared as locals and
    embedded in a single interpolated raw string.

Output is byte-identical for all 10 generated files in the
refgen-pushnot scenario; the generated sources still parse cleanly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Merge EmitDicShim*Forwarders into single interpolated raw strings

Both EmitDicShimIObservableMapForwarders and
EmitDicShimIObservableVectorForwarders previously split their emission
into two separate WriteLine(isMultiline: true, $$..."""...""") calls
because the second block (the IObservableMap.MapChanged /
IObservableVector.VectorChanged event forwarder) needed locals
(obsTarget, obsSelf) that weren't available when the first block was
emitted.

Lift those locals to the top of each method alongside the existing
target / self / icoll locals so a single interpolated multiline raw
string can carry both blocks separated by a blank line. The output is
byte-identical (validated against the post-handler-fix baseline for
all 10 generated files in the refgen-pushnot scenario).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Refactor file-interface and static-class declarations to use callbacks

Replace the two-line 'writer.Write("...prefix..."); WriteTypedefName;
WriteTypeParams; writer.WriteLine()' sequences in
AbiInterfaceIDicFactory.WriteIdicFileInterfaceDecl and
ClassFactory.WriteStaticClass with a single interpolated
'writer.WriteLine($"...{{name}}")' call that embeds a
WriteTypedefNameWithTypeParamsCallback local as the interpolation hole.

Output is byte-identical for all 10 generated files in the
refgen-pushnot scenario.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Refactor StructEnumMarshallerFactory complex-struct emission to use callbacks

Hoist 'WriteTypedefNameCallback' locals for the ABI and projected
typedef names at the top of the 'isComplexStruct' branch and use
single multiline interpolated raw strings for each of the three
method signatures emitted by that branch:

  - ConvertToUnmanaged({{projected}} value) returning {{abi}}
  - ConvertToManaged({{abi}} value) returning {{projected}}
  - Dispose({{abi}} value)

The previous emission split each signature across four to six
separate writer calls (Write prefix; WriteTypedefName ABI;
Write " ConvertToUnmanaged("; WriteTypedefName Projected;
WriteLine multiline body opener) so the typedef-name pieces could be
spliced into a single declaration line. The new form puts each
signature (and its body opener) into one readable multiline raw
string with the callback locals embedded as interpolation holes.

The 'new {{projected}}{{(useObjectInitializer ? "(){" : "(")}}'
hole inlines the existing conditional-suffix choice so the
'return new ...' line stays inside the same raw string as the rest
of the body opener instead of being emitted by a follow-up
'writer.WriteLine(useObjectInitializer ? "(){" : "(")' call.

Output is byte-identical for all 10 generated files in the
refgen-pushnot scenario.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Refactor StructEnumMarshallerFactory Box/Unbox/CreateObject to use callbacks

Hoist the per-type 'abi' and 'projected' WriteTypedefNameCallback
locals to the top of WriteStructEnumMarshallerClass so the
BoxToUnmanaged, UnboxToManaged, and CreateObject emission paths can
share them, then collapse the chunked
'writer.Write([prefix]); WriteTypedefName; writer.Write([infix]);
WriteIidReferenceExpression; writer.WriteLine([suffix])' sequences
in each emitter into single interpolated multiline raw strings.

  - BoxToUnmanaged: the two prior branches (enum/almost/complex
    vs. mapped struct) had identical layout differing only in the
    CreateComInterfaceFlags value. The single shared template now
    selects the flag string via the existing 'hasReferenceFields'
    condition. The string-returning
    'ObjRefNameGenerator.WriteIidReferenceExpression(TypeDefinition)'
    overload supplies the IID hole as a plain string.

  - UnboxToManaged: the prior 'isEnum || almostBlittable' and 'else'
    (mapped struct) branches emitted identical bodies (UnboxToManaged
    of the projected type), so they collapse into one branch. The
    remaining 'isComplexStruct' branch becomes a single multiline raw
    string with '{{projected}}' and '{{abi}}' interpolation holes.

  - CreateObject: each of the two return-statement emitters becomes a
    single 'writer.WriteLine($"...{{name}}...")'. The complex-struct
    branch uses a per-call 'abiFq' callback because that site needs
    'forceWriteNamespace: true' (whereas the shared 'abi' local uses
    'false').

Side effect: this also fixes a latent indent bug in the complex-struct
UnboxToManaged branch. The previous chunked emission wrote a multiline
opener followed by 'WriteTypedefName(ABI)' + 'writer.Write("? abi =
...")' on what should have been the next line; the writer indent was
applied to the multiline lines but not to the typedef-name-then-Write
segment, leaving the 'Nested? abi = ...' line at column 4 while the
following 'return ...' line was at column 12. The new single multiline
raw string aligns both lines to column 12 via uniform writer indent
handling. The change is purely whitespace (verified with a Roslyn
syntax check) and affects three generated files (Microsoft.Windows.
PushNotifications.cs, TestComponent.cs, TestComponentCSharp.cs).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add WriteTypeInheritanceCallback and use it in class/interface declarations

Add a 1:1 callback wrapper for
'InterfaceFactory.WriteTypeInheritance(IndentedTextWriter, ProjectionEmitContext,
TypeDefinition, bool, bool)' (the writer-taking method that emits the
' : Base, Iface1, Iface2<T>' clause for class and interface declarations),
plus a paired callback-returning factory overload with '<inheritdoc>'
on the writer-taking method.

Use the new callback to collapse the chunked
'writer.Write("...prefix..."); WriteTypedefName; WriteTypeParams;
WriteTypeInheritance; writer.WriteLine()' sequences at the two existing
class/interface declaration call sites into single interpolated
'writer.WriteLine($"...{{name}}{{inheritance}}")' calls:

  - InterfaceFactory.WriteInterfaceDeclaration emits
    '{accessibility} interface {{name}}{{inheritance}}'.
  - ClassFactory.WriteClassDeclaration emits
    '{accessibility} {modifiers}class {{name}}{{inheritance}}', with
    the trivial 'static '/'sealed ' modifier choice inlined as a
    'string modifiers' local (rather than going through the
    'WriteClassModifiers' writer helper, which still exists for any
    future caller that needs the writer-side form).

Output is byte-identical for all 10 generated files in the
refgen-pushnot scenario.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Refactor ReferenceImplFactory blittable get_Value to use callback

Collapse the five-call emission sequence in the blittable / blittable-
struct branch of WriteReferenceImpl get_Value (Write opener; WriteTypedefName
Projected; Write infix; WriteTypedefName Projected again; WriteLine closer)
into a single interpolated multiline raw string with a hoisted
WriteTypedefNameCallback local embedded as the type-name interpolation hole
in both the cast and the pointer cast.

Output is byte-identical for all 10 generated files in the
refgen-pushnot scenario.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add WriteTypeNameCallback and migrate string-returning WriteTypeName callers

Add a 1:1 callback wrapper for
'TypedefNameWriter.WriteTypeName(IndentedTextWriter, ProjectionEmitContext,
TypeSemantics, TypedefNameType, bool)' plus a paired callback-returning
factory overload with '<inheritdoc>' on the writer-taking method.

Adding the callback-returning overload at the same signature as the
existing string-returning 'WriteTypeName(context, semantics, ...)'
helper would conflict on return type, so the string-returning overload
is removed and its 11 callers (in AbiClassFactory,
AbiInterfaceIDicFactory, AbiMethodBodyFactory.MethodsClass,
ClassMembersFactory.WriteInterfaceMembers, MappedInterfaceStubFactory,
MetadataAttributeFactory, ObjRefNameGenerator) are migrated to call
the new factory and chain '.Format()' to get the standalone string.

Output is byte-identical for all 10 generated files in the
refgen-pushnot scenario.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Refactor EventTableFactory and ComponentFactory inline writes to use callbacks

Collapse the 'writer.Write(prefix); TypedefNameWriter.WriteTypeName(writer,
ctx, semantics, ...); writer.Write[Line](suffix)' three-call sequences in
two emit sites into single interpolated 'writer.WriteLine($"...{name}...")'
calls with a hoisted WriteTypeNameCallback local as the interpolation hole:

  - EventTableFactory.EmitDoAbiAddEvent (non-generic branch) emits
    '        var __handler = {{abiTypeName}}Marshaller.ConvertToManaged({{handlerRef}});'.
  - ComponentFactory.WriteFactoryMethodParameters (typed-parameter branch)
    emits '{{projectedType}} {{paramName}}'.

Output is byte-identical for all 10 generated files in the
refgen-pushnot scenario.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add WriteProjectionReturnTypeCallback + WriteParameterListCallback

Add the two highest-leverage method-signature callbacks identified by
the multi-agent codebase analysis (M1+M2). They pair naturally because
8 of the 10 affected call sites use both callbacks back-to-back to emit
a single method declaration.

Migrated sites:
- AbiDelegateFactory.cs:174 (Invoke extension)
- AbiInterfaceIDicFactory.cs:302, 435 (DIM thunks)
- AbiMethodBodyFactory.MethodsClass.cs:65 (Methods class entry)
- ClassFactory.cs:358 (static-class method emission)
- ClassMembersFactory.WriteInterfaceMembers.cs:315 (UnsafeAccessor extern, return-type only), :328 (generic-iface wrapper), :355 (non-generic wrapper), :383 (overridable explicit-iface impl)
- InterfaceFactory.cs:242 (interface member declaration)

Output is byte-identical for all 10 generated files in the
refgen-pushnot scenario.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add WriteIidExpressionCallback, drop string overload, migrate callers

Mirror the established WriteTypedefName / WriteTypeName migration
pattern for ObjRefNameGenerator.WriteIidExpression: add a 1:1 callback
wrapper, expose a same-named callback-returning factory overload
(with <inheritdoc> on the writer-taking method), remove the now-
conflicting string-returning convenience overload, and migrate its
7 string-callers to call the factory and chain '.Format()'.

Also rewrite the 2 inline writer-taking call sites in ClassFactory.cs
(the GetActivationFactory return statement at :541-555 and the
IsOverridableInterface OR-chain at :732-734) to use the callback as an
interpolation hole, collapsing each chunked emission into a single
interpolated raw string.

Output is byte-identical for all 10 generated files in the
refgen-pushnot scenario.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add WriteIidReferenceExpressionCallback, drop string overload, migrate callers

Same pattern as M3 (WriteIidExpressionCallback) but for the
IReference<T>-flavored IID variant on ObjRefNameGenerator. Add the
1:1 callback wrapper, expose the same-named factory overload (with
<inheritdoc>), remove the string-returning convenience overload, and
update the 4 callers in AbiDelegateFactory.cs:199,413 and
StructEnumMarshallerFactory.cs:257,301 to declare the local as
WriteIidReferenceExpressionCallback. The local is consumed only via
interpolated raw strings on the writer at all 4 sites, so no template
changes are needed.

Output is byte-identical for all 10 generated files in the
refgen-pushnot scenario.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add WriteEventTypeCallback (both overloads) and migrate callers

Add a single WriteEventTypeCallback struct that carries the nullable
'currentInstance' parameter (the only difference between the two
writer-taking overloads, since the 3-arg overload simply delegates to
the 4-arg one with null). Expose two same-named factory overloads on
TypedefNameWriter (with <inheritdoc> on the corresponding writer-taking
methods), one per shape.

Remove the conflicting string-returning 'WriteEventType(context, evt)'
overload and migrate its single caller (EventTableFactory.cs:29) to
declare a WriteEventTypeCallback local that gets interpolated directly
into the surrounding multiline raw string.

Refactor the 5 inline writer-taking sites in AbiInterfaceIDicFactory.cs:351,515,
ClassFactory.cs:387, ClassMembersFactory.WriteInterfaceMembers.cs:568,
InterfaceFactory.cs:269 to use the callback as a {{eventType}}
interpolation hole, collapsing each chunked emission into a single
interpolated raw string per event declaration.

Output is byte-identical for all 10 generated files in the
refgen-pushnot scenario.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add WriteAbiTypeCallback + WriteEscapedIdentifierCallback paired

Add two callbacks together because both apply inside
AbiInterfaceFactory.WriteAbiParameterTypesPointer (the densest
chunked-emission method in the project, 125+ lines pre-refactor).
Add 1:1 wrappers, factory overloads on AbiTypeWriter and
IdentifierEscaping (each with <inheritdoc>), and rewrite the helper
to use the two callbacks plus per-branch single 'writer.Write(...)'
calls.

Also migrate two smaller call sites that use the same helpers
inline:

  - ConstructorFactory.FactoryCallbacks.cs:490 (factory-callback ABI
    type list)
  - ProjectionFileBuilder.cs:235, :247, :255 (positional-ctor field
    and assignment emission)

Note: each branch in the rewritten WriteAbiParameterTypesPointer uses
an explicit 'if (includeParamNames) { writer.Write($"...{name}") }
else { writer.Write($"...") }' pattern. A conditional ternary
'writer.Write(cond ? $"A{cb}" : $"B{cb}")' would silently force both
branches to evaluate as 'string' (calling 'ToString()' on the
callback), bypassing the interpolated-string handler and emitting
the callback's fully-qualified type name into the output. Always use
explicit if/else so each branch gets its own handler-dispatching
interpolated 'writer.Write($"...")' call.

Output is byte-identical for all 10 generated files in the
refgen-pushnot scenario.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add WriteProjectedSignatureCallback, drop string overload, migrate callers

Largest single migration in the callback series: 17 callers across 7
files. Add a 1:1 callback wrapper, expose the callback-returning
factory overload on MethodFactory (with <inheritdoc>), and remove the
conflicting string-returning convenience overload.

Selective per-caller migration following the merged report's policy
("immediate-emission callers switch to callback; stateful callers
that consume the string outside of writer interpolation use
.Format()"):

  - 14 sites (AbiMethodBodyFactory.DoAbi.cs:80,110,182,187,192,244,394;
    AbiMethodBodyFactory.RcwCaller.cs:285,1167,1307;
    ConstructorFactory.FactoryCallbacks.cs:177; EventTableFactory.cs:81;
    ReferenceImplFactory.cs:88,115) immediately interpolate the result
    into writer.WriteLine($"..."); locals switched from 'string' to
    'WriteProjectedSignatureCallback'.
  - 3 sites (InterfaceFactory.WritePropType:222 and
    AbiTypeHelpers.AbiTypeNames:28 each return the string from a
    helper method that surfaces it via non-writer APIs;
    AbiMethodBodyFactory.RcwCaller.cs:1352 uses the result both in a
    string equality check and in interpolation) keep 'string' locals
    and chain '.Format()' to materialise it.

Output is byte-identical for all 10 generated files in the
refgen-pushnot scenario.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add WriteProjectionTypeCallback, drop string overload, migrate callers

Add a 1:1 callback wrapper, expose the same-named factory overload on
TypedefNameWriter (with <inheritdoc>), and remove the conflicting
string-returning convenience overload.

Migrate string callers selectively:
- 11 sites switch the 'string elementProjected'/'string fieldType'
  local to WriteProjectionTypeCallback and remove the intermediate
  string allocation: AbiMethodBodyFactory.DoAbi.cs (7 sites),
  AbiMethodBodyFactory.RcwCaller.cs (4 sites), ConstructorFactory.FactoryCallbacks.cs (1 site),
  ProjectionFileBuilder.cs:412 (1 site).
- 1 site (ProjectionFileBuilder.cs:198 'fieldType') stores the result
  in a List<(string TypeStr, ...)> tuple consumed elsewhere; keep as
  string with '.Format()'.

Also refactor the 4 inline writer-taking call sites in MethodFactory.cs
(WriteProjectedSignature SZ array branches and WriteProjectionParameterType
PassArray/FillArray/ReceiveArray branches) plus 2 in
ConstructorFactory.FactoryCallbacks.cs:138,144 to use the callback as
a {{elem}} interpolation hole inside a single writer.Write($"...") call.

Output is byte-identical for all 10 generated files in the
refgen-pushnot scenario.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add WriteInterfaceTypeNameForCcwCallback and use it at 4 call sites

Add a 1:1 callback wrapper for
ClassMembersFactory.WriteInterfaceTypeNameForCcw(IndentedTextWriter,
ProjectionEmitContext, ITypeDefOrRef) + a same-named factory overload
with <inheritdoc>.

Migrate the 4 inline writer-taking call sites
(AbiInterfaceIDicFactory.cs:491, ClassMembersFactory.WriteClassMembers.cs:181,
ClassMembersFactory.WriteInterfaceMembers.cs:103, :386) to use the
callback as an interpolation hole. The site at
ClassMembersFactory.WriteInterfaceMembers.cs:386 combines naturally with
M1+M2's WriteProjectionReturnTypeCallback + WriteParameterListCallback
locals to fully collapse the overridable explicit-interface DIM thunk
to a single writer.Write($"...{ret} {iface}.{name}({parms}) => ...") call.

Output is byte-identical for all 10 generated files in the
refgen-pushnot scenario.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Extract AbiTypeHelpers.GetAbiLocalTypeName and apply at 3 RcwCaller sites

Three near-identical ABI-type-selection dispatches in
AbiMethodBodyFactory.RcwCaller.cs (out-param default at :376-396,
ReceiveArray element type at :419-436, SzArray retval element type
at :514-538) all map a TypeSignature to its ABI local C# type name
('void*' / 'global::ABI.System.Type' / 'global::ABI.System.Exception'
/ ABI struct name / mapped ABI name / blittable struct name /
primitive). Extract a single helper
'AbiTypeHelpers.GetAbiLocalTypeName(IndentedTextWriter, ProjectionEmitContext,
TypeSignature)' that performs the unified dispatch and call it from
each of the three sites, replacing each 18-25 line if/else block with
a single 'string abi = AbiTypeHelpers.GetAbiLocalTypeName(...);
writer.WriteLine($"{abi}...");' pair.

The merged report's 4th candidate site (writeback expression at :1146-
1221) is a different pattern (marshaller call expression, not type
name) and is not part of this extraction.

Output is byte-identical for all 10 generated files in the
refgen-pushnot scenario.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Collapse DoAbi branch dispatches: out-param default + out-param writeback

Two branch-consolidation cleanups in AbiMethodBodyFactory.DoAbi.cs:

1. Lines 175-196 (default-initialized return local): the previous
   4-branch if/else (returnIsString -> "string"; returnIsRefType,
   returnIsReceiveArrayDoAbi, and the default -> projected signature)
   had three branches that emitted identical 'WriteProjectedSignatureCallback
   projected = ...; writer.WriteLine($"{projected} ...")' code. Collapse
   into 'if (returnIsString) string-literal; else projected-callback'.

2. Lines 534-588 (per-parameter writeback to ABI struct slot): the
   previous 8-branch if/else (string / object / runtime-class-or-interface
   / generic instance / enum / bool / char / complex-struct / default)
   each wrote a 'writer.Write("    *ptr = "); writer.Write(rhs);
   writer.WriteLine(";");' triplet where 'rhs' is a small string
   expression. Hoist 'rhs' into a 'string rhs' local computed by the
   if/else, then emit 'writer.WriteLine($"    *{ptr} = {rhs};");'
   once. The enum, bool, char, and default branches all produced the
   same '__{raw}' RHS so they merge into one default-branch.

Output is byte-identical for all 10 generated files in the
refgen-pushnot scenario.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Collapse 4 ConstructorFactory.Composable base-chaining ctors via table

ConstructorFactory.Composable.WriteComposableConstructors emitted 4
near-identical 'protected T(...) :base(...) { ... }' blocks that
differed only in the constructor's parameter list and matching
':base(...)' argument list. Replace the 4 duplicated WriteLine +
WriteBlock blocks with a 4-entry '(string parameters, string baseArgs)[]'
table iterated in a single foreach.

Eliminates ~30 lines of duplication while preserving the exact emission
(including the optional GC.AddMemoryPressure body line gated on
'gcPressureBody').

Output is byte-identical for all 10 generated files in the
refgen-pushnot scenario.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Consolidate event-accessor branches via 'string accessors' local

Both ClassFactory.WriteStaticClass (static event emission at :385-407)
and ClassMembersFactory.WriteInterfaceMembers.WriteEventEmission
(:565-596) had the same pattern: emit the event declaration header in
one multiline raw string, then branch on emit context to select the
accessor body, then emit a trailing '}'. Lift the branched accessor
body into a 'string accessors' local computed by the if/else, then
emit the whole event declaration in one interpolated multiline raw
string with the '{{accessors}}' hole.

The reader can now see the full emitted event shape (header + body +
closing brace) in one writer call rather than reconstructing it across
three separate WriteLines.

Output is byte-identical for all 10 generated files in the
refgen-pushnot scenario.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* M15: Consolidate ComponentFactory factory-class emission via 'activateBody' local

Collapse the chunked Write/WriteLine calls that emit a factory class body
in ComponentFactory.WriteFactoryClass into a single multi-line interpolated
raw string. The conditional ActivateInstance body (one of two single-line
expressions) is hoisted into a string local and inlined via {{activateBody}}.

Output is byte-identical for all 10 generated files in the refgen-pushnot
scenario.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* M16: WriteAbiParameterTypesPointerCallback

Add WriteAbiParameterTypesPointerCallback + factory overload on
AbiInterfaceFactory and migrate the 4 call sites:

- AbiInterfaceFactory.WriteInterfaceVftbl: vtable function-pointer
  declaration in vftbl struct (was: Write/WriteAbi/WriteLine chunked
  emission, now: single interpolated WriteLine with {abiParams} hole).
- AbiInterfaceFactory.WriteInterfaceImpl Do_Abi emission: multiline raw
  string ending in 'Do_Abi_X(' + WriteAbi + Write(')') -> single
  interpolated multiline ending in 'Do_Abi_X({abiParams})'.
- AbiDelegateFactory.WriteDelegateImpl Invoke emission: same shape as
  Do_Abi - merged into a single multiline raw string.
- AbiDelegateFactory.WriteDelegateVftbl: 'delegate*<' + WriteAbi +
  ', int> Invoke;' -> single multiline raw string with {invokeParams}
  inline between '<' and ', int>'.

Output is byte-identical for all 10 generated files in the refgen-pushnot
scenario.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* M17: WriteFactoryMethodParametersCallback + WriteFactoryReturnTypeCallback

Add callback structs + factory overloads for ComponentFactory's two
factory-method-emission helpers. Both helpers are now public to enable
the callback dispatch path.

Migrates the 4 WriteFactoryMethodParameters call sites + 1
WriteFactoryReturnType call site:

- WriteFactoryActivatableMethod: was 5 chunked Write/WriteLine calls
  with two embedded parameter-list emissions; now a single WriteLine
  with two {typedParams}/{nameOnlyParams} callback holes.
- WriteStaticFactoryMethod: was 6 chunked Write/WriteLine calls with
  embedded return-type + two parameter-list emissions; now a single
  WriteLine with {retType}, {typedParams}, {nameOnlyParams} holes.

Output is byte-identical for all 10 generated files in the refgen-pushnot
scenario.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* M18: WriteGuidAttributeCallback

Add WriteGuidAttributeCallback + factory overload on InterfaceFactory
and migrate the 2 call sites:

- AbiInterfaceIDicFactory.WriteInterfaceIdicImpl: 4 chunked
  WriteLine + WriteGuidAttribute + WriteLine + WriteLine calls
  collapsed into a single multi-line interpolated raw string with
  {guidAttr} and {parent}.
- InterfaceFactory.WriteInterface: WriteGuidAttribute + WriteLine pair
  collapsed into a single WriteLine(\$"{guidAttr}").

Output is byte-identical for all 10 generated files in the refgen-pushnot
scenario.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* M19a: WriteParameterNameWithModifierCallback (per-element)

Add a per-element WriteParameterNameWithModifierCallback and factory
overload on ClassMembersFactory. Migrates the 6 in-loop call sites that
were emitting an argument list one parameter at a time:

- AbiInterfaceIDicFactory.WriteIdicForwardingMethod (2 loops)
- ClassMembersFactory.WriteInterfaceMembers (3 loops)
- ClassFactory.WriteStaticInterfaceMember (1 loop)

Each loop body now consists of a single Write(\$"{sep}{p}") call (where
'sep' is either ", " or "" depending on element index and whether the
loop has a leading separator), keeping the per-element shape readable
and the 1:1 callback wrapper convention consistent with the rest of the
codebase.

Output is byte-identical for all 10 generated files in the refgen-pushnot
scenario.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Suppress blank lines from empty interpolation holes in multiline writes

When a multiline interpolated raw string in `IndentedTextWriter` contains
an interpolation hole that expands to empty content, the multiline parser
previously still emitted the line's terminating newline, producing a stray
blank line in the output. This commit makes the
`AppendInterpolatedStringHandler` collapse those would-be blank lines
unconditionally.

This is needed to make conditional callback emission work cleanly inside
a single multiline interpolated template. Concretely, the pattern that
will be unlocked by this is:

    writer.WriteLine(isMultiline: true, $$"""
        [WindowsRuntimeMetadata("...")]
        {{valueTypeAttr}}
        {{refTypeAttr}}
        {{comWrappersAttr}}
        public class Foo
        """);

where the conditional attribute callbacks (which early-return based on
`ReferenceProjection` / other build configuration) may expand to
empty. Without the collapse rule, each empty hole would emit a stray
blank line into the output, breaking byte-identical generation.

The behavior is always on (no opt-in flag) so callers do not have to
think about whether their template needs it.

Implementation:

- The `AppendInterpolatedStringHandler` struct drops its `readonly`
  qualifier (already passed everywhere by `ref`) so it can carry the
  mutable `_anyContentBetweenLiterals` tracker used by the suppress
  rule. The existing two constructors are unchanged.
- `AppendLiteral` checks the suppress condition (previous buffer
  ended with newline + no formatted content since the last literal +
  this literal starts with a newline) and, if all three hold, slices
  off the leading newline. Slicing uses a local `ReadOnlySpan<char>`
  so there is no string allocation, and dispatches to the existing
  `IndentedTextWriter.Write(bool, ReadOnlySpan<char>)` overload.
  Both LF and CRLF line endings are matched (raw-string literals
  inherit the source file's line endings; CR-only is not supported).
- All four `AppendFormatted` overloads now capture buffer length
  before/after their work and set `_anyContentBetweenLiterals` when
  the buffer grew, so the suppress check can decide whether the
  intervening holes contributed any content.
- Literal blank lines (e.g. a deliberate blank line inside a single
  raw-string segment) are always preserved because they are emitted by
  the multiline parser in one streaming pass without the suppress
  rule getting a chance to fire between them.

Validation:

- Adds an `InternalsVisibleTo("WinRT.Projection.Writer.TestRunner")`
  entry to the writer project so the test runner can construct an
  `IndentedTextWriter` directly.
- Adds a `smoke` command to the test runner that exercises 10
  scenarios covering: all-empty middle holes, partial-empty mix, all
  non-empty (no suppression), literal blank line preservation,
  multiple empty holes on one line, leading empty hole at buffer
  start, `Write` vs `WriteLine`, indented writer, literal-only
  templates, and string-callback empty-emission parity. All 10 pass.
- Re-runs the full projection writer harness against the three
  validation scenarios (refgen-pushnot / refgen-everything-with-ui /
  refgen-windows): 763/763 generated `.cs` files are byte-identical
  to the pre-change baseline, confirming that no existing call site
  relied on the previous "empty hole produces a blank line" behavior.
- `dotnet publish -r win-x64` of `WinRT.Projection.Generator`
  (Native AOT) succeeds clean.

Co-Authored-By: Copilot <223556219+Copilot@users.noreply.github.com>

* R6-6: Collapse DoAbi 22-line dead 4-branch chain to single line

All four branches of the if/elif/elif/else in 'EmitDoAbiPostCallWriteback'
emit identical "*{retParamName} = {retLocalName};" output (verified by view).
Replace the chunked Write + 4-branch dispatch with a single WriteLine.

Output is byte-identical for all 3 reference scenarios (763 generated .cs
files: 10 pushnot + 406 everything-with-ui + 347 windows).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* R6-30: Dead code and discard cleanups

Removes dead/unused code identified in the R6 analysis:

- Deletes the 13 `string elementInteropArg = InteropTypeNameWriter.EncodeInteropTypeName(...);` +
  `_ = elementInteropArg;` pairs in DoAbi.cs, RcwCaller.cs, FactoryCallbacks.cs.
  Each local was assigned and then immediately discarded with `_ = ...;` - the value
  is never used. Both lines are deleted per pair.
- Deletes the `hasStringParams` block in DoAbi.cs (declaration + loop + discard):
  the flag was computed by iterating all params but never consumed.
- Inlines the two single-line `WritePragmaDisableIL2026` / `WritePragmaRestoreIL2026`
  helpers at their only call site in ProjectionGenerator.Namespace.cs and deletes the
  definitions in MetadataAttributeFactory.cs (each helper had exactly 1 caller).
- Deletes the dead `WriteFundamentalNonProjectedType` helper in TypedefNameWriter.cs
  (0 callers, verified by grep). The sibling `WriteFundamentalType` is kept (1 caller).
- Deletes the stray `//, 1697).` comment fragment at ClassMembersFactory.WriteClassMembers.cs:83.
- Deletes the duplicate `<summary>` XML doc on `WriteDelegateMarshallerOnly` in
  AbiDelegateFactory.cs (two summaries, kept the more specific one).
- Replaces spurious `writer.Write($`"{getterPlat}")` / `writer.Write($`"{setterPlat}")`
  with direct `writer.Write(getterPlat)` / `writer.Write(setterPlat)` at
  ClassMembersFactory.WriteClassMembers.cs:121,149 (the interpolation was a no-op since
  the value is already a string).

Output is byte-identical for all 3 reference scenarios (763 generated .cs files).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* R6-20: Extract MarkAllRequiredInterfacesVisited helper

The 3 `foreach (impl2 in required.Interfaces) { ... _ = visited.Add(r2); }`
blocks in AbiInterfaceIDicFactory.cs (at the IBindableVector, IObservableMap`2,
and IObservableVector`1 special-case handlers) were character-for-character
identical. Extract a private `MarkAllRequiredInterfacesVisited` helper that
encapsulates the iteration + null-check + `visited.Add` logic.

Each call site now reads as one method invocation instead of an 11-line
nested foreach with conditional inner logic, making the actual control
flow of `WriteInterfaceIdicImplMembersForRequiredInterfaces` easier to follow.

Output is byte-identical for all 3 reference scenarios (763 generated .cs files).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* R6-19: Unify FindPropertyInBaseInterfaces variants via Try- pattern

Replaces the two ~50-line duplicated recursive walks
(`FindPropertyInBaseInterfaces` returning `bool` and
`FindPropertyInterfaceInBases` returning `TypeDefinition?`) with one
`TryFindPropertyInBaseInterfaces` method that uses the idiomatic
.NET `Try-` pattern with a `[NotNullWhen(true)] out TypeDefinition?
foundInterface` parameter.

Both callers updated:

- `InterfaceFactory.cs:261` (was the bool variant; now uses `out _`
  since only existence matters for the `new` modifier decision).
- `AbiInterfaceIDicFactory.cs:472` (was the `TypeDefinition?` variant
  + `is not null` follow-up; now a single `if (Try... out var
  baseIfaceWithGetter)` statement).

Removes ~50 lines of duplicated recursion; the original C++ tool had a
single helper. The `Try-` pattern avoids the call-site null-check
follow-up.

Output is byte-identical for all 3 reference scenarios (763 generated .cs files).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* R6-13: ClassFactory IsOverridableInterface — string.Join instead of WriteIf bookkeeping

Replace the mutable `firstClause` bool + 3 `WriteIf(!firstClause, " || ")`
calls in ClassFactory's two override-hook emitters with a `List<string>`
clauses + `string.Join(" || ", clauses)`. The fallback `"false"` is
selected when the list is empty.

The two adjacent override hooks (`HasUnwrappableNativeObjectReference` and
`IsOverridableInterface`) are now emitted via one multiline raw-string
`WriteLine` with the expression bodies inlined as interpolation holes:
the expression now reads at the call site the same way the generated source
appears in the output.

Output is byte-identical for all 3 reference scenarios (763 generated .cs files).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* R6-5: IdentifierEscaping.EscapeIdentifier helper + ParameterInfo.GetEscapedName

Adds two foundational helpers and migrates all open-coded
`CSharpKeywords.IsKeyword(x) ? "@" + x : x` ternaries to the central
helper, eliminating the 24+ verified duplicated escape ternaries across
the codebase:

- `IdentifierEscaping.EscapeIdentifier(string id)` — returns the
  identifier prefixed with `@` if it's a reserved C# keyword.
- `ParameterInfo.GetEscapedName(string defaultName = "param")` —
  extension that combines `p.Parameter.Name ?? defaultName` with the
  escape. Available for future per-site simplifications where the
  `string raw = … ?? "param"` preamble is only used to compute the
  escaped form.

Mechanical migration: every
`writer.Write(CSharpKeywords.IsKeyword(X) ? "@" + X : X)` becomes
`writer.Write(IdentifierEscaping.EscapeIdentifier(X))`, and every
`string Y = CSharpKeywords.IsKeyword(X) ? "@" + X : X;` becomes
`string Y = IdentifierEscaping.EscapeIdentifier(X);` across
AbiDelegateFactory.cs, AbiMethodBodyFactory.DoAbi.cs,
ConstructorFactory.{AttributedTypes,Composable,FactoryCallbacks}.cs,
EventTableFactory.cs, and Helpers/AbiTypeHelpers.cs.

The existing single `WriteParameterName` writer-style helper in
MethodFactory.cs:121-128 is preserved unchanged (intentionally split for
its multiline behavior). Sites where both `raw` (used in
`__{raw}`/`_{raw}` variable names) and `pname` (the escaped form)
are needed are left untouched — only the ternary itself is collapsed.

Output is byte-identical for all 3 reference scenarios (763 generated .cs files).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* R6-9: AbiDelegateFactory event-source — reuse WriteParameterNameWithModifierCallback

The two character-for-character identical loops at AbiDelegateFactory.cs:289-306
and 308-325 (the GetEventInvoke `(args)` formal list and the
`TargetDelegate.Invoke(args)` argument list) were reimplementing the
exact same in/out/escape logic already provided by
`ClassMembersFactory.WriteParameterNameWithModifier`. Replace both with
the existing callback.

Each loop collapses from 18 lines to 5 (one `writer.Write($`"{sep}{p}")`
per element using the existing callback's emission).

Per R6-5 gotcha: the original loops used `"p"` fallback while the
shared helper uses `"param"`. Byte-identical in practice (compiled
metadata never has null parameter names for projected delegates), and
the validation confirms this: output is byte-identical for all 3
reference scenarios (763 generated .cs files).

Also removes the now-unused `WindowsRuntime.ProjectionWriter.Resolvers`
import.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* R6-14: Add ITypeDefOrRef.GetStrippedName() extension

Adds a `GetStrippedName()` extension on `ITypeDefOrRef` that returns
`IdentifierEscaping.StripBackticks(type.Name?.Value ?? string.Empty)`
in one call, and applies it across the 16 sites that did the same
2-line preamble inline.

All 16 sites turned out to have `name` unused after the preamble
(verified by the analyzer reporting IDE0059 "Unnecessary assignment"
on every one), so the `string name = type.Name?.Value ?? string.Empty;`
line is dropped at each site. The replacement is a single
`string nameStripped = type.GetStrippedName();`.

Sites migrated: AbiClassFactory.cs, AbiInterfaceFactory.cs (4),
AbiInterfaceIDicFactory.cs, AbiDelegateFactory.cs (8),
ReferenceImplFactory.cs, StructEnumMarshallerFactory.cs.

This also lets the actual interesting first statement (the writer call)
sit nearer the top of each `WriteXxx` method, instead of two lines
below mechanical boilerplate.

R6-14b (the `DelegateNames` record extracting shared name resolution
across 3 methods in AbiDelegateFactory.cs) is deferred — the 3-method
duplication is narrow enough that R6-14a covers the bulk of the
readability win.

Output is byte-identical for all 3 reference scenarios (763 generated .cs files).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* R6-3: WriteCallArgumentsCallback for argument-forwarding loops

Adds a `WriteCallArguments` writer-style helper + corresponding
`WriteCallArgumentsCallback` to `MethodFactory`. The helper takes a
`bool leadingComma` flag to support both shapes:

- `leadingComma: true` -> emits `, p1, p2, ..., pN` (for appending to a
  call site that already passed at least one arg, e.g. an objref).
- `leadingComma: false` -> emits `p1, p2, ..., pN` (for the first
  argument of a call).

Internally wraps the existing `WriteParameterNameWithModifierCallback`
per element so all in/out/escape logic stays centralized.

6 hand-rolled per-element loops collapse to a single `{args}`
interpolation hole inside a one-line `writer.WriteLine($`"...({args});")`:

- ClassFactory.cs:368-374 (static method dispatch)
- ClassMembersFactory.WriteInterfaceMembers.cs:339-345 (accessor dispatch)
- ClassMembersFactory.WriteInterfaceMembers.cs:365-371 (instance dispatch)
- ClassMembersFactory.WriteInterfaceMembers.cs:385-391 (overridable interface impl)
- AbiInterfaceIDicFactory.cs:307-313 (IDIC method forwarder)
- AbiInterfaceIDicFactory.cs:446-451 (IDIC method dispatch)

Output is byte-identical for all 3 reference scenarios (763 generated .cs files).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* R6-21: WriteProjectionParameter[Type]Callback for single-parameter sites

Adds `WriteProjectionParameterCallback` (type + name) and
`WriteProjectionParameterTypeCallback` (type only) + the matching
factory overloads on `MethodFactory`. Migrates the 4 chunked
single-parameter callsites:

- ClassMembersFactory.WriteInterfaceMembers.cs:319 (UnsafeAccessor signature param)
- ConstructorFactory.Composable.cs:89 (composable ctor user-param list)
- ConstructorFactory.FactoryCallbacks.cs:39 (factory-args struct ctor list)
- ConstructorFactory.FactoryCallbacks.cs:53 (factory-args struct field decl - uses TypeCallback)

Each loop body becomes a single `writer.Write($`"{sep}{p}")` expression
using the new callback. Together with the existing
`WriteParameterNameWithModifierCallback` (per-element name + modifier)
this unlocks single-multiline emission for several adjacent sites
(R6-22 builds on this).

Output is byte-identical for all 3 reference scenarios (763 generated .cs files).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* R6-22: Consolidate generic interface method emission into one multiline

The UnsafeAccessor + dispatch block in
`ClassMembersFactory.WriteInterfaceMembers.cs:306-341` was 4 chunked
emissions interleaved with a per-parameter loop and conditional ref/
non-ref dispatch. Collapse into a single multiline raw-string emission
by pre-computing the variant locals first:

- `accessorParams`: leading-comma-prefixed projected parameter list
  (built via the new `WriteProjectionParameter` callback's `.Format()`
  + `string.Concat` over the parameters).
- `body`: `throw null;` in ref-projection mode, otherwise the
  `{accessor}(null, {objRef}{callArgs});` dispatch using the existing
  `WriteCallArguments` callback.
- `platformTrimmed`: `platformAttribute` with its trailing newline
  stripped so the multiline template line that holds it doesn't produce
  a stray blank line when the attribute is empty (the writer's
  `AppendInterpolatedStringHandler` already suppresses the line when
  the hole expands to empty).

The result: one `writer.WriteLine(isMultiline: true, $`"""...""")` whose
shape directly mirrors the emitted source.

Depends on R6-3 (`WriteCallArgumentsCallback`) and R6-21
(`WriteProjectionParameterCallback`).

Output is byte-identical for all 3 reference scenarios (763 generated .cs files).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* R6-1: Deduplicate WriteDefault/ExclusiveToInterfacesClass

The two methods at MetadataAttributeFactory.cs:402-460 were
character-for-character identical except for 3 string literals:
- the attribute name (`WindowsRuntimeDefaultInterface` vs
  `WindowsRuntimeExclusiveToInterface`)
- the generated class name
- the output file name

Factor the shared 28-line body into a private
`WriteInterfaceMapClass(settings, sortedEntries, attributeName,
className, fileName)` method. The two public entry-point methods
become 1-line wrappers.

Removes ~30 lines of mechanical duplication and prevents drift between
the two emitters.

Output is byte-identical for all 3 reference scenarios (763 generated .cs files).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* R6-2: Callbackify MetadataAttributeFactory Write*Attribute helpers

Adds 6 callback variants for the per-attribute helpers in
`MetadataAttributeFactory` + 1 for `CustomAttributeFactory.WriteTypeCustomAttributes`,
plus internal `*Body` helpers that emit the attribute body without the
trailing newline. The writer-version of each helper is preserved and
now delegates to its body method + `writer.WriteLine()` (conditional
on the body having written anything, for the 3 ref-projection-gated
helpers).

The attribute-per-bracket invariant is preserved: each callback emits
exactly one `[Attr(...)]` and nothing else. Multiple attributes are
never merged into a comma-separated `[A, B]` list — the multiline
template provides one line per attribute hole.

Conditional emission flows through cleanly: the 3 ref-projection-gated
helpers (`WriteValueTypeWinRTClassNameAttribute`,
`WriteWinRTReferenceTypeAttribute`, `WriteComWrapperMarshallerAttribute`)
emit nothing in ref-projection mode, and the blank-line suppression in
`AppendInterpolatedStringHandler` (commit 158a9813) drops the empty
template line so output stays byte-identical.

Migrates 7 type-header emission sites to single multiline raw-string
templates:

- `InterfaceFactory.cs:427`: metadata + Guid (2 attrs + decl header)
- `ProjectionFileBuilder.cs:107` (enum): 5 attrs + decl
- `ProjectionFileBuilder.cs:218` (struct): 5 attrs + decl
- `ProjectionFileBuilder.cs:381` (attribute class): 2 attrs + decl
- `ClassF…
…2429)

* Add WinRT.Internal project to solution

Add a new SDK project src/WinRT.Internal/WinRT.Internal.csproj targeting net10.0 with implicit usings and nullable enabled. Also update cswinrt.slnx to include the new project (solution entry: WinRT.Internal/Windows.Internal.csproj).

* Port WindowsRuntime.Internal.idl to C# in WinRT.Internal

Port the contents of src/cswinrt/WindowsRuntime.Internal.idl to C#
source files in the new src/WinRT.Internal project, in preparation for
removing the legacy C++ cswinrt project. The C# definitions cover:

- HWND struct (custom-mapped to System.IntPtr by the projection writer).
- [ProjectionInternal] custom attribute (consumed by CsWinRT to emit
  internal projections for the marked interfaces).
- All 14 interop interfaces (IAccountsSettingsPaneInterop,
  IDisplayInformationStaticsInterop, IDragDropManagerInterop,
  IInputPaneInterop, IPlayToManagerInterop, IPrintManagerInterop,
  IRadialControllerConfigurationInterop,
  IRadialControllerIndependentInputSourceInterop,
  IRadialControllerInterop, ISpatialInteractionManagerInterop,
  ISystemMediaTransportControlsInterop, IUIViewSettingsInterop,
  IUserConsentVerifierInterop, IWebAuthenticationCoreManagerInterop)
  with their original IIDs, parameter lists, and reference links
  to the corresponding Win32 *interop.idl documentation pages.

Each interface carries [Guid(...)] (from System.Runtime.InteropServices)
and [ProjectionInternal], and exposes the same methods as the IDL with
the same parameter order. The "riid" out-parameter selector uses
"ref Guid riid" rather than "in" / "[In] ref" so that the C# compiler
emits a plain "Guid&" byref without "modreq(InAttribute)" /
"modopt(IsReadOnlyAttribute)" decorations.

The csproj targets "net10.0-windows10.0.26100.1" (revision .1 selects
the cswinrt3 Windows SDK projection reference assemblies, which carry
[WindowsRuntimeMetadata] attributes the WinMD generator reads) and
pins WindowsSdkPackageVersion to the matching SDK NET Ref package so
the implicit FrameworkReference resolves. All CsWinRT MSBuild
processing (CsWinRTEnabled / CsWinRTGenerateProjection /
CsWinRTGenerateInteropAssembly[2]) is disabled because this project
itself feeds back into the CsWinRT build pipeline. It references the
locally built WinRT.Runtime.dll directly via a HintPath so it does
not require the Microsoft.Windows.CsWinRT NuGet package.

The output .dll is only an intermediate artifact; the .winmd that the
CsWinRT toolchain consumes will be wired up in a follow-up commit.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* WinMD generator: preserve input parameter direction, pin assembly version

Two small fixes to the WinMD generator needed for the C# port of
WindowsRuntime.Internal.idl to produce metadata compatible with both
the existing toolchain and the original C++-generated .winmd:

1. Preserve [In] / [Out] parameter direction flags from input
   parameters. Previously, GetWinRTParameterAttributes unconditionally
   returned ParameterAttributes.Out for any by-reference parameter,
   which is wrong for "ref Guid riid"-style COM interop signatures
   that MIDL emits as [in] ref. The new logic honors any explicit
   In/Out flag already set by the C# compiler on the input parameter,
   and only falls back to a type-based heuristic when no flag is set
   (Span<T> -> [out], everything else including byref -> [in]).
   This matches MIDL's convention for "ref const T" parameters and
   keeps the projection writer's ParameterCategoryResolver classifying
   them as Ref rather than Out.

2. Always stamp the .winmd assembly identity with version
   255.255.255.255 (the WinRT "unbound" convention used by every
   Windows Runtime metadata assembly). The "--assembly-version"
   argument is still used as-is for the type-level
   "[Windows.Foundation.Metadata.Version]" attribute applied to each
   authored type. Verified against the existing TestComponent and
   TestComponentCSharp .winmd outputs, both of which carry
   v255.255.255.255 as their assembly identity regardless of the
   project's <AssemblyVersion>.

Also bumps the WinRT.Internal csproj to use AssemblyName
"WindowsRuntime.Internal" so the produced .dll (and its .winmd) match
the identity that the rest of the CsWinRT toolchain expects.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Wire WinMD generation into WinRT.Internal build

Adds an MSBuild target to WinRT.Internal.csproj that invokes
'cswinrtwinmdgen.exe' (built from this solution) after CoreCompile and
emits 'WindowsRuntime.Internal.winmd' next to the compiled
'WindowsRuntime.Internal.dll' in '$(TargetDir)'.

Adds ProjectReference items to WinRT.WinMD.Generator (the tool
'cswinrtwinmdgen.exe') and WinRT.Generator.Tasks (the
RunCsWinRTWinMDGenerator MSBuild task) with
ReferenceOutputAssembly=false so they participate in the build graph
without becoming runtime references. The 'UsingTask' resolves the task
assembly out of the locally built WinRT.Generator.Tasks output, and
the tool path is computed from the locally built WinRT.WinMD.Generator
output. 'CsWinRTToolsArchitecture=AnyCPU' is passed so the tool task
uses the top-level 'cswinrtwinmdgen.exe' apphost wrapper (no per-arch
publish folder).

Verified: the produced 'WindowsRuntime.Internal.winmd' contains
exactly the same set of public types as the legacy C++-generated
'WindowsRuntime.Internal.winmd' (HWND struct, all 14 interop
interfaces with their original IIDs and method counts, and the
ProjectionInternalAttribute class). The only extra entry is a
synthesized 'IProjectionInternalAttributeClass' interface that the
C# WinMD generator emits for attribute classes; it is non-public and
is filtered out by the projection writer's
'AssemblyAnalyzer.DiscoverPublicTypes' step.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Route CsWinRTInteropMetadata to WinRT.Internal output

Now that WinRT.Internal produces WindowsRuntime.Internal.winmd via the
C# WinMD generator, point all build infrastructure at the new file
instead of the legacy cswinrt (C++) bin directory. The output cswinrt
project still exists on disk for now but no longer needs to be built
for any consumer of WindowsRuntime.Internal.winmd to function; that
project will be deleted in a follow-up commit.

Specifically:

- src/Directory.Build.props:
  - 'CsWinRTInteropMetadata' now resolves to
    'src/WinRT.Internal/bin/$(Configuration)/$(CsWinRTInternalTargetFramework)/WindowsRuntime.Internal.winmd'
    (with 'CsWinRTInternalTargetFramework' defaulting to
    'net10.0-windows10.0.26100.1', matching the WinRT.Internal csproj
    TFM that selects the cswinrt3 SDK projection reference assemblies).
  - 'AssemblyVersionNumber' now defaults to '3.0.0.0' (the WinRT
    runtime baseline for CsWinRT 3.0). Without this, dev builds of
    WinRT.Runtime would stamp v1.0.0.0 by default, and the cswinrt3
    SDK projection would fail to resolve against the local runtime.
  - 'CsWinRTPath' / 'CsWinRTExe' are kept defined (still pointing at
    the legacy cswinrt bin folder), but only as path strings: the C#
    'cswinrtprojectiongen.exe' tool no longer reads or executes the
    .exe path, so the file no longer needs to exist. They will be
    removed entirely once 'src/cswinrt' itself is dropped.

- src/build.cmd: 'interop_winmd' now points at the WinRT.Internal
  output instead of '_build/{platform}/{config}/cswinrt/bin/'.

- build/AzurePipelineTemplates/CsWinRT-Build-Steps.yml: the "Stage
  WindowsRuntime.Internal.winmd" task copies from
  'src/WinRT.Internal/bin/$(BuildConfiguration)/net10.0-windows10.0.26100.1/'
  rather than the cswinrt bin folder; staging into
  '$(StagingFolder)/native' is preserved so the downstream nuget pack
  step (CsWinRT-PublishToNuGet-Steps.yml) still finds the file at the
  same place.

- src/cswinrt.slnx: WinRT.Internal no longer carries '<Build
  Project="false" />' so it participates in default solution-level
  builds.

- src/WinRT.Internal/WinRT.Internal.csproj: switches from
  '<Reference HintPath="..." />' to '<ProjectReference Private="false"
  />' for WinRT.Runtime so that 'dotnet build' on this project alone
  implicitly builds WinRT.Runtime2 first (no copy of WinRT.Runtime.dll
  into bin).

Verified end-to-end: 'dotnet build src/WinRT.Sdk.Projection/...' now
consumes the new WindowsRuntime.Internal.winmd and produces
WinRT.Sdk.Projection.dll containing the expected
'ABI.WindowsRuntime.Internal.IAccountsSettingsPaneInteropMethods'
internal projection (alongside the regular projected
'Windows.UI.ApplicationSettings.AccountsSettingsPane' runtime class),
confirming the full interop pipeline still works.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Update WinRT.Internal sources and csproj

Refactor several WinRT.Internal sources and adjust project settings:

- HWND.cs: add pragma to suppress naming warning, rename reserved field to __Reserved, document projection to nint and add reference link.
- Interop interfaces: normalize GUID literals to uppercase for IPrintManagerInterop, IRadialControllerConfigurationInterop, ISystemMediaTransportControlsInterop, and IUIViewSettingsInterop.
- ProjectionInternalAttribute.cs: add C# language tag to the code sample and convert the attribute type to a forward declaration (public sealed class ProjectionInternalAttribute : Attribute;).
- WinRT.Internal.csproj: clean up XML/comments, include BOM, disable nullable (Windows Runtime types don't support nullability annotations), adjust NoWarn entries, inline ProjectReference entries, and reformat UsingTask/Target elements.

These changes improve consistency, clarify projection mappings, and update build settings to match the Windows Runtime metadata requirements.

* Use TaskHostFactory to avoid WinRT.Generator.Tasks file lock on rebuild

When Visual Studio (or the persistent 'dotnet build-server') loads
'WinRT.Generator.Tasks.dll' to run the 'RunCsWinRTWinMDGenerator'
custom MSBuild task, the main MSBuild process keeps the assembly
loaded for the lifetime of the build session. The 'ProjectReference'
from WinRT.Internal to WinRT.Generator.Tasks then triggers
WinRT.Generator.Tasks to rebuild on the next incremental build (or
the next 'right-click -> Build' in VS), which fails with:

  warning MSB3026: Could not copy ... WinRT.Generator.Tasks.dll ...
  The file is locked by: "MSBuild.exe (...) , MSBuild.exe (...), ..."
  error MSB3027: ... Exceeded retry count of 10.

Adding 'TaskFactory="TaskHostFactory"' to the 'UsingTask' loads the
task in a separate 'MSBuild.TaskHost' child process, which exits
after the build, so the main MSBuild process never keeps a handle
to 'WinRT.Generator.Tasks.dll'. Subsequent rebuilds can then
overwrite it without contention.

Verified with three back-to-back 'dotnet build' invocations that
each trigger 'CoreCompile' on WinRT.Internal (and, in the third
case, a rebuild of WinRT.Generator.Tasks itself): all complete
cleanly with 0 warnings / 0 errors and 'WindowsRuntime.Internal.winmd'
gets regenerated.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Run WinMD generator and mark output as FileWrites

Add explicit properties and invoke the WinMD generator task to produce this project's .winmd and ensure incremental builds recognize the generated file. Introduces _CsWinRTWinMDGenToolsDirectory and _CsWinRTInternalWinMDOutputPath, calls RunCsWinRTWinMDGenerator with those paths, and adds a FileWrites entry for the generated .winmd. Also reformats the UsingTask tag and adds inline comments for clarity.

* Bypass MSBuild task to avoid WinRT.Generator.Tasks.dll file lock

The previous 'TaskFactory="TaskHostFactory"' attempt did not fully
resolve the file-lock issue in Visual Studio: VS keeps multiple
'MSBuild.exe' build-server processes alive across IntelliSense and
build sessions, and at least one of them ends up holding a handle to
'WinRT.Generator.Tasks.dll' even with the task host opt-in. The result
in VS is the same 'MSB3027' (file locked by multiple 'MSBuild.exe'
processes) error on every other build of WinRT.Internal.

The root issue is that this project depends on its own custom MSBuild
task ('RunCsWinRTWinMDGenerator' from WinRT.Generator.Tasks) and is
also part of the build graph that produces that task assembly via a
'ProjectReference'. Any time MSBuild loads the task DLL during a build
session and VS later tries to rebuild it, the copy fails.

Rather than chase that contention, this commit removes the custom-task
dependency for this project entirely:

- Drops the 'ProjectReference' to WinRT.Generator.Tasks (no longer
  needed).
- Drops the 'UsingTask' / 'RunCsWinRTWinMDGenerator' call.
- Invokes 'cswinrtwinmdgen.exe' directly via a stock '<Exec>' task
  with a generated '.rsp' response file passed as '@<file>' (the same
  format 'ConsoleAppFramework' / 'WinMDGeneratorArgs.ParseFromResponseFile'
  already understands).

The response file is written under '$(IntermediateOutputPath)' via
'<WriteLinesToFile WriteOnlyWhenDifferent="true">' and registered as
a 'FileWrites' item so it's tracked by the incremental-build cache.
The end-to-end behavior is identical to before — same arguments,
same generator, same '.winmd' output — but the build never loads
'WinRT.Generator.Tasks.dll' into MSBuild's process space, so it can
freely be rebuilt at any time.

Verified with four back-to-back 'dotnet build' invocations on the
same build server, each triggering a different point in the
dependency chain (HWND.cs source change -> rebuild of
'WinRT.Generator.Tasks.dll' itself -> rebuild of
'cswinrtwinmdgen.exe' itself), all completing with 0 errors and the
'.winmd' regenerated correctly each time.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Polish WinRT.Internal.csproj and qualify Span types

Reformat and clarify WinRT.Internal.csproj: normalize header, collapse XML comment into a block, wrap ProjectReference attributes across lines for readability, tweak comment wording, add comments around preparing/writing the generator '.rsp' file, and mark the response and output files in FileWrites to help incremental builds. Also small doc fix in WinMDWriter: fully qualify ReadOnlySpan/Span as System.ReadOnlySpan/System.Span in XML docs to avoid ambiguity.

* Polish comments and clean up build script

Clarify and normalize in-code comments and formatting across the repo and remove a stale comment in the build script. Changes touch Directory.Build.props, WinMD.WinMD.Generator (WinMDValues.cs, WinMDWriter.Members.cs, WinMDWriter.cs) and build.cmd. All edits are comment/whitespace-level (consistent quoting, naming like 'Windows Runtime'/'WinRT.Runtime', and punctuation) and do not change runtime behavior.

* Resolve WinMD generator via effective tools dir

Use CsWinRTWinMDGenEffectiveToolsDirectory to locate cswinrtwinmdgen.exe instead of hard-coding the net10.0 output folder. Adds explanatory comment and changes _CsWinRTWinMDGenExePath to normalize the effective tools directory path so builds succeed when tools are published under RID subfolders (e.g. net10.0\win-arm64) such as in CI/PublishBuildTool scenarios. Prevents MSB3073/exit code 9009 caused by missing tool path on non-x64 architectures.

* Build WinMD generator for the host so the arm64 leg can run it

The arm64 CI leg cross-compiles on an x64 host. With 'PublishBuildTool=true'
and 'BuildToolArch=arm64' (set by CI), the 'WinRT.WinMD.Generator'
ProjectReference would build 'cswinrtwinmdgen.exe' as an arm64 Native AOT
binary, which cannot execute on the x64 host and fails with exit code 216
(ERROR_EXE_MACHINE_TYPE_MISMATCH).

The generated WindowsRuntime.Internal.winmd is always needed, so skipping
generation isn't viable. Instead, add 'UndefineProperties' to the generator
ProjectReference to drop 'BuildToolArch'/'PublishBuildTool' (and
'RuntimeIdentifier'/'SelfContained'), forcing a framework-dependent build for
the build host (output under 'net10.0\', host-arch apphost). The '.winmd' is
architecture-neutral, so a host-built generator produces identical metadata.
The exec path is updated to that host-built 'net10.0\cswinrtwinmdgen.exe'.

The separate, solution-level build of WinRT.WinMD.Generator (which produces
the arch-specific binary shipped in the NuGet package) is keyed on different
global properties and is unaffected; its 'win-<arch>\' output coexists with
the framework-dependent 'net10.0\' output in a distinct directory.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Remove unnecessary assembly name

* Simplify XML docs for ProjectionInternalAttribute

Clean up the XML documentation for ProjectionInternalAttribute by merging paragraphs and removing the Modern IDL code block. This makes the remarks more concise while preserving the explanation that interfaces marked with the attribute are projected as internal and that hand-authored extension methods (e.g. ComInteropExtensions) expose user-friendly wrappers.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Remove cswinrt native project and refs

Remove the cswinrt native project and its references across the repo. Deleted the src/cswinrt project files and sources (Directory.Build.props/targets, headers, sources, IDL, generators, etc.), removed ProjectReference entries to cswinrt.vcxproj from multiple Projections and Test csproj files, and updated src/cswinrt.slnx to drop the cswinrt project and related build dependencies. This cleans up the solution by removing the legacy/native cswinrt component and its build hooks.

* Remove writer validation harness

Delete the golden-output validation harness and its documentation for WinRT.Projection.Writer. Removes src/WinRT.Projection.Writer/eng/validate-writer-output.ps1 and the accompanying README.md which captured/validated per-scenario SHA256 manifests for emitted .cs files (capture/validate/capture-and-validate modes and baseline files).

* Remove WinRT Projection Writer TestRunner project

Delete the WinRT.Projection.Writer.TestRunner application and its project file. Removes src/WinRT.Projection.Writer.TestRunner/Program.cs (the CLI test runner implementing compare/rsp/smoke modes and various smoke tests) and src/WinRT.Projection.Writer.TestRunner/WinRT.Projection.Writer.TestRunner.csproj to clean up the repository.

* Update Copilot instructions and docs for the C++->C# code writers port

Update the Copilot instructions and supporting .md docs to reflect the deletion of the legacy C++ 'cswinrt.exe' code writers and its replacement by the C# WinRT.Projection.Writer library (consumed in-process by the new cswinrtprojectionrefgen.exe and the existing cswinrtprojectiongen.exe).

.github/copilot-instructions.md:
- Repository structure: removed 'cswinrt/' entry, added WinRT.Projection.Writer/ (library) and WinRT.Projection.Ref.Generator/ (CLI tool); renumbered the 9 projects to 10.
- Architecture Mermaid diagram: replaced 'cswinrt.exe' with 'cswinrtprojectionrefgen.exe' as the CsWinRTGenerateProjection front-end; added a 'Shared projection writer' callout explaining the in-process library model.
- Project details: replaced the entire 'cswinrt.exe' section with two new sections - 'Projection writer' (public ProjectionWriter.Run API, options, resources/additions/base layout, internal-interop-interfaces description relocated here) and 'Reference projection generator' (Native AOT CLI tool replacing the legacy invocation in CsWinRTGenerateProjection). Renumbered the remaining sections (Impl/Projection/Interop/WinMD/Generator-tasks/SDK-projection-builds) to 5-10. Rewrote the Projection generator section to drive the writer in-process (no more 'invoke cswinrt.exe @response.rsp').
- WinRT.Runtime2 directory tree: refreshed to current layout - added Bindables, Buffers, Dispatching, Extensions, Placeholders, ProjectionDllExports, Streams, System.Runtime.InteropServices and InteropServices/Attributes; removed Windows.Foundation.Collections.
- Source generator section: 4 analyzers / 9 diagnostics -> 5 analyzers / 10 diagnostics; added CSWINRT2009 (ComImportInterfaceAnalyzer warning).
- Generator tasks: 4 tasks -> 5 tasks (added RunCsWinRTProjectionRefGenerator).
- Code style: dropped the 'C++ project (cswinrt)' subsection (project deleted).
- Naming / build-tool conventions: 4 .NET build tools -> 5; new WindowsRuntime.ProjectionWriter / WindowsRuntime.ReferenceProjectionGenerator namespaces; updated assembly-name list.
- Error ID ranges: added Reference Projection Generator + Projection Writer rows; corrected ranges for Impl (0001-0014), Interop (0001-0097), WinMD (added 9999); documented the writer's 5xxx reservation within the shared CSWINRTPROJECTIONGEN prefix.
- NuGet pipeline table: dropped 'CsWinRTExe' from the props row, updated CsWinRT.targets description, added Microsoft.Windows.CsWinRT.Native.targets row.
- 'by cswinrt.exe' prose mentions in the custom-mapped / manually-projected sections replaced with 'by the projection writer'.

docs/structure.md:
- Removed the 'src/cswinrt' section.
- Added 'src/WinRT.Projection.Writer' and 'src/WinRT.Projection.Ref.Generator' sections in alphabetical position.
- Dropped 'cswinrt.exe' from the NuGet contents sentence.
- Updated WinRT.Projection.Generator to reference the writer library.
- Updated WinRT.Generator.Tasks description to include the new ref-generator task.

docs/usage.md:
- 'cswinrt -?' -> 'cswinrtprojectionrefgen -h' for the CLI help mention.
- Updated the prose and the Mermaid edge label in the 'Generate reference projection' diagram.

docs/interop.md, docs/diagnostics/cswinrt30001.md, nuget/readme.md:
- Replaced 'cswinrt.exe' mentions with references to the CsWinRT projection writer (kept 'cswinrtinteropgen.exe' where mentioned, that tool is unchanged).

.github/skills/update-copilot-instructions/SKILL.md (per the skill's own Step 7):
- Updated the project list from 8 projects to 10.
- Replaced the 'cswinrt.exe' validation entry with 'Projection writer' + 'Reference projection generator' entries.
- Expanded the Generator tasks validation checklist to include RunCsWinRTProjectionRefGenerator and added a check that the projection generator path is documented as in-process (no cswinrt.exe subprocess).

Remaining 'cswinrt.exe' mentions in the doc tree are intentional historical context only:
- The two new sections in copilot-instructions.md and structure.md (Projection writer / Reference projection generator) reference it as 'the legacy C++ projection compiler from CsWinRT 2.x which has been deleted'.
- src/Samples/NetProjectionSample/README.md keeps an old MSB3073 error example from CsWinRT 1.4.1 - left alone since it's a verbatim historical error message.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Remove leftover cswinrt.exe references

Now that the C# WinRT.Internal project owns WindowsRuntime.Internal.winmd
production end-to-end and no consumer depends on the legacy cswinrt.exe
binary being built (or even present), purge every remaining reference to
the C++ tool across the codebase:

- 'nuget/Microsoft.Windows.CsWinRT.nuspec': drop the
  '<file src="$cswinrt_exe$" target="tools\win-x86"/>' entry that packaged
  cswinrt.exe into the NuGet.
- 'src/build.cmd': drop 'cswinrt_exe' variable and the
  'cswinrt_exe=%cswinrt_exe%;' parameter passed to 'nuget pack'.
- 'src/Directory.Build.props': drop '$(CsWinRTPath)' / '$(CsWinRTExe)'
  legacy build-output paths.
- 'src/WinRT.Sdk.Projection/WinRT.Sdk.Projection.csproj' and the four
  call sites in 'nuget/Microsoft.Windows.CsWinRT.CsWinRTGen.targets':
  drop the 'CsWinRTExePath="$(CsWinRTExe)"' argument passed to the
  'RunCsWinRTMergedProjectionGenerator' MSBuild task.
- 'nuget/Microsoft.Windows.CsWinRT.props': drop the '<CsWinRTExe>'
  property definition.
- 'nuget/Microsoft.Windows.CsWinRT.Authoring.targets': drop
  '<CompilerVisibleProperty Include="CsWinRTExe" />'.
- 'nuget/Microsoft.Windows.CsWinRT.targets': drop the entire dead
  response-file generation subsystem ('CsWinRTCommand',
  'CsWinRTCommandVerbosity', 'CsWinRTWindowsMetadataInput',
  'CsWinRTPublicExclusiveTo', 'CsWinRTDynamicallyInterfaceCastableExclusiveTo'
  legacy override, 'CsWinRTReferenceProjection' legacy override,
  the 'CsWinRTParams' block, the 'WriteLinesToFile' that wrote
  cswinrt.exe-style args to 'cswinrt.rsp', the 'CsWinRTResponseFile'
  property, the 'cswinrt.rsp' file used as the target's Outputs /
  'UpToDateCheckBuilt' marker, the dead 'CsWinRTCleanGenerateProjectionOutputs'
  target plus its 'CallTarget' / 'OnError' calls, and the
  'CsWinRTProjectionExitCode' output capture). The
  'RunCsWinRTProjectionRefGenerator' task constructs its own
  arguments directly, so none of this content was being consumed.
- 'src/WinRT.Generator.Tasks/RunCsWinRTMergedProjectionGenerator.cs':
  drop the 'CsWinRTExePath' property + its '[MemberNotNullWhen]'
  attribute + the 'AppendResponseFileCommand(..., "--cswinrt-exe-path", ...)'
  call. The corresponding '--cswinrt-exe-path' arg is also dropped
  from 'ProjectionGeneratorArgs.cs' / 'ProjectionGeneratorArgs.Parsing.cs'
  in 'WinRT.Projection.Generator' (the value was never consumed).
- Source-file comments: trim every '<c>cswinrt.exe</c>' / 'Mirrors the
  C++ <arg>' mention from:
  - 'src/WinRT.Runtime2/Properties/WindowsRuntimeConstants.cs' (the
    'PrivateImplementationDetailObsoleteMessage' now references
    'cswinrtprojectiongen.exe' instead).
  - 'src/WinRT.Projection.Writer/' file-header banners in emitted
    .cs files (now read 'This file was generated by C#/WinRT version ...').
  - 'src/WinRT.Projection.Writer/Helpers/SignatureGenerator.cs' xmldoc.
  - 'src/WinRT.Projection.Writer/WinRT.Projection.Writer.csproj'
    informational comment about the banner.
  - 'src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs'
    and 'ProjectionGenerator.Generate.cs' inline comments.
  - 'src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs'
    and 'ReferenceProjectionGeneratorArgs.cs' summary docs.
  - 'src/WinRT.Generator.Tasks/RunCsWinRTProjectionRefGenerator.cs'
    type doc and per-property '"Mirrors the C++ ..." doc lines.
  - 'src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs'
    activation-factory remarks.
- Docs cleanup: '.github/copilot-instructions.md', 'docs/structure.md',
  '.github/skills/update-copilot-instructions/SKILL.md',
  'src/Samples/NetProjectionSample/README.md', 'nuget/readme.md' now
  describe the C# projection writer / refgen tools without
  referencing the legacy C++ tool or its CLI args.
- AzurePipelines: drop the "Stage CsWinRT" task that copied
  'cswinrt.exe' / 'cswinrt.pdb' into the staging folder, remove
  'native/cswinrt.exe' from the signing list in
  'CsWinRT-BuildAndTest-Stage-OneBranch.yml', and drop the
  'cswinrt_exe=...' property from the 'nuget pack' invocation in
  'CsWinRT-PublishToNuGet-Steps.yml'.

Verified end-to-end: 'dotnet build' of both 'WinRT.Internal' (produces
'WindowsRuntime.Internal.winmd') and 'WinRT.Sdk.Projection' (consumes
that winmd via 'cswinrtprojectiongen.exe' to produce
'WinRT.Sdk.Projection.dll') both succeed with 0 warnings / 0 errors.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Refresh Copilot instructions for WinRT.Internal and post-cswinrt cleanup

Update '.github/copilot-instructions.md' and the matching update skill
to reflect the current 11-project structure and the complete removal of
the legacy C++ cswinrt.exe tool from this branch.

copilot-instructions.md:
- Repository structure tree: add 'WinRT.Internal/' as project 11.
- WinRT.Runtime2 directory tree: add the actual nested subdirectories
  that were missing — 'ABI/Windows.Storage.Streams/',
  'Windows.Foundation/{Collections,Extensions,Metadata}/',
  'Windows.Storage.Streams/Extensions/',
  'Exceptions/{Microsoft.UI.Xaml,Windows.UI.Xaml}/', and inline notes for
  the second-level subdirs under 'InteropServices/{AsyncInfo,Buffers,
  Marshalling,Streams}/'.
- WinRT.SourceGenerator2 project settings: fix the documented assembly
  name from 'WinRT.SourceGenerator2' to 'WinRT.SourceGenerator' (the
  produced .dll name; the project folder retains the '2' suffix for
  repo history).
- Projection writer directory tree: add the previously missing
  top-level 'Attributes/' folder and 'Extensions/' entry.
- Projection writer "Internal interop interfaces" paragraph: rewrite
  to describe 'WindowsRuntime.Internal.winmd' as produced from the C#
  WinRT.Internal project rather than as a "separately maintained" file.
- Add a new project section "11. WinRT.Internal" describing its role
  (producer of 'WindowsRuntime.Internal.winmd'), project settings
  (TFM 'net10.0-windows10.0.26100.1', disabled CsWinRT integration,
  'IsPackable=false', pinned 'WindowsSdkPackageVersion'), contents
  (HWND struct, 'ProjectionInternalAttribute', and the 14 'I*Interop'
  C# source files), and MSBuild integration (the
  'GenerateWindowsRuntimeInternalWinMD' target invokes
  'cswinrtwinmdgen.exe' via '<Exec>' to sidestep the 'MSB3027'
  file-lock issue, and wires the output via '$(CsWinRTInteropMetadata)').
- Naming conventions: add 'WindowsRuntime.Internal' to the namespace
  list as the interop metadata authoring project.
- Other directories tests row: extend the test-project list with all
  current projects discovered under 'src/Tests/' (AuthoringWuxTest,
  AuthoringConsumptionTest, AuthoringWinUITest,
  AuthoringWuxConsumptionTest, BuildDeterminismTest, DiagnosticTests,
  RuntimeFrameworkVersion, OOPExe, HostTest), and correct
  'ObjectLifetimeTests/' to 'ObjectLifetimeTests.Lifted/'.

update-copilot-instructions/SKILL.md:
- Bump the project count in step 2 from 10 to 11.
- Add the new project 11 (WinRT.Internal) with its validation checklist.
- Add a note to project 2's checklist about the assembly-name vs
  folder-name mismatch.
- Add a note to project 3's checklist that 'Attributes/' and
  'Extensions/' are top-level dirs in the projection writer.
- Add notes to projects 6 and 9 to verify there is no leftover
  'CsWinRTExePath' field / parameter (a regression we hit during the
  cswinrt cleanup).
- Add 'SdkPackageVersion' to project 10's build parameters list.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Restore build-order dependency: Windows.csproj -> WinRT.Internal

The CI build was failing on x86/x64 (green on arm64) with:

  CSWINRTPROJECTIONREFGEN0005: The input metadata path
  '...\src\WinRT.Internal\bin\Debug\net10.0-windows10.0.26100.1\WindowsRuntime.Internal.winmd'
  does not exist

Root cause: 'Projections/Windows/Windows.csproj' (Microsoft.Windows.SDK.NET)
includes the whole 'Windows' namespace ('-include Windows'), so its reference
projection generation consumes 'WindowsRuntime.Internal.winmd' via the
'CsWinRTInteropMetadata' property. That .winmd is produced at build time by the
'WinRT.Internal' project (added in #2429), but no project had a build-order
dependency on it. The ordering was previously provided *implicitly*: every
projection project carried a ProjectReference to the slow-building C++
'cswinrt/cswinrt.vcxproj', which delayed projection generation long enough for
the fast C# 'WinRT.Internal' project to finish first. This PR removed those
(genuinely dead) cswinrt.vcxproj references, exposing the latent race so the
solution build could start Windows.csproj's projection generation before the
internal .winmd existed.

arm64 was unaffected because all the consuming projection projects are
'<Build Project="false" />' on ARM/ARM64 and never build there.

Fix: add an explicit '<BuildDependency Project="WinRT.Internal/WinRT.Internal.csproj" />'
to the Windows.csproj entry in cswinrt.slnx, matching the existing pattern used
for the generator-tool build dependencies. Windows.csproj is the only project
that directly includes the bare 'Windows' namespace (verified across all of
src/), and every other projection/test project that could be affected
transitively ProjectReferences Windows.csproj, so they order correctly too.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Remove explanatory comment from solution dependency

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Set assembly name to WindowsRuntime.Internal

Ensure the produced .dll/.winmd carry the expected assembly identity by setting AssemblyName and RootNamespace to WindowsRuntime.Internal in WinRT.Internal.csproj. Adds explanatory comment why the explicit name is required so the projection writer and other CsWinRT tools can locate WindowsRuntime.Internal.winmd (avoids defaulting to project name 'WinRT.Internal').

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add debug repro support to 'cswinrtprojectionrefgen'

Mirrors the pattern already used by 'cswinrtimplgen' and 'cswinrtinteropgen'.

When '--debug-repro-directory' is provided, the tool packages all input '.winmd' files (expanded from any 'sdk'/'sdk+'/'local'/version tokens or directory inputs) together with a faithful '.rsp' file into a self-contained 'ref-projection-debug-repro.zip'. The tool also accepts a '.zip' as input and replays the captured run by extracting the archive into a temporary directory, restoring the original filenames, and invoking itself with the rewritten response file.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add debug repro support to 'cswinrtwinmdgen'

Mirrors the pattern already used by 'cswinrtimplgen' and 'cswinrtinteropgen'.

When '--debug-repro-directory' is provided, the tool packages the compiled input '.dll' together with all reference assemblies and a faithful '.rsp' file into a self-contained 'winmd-debug-repro.zip'. The tool also accepts a '.zip' as input and replays the captured run by extracting the archive into a temporary directory, restoring the original filenames, and invoking itself with the rewritten response file.

Also suppresses 'IDE0028' (locally in this project) so the existing 'new(StringComparer.Ordinal)' pattern in 'WinMDWriter' keeps building cleanly under the latest analyzer: simplifying those calls to a collection literal would silently drop the comparer, so the suggestion is not actionable.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add debug repro support to 'cswinrtprojectiongen'

Mirrors the pattern already used by 'cswinrtimplgen' and 'cswinrtinteropgen'.

When '--debug-repro-directory' is provided, the tool packages all input '.dll' references, all input '.winmd' files, and the expanded Windows metadata token (resolved into the actual set of '.winmd' files the writer would consume) together with a faithful '.rsp' file into a self-contained 'projection-debug-repro.zip'. The tool also accepts a '.zip' as input and replays the captured run by extracting the archive into a temporary directory, restoring the original filenames, and invoking itself with the rewritten response file.

The Windows metadata files are bundled into a separate 'windows-metadata/' subfolder; the replay '.rsp' points 'WindowsMetadata' at that absolute path so the writer picks up its contents via recursive scan. This keeps the repro fully self-contained even when the original 'WindowsMetadata' was a token like 'sdk' or '10.0.26100.0' that depends on the host environment.

Also suppresses 'IDE0028' locally to keep building cleanly under the latest analyzer with the existing 'new(StringComparer.Ordinal)' pattern (simplifying those calls to a collection literal would silently drop the comparer).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Refresh Copilot instructions for debug repro support

Document that all five build tools (impl, interop, projection-ref, projection, winmd) now support the '--debug-repro-directory' option. Adds a per-generator one-liner describing each tool's repro contents and zip name, refreshes the Interop generator's existing placeholder one-liner to the same level of detail, adds the new '--debug-repro-directory' row to the WinMD generator's CLI parameters table, expands the shared 'Build tool patterns' section with a comprehensive debug-repro entry, and bumps the error ID ranges to reflect the three new well-known errors added per generator (Reference Projection 0001-0008, Projection 0001-0011, WinMD 0001-0010).

Also updates the 'update-copilot-instructions' skill so future runs validate that each generator's debug repro story remains correctly documented.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ugh it (#2432)

* Unify response-file empty-line handling across all 5 generators

Three of the five generators (Projection, ProjectionRef, WinMD) already skipped blank lines in their response-file parsers, with an explanatory comment noting that the MSBuild 'ToolTask' infrastructure may emit them. The remaining two (Impl, Interop) were ported earlier, before this MSBuild quirk was characterized, and would instead throw 'MalformedResponseFile' on a blank line. The strict behavior was never deliberate — it's the original implementation that wasn't updated — and it's a latent crash waiting to surface the moment a task wrapper emits a blank line (e.g. for grouping or as a side effect of an empty optional value).

Switches Impl + Interop to the same defensive ''skip blank lines'' branch the other three already use, with the same comment, so every parser is now byte-identical at that loop. WinMD already had the behavior but used 'string.IsNullOrEmpty(trimmedLine)' — converted to 'trimmedLine.Length == 0' to match the other four exactly. This also removes the only behavioral booby-trap that would have required a per-tool hook in the upcoming shared-code extraction work.

Observable change: a blank line in a '.rsp' that previously caused 'WellKnownImplException(CSWINRTIMPLGEN0003)' or 'WellKnownInteropException(CSWINRTINTEROPGEN0003)' is now silently no-op. That error path is unreachable in normal operation (no task wrapper currently emits blank lines), so this is a defensive fix with no real-world regression risk.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Phase 1: extract shared CLI infrastructure into WinRT.Generator.Cli

Creates a new internal shared library 'WinRT.Generator.Cli' to host code that was previously duplicated across the five Native AOT CLI generator projects (Impl, Interop, Projection, ProjectionRef, WinMD). This first phase does only the mechanical, behavior-preserving extractions:

- Path/File extensions (G1): 'PathExtensions' and 'FileExtensions' move out of 'WinRT.Interop.Generator/Extensions/' (where four other tools were reaching in via '<Compile Include=...\\Link=...>') and into 'WinRT.Generator.Cli/Extensions/'. The two ad-hoc cross-project links are dropped and replaced by a normal 'ProjectReference'. - Command-line argument name attribute (A1): five identical copies of 'CommandLineArgumentNameAttribute' collapse into one in 'WinRT.Generator.Cli/Attributes/'. - JSON serializer context (E1): five identical 'X*GeneratorJsonSerializerContext' copies (each carrying the same 'JsonSerializable(typeof(Dictionary<string,string>))') collapse into a single shared 'GeneratorJsonSerializerContext' in 'WinRT.Generator.Cli/Helpers/'.

Visibility is preserved ('internal') with 'InternalsVisibleTo' for each of the five consumer assemblies. Namespaces follow the existing per-project convention: extensions stay at the root namespace ('WindowsRuntime.GeneratorCli'), while attributes and helpers go into matching subnamespaces. The new library is wired into 'src/cswinrt.slnx'.

Two ancillary build fixes were needed to unblock validation:

- 'WinRT.Impl.Generator' and 'WinRT.Interop.Generator' csproj files gain a scoped '<NoWarn>IDE0028(;IDE0370)</NoWarn>' for the same SDK analyzer regression already handled in 'WinRT.WinMD.Generator' and 'WinRT.Projection.Generator' (the 'new(StringComparer.Ordinal)' pattern can't be simplified to a collection literal without dropping the comparer). - Five Interop files that previously relied on implicit namespace resolution to pick up 'Path.Normalize'/'Path.IsWithinDirectoryName'/'File.ReadAllLines(Stream)' from the sibling namespace ('IgnoresAccessChecksToBuilder', 'InteropGenerator', 'InteropGenerator.Emit', 'InteropGenerator.DebugRepro', 'InteropGeneratorArgs.Parsing') gain an explicit 'using WindowsRuntime.GeneratorCli;'.

Validated end-to-end with debug-repro round-trips: ProjectionRef 8/8 .cs files 0 diffs, WinMD 5632/5632 bytes 16-byte (MVID-only) diff, Projection 327/327 .cs files 0 diffs, Impl 257536/257536 bytes 0 diffs, Interop smoke-tested. All 5 generators build with 0 warnings in Release and AOT-publish cleanly for win-arm64.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Phase 2: extract shared error contract foundation

Adds the shared error contract that all five CsWinRT CLI generators inherit from, into 'WinRT.Generator.Cli/Errors/':

- 'WellKnownGeneratorException' (D1): abstract base capturing the 'Id' field plus the simple-form 'ToString()' that all four non-Interop tools were duplicating verbatim. - 'UnhandledGeneratorException' (D2): abstract base capturing the phase field plus the standardized error-message 'ToString()'. Per-tool subclasses provide 'ErrorPrefix' and 'GeneratorDescription' (e.g. 'impl generator', 'WinMD generator'). 'QuotePhaseInMessage' is virtual and defaults to false; the interop generator overrides it to true to preserve its historical single-quoted phase formatting. - 'GeneratorExceptionExtensions.IsWellKnown' (D3): the shared cancellation/well-known-base predicate. Per-tool 'IsWellKnown' extensions are deleted.

Each per-tool 'WellKnown<X>Exception' shrinks to a primary-constructor sealed subclass. Interop's 'WellKnownInteropException' keeps its rich superset locally ('_outerException' field, custom 'ToString' that adds inner/outer context, 'ThrowOrAttach' with '[StackTraceHidden]' + '[DoesNotReturn]') — it just delegates the 'Id' + ctor wiring to the shared base.

Each per-tool 'Unhandled<X>Exception' shrinks to a primary-constructor sealed subclass overriding the 2-3 abstract/virtual members. The standardized 'ToString()' output is bit-identical to the previous per-tool implementations (verified).

Each consumer that uses '.IsWellKnown' gains a 'using WindowsRuntime.GeneratorCli.Errors;' import; the per-tool '*ExceptionExtensions.cs' files (5) are deleted.

Validated end-to-end with debug-repro round-trips for all 5 generators: ref-gen 8 files 0 diffs, WinMD 5632 bytes (16-byte MVID-only delta), projection 327 files 0 diffs, impl 257536 bytes 0 diffs, interop smoke-tested. All 5 build with 0 warnings in Release and AOT-publish cleanly for win-arm64.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Phase 3: introduce IGeneratorErrorFactory shared error contract

Adds 'IGeneratorErrorFactory' (a 'static abstract' interface in 'WinRT.Generator.Cli/Errors/') with the six logical errors that every CLI generator throws today through its 'WellKnown*Exceptions' factory:

- 'ResponseFileReadError(Exception)' - 'ResponseFileArgumentParsingError(string, Exception?)' - 'MalformedResponseFile()' - 'DebugReproDirectoryDoesNotExist(string)' - 'DebugReproMissingFileEntryMapping(string)' - 'DebugReproUnrecognizedFileEntry(string)'

All return 'Exception', so the per-tool numeric ID, message text, and concrete 'WellKnown*Exception' subtype remain the sole responsibility of each tool's factory. This is the contract that Phase 4 (response-file plumbing) and Phase 5 (debug-repro helpers) will dispatch through, so shared code can throw via the right per-tool factory without ever knowing the IDs or message strings.

Each per-tool 'WellKnown*Exceptions' shifts from 'internal static class' to 'internal sealed class' implementing 'IGeneratorErrorFactory', with a private constructor to prevent instantiation. Every factory body, error ID, message text, and concrete exception type is preserved verbatim — including the WinMD swap of 'MalformedResponseFile' (id 2) and 'ResponseFileArgumentParsingError' (id 3) versus the other four tools, and the embedded per-tool exe names in 'ResponseFileReadError'.

Interop is special: its public static factories return 'WellKnownInteropException' (typed, so callers chain '.ThrowOrAttach(...)'). C# 'static abstract' interface methods don't support return-type covariance, so Interop uses explicit interface implementations that forward through the typed publics — preserving both call shapes.

Validated end-to-end: ref-gen 8 files 0 diffs, WinMD 5632 bytes (16-byte MVID-only), projection 327 files 0 diffs, impl 257536 bytes 0 diffs, interop smoke-tested. All 5 build clean (0 warnings) in Release and AOT-publish cleanly for win-arm64.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Phase 4: extract response-file parsing and formatting via reflection

Adds 'ResponseFileParser<TArgs, TErr>' and 'ResponseFileBuilder<TArgs>' in 'WinRT.Generator.Cli/Parsing/', collapsing four candidates (B1 parsing core, B2 typed Get* helpers, B3 reflection lookup, C1 formatting) into one reflection-based mechanism that walks 'Type.GetProperties()' once per call.

Per-property handling:

- '[CommandLineArgumentName("--xxx")]' → CLI flag name. - 'required' modifier (via 'RequiredMemberAttribute') → throw 'TErr.ResponseFileArgumentParsingError(prop.Name)' if missing or parse fails. - 'string?' (via 'NullabilityInfoContext') → null on miss. - '[DefaultValue("…")]' (only on 'ProjectionGeneratorArgs.AssemblyName') → applied when missing. - 'CancellationToken'-typed property → set from parser's token argument. - 'Convert.ChangeType(string, propertyType, InvariantCulture)' for primitives ('bool', 'int'). - 'string[]' → 'Split(',', RemoveEmptyEntries | TrimEntries)'.

The parser uses 'RuntimeHelpers.GetUninitializedObject' to bypass the runtime 'RequiredMemberAttribute' enforcement at construction time, then populates each public property via 'PropertyInfo.SetValue'. All AOT-safe under '[DynamicallyAccessedMembers(PublicProperties)]' on 'TArgs'.

Each per-tool 'XGeneratorArgs.cs' loses its 'partial' modifier and gains three thin forwarding wrappers ('ParseFromResponseFile(string|Stream, CancellationToken)' and 'FormatToResponseFile()') so call sites are unchanged. The corresponding '*Args.Parsing.cs' and '*Args.Formatting.cs' partial files (10 files total across the 5 generators) are deleted entirely.

All exceptions continue to route through the per-tool 'WellKnown*Exceptions' factory via 'TErr : IGeneratorErrorFactory', so error IDs (including WinMD's '2'/'3' swap), messages (including embedded 'cswinrtimplgen' etc. tool names), and concrete exception types are preserved bit-identically. Optional 'bool' / 'int' / 'string[]' parse failures silently fall back to defaults (matching the old 'GetOptional*' behavior); required parse failures throw (matching the old 'GetBooleanArgument' / 'GetInt32Argument' / 'GetStringArrayArgument' behavior).

Validated end-to-end with debug-repro round-trips for all 5 generators: ref-gen 8/8 files 0 diffs, WinMD 5632/5632 bytes 16-byte (MVID-only) delta, projection 327/327 files 0 diffs, impl 257536/257536 bytes 0 byte diffs, interop smoke-tested. All 5 build with 0 warnings in Release and AOT-publish cleanly for win-arm64.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Phase 5: extract shared debug-repro leaf helpers

Adds 'DebugReproPacker' (in 'WinRT.Generator.Cli/DebugRepro/') with the five small leaf helpers that were duplicated near-byte-identically across every '*Generator.DebugRepro.cs':

- 'GetHashedFileName(string)' - Shake128 hash → '{name}_{HEX}{ext}' - 'CopyHashedFilesToDirectory(string[], string, Dictionary, CancellationToken)' - 'CopyHashedFileToDirectory(string?, string, Dictionary, CancellationToken)' (simple variant) - 'CopyPathMapToDirectory(Dictionary, string, string)' - JSON serialize via shared 'GeneratorJsonSerializerContext' - 'ExtractPathMap(ZipArchiveEntry)' - JSON deserialize via the same context

Each per-tool '*Generator.DebugRepro.cs' loses its five private helper definitions and gains 'using WindowsRuntime.GeneratorCli.DebugRepro;' plus the 'DebugReproPacker.' qualifier on call sites. Unused 'using' statements (for 'System.Security.Cryptography', 'System.Text', 'System.Text.Json', 'GeneratorJsonSerializerContext', etc.) are removed.

Interop keeps its rich 'CopyHashedFileToDirectory' local. That variant has reserved-DLL dedupe logic plus 'WellKnownInteropExceptions.ReservedDllOriginalPathMismatchFromDebugRepro(fileName)' on path mismatch — neither generalizable nor shared. It now delegates the hash computation to 'DebugReproPacker.GetHashedFileName'.

Hashed filenames are content-addressed via Shake128 of the original file path, so cross-version replay continues to work: a '.zip' produced by an older build can be unpacked by the new build and vice versa.

Validated end-to-end: ref-gen 8/8 files 0 diffs, WinMD 5632/5632 bytes 16-byte MVID-only delta, projection 327/327 files 0 diffs, impl 257536/257536 bytes 0 byte diffs, interop smoke-tested. All 5 build with 0 warnings in Release and AOT-publish cleanly for win-arm64.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Phase 6: extract shared Run entry-point preamble into GeneratorHost.Prepare

Adds 'IGeneratorArgs' (a marker interface with the two well-known properties: 'Token' and 'DebugReproDirectory') and 'GeneratorHost' (in 'WinRT.Generator.Cli/'), both encapsulating the unpack-debug-repro -> parse-response-file -> save-debug-repro preamble that every generator's 'Run' method previously had inline.

Each generator's 'Run' method shrinks from ~80 LOC to ~12 LOC: a single call to 'GeneratorHost.Prepare<TArgs>(...)' with per-tool callbacks (unpack/parse/save delegates + 'wrapUnhandled' lambda that constructs the per-tool 'Unhandled*Exception' + 'ConsoleApp.Log' for the progress messages + the tool name for the log strings).

All five args records gain ': IGeneratorArgs' — no new properties are required because each already has 'public required CancellationToken Token { get; init; }' and 'public string? DebugReproDirectory { get; init; }' with matching signatures.

Behavior preserved exactly:

- Same phase names ('unpack-debug-repro', 'parsing', 'save-debug-repro') routed through each tool's per-tool 'Unhandled*Exception' wrapper. - Same log messages, byte-identical (the shared formatter substitutes the tool name via interpolation). - Same cancellation boundaries at each phase. - Same 'isUsingDebugRepro' flag controlling save-on-replay suppression. - Same 'DebugReproDirectory is not null' guard. - 'static (phase, e) => new Unhandled*Exception(phase, e)' lambdas — the 'static' modifier ensures no closure allocations per call.

The shared 'GeneratorHost' takes 'Action<string> log' as a parameter (instead of pulling in a ConsoleAppFramework dependency on the shared library), letting consumers pass their existing 'ConsoleApp.Log' method group.

Validated end-to-end for all 5 generators: ref-gen 8/8 files 0 diffs, WinMD 5632/5632 bytes 16-byte (MVID-only) delta, projection 327/327 files 0 diffs, impl 257536/257536 bytes 0 byte diffs, interop smoke-tested. All 5 build clean (0 warnings) in Release and AOT-publish cleanly for win-arm64.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Rename WinRT.Generator.Cli to WinRT.Generator.Core

Move shared generator infrastructure into a core project. Several source files were relocated from src/WinRT.Generator.Cli/* to src/WinRT.Generator.Core/*, the project file was renamed and its metadata updated (Description, AssemblyTitle, RootNamespace, removal of AssemblyName), dependent generator project references were updated to point at WinRT.Generator.Core.csproj, and the solution file was adjusted to reference the new project path.

* Rename GeneratorCli namespace to Generator

Refactor namespaces and usings: replace WindowsRuntime.GeneratorCli.* with WindowsRuntime.Generator.* (and related sub-namespaces like Attributes, DebugRepro, Errors, Helpers, Parsing, Extensions) across the core library and all generator projects (Impl, Interop, Projection, ReferenceProjection, WinMD). This unifies naming/imports across many files; no behavioral logic changes were made.

* Use <inheritdoc/> for generator args docs

Replace explicit XML <summary> comments with <inheritdoc/> on the Token and DebugReproDirectory properties to inherit documentation and reduce duplication. Applied to generator argument classes: ImplGeneratorArgs, InteropGeneratorArgs, ProjectionGeneratorArgs, ReferenceProjectionGeneratorArgs, and WinMDGeneratorArgs (five files).

* Remove ParseFromResponseFile/FormatToResponseFile wrappers from args types

Each of the 5 generator '*Args' types had three thin one-liner wrappers
('ParseFromResponseFile(string)', 'ParseFromResponseFile(Stream)',
'FormatToResponseFile()') that forwarded straight to the shared
'ResponseFileParser.Parse<TArgs, TErr>' and 'ResponseFileBuilder.Format'
APIs. They contributed more XML doc lines than actual code, with no
behavioral value beyond closure over the per-tool 'WellKnown*Exceptions'
factory.

Delete the 15 wrappers (3 per args type) and inline all call sites:

* '*Generator.cs' 'parseFromResponseFile:' callback now uses the parser
  method group directly ('ResponseFileParser.Parse<XArgs, WellKnownXExceptions>')
  instead of forwarding through the args type.
* '*Generator.DebugRepro.cs' stream-parse call sites use the same parser
  method directly, and format call sites use 'ResponseFileBuilder.Format'
  directly.
* Unused 'System.IO', 'WindowsRuntime.Generator.Parsing' and per-tool
  'Errors' usings removed from each '*Args.cs'; the parsing using is
  added to the DebugRepro and Generator files that now need it.
* Repoint the broken XML cref in 'WinMDGenerator' and update the MSBuild
  comment in 'WinRT.Internal.csproj' that named the deleted method.

Net diff: 15 files, +52 / -207 LOC. All 5 generators still build with
zero warnings (Release) and pass end-to-end debug-repro validation
(ref-gen: 8/8 0 diffs, WinMD: 16-byte MVID-only delta, proj-gen:
327/327 0 diffs, impl-gen: 0 byte diffs, interop-gen: smoke OK).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Always quote the phase name in unhandled generator messages

'UnhandledGeneratorException' had a 'QuotePhaseInMessage' virtual hook
defaulting to false, overridden to true only by 'UnhandledInteropException',
which forced the 'ToString' implementation to first format the phase via
a conditional and then build the final message by concatenating four
'$"""..."""' raw interpolated string fragments.

The behavioral discrepancy between tools was almost certainly an accident
(both pieces of code predate the shared base class). Remove the hook and
always wrap the phase name in single quotes for every generator. With the
conditional gone, the entire message collapses into a single regular
interpolated string and the override is no longer needed.

All 5 generators build clean and pass debug-repro validation (ref-gen:
8/8 0 diffs, WinMD: 16-byte MVID-only delta, proj-gen: 327/327 0 diffs,
impl-gen: 0 byte diffs, interop-gen: smoke OK).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Delete dead SignatureComparerExtensions copies in Impl + Projection

'WinRT.Impl.Generator' and 'WinRT.Projection.Generator' each ship a copy
of 'SignatureComparerExtensions' that has zero call sites in their own
project. The real consumer is 'WinRT.Interop.Generator', which has its
own (larger, strict-superset) copy at
'src/WinRT.Interop.Generator/Extensions/SignatureComparerExtensions.cs'.

The two dead copies appear to have been left behind during earlier
refactors. Delete them. No behavioral change is possible since they had
no callers; the Interop copy is untouched and continues to serve its
local consumers.

Net diff: -141 LOC across 2 files. Both projects build with 0 warnings
in Release.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add debug-repro orchestration helpers to DebugReproPacker

Extend 'DebugReproPacker' with three orchestration helpers that capture
the preamble/tail boilerplate that all 5 generators' 'SaveDebugRepro'
and 'UnpackDebugRepro' methods used to repeat verbatim:

* 'BeginSave<TError>(directory, toolName, archiveFileName)' validates
  the user-provided directory exists (throws the per-tool
  'TError.DebugReproDirectoryDoesNotExist' via the shared
  'IGeneratorErrorFactory' contract), builds the target '.zip' path
  and creates a fresh '{toolName}-debug-repro-{Guid}' staging dir.
* 'FinalizeSave(tempDirectory, zipPath)' deletes any pre-existing
  archive, zips the staging dir, and deletes the staging dir.
* 'CreateUnpackTempDirectory(toolName)' creates a fresh
  '{toolName}-debug-repro-unpack-{Guid}' directory.

Each '*Generator.DebugRepro.cs' now calls these helpers in place of
the inlined boilerplate. Per-tool concerns stay per-tool: subdirectory
layout, file-copy strategy, '.rsp' build, and JSON path-map writes
are all unchanged. Per-tool exception identity is fully preserved
because every 'BeginSave<TError>' call binds to the tool's own
'WellKnown*Exceptions' static-abstract factory.

Net diff: 6 files, +103 / -140 LOC (-37 LOC). All 5 generators build
clean and pass end-to-end debug-repro validation (ref-gen: 8/8 0 diffs,
WinMD: 16-byte MVID-only delta, proj-gen: 327/327 0 diffs, impl-gen:
0 byte diffs, interop-gen: smoke OK).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Move RuntimeContextExtensions.LoadModule to Core

'Impl', 'Interop', and 'WinMD' each shipped their own copy of
'RuntimeContextExtensions.LoadModule', with bodies that are
character-for-character identical (only the receiver parameter name
differed). Impl had only the 'PEImage' overload, Interop had only the
'string' overload, WinMD had both.

Consolidate to a single file under 'WinRT.Generator.Core' that carries
both overloads. All three consumer projects pick up the overload they
already used; no behavioral change.

This adds an 'AsmResolver.DotNet' package reference to
'WinRT.Generator.Core' (all 5 generators already reference AsmResolver
directly).

Net diff: 6 files, +63 / -126 LOC (-63 LOC). All 5 generators build
with 0 warnings and pass end-to-end debug-repro validation (ref-gen:
8/8 0 diffs, WinMD: 16-byte MVID-only delta, proj-gen: 327/327
0 diffs, impl-gen: 0 byte diffs, interop-gen: smoke OK).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add GeneratorPhaseRunner to wrap per-phase try/catch + log

Every generator's 'Run' method historically opens with a single
'GeneratorHost.Prepare<TArgs>' call (which handles the shared
unpack/parse/save preamble) and then proceeds through a series of
phases (discovery, processing, emit, ...). Each phase was wrapped in
an identical:

    try
    {
        ConsoleApp.Log("...");
        DoPhase(args);
    }
    catch (Exception e) when (!e.IsWellKnown)
    {
        throw new UnhandledXxxException("phase-name", e);
    }

That is now expressed as a single 'runner.RunPhase' call. The
'GeneratorPhaseRunner' readonly struct captures the per-tool
'wrapUnhandled' + 'log' delegates once (it is returned bound to them
by 'GeneratorHost.Prepare') and offers four overloads ('Action' /
'Func<T>', each with or without a log message). All four invariably
route through the per-tool 'wrapUnhandled' delegate so each tool
keeps throwing its own 'UnhandledXxxException' with the right phase
name.

'GeneratorHost.Prepare<TArgs>' now returns '(TArgs Args,
GeneratorPhaseRunner Runner)' instead of just 'TArgs'. The 5 'Run'
methods deconstruct the tuple and use the runner for each phase.

Impl's 'LoadOutputModule(args, out runtimeContext, out outputModule)'
becomes a tuple-return 'LoadOutputModule(args) -> (RuntimeContext,
ModuleDefinition)' so it fits the 'Func<T>' overload cleanly.

'ThrowIfCancellationRequested' calls stay per-tool at the call sites
(the pattern isn't uniform: some phases use 'args.Token', WinMD uses
'token', and the last phase typically has no check). ReferenceProjection's
second phase wraps to a well-known 'CsWinRTProcessError' rather than
the 'Unhandled' factory, so it's left as an inline try/catch.

Net diff across modified files: -84 LOC. Including the new
'GeneratorPhaseRunner.cs' (~123 LOC, mostly XML docs), the overall
file diff is +39 LOC but with a much lower density of error-handling
boilerplate at the per-tool call sites. All 5 generators build with
0 warnings and pass end-to-end debug-repro validation (ref-gen: 8/8
0 diffs, WinMD: 16-byte MVID-only delta, proj-gen: 327/327 0 diffs,
impl-gen: 0 byte diffs, interop-gen: smoke OK).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Centralize IGeneratorErrorFactory message strings in WellKnownGeneratorMessages

Each per-tool 'WellKnown*Exceptions' factory implements the same 6
logical errors from 'IGeneratorErrorFactory' with byte-identical
message text (5 of 6 strings are character-for-character identical
across all 5 tools; the sixth, 'ResponseFileReadError', only varies
by the embedded tool name).

Move the message templates to a new shared 'WellKnownGeneratorMessages'
class in 'WinRT.Generator.Core' and have each per-tool factory call
through to it. Per-tool error ID prefixes (e.g. 'CSWINRTIMPLGEN0001'
vs 'CSWINRTINTEROPGEN0028'), per-tool concrete exception types
('WellKnownImplException' / 'WellKnownInteropException' / ...) and
per-tool numeric IDs are all unchanged.

While here, replace the per-tool '<summary>' doc on each of these 6
methods with '<inheritdoc cref="IGeneratorErrorFactory.X(...)"/>'. The
per-tool summaries were byte-identical to the interface summaries, so
this is no info loss; it also avoids future drift.

Net diff: 6 files (5 modified + 1 new), -60 LOC across the per-tool
factories and +70 LOC for the new central messages file. The real win
is the divergence-protection: any future message tweak now lives in
one place. All 5 generators build clean and pass end-to-end debug-repro
validation (ref-gen: 8/8 0 diffs, WinMD: 16-byte MVID-only delta,
proj-gen: 327/327 0 diffs, impl-gen: 0 byte diffs, interop-gen: smoke OK).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Consolidate MvidGenerator + IncrementalHashExtensions in Core

Both Impl and Interop shipped their own 'MvidGenerator' with
complementary overloads ('CreateMvid(Guid, Guid)' for Impl;
'CreateMvid(params IEnumerable<string>)' for Interop). Interop's
overload also depended on a local 'IncrementalHashExtensions.AppendData(Stream)'
extension whose only consumer was that one method.

Move both 'MvidGenerator' overloads into a single file in 'WinRT.Generator.Core'
and relocate 'IncrementalHashExtensions' alongside it. Each consumer continues
to call the exact overload it already used, with no behavioral change. The
impl-gen end-to-end output is byte-identical (257536 bytes, 0 byte diffs)
which proves the 'Guid+Guid' MVID computation is preserved exactly.

Net diff: 5 files changed, 2 new + 3 deleted. All 5 generators build with
0 warnings and pass end-to-end debug-repro validation (ref-gen: 8/8 0 diffs,
WinMD: 16-byte MVID-only delta, proj-gen: 327/327 0 diffs, impl-gen:
0 byte diffs, interop-gen: smoke OK).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Consolidate WellKnownPublicKeys + WellKnownPublicKeyTokens in Core

The 160-byte CsWinRT 3.0 strong-name public keys lived in several
inconsistent places across the generators:

* 'ImplValues.PublicKeyData' (Impl) - the SDK projection key, used 3x,
  but the type/property name suggested it was a generic 'PublicKey'.
* 'InteropValues.WindowsSdkProjectionPublicKey[Data]' (Interop) -
  the same SDK projection key, dead code (0 callers).
* 'InteropValues.CsWinRTPublicKey[Data]' (Interop) - the real CsWinRT
  runtime key, used 1x.
* 'ProjectionGenerator.Emit.CsWinRTPublicKey' (Projection) - the SDK
  projection key again, used 1x, but misnamed as the CsWinRT key.

The const-string forms ('PublicKey', 'CsWinRTPublicKey',
'WindowsSdkProjectionPublicKey') had 0 callers anywhere - leftover
dead code. The same 'B5FC90E7...' byte sequence appeared 3 times,
which is exactly the kind of duplication that risks accidental drift.

Consolidate into a single 'WellKnownPublicKeys' in 'WinRT.Generator.Core':

* 'WindowsSdkProjection' - the precompiled SDK projection assembly key
  (used by Impl forwarder refs and by Projection's Roslyn delay-signing).
* 'CsWinRT' - the real CsWinRT 3.0 runtime release key (used by Interop
  when emitting refs to 'WinRT.Runtime.dll').

Each live caller is repointed; Projection wraps the 'byte[]' as
'ImmutableArray<byte>' on the fly via 'ImmutableCollectionsMarshal'
for Roslyn's API. The dead-code declarations are deleted, including
the now-empty 'ImplValues' and 'InteropValues' files entirely.

The public key tokens ('Interop\WellKnownPublicKeyTokens.cs' with
6 entries + 'WinMD\WellKnownPublicKeyTokens.cs' with 1 entry) are
unioned into 'WinRT.Generator.Core\References\WellKnownPublicKeyTokens.cs'
(7 entries total: MSCorLib + the 6 from Interop).

Net diff: 10 modified files + 2 new + 4 deleted (16 files total),
-22 LOC. Removes the dead 'WindowsSdkProjectionPublicKey[Data]'
declarations from 'InteropValues' (~31 LOC), fixes the
'PublicKey'/'CsWinRTPublicKey'/'WindowsSdkProjection' naming
inconsistency, and centralizes the canonical byte sequences in one
file. The byte-identical impl-gen output is the strongest possible
confirmation that the consolidated keys match the originals exactly.

All 5 generators build with 0 warnings and pass end-to-end debug-repro
validation (ref-gen: 8/8 0 diffs, WinMD: 16-byte MVID-only delta,
proj-gen: 327/327 0 diffs, impl-gen: 0 byte diffs, interop-gen: smoke OK).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Make GeneratorPhaseRunner generic in TArgs and capture the args itself

'GeneratorHost.Prepare<TArgs>' previously returned a tuple
'(TArgs Args, GeneratorPhaseRunner Runner)', and each per-tool 'Run'
method captured the 'args' local in a closure for every phase body
('runner.RunPhase("phase", () => DoPhase(args, ...))').

Make 'GeneratorPhaseRunner' generic in 'TArgs', have it capture the
parsed args directly, and pass them to every body delegate. The
'RunPhase' overloads now take 'Action<TArgs>' / 'Func<TArgs, T>'
instead of 'Action' / 'Func<T>'. Per-tool exception identity is fully
preserved because the per-tool 'wrapUnhandled' delegate is still
invoked unchanged.

Phase bodies that only need 'args' now bind to a method group (e.g.
'body: Discover' instead of 'body: () => Discover(args)') and allocate
zero closures per invocation. Five phases across the five generators
benefit from this: WinMD 'Discover', Interop 'Discover', Projection
'ProcessReferences', ProjectionRef 'BuildWriterOptions', and Impl
'LoadOutputModule'. The remaining eight phases (which need additional
state besides 'args') still use lambdas, but with 'args' as an
explicit parameter rather than a captured local.

Rename 'GeneratorHost.Prepare<TArgs>' to 'GeneratorHost.CreateRunner<TArgs>'
since the method now exclusively returns the runner (no longer a
preamble-only operation that needs a separate "prepare" verb).

Net diff: 7 files, +83 / -67 LOC. All 5 generators build with
0 warnings and pass end-to-end debug-repro validation (ref-gen: 8/8
0 diffs, WinMD: 16-byte MVID-only delta, proj-gen: 327/327 0 diffs,
impl-gen: 0 byte diffs, interop-gen: smoke OK).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Simplify response-file parsing and messages

Refactor response-file parsing and related generator messaging:

- Consolidate and simplify WellKnownGeneratorMessages (make ResponseFileReadError a constant and clean up XML docs) and update per-tool exception factories to use the new message form.
- Restructure ResponseFileParser: split line parsing into a map builder and a Populate method, adjust DynamicallyAccessedMembers annotations, remove nullable-inspection/NullabilityInfo usage, and simplify default-application logic.
- Tidy ResponseFileBuilder formatting and annotations; minor comment and formatting cleanups.
- Remove an unused PathExtensions.Normalize(ReadOnlySpan<char>) overload and add small GeneratorHost/GeneratorPhaseRunner code cleanups (target-typed new, pragma for warnings, reorder assignments).

These changes reduce duplication, simplify trimming/reflection annotations for the linker, and clarify parsing behavior without changing external behavior.

* Standardize generator naming and runner calls

Rename the UnhandledGeneratorException property from GeneratorDescription to GeneratorName and update the standardized ToString message to use the new name ("The CsWinRT {GeneratorName} generator..."). Adjust all per-tool unhandled exception types to provide GeneratorName values. Remove explicit generic type args from several GeneratorHost.CreateRunner calls (relying on inference). Also apply minor XML-doc and cref cleanups (simplify exception/type cref qualifications and projection writer cref) and a small doc fix in WindowsRuntimeExceptionExtensions.

* Move InternalsVisibleTo declarations from AssemblyInfo.cs to .csproj

The 'InternalsVisibleTo' items in
'src/WinRT.Generator.Core/Properties/AssemblyInfo.cs' are emitted by
the .NET SDK directly from MSBuild '<InternalsVisibleTo>' items when
they're declared in the project file, so there is no need for a
hand-written 'AssemblyInfo.cs' to host them.

Move the 5 entries (one per consuming generator) into
'WinRT.Generator.Core.csproj' and delete the now-empty
'Properties/AssemblyInfo.cs' (plus its empty parent folder).

All 5 generators build with 0 warnings, confirming the SDK-emitted
attributes still grant the same internals access.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Remove redundant global/System qualifiers

Simplify references by removing unnecessary qualification prefixes: replace System.StringComparison with StringComparison and global::WindowsRuntime.ProjectionWriter.ProjectionWriter.Run with ProjectionWriter.ProjectionWriter.Run. This is a code cleanup across generator tasks (RunCsWinRTWinMDGenerator.cs, ProjectionGenerator.Generate.cs, ReferenceProjectionGenerator.cs) with no behavior change.

* Use primary constructor for GeneratorPhaseRunner

Convert GeneratorPhaseRunner<TArgs> to use C# primary-constructor syntax, removing explicit private fields and the manual constructor. Replace uses of _wrapUnhandled and _log with the positional parameters wrapUnhandled and log, and make Args return the positional args field. Add XML param docs for the new parameters. This is a refactor to reduce boilerplate without changing behavior.

* Auto-call ThrowIfCancellationRequested after each RunPhase body

Every per-tool 'Run' method previously placed a manual
'args.Token.ThrowIfCancellationRequested()' between consecutive
phases - 8 explicit calls across the 5 generators.

Bake that check into 'GeneratorPhaseRunner<TArgs>.RunPhase' (all 4
overloads): after the body completes successfully, the runner calls
'args.Token.ThrowIfCancellationRequested()' before returning. The
check sits OUTSIDE the 'try'/'catch' so the resulting
'OperationCanceledException' propagates without going through the
per-tool 'wrapUnhandled' delegate, which is already a no-op for it
('OperationCanceledException' is in 'IsWellKnown').

Per-tool 'Run' methods drop the 8 redundant
'runner.Args.Token.ThrowIfCancellationRequested()' calls; the last
phase of each generator now does one harmless extra cancellation
check before the success log message.

Net diff: 6 files, +18 / -18 LOC. All 5 generators build with 0 warnings
and pass end-to-end debug-repro validation (ref-gen: 8/8 0 diffs,
WinMD: 16-byte MVID-only delta, proj-gen: 327/327 0 diffs, impl-gen:
0 byte diffs, interop-gen: smoke OK).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Rename file to GeneratorPhaseRunner{TArgs}.cs

Rename src/WinRT.Generator.Core/GeneratorPhaseRunner.cs to src/WinRT.Generator.Core/GeneratorPhaseRunner{TArgs}.cs. No source changes were made; this update clarifies the file name to reflect the generic TArgs type parameter.

* Add CodeQL annotations & clarify IID comments

Add CodeQL [SM02196] explanatory comments to MvidGenerator to indicate the SHA1-based MVID/hash buffer is fully populated and that the hash is not used for authentication. Also refine punctuation. In WinMDWriter.Attributes, correct and clarify the comment to state that Windows Runtime uses UUID v5 SHA1 to generate IIDs for parameterized types (instead of saying GUIDs). These changes document security rationale for SHA1 usage and silence static analysis warnings.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…2435)

* Port IReadOnlyDictionary Keys/Values codegen fix from #2427

Ports the 'cswinrt/code_writers.h' fix from PR #2427 to the C# projection
writer, for the 'IMapView<K,V>' -> 'IReadOnlyDictionary<K,V>' stub path.

Three changes to the emitted 'Keys'/'Values' members:

* The '[UnsafeAccessor]' for 'Keys'/'Values' now returns 'IEnumerable<T>'
  (was 'ICollection<T>'), matching the actual signature of the interop
  helper 'IReadOnlyDictionaryMethods.Keys/Values' (the public property was
  already 'IEnumerable<T>', so the accessor return type was a latent
  mismatch that happened to bind to the wrong contract).
* The accessor receiver is now 'WindowsRuntimeObject windowsRuntimeObject'
  (the projected runtime class itself, passed as 'this') instead of the
  interface 'WindowsRuntimeObjectReference objRef'.
* The public 'Keys'/'Values' properties cache the returned collection via
  the C# 14 'field' keyword ('=> field ??= ...(null, this)'), so repeated
  accesses preserve reference identity instead of allocating a fresh
  wrapper each time.

To support a non-default receiver, 'EmitUnsafeAccessor' gains an optional
'receiver' parameter (defaulting to 'WindowsRuntimeObjectReference objRef');
'Keys'/'Values' are emitted via direct calls (with the 'WindowsRuntimeObject'
receiver) ahead of the remaining accessors, which continue to flow through
the table-based 'EmitUnsafeAccessors'.

The interop-generator and unit-test parts of #2427 are intentionally not
included here; they arrive when this branch merges back into staging/3.0.

Verified by generating the full 'Windows.winmd' reference projection and
inspecting the emitted 'IReadOnlyDictionaryMethods' stubs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Port IDictionary Keys/Values codegen fix from #2427

Ports the 'cswinrt/code_writers.h' fix from PR #2427 to the C# projection
writer, for the 'IMap<K,V>' -> 'IDictionary<K,V>' stub path.

Mirrors the 'IReadOnlyDictionary' change in the previous commit (the
return type stays 'ICollection<T>' here, matching the interop helper
'IDictionaryMethods.Keys/Values'):

* The '[UnsafeAccessor]' for 'Keys'/'Values' now takes the projected
  runtime class ('WindowsRuntimeObject windowsRuntimeObject', passed as
  'this') instead of the interface 'WindowsRuntimeObjectReference objRef'.
* The public 'Keys'/'Values' properties cache the returned collection via
  the C# 14 'field' keyword ('=> field ??= ...(null, this)'), preserving
  reference identity across accesses.

'Keys'/'Values' are emitted via direct 'EmitUnsafeAccessor' calls (with the
'WindowsRuntimeObject' receiver) ahead of the remaining accessors, which
continue to flow through the table-based 'EmitUnsafeAccessors'.

Verified by generating the full 'Windows.winmd' reference projection: the
emitted 'IDictionaryMethods' stubs match the expected output, the run is
deterministic (347/347 files, 0 diffs across two runs), and the
'WindowsRuntimeObject' receiver appears only on dictionary 'Keys'/'Values'
(all other collection accessors keep the 'WindowsRuntimeObjectReference'
receiver).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Ports the 'cswinrt/code_writers.h' fix from PR #2436 (`Skip emitting
[ApiContract] attribute for implementation assemblies`) to the C#
projection writer.

In 'CustomAttributeFactory.WriteCustomAttributes', the
'Windows.Foundation.Metadata' attribute filter now gates 'ApiContract'
the same way as 'ContractVersion': both are emitted only for reference
projections. Previously 'ApiContract' fell into the catch-all 'else if'
branch and was dropped for every projection (reference and
implementation alike); now it is emitted for reference projections and
skipped for implementation projections, matching the C++ generator.

Verified by generating the full 'Windows.winmd' projection in both modes:

* Reference mode: 91 '[ApiContract]' attributes emitted, all applied to
  'enum' types (consistent with 'ApiContractAttribute's
  '[AttributeUsage(AttributeTargets.Enum)]'). The new output differs from
  the pre-change baseline by exactly those 91 added lines (73 files,
  +91/-0, no other changes).
* Implementation mode: 0 '[ApiContract]' emitted, and byte-identical to
  the pre-change output (347/347 files, 0 diffs), confirming the change
  only affects reference projections.

'Windows.Foundation.Metadata.ApiContractAttribute' is provided by
'WinRT.Runtime2' (the same library that provides the already-emitted
'ContractVersionAttribute'), so the emitted '[ApiContract]' resolves and
compiles.

The interop-generator and test parts of #2436, if any, arrive when this
branch merges back into staging/3.0.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@Sergio0694 Sergio0694 added code cleanup Code cleanup and refactoring tooling CsWinRT 3.0 labels Jun 13, 2026
@Sergio0694 Sergio0694 requested a review from manodasanW June 13, 2026 23:38
Sergio0694 and others added 3 commits June 13, 2026 17:17
Refresh the Copilot instructions and the update-copilot-instructions skill to

reflect the merged C++->C# projection generator workstream:

- Add WinRT.Generator.Core as project #9 (shared CLI build-tool infrastructure

  library) with a project-settings + "What it provides" section, and renumber

  Generator tasks/SDK projection/WinRT.Internal to 10/11/12 (repo tree, section

  headers, and the cross-reference in the projection writer section).

- Note the WinRT.Generator.Core project reference on all five CLI generators.

- Add the WindowsRuntime.Generator namespace and expand the build-tool-patterns

  section (GeneratorHost.CreateRunner, GeneratorPhaseRunner, reflection-based

  ResponseFileParser/Builder, IGeneratorErrorFactory routing).

- Fix drift: remove the non-existent Attributes/ folder from the projection

  writer tree, add additions-exclude/verbose to ProjectionWriterOptions, and fix

  the Tests table (ObjectLifetimeTests, drop removed RuntimeFrameworkVersion).

- Sync the skill's step-2 project list to 12 projects (add Generator core).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Refresh the interop-generator skill (and its update skill) to reflect the shared

WinRT.Generator.Core library introduced in this branch, plus accumulated drift:

- Add a "Shared infrastructure" section mapping each piece now consumed from

  WinRT.Generator.Core (GeneratorHost/GeneratorPhaseRunner entry-point scaffold,

  ResponseFileParser/Builder, DebugReproPacker, IGeneratorErrorFactory and the

  WellKnownGeneratorException/UnhandledGeneratorException bases, MvidGenerator,

  WellKnownPublicKeys/Tokens, and the moved Extensions/*).

- Remove the moved/deleted files from the project tree (Attributes/, the five

  moved Extensions, Helpers/MvidGenerator + JsonSerializerContext,

  InteropGeneratorArgs.Parsing/Formatting, References/InteropValues +

  WellKnownPublicKeyTokens) and fix counts (Extensions 23, TypeMapping ~50).

- Update the pipeline diagram and emit prose: GeneratorHost.CreateRunner +

  RunPhase flow, and 16 (not 26) generic Define*Types() methods.

- Rewrite the .rsp and debug-repro sections to describe the shared parsing and

  DebugReproPacker delegation (keeping the local reserved-DLL dedupe variant).

- Fix the diagnostics section: error range 1-97 (not 1-81), ~42 KB, shared base

  classes + IGeneratorErrorFactory routing, and a themed (non-contiguous) error

  category table derived from the actual code.

- Resolvers: drop removed PathAssemblyResolver, add new InterfaceIIDResolver;

  correct ReservedIIDsMap to 5 entries (IMarshal excluded).

- Sync the update-interop-generator-instructions skill's verification steps.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove historical/migration framing introduced when documenting the shared

WinRT.Generator.Core library, so the docs describe only the current state:

- Reframe the "Shared infrastructure" table from "Replaces (formerly local)" to

  "What it provides", and drop "used to live"/"were moved"/"stays in this project".

- Drop "formerly InteropValues/WellKnownPublicKeyTokens" and "now come from" from

  the References note, and "now lives ... rather than locally" from the MVID note.

- Apply the same cleanup to the update-interop-generator-instructions checklist.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

code cleanup Code cleanup and refactoring CsWinRT 3.0 tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants