Skip to content

Port Keys/Values dictionary codegen fix to the C# projection writer#2435

Open
Sergio0694 wants to merge 2 commits into
staging/CodeWritersfrom
user/sergiopedri/port-keys-values-codegen
Open

Port Keys/Values dictionary codegen fix to the C# projection writer#2435
Sergio0694 wants to merge 2 commits into
staging/CodeWritersfrom
user/sergiopedri/port-keys-values-codegen

Conversation

@Sergio0694

Copy link
Copy Markdown
Member

Summary

Ports the projection code-generation fix from merged PR #2427 (Fix missing Keys/Values on projected dictionary types) from the C++ generator (src/cswinrt/code_writers.h) to the C# projection writer. Only the projection writer is changed here; the interop-generator and unit-test parts of #2427 arrive when this branch is merged back into staging/3.0.

Motivation

PR #2427 fixed the projected Keys/Values members on dictionary types (IMap<K,V> -> IDictionary<K,V> and IMapView<K,V> -> IReadOnlyDictionary<K,V>). Since the C# projection writer is a port of the C++ cswinrt code writers, it needs the same fix so it emits identical stubs. Without it, the C# writer would emit Keys/Values accessors that bind to the wrong interop contract and re-allocate a fresh collection wrapper on every access.

Changes

  • src/WinRT.Projection.Writer/Factories/MappedInterfaceStubFactory.cs: ported the fix to both dictionary stub emitters:
    • Keys/Values [UnsafeAccessor] declarations now take the projected runtime class (WindowsRuntimeObject windowsRuntimeObject, passed as this) instead of the interface object reference (WindowsRuntimeObjectReference objRef), matching the interop helper signatures introduced by Fix missing Keys/Values on projected dictionary types #2427.
    • For the read-only path, the Keys/Values accessor return type is corrected from ICollection<T> to IEnumerable<T> to match the actual IReadOnlyDictionaryMethods.Keys/Values contract (the public property was already IEnumerable<T>, so the accessor return type was a latent mismatch).
    • The public Keys/Values properties now cache the returned collection via the C# 14 field keyword (=> field ??= ...(null, this)), preserving reference identity across repeated accesses.
    • EmitUnsafeAccessor gains an optional receiver parameter (defaulting to WindowsRuntimeObjectReference objRef) so the dictionary Keys/Values accessors can opt into the WindowsRuntimeObject receiver while every other accessor is unchanged.

Validation

  • Build is clean (0 warnings) for the projection writer and the reference projection generator.
  • Generated the full Windows.winmd reference projection (347 files): both IDictionaryMethods and IReadOnlyDictionaryMethods stubs match the expected output, and the new WindowsRuntimeObject receiver appears only on dictionary Keys/Values (all other collection accessors keep the WindowsRuntimeObjectReference receiver).
  • The run is deterministic (347/347 files, 0 diffs across two runs).

Sergio0694 and others added 2 commits June 12, 2026 10:51
Ports the 'cswinrt/code_writers.h' fix from PR #2427 to the C# projection
writer, for the 'IMapView<K,V>' -> 'IReadOnlyDictionary<K,V>' stub path.

Three changes to the emitted 'Keys'/'Values' members:

* The '[UnsafeAccessor]' for 'Keys'/'Values' now returns 'IEnumerable<T>'
  (was 'ICollection<T>'), matching the actual signature of the interop
  helper 'IReadOnlyDictionaryMethods.Keys/Values' (the public property was
  already 'IEnumerable<T>', so the accessor return type was a latent
  mismatch that happened to bind to the wrong contract).
* The accessor receiver is now 'WindowsRuntimeObject windowsRuntimeObject'
  (the projected runtime class itself, passed as 'this') instead of the
  interface 'WindowsRuntimeObjectReference objRef'.
* The public 'Keys'/'Values' properties cache the returned collection via
  the C# 14 'field' keyword ('=> field ??= ...(null, this)'), so repeated
  accesses preserve reference identity instead of allocating a fresh
  wrapper each time.

To support a non-default receiver, 'EmitUnsafeAccessor' gains an optional
'receiver' parameter (defaulting to 'WindowsRuntimeObjectReference objRef');
'Keys'/'Values' are emitted via direct calls (with the 'WindowsRuntimeObject'
receiver) ahead of the remaining accessors, which continue to flow through
the table-based 'EmitUnsafeAccessors'.

The interop-generator and unit-test parts of #2427 are intentionally not
included here; they arrive when this branch merges back into staging/3.0.

Verified by generating the full 'Windows.winmd' reference projection and
inspecting the emitted 'IReadOnlyDictionaryMethods' stubs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Ports the 'cswinrt/code_writers.h' fix from PR #2427 to the C# projection
writer, for the 'IMap<K,V>' -> 'IDictionary<K,V>' stub path.

Mirrors the 'IReadOnlyDictionary' change in the previous commit (the
return type stays 'ICollection<T>' here, matching the interop helper
'IDictionaryMethods.Keys/Values'):

* The '[UnsafeAccessor]' for 'Keys'/'Values' now takes the projected
  runtime class ('WindowsRuntimeObject windowsRuntimeObject', passed as
  'this') instead of the interface 'WindowsRuntimeObjectReference objRef'.
* The public 'Keys'/'Values' properties cache the returned collection via
  the C# 14 'field' keyword ('=> field ??= ...(null, this)'), preserving
  reference identity across accesses.

'Keys'/'Values' are emitted via direct 'EmitUnsafeAccessor' calls (with the
'WindowsRuntimeObject' receiver) ahead of the remaining accessors, which
continue to flow through the table-based 'EmitUnsafeAccessors'.

Verified by generating the full 'Windows.winmd' reference projection: the
emitted 'IDictionaryMethods' stubs match the expected output, the run is
deterministic (347/347 files, 0 diffs across two runs), and the
'WindowsRuntimeObject' receiver appears only on dictionary 'Keys'/'Values'
(all other collection accessors keep the 'WindowsRuntimeObjectReference'
receiver).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
string enumerableObjRefName = "_objRef_System_Collections_Generic_IEnumerable_" + IidExpressionGenerator.EscapeTypeNameForIdentifier(kvLong, stripGlobal: false) + "_";

writer.WriteLine();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this branch have the tests you added in the other PR?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not yet, I need to merge staging/3.0 into this staging still.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants