Add property-union tests for the completion popup, a broad value in the
union, and value/stringification collisions. Rename the existing
property-union tests to a `property union:` prefix so they can be
selected with `--test-name-pattern`, and extend the autocomplete helper
with a `key` option so a probe can complete on a property other than
`kind`.
The tagged-union matcher selected handler parameters with
`Extract<T, Record<K, V>>`, which only keeps a member whose
discriminant is a singleton literal. A property that is itself a
union (`{ color: "red" | "green" | "blue" }`) or optional
(`{ type?: "x" }`) matched no member, so every handler received
`never`, and the fallback saw the whole shape instead of the
unhandled tags.
Replace the selector with `Narrowed`, which distributes over `T`,
drops a member whose `K` cannot take the tag, and narrows `K`. The
`T[K] extends V` fast path keeps an exact member (a discriminated
union's declared interface) untouched. The fallback reuses
`Narrowed` over `Exclude<Tags, HandledTags>`, so a union-valued
property narrows to the unhandled tags rather than the whole shape.
Matching a defined tag on an optional property makes the key
required (`{ type: "x" }`); the `undefined` tag yields
`{ type?: never }` under `exactOptionalPropertyTypes` (absence) or
`{ type?: undefined }` when the property admits an explicit
`undefined`.
An open universe (`string`, `number`, a template literal) makes the
handler map index-like, so a partial object satisfied the exhaustive
overload and the runtime `dispatch` throw was reachable. A universe
containing a member and its stringification (`"true" | true`, `1 | "1"`)
collapsed two members onto one key.
- reject broad universes and value/stringification collisions at the
factory, with a diagnostic naming the reason
- invert `PatternKey` against the universe (`Member<T, K>`) instead of
`PatternParam<K>`, so a standalone `"true"` is typed `"true"`
- intersect `UniverseGate<T>` into the handler and fallback parameters,
preserving `R` inference and the popup
A `boolean` / `null` / `undefined` discriminant cannot key a handler
map, so the tagged-union matcher now routes it through the
`PatternKey` / `PatternParam` projection the primitive-union matcher
already used. Both matchers share the projection from
`matcher-shared.ts`; `MapTaggedUnion`, `Handlers`, `HandledMembers`
and `MustBePartial` key off the projected form and invert it to
recover the real member.
Covers exhaustive and fallback dispatch, the redundant-fallback
guard, and the LSP popup offering the tags by name.