Commit 561a427
committed
winmd-inspect: disambiguate colliding names with fabricated namespaces
Disambiguate the names two reached declarations would otherwise share, so every
signature spelling resolves to the declaration it names and the generated source
compiles. Builds on the nesting slice, adding the collision tally, the
fabricated namespace containers, and the frontier logic.
The projection is one flat top-level Swift scope, so a name is ambiguous when
two or more of the top-level declarations the closure emits bear it *of any
kind* — a second value type, an interface or delegate, or a runtime class.
Because only a value type can be wrapped, an ambiguous value type is spelled and
emitted fully namespace-qualified under fabricated namespace `enum` containers
(`A.Point`/`B.Point`), while the same-named `protocol` stays bare. Ambiguity is
counted over the *projected* (arity-stripped) name the emission carries and only
over the reached declarations, so an unreachable same-named type never forces a
wrap; the qualification keys off the value type's raw `namespace.name` identity
(so a same-named generic protocol does not borrow it) and confirms the reference
resolves locally (so an external same-named type spells bare). The decode
spelling, the emit nesting, and the collision tally derive from one source on
`Storage`, so they cannot drift.
The per-scope validation reconciles every declaration scope — the top-level
roots and each container's direct children — so members bear distinct labels: a
residual clash faults rather than emitting uncompilable output. Two same-named
top-level protocols fault `ambiguous`; a fabricated namespace container
shadowing an emitted type, or a generic interface/delegate's synthesized
`internal protocol <name>ABI` colliding with a co-emitted type, or a fabricated
container shadowing a signature-referenced frontier type the closure spells but
does not emit, faults `collision`. The `<name>ABI` occupant is counted only when
the *selected* template actually declares it — read off the *code* the body
renders, not assumed from the metadata arity — so a `-I` template that omits the
helper, or one that names it only in a comment or a string literal, does not
fault a spurious collision against a real same-named type. A frontier — a
name the closure spells but
does not emit — is derived once from the emitted-set complement rather than
enumerated per reason: the walk records every spelled reference (a
signature-named type or an interface's named base) as its emitted top-level
spelling paired with the `TypeDef` `Id` it resolves to, and a reference is a
frontier exactly when that `Id` (its outermost encloser's, so a nested `A.Inner`
turns on its root `A`, not on `Inner`) is absent from the emitted set. One rule
then covers every way a name goes unemitted — a runtime class, a GUID-less
shape, a `known`-bridged or layout-rejected value type, a metadata-nested
protocol, a value type under a non-container, and an external reference
(including an
external base the `bases` view names though the local-only `requires` walk does
not). The one frontier set serves both seams: the fabricated-namespace shadow
check faults a fabricated `enum` whose name a frontier reference visible in its
scope bears, keyed per reference to its owner — a root segment is a top-level
`enum` visible as a bare name from any scope, so it shadows a frontier of its
name wherever owned; an inner segment (the `B` of `enum A.enum B` wrapping
`A.B.Point`) is visible only from within its own subtree, so it shadows only a
reference owned by a type whose fabricated enclosing path includes it, where a
bare `B` binds outward to the enclosing `A.B`, not the external type — and
the reached-only ambiguity tally treats it
as a distinct top-level bearer — a reached `A.Point` beside a frontier `B.Point`
(external, layout-rejected, `known`-bridged to the same target, or an inherited
base) wraps and qualifies to `A.Point` rather than spelling a bare `Point` that
captures the frontier's reference. Deriving from the complement, not a name
subtraction, keeps a top-level frontier `A` reserved even when an unrelated
nested `Foo.A` is emitted, and reading the `known` bridge name as the spelling
lets a value type of that target name contend. A post-walk reachability prune
keeps only the declarations reachable from the seeds through emittable nodes,
dropping the orphans a discarded frontier would otherwise leave behind, and the
emit renders each kept body once the reached-only qualification sets are fixed,
so a body is spelled with the same set that folds it into the tree.
Integration tests cover two same-named value types each wrapped in its namespace
`enum`, a value type colliding with a same-named protocol, arity and
same-namespace-arity collisions, a generic type's ABI name colliding with a
metadata type, a fabricated container shadowing an emitted type (at the root and
a level down) and a frontier type (top-level, nested — whose enclosing root it
shadows —, an external interface base, and an external base a fabricated
`enum A` must not un-frontier merely because an unrelated nested `Foo.A` is
emitted), a reached value type wrapping against a frontier of the same name (an
external
referent, a layout-rejected value type, an external base, and a `known` bridge
target each), an unreachable same-named type leaving a reached one bare, an
inner fabricated segment shadowing a bare frontier reference owned by a type in
its subtree (faulting where a root-only check would let uncompilable source
through), a `-I` template that omits the ABI helper leaving its generic type
uncontended, a by-value type named through a `TypeRef` chain under a generic
encloser dropping its declaration (the kind read off the resolved definition,
not the misclassifying reference row), a frontier reference colliding with a
generic type's synthesized ABI helper faulting, a boilerplate comment naming a
reached struct not marking it emitted (so two same-named value types stay bare
rather than fabricating an undeclared `A.Point`), an ABI helper named only in a
comment not counting as an occupant against a real co-emitted one, and a `let
{{name}} = 0` value-declaration boilerplate — a variable of the type's name, not
a type — likewise not marking the struct emitted.
The collision fixtures assemble their metadata through `WinMDFixture`, an
in-target `#~` assembler that interns the string and blob heaps and computes the
table ranges, so a fixture declares its rows rather than hand-maintaining the
byte offsets every edit would otherwise re-derive, and a fluent `WinMDBuilder`
facade over it lets a fixture state its interfaces, value types, and members and
assigns the rids, links the member lists, encodes the signatures, and resolves
the references — so a scenario reads in a dozen declarative lines instead of a
hundred of hand-encoded bytes. Several collision fixtures are stated through it.
Eleven review findings across the identity/visibility seam are resolved here. A
by-value type nested beneath a generic encloser has no valid unqualified
spelling, so rather than render it as an opaque pointer — which would silently
change a by-value struct's ABI layout — the whole containing declaration is
dropped (a by-reference type stays the harmless opaque pointer). The
value/reference kind is read off the referent's resolved local definition, not
the reference row: a `TypeRef` carries neither `Flags` nor `Extends`, so reading
its kind off the raw row would misclassify every reference as a class and drop a
value type named through a `TypeRef` as an opaque pointer, corrupting the
struct's ABI — so a `TypeRef`-named nested value type is dropped alongside a
directly-named one. The inheritance
edge is resolved by identity, not by name: the `bases` view carries the
InterfaceImpl provenance and the render query resolves the local Id, so an
external base sharing a `TypeName` with a local nested interface spells bare
rather than adopting the local one. A nested base reserves the root of its
qualified spelling (`Outer` of `Outer.IChild`) as its frontier label. The
fabricated-namespace shadow check is rewritten to mirror Swift's own name
lookup — a bare frontier reference resolves outward through its owner's
enclosing scopes to the top level, so a top-level protocol, the fabricated
segment above it, or a sibling container visible through a shared ancestor all
capture it and fault — one rule subsuming the top-level, inner, and sibling
cases. A scope's occupants include the synthesized `<name>ABI` helpers the
template emits beside their generic types, not the emitted node labels alone, so
a bare frontier reference resolving to a helper is shadowed just as one
resolving to a member declaration is. The helper's own occupancy, and a node's
emittedness, are both read from the *code* a body declares — every identifier
following a type-declaration keyword (`<keyword> <Name>`), with comments and
string literals skipped. The keyword set spans the projection languages the
templates target — Swift's `struct`/`class`/`enum`/`protocol`/`actor`/
`typealias` and the C-family `interface` a custom template may spell — so it is
template-agnostic yet restricted to *type* declarations: a `let {{name}} = 0` or
`func {{name}}()` boilerplate line, a value or function of the type's name, does
not count it. Diffing the whole rendered text against a type-free skeleton (the
earlier emittedness test) mistook per-type boilerplate outside the kind sections
— a `// generated for {{name}}` comment — for a declaration, so a phantom struct
entered the emitted set and fabricated an `enum A` qualifying an undeclared
`A.Point`; a raw substring scan for the ABI helper matched `protocol FooABI`
inside a comment or a generated string, faulting a spurious collision against a
real co-emitted `FooABI`; and crediting any identifier after any other let a
`let {{name}}` boilerplate readmit the phantom struct. Reading the declared type
names off the code alone settles all three.1 parent b6b7638 commit 561a427
13 files changed
Lines changed: 3779 additions & 235 deletions
File tree
- Sources
- SQLEngineWinMD
- Resources/Queries
- WinMDSynthesis
- WinMD
- winmd-inspect
- Resources/Render
- Tests/winmd-inspectTests
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
56 | 56 | | |
57 | 57 | | |
58 | 58 | | |
59 | | - | |
60 | | - | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
61 | 63 | | |
62 | 64 | | |
63 | 65 | | |
| |||
1058 | 1060 | | |
1059 | 1061 | | |
1060 | 1062 | | |
1061 | | - | |
| 1063 | + | |
| 1064 | + | |
1062 | 1065 | | |
1063 | 1066 | | |
1064 | 1067 | | |
1065 | 1068 | | |
1066 | 1069 | | |
1067 | | - | |
| 1070 | + | |
| 1071 | + | |
1068 | 1072 | | |
1069 | 1073 | | |
1070 | 1074 | | |
| |||
1087 | 1091 | | |
1088 | 1092 | | |
1089 | 1093 | | |
1090 | | - | |
| 1094 | + | |
| 1095 | + | |
1091 | 1096 | | |
1092 | 1097 | | |
1093 | 1098 | | |
| |||
1107 | 1112 | | |
1108 | 1113 | | |
1109 | 1114 | | |
1110 | | - | |
| 1115 | + | |
| 1116 | + | |
1111 | 1117 | | |
1112 | 1118 | | |
1113 | 1119 | | |
| |||
1126 | 1132 | | |
1127 | 1133 | | |
1128 | 1134 | | |
1129 | | - | |
| 1135 | + | |
| 1136 | + | |
1130 | 1137 | | |
1131 | 1138 | | |
1132 | 1139 | | |
1133 | 1140 | | |
1134 | 1141 | | |
1135 | | - | |
| 1142 | + | |
| 1143 | + | |
1136 | 1144 | | |
1137 | 1145 | | |
1138 | 1146 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
2 | 9 | | |
3 | 10 | | |
| 11 | + | |
| 12 | + | |
4 | 13 | | |
5 | 14 | | |
6 | 15 | | |
| |||
10 | 19 | | |
11 | 20 | | |
12 | 21 | | |
| 22 | + | |
| 23 | + | |
13 | 24 | | |
14 | 25 | | |
15 | 26 | | |
| |||
18 | 29 | | |
19 | 30 | | |
20 | 31 | | |
21 | | - | |
22 | | - | |
23 | | - | |
24 | | - | |
25 | | - | |
26 | | - | |
27 | | - | |
28 | | - | |
29 | | - | |
30 | | - | |
31 | | - | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
32 | 37 | | |
33 | 38 | | |
| 39 | + | |
| 40 | + | |
34 | 41 | | |
35 | 42 | | |
36 | 43 | | |
| |||
41 | 48 | | |
42 | 49 | | |
43 | 50 | | |
| 51 | + | |
| 52 | + | |
44 | 53 | | |
45 | 54 | | |
46 | 55 | | |
| |||
0 commit comments