Skip to content

Add test coverage for marshalling dictionary Keys/Values collections - #2539

Open
Sergio Pedri (Sergio0694) wants to merge 1 commit into
staging/3.0from
user/sergiopedri/dictionary-keys-values-marshalling-test
Open

Add test coverage for marshalling dictionary Keys/Values collections#2539
Sergio Pedri (Sergio0694) wants to merge 1 commit into
staging/3.0from
user/sergiopedri/dictionary-keys-values-marshalling-test

Conversation

@Sergio0694

Copy link
Copy Markdown
Member

Summary

Adds a unit test covering the scenario from #2537: marshalling a dictionary's Keys/Values collections across the Windows Runtime ABI. Investigation confirmed the interop generator already handles this correctly in CsWinRT 3.0, so this PR adds no generator changes — only the missing test coverage that pins the behavior down.

Motivation

Issue #2537 reports a Native AOT crash under CsWinRT 2.x when binding a XAML control to someDictionary.Values (e.g. {x:Bind vm.Dict.Values}). The interesting part of that scenario is that the object crossing the ABI is not the dictionary at all: it is the nested Dictionary<TKey, TValue>.KeyCollection / ValueCollection type, which needs a CCW exposing IIterable<T> and IBindableIterable.

Those types are never named in user code, and cannot be: Keys/Values are declared as IEnumerable<T> on IReadOnlyDictionary<TKey, TValue> and as ICollection<T> on IDictionary<TKey, TValue>, so the concrete nested types appear nowhere in the consuming assembly's metadata. They only get marshalling code if the interop generator derives them from the dictionary instantiation itself.

It does. ModuleDefinitionExtensions.EnumerateTypeSignatures does not just read the TypeSpec table: for each type specification it resolves the type definition and instantiates its members' signatures with that generic context. A Dictionary<int, string> type spec therefore yields the constructed return types of get_Keys/get_Values, and both nested collections flow through the normal user-defined-type discovery path and get CCW entries.

This was verified empirically rather than by reading alone: adding a Dictionary<char, char> instantiation over an otherwise unused pair of type arguments and rebuilding made the matching KeyCollection/ValueCollection CCW entries appear in the generated WinRT.Interop.dll, and removing it made them disappear again. That also rules out the new test passing vacuously against entries some other assembly happened to contribute.

The behavior was untested, though, which is what this PR fixes. It is a scenario that is easy to regress precisely because nothing in user code references the types involved.

Changes

  • src/Tests/UnitTest/TestComponentCSharp_Tests.cs: adds TestDictionaryKeyAndValueCollectionInterfaceMarshalling, modelled on the existing TestCollectionChangedListInterfaceMarshalling. It identifies the two nested collection types by name rather than naming them in code (naming them would put them in the test assembly's metadata, which is exactly what the scenario cannot rely on, making the coverage vacuous), asserts the CCW answers QueryInterface for both IIterable<T> and IBindableIterable, and then enumerates the keys from native code through GetIteratorForCollection and through BindableIterableProperty, which is the path XAML uses to bind an ItemsSource.

Validation

The full unit test suite passes (559/560). The single failure, TestDataTransferManager, is pre-existing and environment-dependent, in a file untouched by this PR.

Binding a XAML control to 'someDictionary.Values' (issue #2537) marshals the
'Dictionary<TKey, TValue>.KeyCollection'/'ValueCollection' nested types across
the ABI, not the dictionary itself, so each needs a CCW exposing 'IIterable<T>'
and 'IBindableIterable'. Neither type is ever named in user code: 'Keys'/'Values'
are declared as 'IEnumerable<T>' on 'IReadOnlyDictionary<TKey, TValue>' and as
'ICollection<T>' on 'IDictionary<TKey, TValue>'.

The interop generator already handles this, off the dictionary instantiation
itself: for every type specification it sees, it resolves the definition and
instantiates the signatures of its members with that generic context, so
'Dictionary<int, string>' yields the constructed nested collection types
returned by 'get_Keys'/'get_Values'. Verified by adding a dictionary
instantiation over an otherwise unused pair of type arguments and observing the
matching CCW entries appear in the generated 'WinRT.Interop.dll'.

That was untested, though, so this pins it down: the test identifies the two
types by name rather than naming them in code (which would put them in the test
assembly's metadata and make the coverage vacuous), checks the CCW answers
'QueryInterface' for both interfaces, and then enumerates the keys from native
code through both 'IIterable<int>' and the bindable iterable that XAML uses for
an 'ItemsSource'.

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

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

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

Labels

CsWinRT 3.0 testing Related to testing infrastructure

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant