Skip to content

Generate CCW vtables for value types implementing WinRT interfaces - #2514

Open
Sergio Pedri (Sergio0694) wants to merge 1 commit into
masterfrom
user/sergiopedri/aot-vtables-for-value-types
Open

Generate CCW vtables for value types implementing WinRT interfaces#2514
Sergio Pedri (Sergio0694) wants to merge 1 commit into
masterfrom
user/sergiopedri/aot-vtables-for-value-types

Conversation

@Sergio0694

Copy link
Copy Markdown
Member

Fixes #2507

Problem

Binding an ImmutableArray<T> from XAML under Native AOT fails with:

WinRT information: Argument 'source' is not a supported vector.

The AOT source generator only ever considered TypeKind.Class when deciding which types need a CCW vtable. ImmutableArray<T> is a struct, so when it was boxed (e.g. assigned to ItemsSource, which is object) the CCW built for it had none of the vtable entries for the WinRT interfaces it actually implements (IReadOnlyList<T>, IList<T>, IList, IEnumerable). XAML's QueryInterface for IBindableVector / IBindableIterable then failed.

This was not specific to ImmutableArray<T> — it affected any value type implementing WinRT interfaces, including user-defined ones. Records and record structs were also never handled, since RecordDeclarationSyntax is a sibling of ClassDeclarationSyntax, not a subtype.

Fix

Value types now go through the exact same two paths classes already use.

Attribute path (types in the assembly being compiled)

  • NeedVtableAttribute now matches any TypeDeclarationSyntax (class, struct, record, record struct) rather than only ClassDeclarationSyntax. ref struct-s are excluded since they can never be boxed, and interfaces/enums/static/abstract types remain excluded.
  • The generated partial declaration previously hardcoded partial class. It now emits the keyword matching the original declaration (struct, record, record struct), which is required for the partial definitions to match. Nested types already used the correct keyword; VtableAttribute now carries TypeKind/IsRecord so top-level types do too.
  • WinRTExposedTypeAttribute already allowed structs; WinRTRuntimeClassNameAttribute and GeneratedWinRTExposedTypeAttribute now do as well.

Lookup table path (types we can't annotate)

  • Kept for exactly the cases that need it: value types from other assemblies (e.g. ImmutableArray<T>) and generic value types where each instantiation needs its own vtable.
  • Projected/custom mapped value types (Windows.Foundation.Point, KeyValuePair<TKey, TValue>, Nullable<T>) are explicitly excluded, since the projection marshals those itself.

Analyzer + code fix

  • CsWinRT1028 ("type is not marked partial") and the "Make type partial" code fix now cover structs, records and record structs, so users get the same guidance for value types they already get for classes. ref struct-s never warn.
  • Diagnostic messages reworded from "Class" to "Type".

Runtime

  • ComWrappersSupport.GetInterfaceTableEntries now honours user-provided IMarshal / ICustomPropertyProvider entries for value types too, not just classes.

Testing

  • AotOptimizerTests (16 tests): ImmutableArray<T> boxed via ImmutableArray.Create, a collection expression, and a builder + ToImmutable() all land on the lookup table with the bindable (IBindableVector/IBindableIterable) entries and enumerator adapters; struct/record/record struct/nested types get [WinRTExposedType] with the right keyword; generic value types go on the lookup table; ref struct, non-boxed, no-WinRT-interface and projected value types produce nothing. RunAotOptimizer now also asserts the generated sources compile, which is what catches a mismatched partial keyword (verified: reverting the keyword fix makes those tests fail with CS0261).
  • DiagnosticAnalyzerTests (6 new): CsWinRT1028 fires for non-partial struct/record/record struct, and does not for partial types, ref struct, or types with no WinRT interfaces.
  • WinRTAotCodeFixerTests (new file, 5 tests): the code fix produces a correct partial declaration for class, struct, record, record struct and nested types.
  • FunctionalTests/Collections (Native AOT end-to-end): round-trips a boxed ImmutableArray<string> through BindableIterableProperty, and exercises the native IIterable<IInspectable> QI + enumeration path (what XAML does) for arrays built via Create, a collection expression, and a builder, plus a same-assembly partial struct and partial record struct.

All 91 source generator tests pass.

The AOT source generator only ever considered classes when deciding which
types need a CCW vtable, so value types implementing WinRT interfaces (eg.
'ImmutableArray<T>', which implements 'IReadOnlyList<T>', 'IList<T>' and
'IList') got no vtable entries when boxed. Binding such a collection from
XAML then failed with "Argument 'source' is not a supported vector".

Value types are now handled the same way classes are:

  - The '[WinRTExposedType]' attribute generator runs for structs, records
    and record structs ('ref struct'-s are skipped, as they can never be
    boxed). The generated partial declaration now uses the keyword of the
    original declaration instead of always using 'class'.
  - CsWinRT1028 and its "Make type partial" code fix now cover those same
    type kinds, so the "mark this partial" guidance is reported for them.
  - The CCW vtable lookup table covers value types from other assemblies
    (which we can't annotate), such as 'ImmutableArray<T>', as well as
    generic value types whose instantiations each need their own vtable.
    Projected/custom mapped value types keep being marshalled by the
    projection itself.

Fixes #2507

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.

@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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Can't bind to ImmutableArray when using Native AOT

1 participant