Refactor projection writer: interpolated-string + callback emission, classifier resolvers, helper consolidation#2419
Open
Sergio0694 wants to merge 169 commits into
Conversation
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.
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.
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.
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>
…eIf 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>
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.
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>
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>
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>
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>
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 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>
…lers
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>
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 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 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>
…ng 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>
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>
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>
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>
…allbacks
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>
…llbacks
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>
…ations
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>
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>
…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>
…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 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>
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>
…e 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 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>
… AbiType* Per follow-up: the AbiTypeShape record was used only by callers immediately reading its .Kind property — the .Signature member was never accessed, and the caller already had the signature at the call site, so the wrapper added zero value. Changes: - Rename AbiTypeShapeKind → AbiTypeKind (enum) - Rename AbiTypeShapeResolver → AbiTypeKindResolver (class + property) - Rename AbiTypeShapeKindExtensions → AbiTypeKindExtensions - Delete the AbiTypeShape record (truly unused wrapper) - AbiTypeKindResolver.Resolve now returns AbiTypeKind directly - Drop the now-redundant .Kind accessor from all call sites Pure rename + signature simplification — generated projection output remains byte-identical across all 3 regen scenarios (0 diffs). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Aligns the method name with AbiTypeKindResolver.Resolve, giving both classification resolvers a uniform 'Resolve(<input>)' entry point. Pure rename — generated projection output remains byte-identical across all 3 regen scenarios (0 diffs). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move the type-shape classification logic directly into Resolve and remove the private ClassifyShape method. Resolve now performs cheap top-level checks (string, object, HRESULT exception, System.Type, Nullable<T>, arrays, generic instances) before running cache-aware predicates via AbiTypeHelpers in the same order as the legacy path. Special-case delegate detection for runtime types and return AbiTypeKind.Unknown as a fallback. This removes an extra indirection and consolidates the classification logic in one place.
…face The helper had a special-case fast path for cross-module typerefs with the 'System' namespace, returning true for 'Uri', 'Type', 'IDisposable', 'Exception'. This was both wrong and incomplete: - 'Type' and 'Exception' are NOT runtime class/interface/delegate at the WinRT ABI -- they're mapped to value-type structs (Windows.UI.Xaml.Interop.TypeName and Windows.Foundation.HResult respectively), so claiming RCI for them is semantically incorrect. - 'IServiceProvider' (mapped to Microsoft.UI.Xaml.IXamlServiceProvider, an interface) was missing from the list entirely. The reason neither bug fired in practice: the System block was dead code on the WinMD-reading path. Custom-mapped types appear in WinMD metadata under their WinRT names (Windows.Foundation.Uri, Windows.Foundation.HResult, Windows.UI.Xaml.Interop.TypeName, etc.), not their .NET-mapped System.* names -- so a cross-module typeref with ns == 'System' is never produced. And for any synthetic signature that DOES reach Resolve with a System.* form, the top-level filters in Resolve (IsHResultException / IsSystemType) intercept them before this helper is consulted. Confirmed empirically: removing the whole block produces 0 diffs across all 3 regen scenarios. Falling through to the cache resolution path (then the !td.IsValueType fallback) handles every actual call site correctly, with no special-case bookkeeping. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Per follow-up to f1a1ed9: WinMD metadata always carries types under their WinRT names. The custom mapping to System.* names happens only on the OUTPUT side (in the projection source the writer emits). Several classifier predicates on the INPUT side were probing for the .NET-side name as an alternative -- branches that never fire. Empirically verified by removing each candidate and running Validate-All across all 3 regen scenarios: - IsHResultException 'System.Exception' branch -- DEAD (WinMD only ever carries Windows.Foundation.HResult). - IsNullableT 'System.Nullable' branch -- DEAD (WinMD only ever carries Windows.Foundation.IReference<T>). - AbiTypeHelpers.Marshallers 'System.Nullable' branch -- DEAD (same reason; the inline comment in the helper already said so). Counter-example NOT removed: IsSystemType (and TypeSemantics.SystemType) still check 'System.Type' -- those branches ARE live because the ECMA-335 custom-attribute blob encoding emits attribute parameters typed as 'Type' as TypeRefs to System.Type directly (not the WinRT TypeName struct). The Windows Foundation metadata winmd (ActivatableAttribute, ComposableAttribute, StyleTypedPropertyAttribute, etc.) exercises this. XML doc updated to record the reason. 0 diffs across all 3 regen scenarios after the cleanup. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There were several uncommented 'ns == \"System\" && name == \"X\"' fast paths sprinkled across the classifiers / TypeSemantics factory. Audited each one empirically by removing it and observing whether output diffs appeared, then either deleted the dead ones or added a brief comment explaining why the live ones are needed. Deleted (dead, fallthrough produces the same answer): - AbiTypeHelpers.IsComplexStruct 'System.Guid' branch -- returns false, and the cache.Find(...) + 'def is null' fallthrough also returns false. Pure redundancy. Kept with a brief comment explaining why: - AbiTypeHelpers.IsAnyStruct 'System.Guid' -- WinRT GUIDs are this BCL struct, but Guid lives in mscorlib (not WinMD), so cache resolution fails and the fallthrough would (incorrectly) classify Guid as Unknown instead of BlittableStruct. - TypeSemantics 'System.Guid' / 'System.Object' -- same mscorlib-only story. Dedicated semantics drive the writers to emit the BCL short name instead of the fully-qualified TypeReference name. - TypeSemantics 'System.Type' -- ECMA-335 attribute-blob encoding emits Type-valued attribute args as TypeRefs to System.Type directly (not the WinRT TypeName struct). ActivatableAttribute, ComposableAttribute, StyleTypedPropertyAttribute, etc. all exercise this path. - AbiTypeHelpers.GetClassHierarchyIndex 'System.Object' -- AsmResolver CAN TryResolve System.Object via the corlib runtime context, which would (incorrectly) make a direct-Object-derivation count as depth 1 instead of 0. Short-circuit avoids the false recursion. Left untouched (already self-documenting via the method's name + XML doc, or via adjacent code context): - TypeDefinitionExtensions.HasNonObjectBaseType -- the System.Object check IS the method's purpose; the name and doc already explain it. - TypedefNameWriter EventSource 'ns == \"System\"' -- post-ApplyMapping; the surrounding code makes the context clear. 0 diffs across all 3 regen scenarios after the audit. Co-Authored-By: Copilot <223556219+Copilot@users.noreply.github.com>
…wName()
- Add GetRawName() extension to PropertyDefinition, EventDefinition, FieldDefinition
(parallel to the existing MethodDefinition.GetRawName() convention).
- Add GetRawName() extension to ITypeDescriptor (efficient single-name access without
allocating the namespace string from Names()).
- Migrate 55 inline 'X.Name?.Value ?? string.Empty' / 'X.Namespace?.Value ?? string.Empty'
sites across 22 files to use the appropriate helper:
* ITypeDescriptor name-only sites use GetRawName().
* ITypeDescriptor namespace-only sites use Names().Namespace.
* Sites reading both name and namespace deconstruct (ns, name) = X.Names().
* Member sites (Property/Event/Field) use the new GetRawName() extensions.
Pure refactor; generated projection output is byte-identical across all regen scenarios.
Co-Authored-By: Copilot <223556219+Copilot@users.noreply.github.com>
Simplify and optimize type deduplication: use TypeDefinition.FullName instead of manually concatenating namespace/name, and use CollectionsMarshal.GetValueRefOrAddDefault to avoid a double dictionary lookup when creating NamespaceMembers. Adds System.Runtime.InteropServices import and initializes NamespaceMembers via ref with null-coalescing assignment. Preserves first-load-wins semantics for conflicting WinMD types.
Replace the verbose reference and name comparison in InterfacesEqualByName with a single return using tuple equality: return a == b || a.Names() == b.Names(); This preserves the original behavior (true for identical references or matching namespace/name tuples) while making the code more concise and readable.
Add ITypeDescriptorExtensions.GetRawNamespace to return the raw metadata namespace (falling back to string.Empty) and document it as a cheaper alternative to the Names tuple when only the namespace is needed. Replace calls to type.Names().Namespace with type.GetRawNamespace() across multiple factories and the generator to avoid allocating the name string unnecessarily. Also include a small doc-comment tweak for GetRawName. Modified files: ITypeDescriptorExtensions.cs, AbiClassFactory.cs, AbiDelegateFactory.cs, AbiInterfaceFactory.cs, StructEnumMarshallerFactory.cs, ProjectionGenerator.Namespace.cs.
Delete two helper methods from MethodSignatureInfoExtensions: ArrayParameters (which yielded parameters whose resolved category was an array shape: PassArray/FillArray/ReceiveArray) and HasParameterOfCategory (which checked whether any parameter had a given ParameterCategory). This simplifies the class by removing these utilities; update callers to use ParameterCategoryResolver or other logic as needed.
Delete unused helper methods WriteClassModifiers and IsFastAbiDefaultInterface from ClassFactory.cs, along with the related comment about the fast-abi flag. This cleans up dead code and removes redundant fast-abi handling no longer required by the projection writer.
Emit different code paths when Settings.ReferenceProjection is enabled. For events, generate throw-null add/remove accessors in reference projection, otherwise generate subscription/unsubscription calls to the ABI event. For static factory object refs, emit a single expression-bodied throw-null property for reference projection, and preserve the previous activation-factory getter (with IID lookup) for the non-reference case. Also refactors multiline writer usage and cleans up surrounding formatting.
Four parallel analysis agents (claude-opus-4.7, claude-opus-4.6, gpt-5.5, gpt-5.3-codex) ran on src\WinRT.Projection.Writer\ to identify unused symbols. Findings were validated by greping each candidate across the full repo to confirm zero callers outside the cited definition. Deletions: - WellKnownProjectionWriterExceptions.InternalInvariantFailed (5001) and CannotResolveType (5002): both unreferenced (CannotResolveType's only caller was MetadataCache.FindRequired, which is itself unreferenced). - MetadataCache.FindRequired(string). - MappedTypes.HasNamespace(string). - AbiTypeHelpers.GetReturnLocalName. - MethodSignatureInfo.ReturnParameterName + cascading ProjectionNames.DefaultReturnParameterName constant. - TypeSemantics.BoundGenericParameter nested record (FundamentalTypes shape is unaffected; only the unused variant is removed). - TypeSemantics.FundamentalTypes.ToDotNetType helper. - IndentedTextWriter.GetSubstring(int, int). - IndentedTextWriter.FlushToString(). - TypeFilter.Empty static property and MatchesAllByDefault property. - ProjectionNames.VoidPointer constant. - WellKnownAbiTypeNames.AbiSystemExceptionPointerData, AbiSystemTypeMarshaller, AbiSystemExceptionMarshaller constants. - AbiTypeKindResolver.IsAnyStruct(TypeSignature) instance wrapper (the active AbiTypeHelpers.IsAnyStruct(MetadataCache, TypeSignature) static helper used by Resolve() stays). - TypeSignatureExtensions.SzArrayElement() extension (zero callers). - ParameterCategoryExtensions.IsAnyArray() extension (zero callers). - Drop now-unused 'using WindowsRuntime.ProjectionWriter.References;' from Models\MethodSignatureInfo.cs (was only there for DefaultReturnParameterName). 156 deletions total. Build clean with 0 warnings; validation harness reports 0 diffs across all 3 regen scenarios (pushnot, everything-with-ui, windows). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…able' terminology
Restructure the struct classifiers in AbiTypeHelpers.Blittability.cs around two clear concepts (blittable vs complex), matching the model the interop generator already uses. Three problems addressed:
1. IsComplexStruct had two <summary> tags - one wrong ('any struct that can be passed directly across the ABI', which is the opposite of complex) and one right. Drop the wrong one and expand the correct one with a <remarks/> section listing exactly which fields disqualify a struct (string, object, runtime class, generic instance, marshalling-mapped, nested complex).
2. Remove the 'almost-blittable' terminology throughout the project. The concept doesn't exist - structs are either blittable (projected layout matches the ABI byte-for-byte) or they aren't. The interop generator's blittability check is the reference model: WindowsRuntimeExtensions.IsBlittable + IsManagedValueType. Updated comments in StructEnumMarshallerFactory.cs (4 sites), AbiMethodBodyFactory.DoAbi.cs (2 sites), AbiMethodBodyFactory.RcwCaller.cs (1 site), AbiTypeWriter.cs (1 site), and AbiTypeHelpers.Blittability.cs (3 sites).
3. Rename IsAnyStruct -> IsBlittableStruct. The old name suggested 'any struct type'; the implementation actually filters out structs whose fields would force per-field marshalling, i.e. it returns true exactly when the struct is blittable. New name matches AbiTypeKindResolver.IsBlittableStruct (the existing high-level wrapper) and AbiTypeKind.BlittableStruct (the kind it maps to). Also renamed the 'almostBlittable' local in StructEnumMarshallerFactory.WriteStructEnumMarshallerClass to 'blittableStruct' for consistency.
Also fixed a dangling/partial comment in IsComplexStruct that started mid-sentence with 'RequiresMarshaling, regardless...' - rewrote it to explain the mapped-struct short-circuit clearly.
Build clean with 0 warnings; validation harness reports 0 diffs across all 3 regen scenarios (pushnot, everything-with-ui, windows). No semantic changes - all renames are call-site mechanical and all other edits are XML doc and comment improvements.
Co-Authored-By: Copilot <223556219+Copilot@users.noreply.github.com>
Replace the "ComplexStruct" terminology with "NonBlittableStruct" across the projection writer. Renamed enum members, resolver methods, helper functions, model fields, and related properties (AbiTypeKind, AbiArrayElementKind, IsNonBlittableStruct, MethodSignatureMarshallingFacts, etc.), and updated all call sites in AbiMethodBodyFactory, StructEnumMarshallerFactory, AbiTypeHelpers, and related files. This is a rename/refactor to clarify that these structs are non-blittable (require per-field marshalling); no functional logic changes were introduced beyond the identifier and comment updates.
Replace fully-qualified BCL type names with C# keyword aliases (string, object) in XML documentation comments for consistency and readability. Changes touch TypeSemantics.cs, AbiTypeKind.cs, MethodSignatureMarshallingFacts.cs, and WellKnownTypeNames.cs. No runtime behavior changes.
Update comments in AbiMethodBodyFactory.DoAbi to use the term 'non-blittable structs' instead of 'complex structs' and remove redundant 'server-side' qualifiers. Also shorten several Nullable<T> and System.Type comment lines for clarity and consistency. No functional code changes; only comment/text updates in src/WinRT.Projection.Writer/Factories/AbiMethodBodyFactory.DoAbi.cs.
Remove an extraneous period in a comment inside src/WinRT.Projection.Writer/Factories/ConstructorFactory.FactoryCallbacks.cs. This is a non-functional, cosmetic change to the comment "Sealed Invoke signature is multi-line" to match code style.
Correct a comment in src/WinRT.Projection.Writer/Generation/ProjectionGenerator.cs: change "Phase 3..6: parallel emission" to "Phase 2: parallel emission" to accurately reflect the pipeline stage. No functional changes; this improves clarity for maintainers.
Refactor GetCategory to use C# pattern matching on TypeDefinition (IsInterface, IsEnum, IsValueType, IsDelegate) instead of manual BaseType and namespace/name checks. Removes an unused 'using AsmResolver' and simplifies the method body without changing behavior (defaulting to TypeCategory.Class).
Mechanical rename of the type-categorization enum from 'TypeCategory' to 'TypeKind' and relocation from Metadata/TypeCategorization.cs to its own file Models/TypeKind.cs (alongside the other writer model enums - AbiTypeKind, ParameterCategory, AbiArrayElementKind, etc.). Updated the corresponding well-known exception helper 'UnknownTypeCategory' -> 'UnknownTypeKind' (and its error message), and added 'using WindowsRuntime.ProjectionWriter.Models;' to all 13 files that newly reference the relocated enum. TypeCategorization.GetCategory and the IsAttributeType / IsStatic / IsFlagsEnum / IsGeneric / IsApiContractType / IsExclusiveTo / IsProjectionInternal helpers are unchanged in behavior; they now reference TypeKind.X instead of TypeCategory.X internally. Build clean (0 warnings); validation harness reports 0 diffs across all 3 regen scenarios (pushnot, everything-with-ui, windows). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…-comparison callers Add five fundamental type-classifier extension properties on AsmResolver's TypeDefinition (TypeDefinitionExtensions.cs): - IsStruct: value type that isn't an enum - IsAttributeType: class whose immediate base is System.Attribute (XML <remarks/> calls out the 'immediate base only' restriction; matches the original loop semantics exactly) - IsStatic: class that is both abstract and sealed - IsFlagsEnum: enum carrying [System.FlagsAttribute] - IsGeneric: type has any generic type parameters Migrate all direct-comparison call sites: - TypeCategorization.GetCategory(t) == TypeKind.Interface/Enum/Struct/Delegate → t.IsInterface / t.IsEnum / t.IsStruct / t.IsDelegate - TypeCategorization.GetCategory(t) != TypeKind.X / 'is TypeKind.X' / 'is not TypeKind.X' → same with negation - TypeCategorization.IsAttributeType(t) / IsStatic(t) / IsFlagsEnum(t) / IsGeneric(t) → t.IsAttributeType / t.IsStatic / t.IsFlagsEnum / t.IsGeneric Remove the four corresponding static helpers from Metadata/TypeCategorization.cs; simplify the three remaining WinRT-specific helpers (IsApiContractType, IsExclusiveTo, IsProjectionInternal) to use the new properties internally instead of calling GetCategory. Comparisons against TypeKind.Class (and multi-kind 'is TypeKind.X or TypeKind.Y' patterns) are intentionally left untouched - they will be migrated in the next commit when the new TypeKindResolver is introduced. Pruned the 'using WindowsRuntime.ProjectionWriter.Models;' from the two files that no longer reference TypeKind after this migration (AbiStructFactory, TypedefNameWriter). Build clean (0 warnings); validation harness reports 0 diffs across all 3 regen scenarios (pushnot, everything-with-ui, windows). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…sions, delete TypeCategorization.cs Final cleanup of the Metadata/TypeCategorization static class. Three concerns: 1. Introduce Resolvers/TypeKindResolver.cs - a small static class mirroring AbiTypeKindResolver's shape, with a single 'public static TypeKind Resolve(TypeDefinition type)' method that replaces TypeCategorization.GetCategory. Migrate all remaining GetCategory callers (mostly local-variable stores feeding switch statements + the multi-kind 'is TypeKind.Class or TypeKind.Delegate' patterns) to TypeKindResolver.Resolve. 2. Move the three remaining WinRT-specific helpers from TypeCategorization to TypeDefinitionExtensions as extension properties: - IsApiContractType: struct + [Windows.Foundation.Metadata.ApiContractAttribute] - IsExclusiveTo: interface + [Windows.Foundation.Metadata.ExclusiveToAttribute] - IsProjectionInternal: type marked [WindowsRuntime.Internal.ProjectionInternalAttribute] These live alongside the other WinRT-specific TypeDefinition extensions already in the file (GetVersion, GetContractVersion, TryGetWindowsRuntimeGuid, ...). 3. Delete Metadata/TypeCategorization.cs - all of its content has now been redistributed. Tidied up the using directives in AbiStructFactory.cs (added 'Metadata' for TypedefNameType, which was previously coming in transitively through 'using Metadata' for TypeCategorization) and AbiTypeHelpers.AbiTypeNames.cs (kept 'Metadata' for MetadataCache). Build clean (0 warnings); 'dotnet publish -r win-x64' for WinRT.Projection.Generator completes Native AOT codegen with no warnings; validation harness reports 0 diffs across all 3 regen scenarios (pushnot, everything-with-ui, windows). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Polish pass over the type-kind helpers introduced in the previous three commits: - Add per-member XML <summary/> docs to TypeKind (Interface, Class, Enum, Struct, Delegate). - Strongly-type WellKnownProjectionWriterExceptions.UnknownTypeKind to accept TypeKind instead of object, and refine the error message text. - Simplify TypeDefinitionExtensions.IsStatic to 'type.IsAbstract && type.IsSealed' (the explicit non-class short-circuit is unnecessary: in IL, only classes can be both abstract and sealed - enums/structs/delegates are sealed but not abstract, and interfaces are abstract but not sealed). - Trim the IsAttributeType <remarks/> to drop the redundant 'WinMD attribute types always extend Attribute directly' sentence (the immediate-base restriction is already clearly stated). - Drop a stray trailing blank line in AbiTypeHelpers.cs. Build clean (0 warnings); validation harness reports 0 diffs across all 3 regen scenarios (pushnot, everything-with-ui, windows). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Rename local and parameter variables named 'category'/'cat' to 'kind' across the projection writer codebase for naming consistency. Updated parameter names in ProjectionFileBuilder, adjusted switch statements and exception calls, and replaced local usages in AbiClassFactory, ComponentFactory, MetadataAttributeFactory, StructEnumMarshallerFactory, ProjectionGenerator (Namespace and GeneratedIids), AbiTypeHelpers.Blittability, AbiTypeWriter, SignatureGenerator, and NamespaceMembers. No functional changes intended.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Follow-up to #2415 (initial C++→C# port of
cswinrt) and #2418 (first refactor pass). This PR adds a much larger refactor pass on the C# projection writer ‒ infrastructure for interpolated-string writing and callback-based code emission, several new resolver/classifier types, broad helper consolidation across the writer, removal of dead code identified by multi-agent fleet analysis, and elimination of legacy projection modes that no longer apply.All scenario diffs remain at zero across
pushnot,everything-with-ui, andwindows: nothing here changes the generated projection output.Motivation
The initial C++→C# port (#2415) and the first refactor pass (#2418) gave us a working, faithful C# projection writer. With that baseline in place, this PR focuses on internal API quality so the writer is easier to read, maintain, and extend:
string.Format/WriteLineboilerplate that hid intent and made multi-line code awkward to read. AnIndentedTextWriterinterpolated-string handler plus anIIndentedTextWriterCallbackcallback model collapses dozens of multi-step writes into single interpolated raw-string templates.AbiTypeHelpers. Two new resolvers (TypeKindResolver, the renamedAbiTypeKindResolver) and several new extension properties onTypeDefinitiongive the writer a single, semantic entry point for "what kind of type is this?".MethodSignatureInfo,ParameterCategory, attribute lookup, and identifier-escaping logic were duplicated across factories. New extension/helper types (MethodSignatureMarshallingFacts,MethodSignatureInfoExtensions,ParameterInfoExtensions,IHasCustomAttributeExtensions,IdentifierEscaping,BuildGlobalQualifiedName, …) consolidate those into one shared, documented surface.TypeCategoryvsTypeKind). All of that is now cleaned up.CsWinRTEmbedded*and theProjectionInternalprivate-projection support) had no callers in CsWinRT 3.0 and have been deleted end-to-end.Changes
Grouped thematically (many small commits in each group):
IndentedTextWriter + callback emission model
Writers/IndentedTextWriter.AppendInterpolatedStringHandler.csandWriters/IndentedTextWriter.AppendIfInterpolatedStringHandler.cs: interpolated-string handlers forWrite/WriteLine/WriteIf/WriteLineIf, with the canonicalisMultiline: true, $""""""…""""""argument order at all call sites.Writers/IIndentedTextWriterCallback.cs+IIndentedTextWriterCallbackExtensions.cs: callback infrastructure that lets multi-line templates host arbitrary writer-driven holes, eliminating manualWriteinterleaving.Factories/*Callbacks/*Callback.cs: a family of named callbacks (WriteTypeNameCallback,WriteParameterListCallback,WriteIidExpressionCallback,WriteEventTypeCallback,WriteAbiTypeCallback,WriteFactoryMethodParametersCallback,WriteGuidAttributeCallback, etc.) extracted from inline-string emission and applied across the factories.New resolvers and classifiers
Resolvers/AbiTypeKindResolver.cs(renamed fromAbiTypeShapeResolver;AbiTypeShapewrapper deleted; classification logic inlined intoResolve).Resolvers/TypeKindResolver.cs: single static entry point classifying aTypeDefinitionasTypeKind.Class/Interface/Enum/Struct/Delegate(replacesTypeCategorization.GetCategory).Resolvers/ParameterCategoryResolver.cs:GetParamCategoryrenamed toResolvefor consistency.Models/TypeKind.cs,Models/AbiArrayElementKind.cs,Models/AbiTypeKind.cs,Models/MethodSignatureMarshallingFacts.cs,Models/MethodSignatureInfoExtensions.cs,Models/ParameterInfoExtensions.cs: new model types and extension helpers.Extension/helper consolidation
Extensions/TypeDefinitionExtensions.cs: new classifier properties (IsStruct,IsAttributeType,IsStatic,IsFlagsEnum,IsGeneric) and WinRT-specific predicates (IsApiContractType,IsExclusiveTo,IsProjectionInternal);Metadata/TypeCategorization.csdeleted.Extensions/TypeSignatureExtensions.cs,ITypeDescriptorExtensions.cs,IHasCustomAttributeExtensions.cs,ITypeDefOrRefExtensions.cs,InterfaceImplementationExtensions.cs,MethodDefinitionExtensions.cs,EventDefinitionExtensions.cs,PropertyDefinitionExtensions.cs,CustomAttributeExtensions.cs,FieldDefinitionExtensions.cs: many small extension methods (e.g.Names(),GetRawName(),GetRawNamespace(),MatchesName,TryResolveTypeDef,TryGetFixedArgument<T>,HasNonObjectBaseType,IsAbiRefLike,IsArrayInput,IsReferenceTypeOrGenericInstance,AsSzArray,GetStrippedName,StripByRefAndCustomModifiers,BuildGlobalQualifiedName, …).Helpers/AbiTypeHelpers.*.cs: split into focused partials;IsAnyStructrenamed toIsBlittableStruct;ComplexStructrenamed toNonBlittableStruct; misleading "almost-blittable" comments eliminated;GetAbiLocalTypeName,InterfacesEqualByName,GetNullableInnerInfo,IsBlittableAbiElementconsolidated into shared helpers.Helpers/IdentifierEscaping.cs+ParameterInfo.GetEscapedName: single source of truth for C# identifier escaping.Helpers/SignatureGenerator.cs: extracted from inline signature-string construction across factories; emits via writer to avoid intermediate string allocations.Factory clean-ups and consolidations
Factories/StructEnumMarshallerFactory.cs:Box/Unbox/CreateObject/complex-struct emission paths rewritten to use the callback infrastructure; struct-classifier terminology updated;GetInstanceFieldsextracted.Factories/ComponentFactory.cs: factory-class emission consolidated via a singleactivateBodylocal; reference-projection events & static factory support.Factories/AbiClassFactory.cs,Factories/AbiInterfaceFactory.cs,Factories/AbiDelegateFactory.cs,Factories/AbiStructFactory.cs,Factories/ClassFactory.cs,Factories/ConstructorFactory.cs,Factories/EventTableFactory.cs,Factories/InterfaceFactory.cs,Factories/MetadataAttributeFactory.cs,Factories/ReferenceImplFactory.cs: collapsed branch dispatches, unified default/exclusive-to interface emission, deduplicated property and event-accessor patterns, removed dead 4-branch chains, replaced ad-hoc generic-instance flow with helper-driven code paths.Dead-code and legacy cleanup
ArrayParametersandHasParameterOfCategory(unused).ClassMembersFactory.ResolveInterface(redundant) andIndentedTextWriterExtensions.cs(superseded by the interpolated-string handler)..winmdinputs (e.g.System.Exceptionchecks in classifiers that only ever seesWindows.Foundation.HResult).CsWinRTEmbedded*and embedded-projection-mode leftovers.ProjectionInternalmode is no longer a CsWinRT 3.0 feature).Bug fixes
HasNonObjectBaseTypewas incorrectly returningtruefor theSystem.Objectbase case — fixed to correctly recognise it.BuildGlobalQualifiedNamenow strips backticks (IList\1→IList) so callers don't have to pre-strip.Misc
ProjectionWriter.cs/ProjectionWriterOptions.cs/cswinrt.slnx: small surface trims tied to the removed legacy modes.