Refactor projection writer: cleanup passes, parallelization, and CI-driven correctness fixes#2418
Open
Sergio0694 wants to merge 229 commits into
Open
Refactor projection writer: cleanup passes, parallelization, and CI-driven correctness fixes#2418Sergio0694 wants to merge 229 commits into
Sergio0694 wants to merge 229 commits into
Conversation
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>
…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>
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>
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>
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>
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>
…lper
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>
…e/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>
…efinition 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>
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>
…r-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>
…sed 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>
…edicates) 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>
… 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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
…onEmitContext)
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>
…extWriter, 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>
…rojectionEmitContext)
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>
…rojectionEmitContext) 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>
…r, 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>
F5 - Sort discovered .winmd inputs (Metadata/MetadataCache.cs) Directory.EnumerateFiles returns files in filesystem-dependent order, so the "first-load-wins" duplicate-type behavior in LoadFile (line 173) was effectively environment-dependent. Sort the deduped winmdFiles list with StringComparer.OrdinalIgnoreCase before LoadFile iteration so duplicate-type precedence is deterministic across machines and CI runs. Aligns with the source-generator pattern in TypeMapAssemblyTargetGenerator.cs:92-95. F8 - Replace ContainsKey + indexer with TryAdd (Metadata/MetadataCache.cs:LoadFile) Two-probe `if (ContainsKey) continue; ... [fullName] = type;` collapsed to single-probe `if (!_typesByFullName.TryAdd(fullName, type)) continue;`, matching the interop generator''s prevalent single-probe map mutation style (e.g. InteropGeneratorArgs.Parsing.cs:91). Validation: build clean (0 warnings), all 8 regen scenarios byte-identical (deterministic ordering happens to match the prior filesystem order on this machine/SDK build, so no user-visible change). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…y blanks) F9 - Collapsed 12 sites of double-blank-line gaps to single blanks (raw-string-aware sweep): - Helpers/AbiTypeWriter.cs (8 sites at the if-chain in TypeSemantics.Definition / .Reference cases) - Helpers/WindowsMetadataExpander.cs (2 sites: between usings/namespace and between two ifs in best-version loop) - Metadata/TypeCategorization.cs (2 sites between consecutive ifs in GetCategoryFromBase) F10 - Inserted missing blank lines around block boundaries (5 sites): - Helpers/TypeFilter.cs (between Includes return and Match method declaration) - Generation/ProjectionGenerator.Namespace.cs (between foreach close and WritePragmaRestoreIL2026) - Metadata/MetadataCache.cs (between cache construction, foreach load, sort, and return) N4 - Removed extra blank line before GeneratedIids method close brace: - Generation/ProjectionGenerator.GeneratedIids.cs (line 125 stray blank) - Plus added blank line before WriteInterfaceIidsEnd (matching block-boundary convention) The double-blank sweep used the same raw-string-aware Mark-LinesInRawString helper from prior sweeps to avoid touching emitted projection text inside """ raw-string literals. Validation: build clean (0 warnings), all 8 regen scenarios byte-identical. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ers.cs) The 70+ line sealed NamespaceMembers class lived as a secondary type at the bottom of MetadataCache.cs. R5 opus-4.6 flagged that the interop generator convention for multi-type files pairs a parent with either a file-scoped helper or a nested partial split, not an independent top-level type. Moved NamespaceMembers verbatim to Metadata/NamespaceMembers.cs and removed it from MetadataCache.cs. No behavior change. Validation: build clean (0 warnings), all 8 regen scenarios byte-identical. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
R5 opus-4.6 flagged the 5 IDE-prefix project-level NoWarn entries as visible
csproj-shape divergence vs the interop generator (which has zero IDE-prefix
project NoWarn). Tested removing each suppression individually:
- IDE0010: 22 fires
- IDE0022: 56 fires
- IDE0046: 60 fires
- IDE0060: 28 fires
- IDE0072: 16 fires
All 5 suppressions are still pervasive enough that per-site refactoring is
out of scope for this cleanup pass. The author''s intent ("should be tightened
over time") stands. Updated the csproj comment to reflect the measured fire
counts so future contributors know exactly what they''re inheriting.
No code change beyond the comment.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
N1 - Metadata/TypeSemantics.cs:251 typo
XML doc summary "Type-name kind,." -> "Type-name kind."
N2 - Factories/AbiClassFactory.cs:53-56 truncated comment
Comment block started mid-sentence ("// instantiation (e.g. ...)").
Restored the dropped antecedent: "// If the default interface is a generic
instantiation (e.g. ...)".
N5 - Writers/IndentedTextWriter.cs:404 CurrentIndentLevel placement
Public CurrentIndentLevel property was placed at the bottom of the class,
separated from the related state fields and IncreaseIndent/DecreaseIndent
methods that read it. Moved adjacent to _currentIndentation /
_availableIndentations near the top, matching the source-generator
IndentedTextWriter layout where the state fields are colocated with the
field they conceptually belong with.
N8 - Generation/ProjectionGenerator.cs:80 missing blank line after log = ...
Added blank lines around the foreach iteration to match the
"declare, then act" cadence used elsewhere in the file.
(N3 was already addressed: AbiMethodBodyFactory.DoAbi.cs:38-46 already had a
blank-line group separator between return-type and method-kind booleans.
N4, N6, N7 were addressed in earlier R5 commits.)
Validation: build clean (0 warnings), all 8 regen scenarios byte-identical.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…RTInteropGenerator
CI failure:
nuget\Microsoft.Windows.CsWinRT.targets(678,3): Error MSB4024: The imported
project file "...\nuget\Microsoft.Windows.CsWinRT.CsWinRTGen.targets" could
not be loaded. ''MaxDegreesOfParallelism'' is a duplicate attribute name.
Line 191, position 7.
The first _RunCsWinRTInteropGenerator task invocation in
Microsoft.Windows.CsWinRT.CsWinRTGen.targets had ``MaxDegreesOfParallelism="..."``
declared twice (lines 187 and 191) -- introduced by an earlier R5 commit when
threading the new MaxDegreesOfParallelism setting through the MSBuild surface.
MSBuild rejects duplicate attribute names on a single XML element.
Removed the duplicate at line 191 (kept the one at 187 for ordering symmetry
with the other 5 task invocations in the file). All 6 _RunCsWinRTXxx task
invocations now have exactly one MaxDegreesOfParallelism attribute each;
verified by a per-task duplicate-attribute scan.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…lGenerator
CI failure:
nuget\Microsoft.Windows.CsWinRT.CsWinRTGen.targets(259,7): Error MSB4064:
The "MaxDegreesOfParallelism" parameter is not supported by the
"RunCsWinRTForwarderImplGenerator" task loaded from assembly:
WinRT.Generator.Tasks ...
The R5 commit that exposed MaxDegreesOfParallelism through the MSBuild surface
(580a050 "F7 + F8: ... surface MaxDegreesOfParallelism through projection-
generator CLI") added the attribute to all 6 _RunCsWinRTXxx task invocations,
but only added the corresponding property to two task classes:
* RunCsWinRTInteropGenerator.cs:123 - has property
* RunCsWinRTMergedProjectionGenerator.cs:93 - has property
* RunCsWinRTForwarderImplGenerator.cs - DOES NOT have property
* RunCsWinRTProjectionRefGenerator.cs - DOES NOT have property
* RunCsWinRTWinMDGenerator.cs - DOES NOT have property
The forwarder/impl, projection-ref, and winmd tasks are single-pass tools
without internal parallelism, so the property doesn''t apply to them. The
prior commit (c0d0f57) accidentally only fixed the literal duplicate-attribute
XML problem on the interop task, not the broader "passing the attribute to
tasks that don''t expose it" problem.
Fixed by removing the MaxDegreesOfParallelism="..." attribute from the
RunCsWinRTForwarderImplGenerator invocation at line 259. The 5 remaining usages
(1x InteropGenerator, 4x MergedProjectionGenerator) are all on tasks that
expose the property; the projection-ref and winmd tasks were never receiving
the attribute in the first place.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…tput
CI compiled the generated projection output for the first time after the
parallelism/exception-discipline R5 work and surfaced 5 distinct error
patterns. Three root causes:
(1) IndentedTextWriter: missing trailing newline after multiline raw-string
ending in a preprocessor directive
-------------------------------------------------------------------------
Write(span, isMultiline=true) auto-appends a trailing newline when the
last chunk ends with `;`, `}`, or `]` (since raw `"""..."""` strings never
include a trailing newline before the closing token). Preprocessor lines
like `#nullable disable` end with `e`/`l`, so no newline was appended,
causing the next emission to jam onto the same line:
}
#nullable disablepublic static class XxxMethods
{
Extended the heuristic: also append a trailing newline when the last
chunk starts with `#` (preprocessor directive). Verified by re-running
the writer and confirming `#nullable disable\npublic static class ...`
is now correctly multi-line in every previously-affected file.
Symptoms cleared:
CS8637 (Expected enable, disable, or restore)
CS1022 (Type or namespace definition or end-of-file expected)
CS0106 (modifier public is not valid for this item)
CS0234 (cascade: jammed [assembly:] attributes corrupted parser)
(2) AbiInterfaceIDicFactory: IDIC `file interface` base type emitted without
namespace prefix, creating a self-reference
-------------------------------------------------------------------------
The IDIC helper `file interface IXxx : IXxx` was emitted with the base
type via WriteTypedefName(... forceWriteNamespace=false). When the
surrounding namespace context matches the type's namespace, that path
emits the bare name -- making `file interface IBackgroundTransferOperation
: IBackgroundTransferOperation`, which C# resolves as the file-scoped
helper inheriting from itself. Result: every explicit-interface impl on
the helper failed CS0540 (containing type does not implement interface).
Fixed by passing `forceWriteNamespace=true` so the inheritance becomes
`file interface IBackgroundTransferOperation :
global::Windows.Networking.BackgroundTransfer.IBackgroundTransferOperation`,
which unambiguously points at the projected interface.
Symptom cleared:
CS0540 (containing type does not implement interface)
(3) AbiStructFactory: ABI struct DECLARATION emitted with `global::ABI.Ns.`
prefix, which is illegal as a type-declaration name
-------------------------------------------------------------------------
AbiStructFactory line 52 used WriteTypedefName(... TypedefNameType.ABI,
false) for the struct declaration after `internal unsafe struct `. ABI
types are unconditionally prefixed by WriteTypedefName, producing:
public unsafe struct global::ABI.Windows.Devices.Gpio.GpioChangeCount
-- which is a syntax error (CS1514, CS1513, CS1001) because `global::`
is not valid in a type declaration. The struct is already inside the
proper `namespace ABI.Windows.Devices.Gpio { }` block, so the
declaration just needs the bare type name.
Fixed by replacing the WriteTypedefName call with a direct
`IdentifierEscaping.StripBackticks(type.Name?.Value ?? string.Empty)`
emission.
Symptoms cleared:
CS1514 ({ expected)
CS1513 (} expected)
CS1001 (identifier expected)
How these were missed
---------------------
Local validation was byte-identical comparison against baselines captured
on 5/11 6:28am, BEFORE these bugs were introduced into the writer. None
of the R5 commits touched projection output, so byte-identity always
passed -- but the baselines themselves had pre-existing bugs locked in.
CI catches them because CI actually compiles the generated .cs with csc.
Future-proofing: should add a csc-compile step to the validation harness
so new emission bugs surface locally rather than only in CI.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Mirrors WinRT.SourceGenerator2/Helpers/IndentedTextWriter.cs design — the writer
no longer tries to retroactively patch caller mistakes via buffer-state heuristics.
All spacing/newlines must now be explicit at the call sites (matching the source
generator pattern of "what you call is what you get").
Removed heuristics:
1. Brace-prepend in Write(span, bool): used to insert a newline if buffer ended
with `{`/`}` and content started with whitespace. This was the cause of the
"property accessor split" regression (`{ get; }` -> `{\n get; }`) — the
single-space separator after `{` triggered the rule even though it was
inline-continuation, not an indented code line.
2. Auto-trailing-newline in multiline Write: used to append a newline if the
last chunk ended with `;`, `}`, `]`, or started with `#`. This was a band-aid
for raw multi-line strings ending without a trailing newline before the
closing `"""` token. Callers should now either include the trailing newline
in the raw string or use WriteLine instead of Write.
3. Brace-prepend in WriteLine(content): used to insert a newline if buffer ended
with `{`/`}`. Same family as (1).
4. Always-on idempotent WriteLine(): used to suppress newline if buffer ended
with `\n\n` or `{\n`. Now opt-in via WriteLine(bool skipIfPresent = false),
matching the source-gen API. Default behavior is no suppression.
Why
---
The user pointed out that the source-gen IndentedTextWriter has none of these
auto-fixups except the explicitly-opted-in `skipIfPresent`. All spacing is done
by the caller via correct WriteLine / Block() / etc. calls. Auto-fixups in the
writer are:
- Less efficient (every Write inspects buffer tail)
- A band-aid that hides caller-side mistakes rather than encouraging correct
caller-side calls
- The root cause of the property-split / brace-jam / nullable-jam regressions
documented in the regression report
This commit ONLY changes the writer. It will produce broken output until all
callers are updated to emit explicit newlines where the heuristics used to
silently insert them. Subsequent commits will fix the callers.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…h ;}]#
When the IndentedTextWriter heuristics were removed, every multiline raw string
that ended with a complete-line content (`;`, `}`, `]`, or starting with `#`)
lost its trailing newline (was previously injected by the auto-trail rule).
Mass-converted 147 such call sites from `writer.Write(...""", isMultiline: true)`
to `writer.WriteLine(...""", isMultiline: true)` so the trailing newline is
explicit at the call site.
This is the first batch. Subsequent commits will:
- Add explicit `WriteLine();` after multiline writes ending with `{` (block opens)
- Fix indentation/IncreaseIndent at namespace/block boundaries that were
previously masked by brace-prepend heuristics
- Audit the 28 inline-continuation sites to confirm they should stay as `Write`
Build clean. Output still differs from pre-truth in many places — fixing those
in subsequent commits.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…riteBlock
WriteFileHeader had stayed as Write (not WriteLine), so the banner was missing
its trailing newline. Changed to WriteLine.
WriteDefaultInterfacesClass and WriteExclusiveToInterfacesClass were emitting
the namespace block via `Write("""namespace ABI\n{""")` followed by a foreach
loop and a closing `Write("""}""")`. With the heuristics removed, this produced
"{[Win...]" jamming and 0-indent attributes inside the namespace.
Rewrote both functions using the source-gen pattern:
w.WriteLine("namespace ABI");
using (w.WriteBlock())
{
foreach (...) { w.WriteLine($"[Win...]"); }
w.WriteLine("internal static class WindowsRuntimeDefaultInterfaces;");
}
WriteBlock() handles the `{`, IncreaseIndent, DecreaseIndent, and closing `}`
in one self-contained scope. Output now matches pre-truth byte-for-byte for
this file.
Validation: 77/1553 files now match pre-truth (was 60). Continuing iteratively.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ontext
CI surfaced two errors in the generated Windows.AI.MachineLearning.cs:
CS0246: 'LearningModelPixelRange' could not be found
CS9051: File-local 'IReadOnlyList<ILearningModelFeatureDescriptor>'
cannot be used in non-file-local 'ILearningModelMethods'
Both are caused by the same long-standing gap: the writer was emitting
projected type names without the `global::Ns.` prefix when the type was
defined in the same namespace as `context.CurrentNamespace`. That is correct
when emitting code INSIDE the projected namespace, but wrong when emitting
code inside the corresponding ABI namespace (where the projected namespace
is not in scope, so the bare name doesn't resolve).
The OLD writer (pre-refactor at 11ef00a) had this exact mechanism as
`w.InAbiNamespace` / `w.InAbiImplNamespace` flags on its TypeWriter. The
refactor (R2/R3) lost it during the move from TypeWriter to
ProjectionEmitContext + extension methods.
Restored:
1. ProjectionEmitContext.InAbiNamespace and InAbiImplNamespace bool flags.
2. WriteBeginAbiNamespace(context) / WriteEndAbiNamespace(context) toggle
InAbiNamespace true/false. WriteBeginProjectedNamespace(context) sets
InAbiImplNamespace=true when in component mode (where the projected
namespace itself is `ABI.Impl.Ns`).
3. TypedefNameWriter.WriteTypedefName: extended the namespace-prefix
condition to also fire when emitting a Projected name while the writer
is inside an ABI/ABI.Impl namespace -- matches the OLD writer's
`(nameToWrite == TypedefNameType.Projected && (w.InAbiNamespace || w.InAbiImplNamespace))`
case.
4. Same fix to the Reference branch of WriteTypeName (cross-module typeRef
resolution path).
5. Replaced single-overload WriteEndProjectedNamespace() / WriteEndAbiNamespace()
with the context-aware overloads (callers updated).
Verified by csc-compiling the generated SDK output: zero CS0246 or CS9051
errors against the previously-affected file.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The InterfaceFactory.WriteInterfaceMemberSignatures loops were emitting `writer.WriteLine();` BEFORE each member to add a leading blank line, but with the heuristic-removed writer this added genuine blank lines between members (the old idempotent suppression was hiding this). Pre-truth has NO blank lines between members in interface bodies. Switched to the source-gen pattern: each iteration writes content + trailing `WriteLine`, ensuring the buffer always ends with `\n` at iteration boundary. The next iteration's content is then naturally indented onto a fresh line. Also fixed the `;}` jam where the last member's content was Write (no \n) and the WriteBlock's closing `}` got jammed onto the same line. Validation: 135/1553 files now match pre-truth (was 86). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The WriteStruct emission was using flat multiline raw strings with no IncreaseIndent context, causing struct body content (constructor, properties, operators) to be emitted at the wrong indent level (4 spaces / namespace level instead of 8 spaces / body level). Refactored to use the WriteBlock() pattern: - Outer using (writer.WriteBlock()) for the struct body - Nested using (writer.WriteBlock()) for the constructor body - Nested using (writer.WriteBlock()) for each property body This produces correctly-indented output matching the source-generator style. Verified: PRE and POST compile-error counts are byte-identical across all 10 error codes (CS0246, CS0234, CS0616, CS0538, CS8894, CS0738, CS9334, CS0539, CS0579, CS9333), confirming zero new compile errors introduced. All remaining file differences are whitespace/ formatting improvements. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1. AbiMethodBodyFactory.RcwCaller: returnIsRefType missed NullableT shape Commit a17d659 (P1-3: Wire AbiTypeShapeResolver into emission paths) converted `returnIsRefType = ...IsRuntimeClassOrInterface() || .IsObject() || .IsGenericInstance()` to use the new shape kinds. The OLD predicate `rt.IsGenericInstance()` returned true for Nullable<T> (since Nullable<T> IS a generic instance). The shape resolver now categorizes Nullable<T> as `AbiTypeShapeKind.NullableT` (checked BEFORE GenericInstance), so it never reaches the GenericInstance branch. The bug: for any Nullable<T> getter, the writer emitted: int __retval = default; (delegate*<void*, int*, int>) return (Nullable<T>)__retval; Should be (matches PRE): void* __retval = default; try { (delegate*<void*, void**, int>) return T_Marshaller.UnboxToManaged(__retval); } finally { WindowsRuntimeUnknownMarshaller.Free(__retval); } Caused CI error CS0030 `Cannot convert type 'int' to 'DateTimeOffset?'` on Windows.ApplicationModel.Wallet.cs:2303 (and many other Nullable<T> getters across the SDK). Fix: add `or AbiTypeShapeKind.NullableT` to the returnIsRefType check. 2. MetadataAttributeFactory.WriteWinRTIdicTypeMapGroupAssemblyAttribute and WriteWinRTComWrappersTypeMapGroupAssemblyAttribute: stray newline after `typeof(` split the attribute argument across lines. The multiline raw string ending with `source: typeof(` was emitted via `WriteLine` (which appends a trailing newline) instead of `Write` (no trailing newline). Result: the typedef name appeared on the next line. Compiles fine but produces messy output. Fix: change `WriteLine` to `Write` for boundary multilines that end with `(` since the next emission must continue on the same line. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…thods
ClassFactory.cs (RCW constructor), ConstructorFactory.Composable.cs
(composable ctors and base-chaining ctors), and
ClassMembersFactory.WriteInterfaceMembers.cs (GetInterface/GetDefaultInterface
helpers) were emitting class-method bodies via flat multiline raw strings
that didn't IncreaseIndent for the body. The OLD writer's auto-fixup
heuristics masked this, but with the new `dumb` writer the body content
landed at the same indent as the surrounding braces, producing ugly but
still-compilable code:
public Foo(...)
: base(...)
{
if (GetType() == typeof(Foo))
{
_objRef = NativeObjectReference;
}
}
Fixed by replacing flat multiline + manual `WriteLine(\"}\")` with the
`using (writer.WriteBlock())` pattern used elsewhere. Each method body is
now properly indented one level inside its braces.
143/1553 -> 177/1553 byte-identical vs PRE.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Per code review: when block content is fully fixed (no conditional logic, no per-iteration emission), prefer a single multiline interpolated raw string with the body indented inline rather than splitting into WriteBlock + per-line writes. The single-multiline form is more concise and reads more like the actual generated output. Affects: - ClassFactory.cs (RCW ctor body's GetType()==typeof if-block) - ConstructorFactory.Composable.cs (composable ctor's GetType() if-block) - ClassMembersFactory.WriteInterfaceMembers.cs (GetInterface and GetDefaultInterface bodies) Validation count unchanged (177/1553) -- output is byte-identical. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The PassArray/Out/Ref parameter loops in AbiMethodBodyFactory.RcwCaller
emitted multiline raw strings via WriteLine, which appended a trailing
newline after the parameter content. The next iteration's leading ',\n'
then started at start-of-line where the writer auto-prepends indent, so
the comma landed on its own indented line:
(uint)data.Length, _data
,
columnCount
Should be (matches PRE):
(uint)data.Length, _data,
columnCount
Fix: change WriteLine to Write for these emitters so the next
iteration's leading comma stays inline with the previous parameter.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
My port added a defensive else branch that emitted an empty
`if (GetType() == typeof(X)) { }` block when defaultIfaceObjRef was
empty. The original C++ template (cswinrt/code_writers.h:3169-3178)
unconditionally emits `<defaultObjRef> = NativeObjectReference;`
inside the if block via bind<write_objref_type_name>(default_type_semantics)
which always produces a name. The defensive null check + empty block
in the C# port produced dead code that the C++ never emitted.
Validation count unchanged (178/1553) -- the branch was never reached
in any of our test scenarios, confirming defaultIfaceObjRef is always
non-empty here in practice.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…tics Root cause: commit 175936f (R2 P0-2: Wire AbiTypeShapeResolver into all classification callsites) silently changed the semantics of two predicates that ~30 callsites depend on: 1. `IsBlittablePrimitive`: OLD `AbiTypeHelpers.IsBlittablePrimitive(cache, sig)` returned true for both primitives AND enums (enums marshal as their underlying integer). The new resolver method returned true ONLY for `BlittablePrimitive` shape, EXCLUDING `Enum` shape. - Symptom: `returnIsReceiveArray` and similar checks treated `EnumType[]` returns as primitive scalars instead of arrays. Generated `int __retval` + cast to enum-array (CS0030). - Affected CI errors: `Cannot convert int to NDCertificateFeature[]` (PlayReady), `Cannot convert int to DigitalWindowMode[]` (Devices). 2. `IsAnyStruct`: OLD `AbiTypeHelpers.IsAnyStruct(cache, sig)` returned true ONLY for blittable structs (it explicitly rejected any struct with reference-type fields). The new resolver method returned true for BOTH `BlittableStruct` AND `ComplexStruct` shapes. - Symptom 1: `StructEnumMarshallerFactory.almostBlittable` was wrongly true for complex structs, so `isComplexStruct` became false and the marshaller class skipped emitting `ConvertToUnmanaged`, `ConvertToManaged`, and `Dispose` -- only `BoxToUnmanaged` and `UnboxToManaged` were emitted. - Symptom 2: array/span FillArray classifications wrongly treated `Span<EnumType>` and `Span<ComplexStruct>` as non-blittable, emitting a `CopyToManaged_X` UnsafeAccessor pattern that referenced `ABI.Foo.EnumName*` (which doesn't exist for enums). - Affected CI errors: - `ProfileUsageMarshaller does not contain a definition for 'ConvertToUnmanaged' / 'ConvertToManaged' / 'Dispose'` (CS0117 + CS1503) - `GameControllerSwitchPosition does not exist in ABI.Windows.Gaming.Input` (CS0234) Fix: - `IsBlittablePrimitive` now returns true for `BlittablePrimitive` OR `Enum` shape (matches OLD). - `IsAnyStruct` now returns true ONLY for `BlittableStruct` shape (matches OLD; `ComplexStruct` is excluded). For the few callsites that genuinely want both, `IsComplexStruct` is available separately. Verified all 3 CI semantic errors disappear from the regenerated output: - PlayReady SupportedFeatures now emits the proper ReceiveArray pattern with `uint __retval_length`, `T* __retval_data`, `try`/`UnsafeAccessor`. - GameControllerSwitchPosition Span now emits the proper `fixed(void*)` blittable FillArray pattern. - ProfileUsageMarshaller now emits all 5 methods (ConvertToUnmanaged, ConvertToManaged, Dispose, BoxToUnmanaged, UnboxToManaged). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…t in SDK fallback
Two bugs fixed:
1. AbiMethodBodyFactory.RcwCaller: `returnIsAnyStruct` was true for
both BlittableStruct AND ComplexStruct shapes. The dispatch order in
the local-declaration switch checks `returnIsAnyStruct` BEFORE
`returnIsComplexStruct`, so ComplexStruct returns went to the wrong
branch (`GetBlittableStructAbiType` which returns the PROJECTED type
for non-mapped structs) instead of `GetAbiStructTypeName` (which
correctly returns the ABI struct type).
Symptom: for any complex struct return (struct with TimeSpan/DateTime
fields, e.g. GpioChangeCount, GpioChangeRecord, AccessListEntry,
StorePackageUpdateStatus), the writer emitted:
global::Windows.X.Y.SomeStruct __retval = default; // PROJECTED
(delegate*<void*, global::Windows.X.Y.SomeStruct*, ...>)
return __retval; // no ConvertToManaged!
global::ABI.Windows.X.Y.SomeStructMarshaller.Dispose(__retval); // CS1503
Should be (matches PRE):
global::ABI.Windows.X.Y.SomeStruct __retval = default; // ABI
(delegate*<void*, global::ABI.Windows.X.Y.SomeStruct*, ...>)
return global::ABI.Windows.X.Y.SomeStructMarshaller.ConvertToManaged(__retval);
global::ABI.Windows.X.Y.SomeStructMarshaller.Dispose(__retval);
Affected CI errors: CS1503 `cannot convert from 'Windows.X.Y.Foo' to
'ABI.Windows.X.Y.Foo'` for GpioChangeRecord, GpioChangeCount,
AccessListEntry, StorePackageUpdateStatus (Devices.Gpio,
Storage.AccessCache, Services.Store).
2. ProjectionGenerator.Generate: the SDK-mode fallback at line 228 (which
adds the Windows SDK namespace filters when no Microsoft.Windows.SDK.NET
reference assembly is present in the input refs) was forgetting to set
`hasTypesToProject = true`. The generator's main flow then early-exits
at `if (!processingState.HasTypesToProject) return;`, silently
producing no output. This blocked WinRT.Sdk.Projection.csproj from
compiling.
Now WinRT.Sdk.Projection compiles cleanly with 0 errors against the real
SDK 10.0.26100.1 winmds. This is a real CI-style validation that catches
the kinds of semantic errors we missed before.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Per code review: `IsAnyStruct` returning only blittable structs is misleading -- the name implies `BlittableStruct or ComplexStruct`. Refactor: - Restore `AbiTypeShapeResolver.IsAnyStruct` to its name-suggested semantics (`BlittableStruct or ComplexStruct`). - Add a new `IsBlittableStruct` method that returns true only for `BlittableStruct`. - Bulk-rename all 36 callers of `IsAnyStruct` to `IsBlittableStruct`. These callers were ported from OLD `AbiTypeHelpers.IsAnyStruct` which was always blittable-only, so this preserves the ported semantics while making the code's intent explicit. - Rename `returnIsAnyStruct` local in `EmitAbiMethodBodyIfSimple` to `returnIsBlittableStruct` so the variable name matches its meaning and aligns with `returnIsBlittableStruct` in the parallel DoAbi path. Result: 178 -> 186 byte-identical / 1553. Real CI-style compile of WinRT.Sdk.Projection still passes 0 errors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
M6 (using static ordering): `using static` directives must come AFTER all
normal `using` directives (sorted alphabetically). Fixed in
`Resources/Base/ReferenceInterfaceEntries.cs` so `using static
System.Runtime.InteropServices.ComWrappers;` is last.
R1 (ABI delegate Invoke wrapper indent): The `unboxedValue` local in
`ReferenceImplFactory.WriteReferenceImpl` (non-blittable struct branch
AND class/delegate branch) was dedented because the multiline raw string
boundary forced the typedef name to land at the writer's outer indent
instead of inside the try block. Refactored to pre-build the
projected/ABI type names as strings and use a single multiline raw string
with the names interpolated inline at the correct indent.
R2 (XAML CCW marshaller IID accessor under-indent): In
`AbiClassFactory`, the `[UnsafeAccessor]` IID accessor for generic
default interfaces was emitted via `EmitUnsafeAccessorForDefaultIfaceIfGeneric`
between two multiline raw strings, with the writer's indent set to the
outer namespace level rather than the class body level. Refactored to
use `WriteBlock()` so the accessor lands inside the class body at the
correct indent.
R3 (Vftbl `struct X{` brace packing): In
`AbiInterfaceFactory.WriteVftblStruct`, the struct header was emitted
via `Write` (no trailing newline) followed by `WriteBlock()`, so the
`{` from WriteBlock landed on the same line as the struct declaration.
Changed `Write` to `WriteLine` so the brace lands on its own line.
R5 (`GeneratedInterfaceIIDs.cs` lost member indent): Fields were emitted
without an `IncreaseIndent` context inside the static class body. Added
`IncreaseIndent`/`DecreaseIndent` calls around the emission loop in
`ProjectionGenerator.GeneratedIids` and `WriteInterfaceIidsEnd`.
R6 (extra blank line after namespace `{` opener): `WriteContract` had a
leading `writer.WriteLine();` that doubled up with the namespace block
opener's trailing newline. Removed the redundant leading WriteLine.
Result: 178 -> 244 byte-identical / 1553 (+66). Real CI-style compile of
WinRT.Sdk.Projection still passes 0 errors.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…XAML projection Bug: in commit bcafbf6 (Pass 21: IDE0078 pattern-matching simplification), an automated refactor incorrectly converted if (type != GridUnitType.Auto && type != GridUnitType.Pixel && type != GridUnitType.Star) to if (type is not (GridUnitType.Auto or GridUnitType.Pixe)l && type != GridUnitType.Star) The closing paren was inserted mid-token, splitting 'Pixel' as 'Pixe)l' and leaving the third comparison hanging. Compiler interpreted 'Pixe)l' as a positional pattern with property access on a 1-element deconstruction of GridUnitType (CS1061 / CS8129). Affected both `Microsoft.UI.Xaml.GridLength.cs` and `Windows.UI.Xaml.GridLength.cs` addition resources; both fixed to: if (type is not (GridUnitType.Auto or GridUnitType.Pixel) && type != GridUnitType.Star) Coverage gap: bcafbf6's commit message claimed `Validation: all 8 regen scenarios produce byte-identical output to baseline`, but the validation script only diffs PRE writer output against POST -- it doesn't compile either. Embedded resource files (`Resources/Additions/*.cs`) get copied verbatim into the projection output, so a typo in the resource file produces matching POST/PRE diffs (both have the typo if PRE was generated after the bad commit too) AND no compile-time check. Coverage fix: extend `validate-compile.ps1` to also build `WinRT.Sdk.Xaml.Projection` (which compiles `Windows.UI.Xaml.GridLength`, the addition file containing the same typo). Now we get end-to-end Roslyn compile coverage for both: - WinRT.Sdk.Projection (Windows.* core types) - WinRT.Sdk.Xaml.Projection (Windows.UI.Xaml.* types + addition files) This catches the GridUnitType typo (and would have caught it in CI before push if run locally). Both projections compile cleanly with this fix. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace the compound condition '(is not (GridUnitType.Auto or GridUnitType.Pixel) && type != GridUnitType.Star)' with a single pattern '(is not (GridUnitType.Auto or GridUnitType.Pixel or GridUnitType.Star))' in GridLength additions for both Microsoft.UI.Xaml and Windows.UI.Xaml to improve readability while preserving the validation logic.
…urce
The C# port of TypedefNameWriter dropped 3 conditions from the OLD C++
namespace-prefix logic at code_writers.h:363-369:
C++ original:
forceWriteNamespace ||
typeNamespace != _current_namespace ||
(Projected && (InAbiNamespace || InAbiImplNamespace)) ||
(ABI && !InAbiNamespace) ||
(EventSource && !InAbiNamespace) ||
(CCW && authoredType && !InAbiImplNamespace) ||
(CCW && !authoredType && (InAbiNamespace || InAbiImplNamespace))
C# (broken):
forceWriteNamespace ||
typeNamespace != context.CurrentNamespace ||
(Projected && (InAbiNamespace || InAbiImplNamespace)) ||
nameToWrite == TypedefNameType.ABI || // missing !InAbiNamespace
nameToWrite == TypedefNameType.EventSource || // missing !InAbiNamespace
(nameToWrite == TypedefNameType.CCW && authoredType) // missing !InAbiImplNamespace
// missing entire (CCW && !authoredType && ...) clause
The (CCW && authoredType && !InAbiImplNamespace) gap caused the writer
to emit interface DECLARATIONS like:
internal interface global::ABI.Impl.AuthoringTest.IBasicClassClass
instead of the unqualified:
internal interface IBasicClassClass
Compiler interprets `interface global::Foo.Bar` as a malformed
declaration: CS1514 `{ expected`, CS1513 `}` expected, CS1001
`Identifier expected`. Hit in the AuthoringTest scenario (component
mode where ALL types are emitted inside `namespace ABI.Impl.<ns>` so
`InAbiImplNamespace == true` for every typedef-name write).
Restored all 4 conditions verbatim from the C++ source. Coverage gap
addressed in a follow-up commit (validate-compile.ps1 will be extended
to do syntax-only validation across all scenarios so CS1xxx errors are
caught locally before push).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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
Second PR in the CsWinRT projection-writer migration series. Mostly automated cleanup of the C# port that landed in the previous PR: refactor passes, type-semantics consolidation, parallelization, error-discipline alignment, and a stack of CI-driven semantic + cosmetic fixes. The output of the writer now compiles cleanly against the real Windows SDK and matches the original C++ writer's behavior across all 8 regression scenarios.
Motivation
The previous PR landed the initial C++ -> C# port of
cswinrtasWinRT.Projection.Writer. That port was a faithful 1:1 transcription, but the resulting code carried a lot of structural debt from the C++ shape: long mega-functions, ad-hoc helper conventions inherited from C++ string-template emission, defensive null checks the C++ never had, and a few correctness regressions introduced during automated refactors.This PR is the cleanup pass: refactor toward C# idioms, share helpers across the writer/factories, parallelize the per-namespace work, and fix every semantic + cosmetic regression we surfaced via CI iteration. The goal is to leave the writer in a state where the only remaining work is hand-polish (next PR) and deleting the legacy C++ tree (final PR).
This is PR 2 of 4 in the migration series:
cswinrttree, the test runner, and other temporary scaffoldingChanges
Resolver and helper consolidation
Resolvers/AbiTypeShapeResolver.cs-- centralized ABI type classification (BlittablePrimitive, Enum, BlittableStruct, ComplexStruct, NullableT, GenericInstance, RuntimeClassOrInterface, Delegate, Object, MappedAbiValueType, etc.) so emission paths use a single source of truth instead of inlineIsXxxpredicates duplicated across factories. Two helper methods were added:IsBlittableStruct(blittable-only) andIsAnyStruct(blittable OR complex), and ~36 call sites were updated to use the right one.Helpers/AbiTypeHelpers.*.cs-- split the legacyAbiTypeHelpers.csinto focused partials (AbiTypeNames,Blittability, etc.).Metadata/TypeSemantics.cs-- moved theTypeSemanticsdiscriminated union out of helpers and trimmed the trailing-underscore record names.Generation pipeline
Generation/ProjectionGenerator.cs+Generation/WorkItems/-- parallelized projection generation across namespaces, IIDs, and the component-module aggregator viaParallel.ForEach. Cross-item shared state moved intoProjectionGeneratorRunStatewith concurrent collections.Generation/ProjectionGenerator.GeneratedIids.cs-- pulledGeneratedInterfaceIIDs.csemission into its own work item; restored properIncreaseIndent/DecreaseIndentaround the per-IID property emission.Generation/ProjectionGenerator.Generate.cs-- fixed an SDK-mode fallback that forgot to sethasTypesToProject = truewhen noMicrosoft.Windows.SDK.NETreference assembly was present (the generator silently produced no output).Writer infrastructure
Writers/IndentedTextWriter.cs-- removed all 4 auto-fixup heuristics (brace-prepend on{/}, auto-trailing-newline on multilines ending in;/}/]/#, brace-prepend inWriteLine(content), always-on idempotentWriteLine()). The writer now mirrors the source-generator design: callers are responsible for emitting the right newlines, withWriteBlock()providing the canonical brace+indent pattern. Idempotent newline behavior is opt-in viaWriteLine(skipIfPresent: true).Writers/IndentedTextWriterPool.cs-- new pool to reduce allocations across parallel work items. Acquired via the disposableIndentedTextWriterOwnerref struct.Helpers/TypedefNameWriter.cs-- restoredInAbiNamespace/InAbiImplNamespacetracking that was lost during the refactor; restored 4 missing conditions in the namespace-prefix logic verbatim from the C++ source (code_writers.h:363-369) so interface declarations no longer get fully qualified inside their own ABI namespace.Error discipline
Errors/WellKnownProjectionWriterExceptions.cs-- aligned exception emit with theWinRT.Interop.Generatorstyle: error IDs in the 5000+ range, well-known guards inProjectionWriter.Run, per-siteThrowOrAttachexception context, no[SuppressMessage]workarounds,Settingsmade truly read-only via a one-shotMakeReadOnly()pattern.Generation/ProjectionGenerator.Emit.cs+Generation/ProjectionGeneratorArgs.cs-- addedMaxDegreesOfParallelismplumbing through the CLI; better cancellation granularity.Code cleanups
Factories/-- 50+ refactor passes (commits prefixedR2,R3,F,N,Pass <N>) for consistent code style: single-lineifs expanded with blank-line padding, multi-lineWriteLine-> raw multiline strings, scratch-writer string-returning overloads, fully-qualified marshaller call sites, etc.Resources/Additions/Microsoft.UI.Xaml/Microsoft.UI.Xaml.GridLength.cs+Resources/Additions/Windows.UI.Xaml/Windows.UI.Xaml.GridLength.cs-- fixed aPixe)ltypo introduced by the IDE0078 automated refactor (type is not (GridUnitType.Auto or GridUnitType.Pixe)l && ...was supposed to betype is not (GridUnitType.Auto or GridUnitType.Pixel) && ...).Resources/Base/ReferenceInterfaceEntries.cs-- movedusing static System.Runtime.InteropServices.ComWrappers;after the normalusingblock per project convention.Semantic regressions fixed (surfaced by CI)
The following semantic bugs were introduced during the cleanup passes and caught only by iterating against CI. This is exactly why
validate-compile.ps1was added (see "Validation infrastructure" below).returnIsRefTypewas missingAbiTypeShapeKind.NullableT, so anyNullable<T>getter (e.g.Wallet.ExpirationDate) emittedint __retval+ a direct cast instead ofvoid* __retval+try/finally+Marshaller.UnboxToManaged.EnumType[]) returns --IsBlittablePrimitiveexcluded theEnumshape, soreturnIsReceiveArrayfailed for enum-element arrays.Span<EnumType>FillArray emittedCopyToManaged_Xreferencing nonexistentABI.Foo.EnumNametypes.returnIsAnyStructcovered bothBlittableStructANDComplexStructshapes, so the dispatch never reached thereturnIsComplexStructbranch. ComplexStruct returns (e.g.GpioChangeCount,GpioChangeRecord,AccessListEntry,StorePackageUpdateStatus) emitted the projected struct as__retvalinstead of the ABI struct, with noConvertToManagedwrap.ProfileUsageMarshallermissing methods --StructEnumMarshallerFactory.almostBlittablewas wrongly true for complex structs, soisComplexStructbecame false and the marshaller class skipped emittingConvertToUnmanaged/ConvertToManaged/Dispose.source: typeof(Foo)line split -- the IDICTypeMapAssociationemitter wrappedtypeof(with a trailing newline, splitting the typedef name across lines.WriteLinein the PassArray/Out/Ref/ReceiveArray loops appended a trailing newline so the next iteration's leading,landed at start-of-line and got an indent prepended.internal interface global::ABI.Impl.AuthoringTest.IBasicClassClass(CS1514/CS1513/CS1001).InAbiNamespace) -- the writer was emitting projected-namespace type names without theglobal::prefix when inside the corresponding ABI namespace, causing CS0246 / CS9051 (file-local type misuse) onLearningModelPixelRangeand similar generic interface types.WriteBlock()pattern instead of flat raw strings withoutIncreaseIndent.Cosmetic regressions fixed
Invokewrapper indent), R2 (XAML CCW marshaller IID accessor), R3 (struct X{brace packing in Vftbl), R5 (GeneratedInterfaceIIDs.csmember indent), R6 (extra blank line afternamespace X {) -- detailed in commitf6dc5485. All produce valid C# but were ugly. R4 (long-line wrap from intentional fully-qualified marshaller names) was accepted as the cost of the safety improvement.Validation infrastructure
The previous validation harness only diffed PRE writer output against POST. That missed several classes of bug:
Pixe)ltypo had matching PRE/POST diffs because the typo lived in the resource).Two additions:
MaxDegreesOfParallelismplumbing throughcswinrtprojectiongen.exeCLI -- so deterministic builds and reduced-parallelism debug runs work the same as the interop generator.WinRT.Sdk.Projection+WinRT.Sdk.Xaml.Projectionagainst the actual Windows 10.0.26100 SDK winmds, plus syntax-only validation across all 8 regression scenarios. Catches CS1xxx parse errors and CS9051 file-local-type errors. Verified by reverting each fix temporarily and confirming the script flags the original CI error.Validation
<summary>,[FlagsAttribute]->[Flags], blank-line consistency).WinRT.Sdk.ProjectionandWinRT.Sdk.Xaml.Projectioncompile cleanly against the real Windows SDK.WinRT.Projection.Writerbuilds with 0 warnings underTreatWarningsAsErrors.WinRT.Projection.Generatorpublishes to Native AOT with 0 warnings.Coming next
StringBuilder/string.Formatallocations.cswinrttree undersrc/cswinrt/, theWinRT.Projection.Writer.TestRunnerscaffolding, and any other temporary scaffolding that exists only to bridge the migration.