Skip to content

Commit 4ca5e73

Browse files
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 rendered body, not assumed from the metadata arity — so a `-I` template that omits the helper 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), and a `-I` template that omits the ABI helper leaving its generic type uncontended. 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. Six 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 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. Emittedness is read from whether the selected kind section actually declared the type (its body differs from the template's type-free skeleton), so a template carrying only shared boilerplate outside its sections declares nothing and its type stays an unemitted frontier.
1 parent b6b7638 commit 4ca5e73

13 files changed

Lines changed: 3453 additions & 209 deletions

File tree

Sources/SQLEngineWinMD/Database+SQL.swift

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,10 @@ public import WinMD
5656
extension WinMD.Storage: SQLEngine.Catalog {
5757
/// The optional tables the bundled queries reference — tables ECMA-335 lets a
5858
/// database omit from the tables stream (`TypeSpec` §II.22.39 when nothing is
59-
/// generic-instantiated, `NestedClass` §II.22.32 when nothing nests) that a
60-
/// bundled view or the closure walk still names. `table(named:)` resolves an
59+
/// generic-instantiated, `NestedClass` §II.22.32 when nothing nests,
60+
/// `ClassLayout` §II.22.8 when no type declares an explicit layout or packing)
61+
/// that a bundled view or the closure walk still names. `table(named:)`
62+
/// resolves an
6163
/// absent one to an empty relation and `relations()` enumerates it, so a
6264
/// `SELECT … FROM` such a table reads no rows rather than faulting on a
6365
/// missing relation. Only these are synthesised — not the whole table
@@ -1058,13 +1060,15 @@ extension WinMD.Storage {
10581060
/// undecodable signature, or an unresolvable one.
10591061
package borrowing func decode(return method: Int,
10601062
generics: Array<String>? = nil,
1061-
in dialect: Dialect) -> String? {
1063+
in dialect: Dialect,
1064+
qualifying: Set<String> = []) -> String? {
10621065
guard let table = opened("MethodDef") else { return nil }
10631066
let cursor = WinMD.Cursor(copy self, table)
10641067
guard let tuple = cursor[method - 1],
10651068
let row = Row<Metadata.Tables.MethodDef>(tuple),
10661069
let signature = try? row.prototype,
1067-
let resolver = try? Resolver(of: signature, with: self) else {
1070+
let resolver = try? Resolver(of: signature, with: self,
1071+
qualifying: qualifying) else {
10681072
return nil
10691073
}
10701074
return signature.returns.decode(generics: generics, with: resolver,
@@ -1087,7 +1091,8 @@ extension WinMD.Storage {
10871091
/// it, so threading it is always safe.
10881092
package borrowing func decode(parameter: Int,
10891093
generics: Array<String>? = nil,
1090-
for dialect: Dialect) -> String? {
1094+
for dialect: Dialect,
1095+
qualifying: Set<String> = []) -> String? {
10911096
guard let table = opened("Param") else { return nil }
10921097
let params = WinMD.Cursor(copy self, table)
10931098
guard let param = params[parameter - 1],
@@ -1107,7 +1112,8 @@ extension WinMD.Storage {
11071112
guard position >= 1, position <= signature.parameters.count else {
11081113
return nil
11091114
}
1110-
guard let resolver = try? Resolver(of: signature, with: self) else {
1115+
guard let resolver = try? Resolver(of: signature, with: self,
1116+
qualifying: qualifying) else {
11111117
return nil
11121118
}
11131119
let name = param.ordinal(for: "Name").flatMap { try? param.string($0) }
@@ -1126,13 +1132,15 @@ extension WinMD.Storage {
11261132
/// type through it. `nil` mirrors the same undecodable contract — an absent
11271133
/// row, an undecodable signature, or an unresolvable one — so the walk can
11281134
/// treat a malformed field as a frontier.
1129-
package borrowing func decode(field: Int, in dialect: Dialect) -> String? {
1135+
package borrowing func decode(field: Int, in dialect: Dialect,
1136+
qualifying: Set<String> = []) -> String? {
11301137
guard let table = opened("FieldDef") else { return nil }
11311138
let cursor = WinMD.Cursor(copy self, table)
11321139
guard let tuple = cursor[field - 1],
11331140
let row = Row<Metadata.Tables.FieldDef>(tuple),
11341141
let signature = try? row.declaration,
1135-
let resolver = try? Resolver(of: signature, with: self) else {
1142+
let resolver = try? Resolver(of: signature, with: self,
1143+
qualifying: qualifying) else {
11361144
return nil
11371145
}
11381146
return signature.type.decode(with: resolver, dialect: dialect)

Sources/SQLEngineWinMD/Resources/Queries/bases.sql

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
CREATE VIEW bases AS
2+
-- Each plain base carries the InterfaceImpl provenance the render resolves it
3+
-- by: `ref` is the base's `Interface_TypeRef` Id (a reference the render follows
4+
-- through the scope chain), `def` its `Interface_TypeDef` Id (a local definition
5+
-- named directly). Exactly one is non-NULL per plain arm. The render keys the
6+
-- inheritance spelling off the resolved *identity* rather than correlating the
7+
-- name against a separate query — an external base that merely shares a bare
8+
-- `TypeName` with a local nested interface must not adopt the local one's Id.
29
SELECT
310
b.TypeName AS base,
11+
b.Id AS ref,
12+
NULL AS def,
413
NULL AS spec
514
FROM
615
InterfaceImpl i
@@ -10,6 +19,8 @@ WHERE
1019
UNION
1120
SELECT
1221
d.TypeName AS base,
22+
NULL AS ref,
23+
d.Id AS def,
1324
NULL AS spec
1425
FROM
1526
InterfaceImpl i
@@ -18,19 +29,15 @@ WHERE
1829
i.Class = :parent
1930
UNION
2031
-- A generic base interface is named through a TypeSpec: the InterfaceImpl's
21-
-- Interface coded index tags TypeSpec, which has no TypeName of its own. A
22-
-- generic instantiation (GENERICINST <base> <args…>) resolves to its generic
23-
-- base, whose Id the adapter decodes to the TypeSpec's Base_TypeRef or
24-
-- Base_TypeDef key -- the 1-based Id of the base in its table. So the name
25-
-- reads through a seekable join against the TypeRef and TypeDef base tables,
26-
-- mirroring the `identities` view's two TypeSpec arms without re-evaluating a
27-
-- four-way UNION per InterfaceImpl row. Without these arms the generic base is
28-
-- dropped. The TypeSpec's own Id rides alongside as `spec` (NULL on a plain
29-
-- base): the generic definition's TypeName carries an invalid arity suffix and
30-
-- drops the type arguments, so the render decodes the complete constructed
31-
-- spelling from the TypeSpec signature rather than emitting that TypeName.
32+
-- Interface coded index tags TypeSpec, which has no TypeName of its own. The
33+
-- name reads through a seekable join against the TypeRef and TypeDef base
34+
-- tables, mirroring the `identities` view's two TypeSpec arms. The TypeSpec's
35+
-- own Id rides alongside as `spec` (NULL on a plain base); the render omits a
36+
-- generic base (`spec IS NULL`), so its `ref`/`def` are NULL.
3237
SELECT
3338
r.TypeName AS base,
39+
NULL AS ref,
40+
NULL AS def,
3441
i.Interface_TypeSpec AS spec
3542
FROM
3643
InterfaceImpl i
@@ -41,6 +48,8 @@ WHERE
4148
UNION
4249
SELECT
4350
d.TypeName AS base,
51+
NULL AS ref,
52+
NULL AS def,
4453
i.Interface_TypeSpec AS spec
4554
FROM
4655
InterfaceImpl i

0 commit comments

Comments
 (0)