AOT optimizer emits unqualified generic type arguments in GlobalVtableLookup, so the entries never match Type.ToString()
Nobody has claimed this yet.
- Dominant language
- C#
- Stars
- 665
- Forks
- 134
- Avg merge
- 1d 3h
- Merged PRs (30d)
- 32
Description
Summary
The AOT optimizer's generated GlobalVtableLookup compares type.ToString() against string literals, but it emits those literals using a display name for generic type arguments rather than the metadata name. When the argument's namespace is imported with a using at the instantiation site, the emitted key is unqualified and can therefore never match — the lookup silently misses, the object gets no vtable entries, and the call fails at runtime.
Configuration
Microsoft.Windows.CsWinRT2.3.1 (generated header sayscswinrt.exe version 2.3.1.260716.1)- .NET 10,
UseUwp=true, NativeAOT,CsWinRTAotWarningLevel=3
What is generated
private static ComWrappers.ComInterfaceEntry[] LookupVtableEntries(Type type)
{
string typeName = type.ToString();
if (typeName == "..."
Type.ToString() for a constructed generic always yields fully-qualified arguments, e.g.
Telegram.Collections.SortedObservableCollection1[Telegram.Td.Api.ConnectedWebsite]`.
But the emitted literals are not consistently qualified. From one build of our app:
"Telegram.Collections.SortedObservableCollection`1[ChatMember]"
"Telegram.Collections.SortedObservableCollection`1[ConnectedWebsite]"
"Telegram.Collections.SortedObservableCollection`1[GroupCallMessage]"
"Telegram.Collections.SortedObservableCollection`1[Telegram.Td.Api.GroupCallMessage]"
"Telegram.Collections.SortedObservableCollection`1[Telegram.Td.Api.User]"
The GroupCallMessage pair is the whole bug in two lines: the same constructed type registered twice,
unqualified by the optimizer's own discovery and qualified by an explicit
[assembly: GeneratedWinRTExposedExternalType(typeof(...))]. Only the second one ever matches.
A single entry can even mix the two, which shows the string is a display name rather than a metadata name:
"System.Collections.Generic.Dictionary`2[System.Type,Animation]"
The instantiation site is new SortedObservableCollection<ConnectedWebsite>(...) in a file that has
using Telegram.Td.Api;. Writing the argument fully qualified at that site, or declaring the type via
GeneratedWinRTExposedExternalType, produces a matching entry.
Impact
The failure is silent at build time and only shows up when the object crosses the ABI — for us, assigning
such a collection to ItemsSource, which surfaces as E_INVALIDARG (and on a DispatcherQueue, as a
fail-fast rather than a catchable exception). Nothing in the build output indicates the entry is dead.
In our app 38 of 804 generic registrations are in this state; after discarding open generics and tuple
artefacts, 10 are real instantiations that are reachable at runtime.
Suggested fix
Emit the type-argument names the same way Type.ToString() renders them — the metadata name, unaffected
by using directives at the instantiation site — so that the generated comparison can match. Comparing on
something less brittle than a formatted string (a RuntimeTypeHandle, or Type equality) would remove the
class of bug entirely.
Detecting it in an existing app
Scanning the generated WinRTGlobalVtableLookup.g.cs for registrations that exist only with an
unqualified type argument — i.e. no fully-qualified sibling for the same constructed type — finds them
mechanically, and every hit is a real one.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the generated WinRTGlobalVtableLookup.g.cs and the GlobalVtableLookup generation path; compare generic-argument rendering for optimizer discovery versus GeneratedWinRTExposedExternalType registrations. Done means generated keys use the qualified form expected by Type.ToString() and affected constructed types resolve vtable entries at runtime; no regression test is named.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- build-system, desktop
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100