Skip to content

Commit f93536b

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; the frontier reservation the shadow check and contention need is the following slice. The projection is one flat top-level Swift scope, so a name is ambiguous when two or more of the emitted top-level declarations bear it, of any kind. Only a value type can be wrapped, so an ambiguous value type is emitted fully namespace-qualified under fabricated namespace `enum` containers (`A.Point`/`B.Point`) while the same-named `protocol` stays bare. A closure integration test pins this cross-kind case end to end — a reached struct `A.Point` beside an interface, then a delegate, `B.Point` — so the value type wraps under `enum A` while the emitted non-value declaration stays a bare top-level `Point`, no redeclaration. Ambiguity is counted over the projected (arity-stripped) name 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 and confirms the reference resolves locally. One `Storage.spelling(resolved:qualifying:)` rule drives the decode spelling, the emit nesting, and — through a `spelling(of id:)` overload — the inheritance clause, so a nested base under a wrapped encloser reads `A.Outer.IChild` exactly as a signature naming it decodes, and the three seams cannot drift. Nesting folds only a type's *primary* declaration into its container: a custom template may frame the type with a file-scope `import` header or an `extension` footer, which Swift permits only at file scope, so `Surface.partition` — locating the primary declaration off the syntax tree — splits them off the rendered body and they bubble up, through every enclosing container, to the roots. This is symmetric across a real `.type` container and a fabricated `.space` namespace: `nest` renders a node as a (header, declaration, footer) triple, folds each member's declaration in, and accumulates its members' hoisted headers and footers to pass further up. A hoisted auxiliary lands in the root file scope, so the per-scope collision check attributes it there rather than its container — and every nested type's auxiliaries hoist, however deep it sits, so two descendants of different wrapped containers each hoisting a same-named `struct Helper` are a file-scope redeclaration a per-container check would miss. Emittedness — which reached types the closure actually declares — is read from the rendered body off the syntax tree (`Surface.declarations`): the top-level `struct`/`class`/`enum`/`protocol`/`actor` and `typealias` names, so per-type boilerplate, a documented-but-omitted ABI helper, a value declaration of the type's name, a nested member, or a declaration inside a comment does not count. Every *other* top-level name a body declares — not just the primary and the specially recognised generic `<name>ABI` helper, but any auxiliary a custom template emits (a `struct A` beside a `protocol IRoot`) — is recorded as a scope occupant, so the per-scope collision check faults a fabricated namespace `enum A` or another declaration that redeclares it. The collision tally and the emit both key off this emitted set, computed once after a reachability prune, so a body is spelled with the same set that folds it into the tree. The emitted set is re-pruned against the *bodied* declarations before it drives the tally: the first prune propagates a node's references on its metadata kind, but a custom template may omit an intermediate declaration (render nothing for a reached `struct Point`), so a type reached only through that omitted node — a `Widget` a `Point` field names — is an orphan the emitted source never references, dropped rather than left to render unattached and fault an unrelated collision. A by-value type nested beneath a generic encloser — named directly or through a `TypeRef`, its kind read off the resolved definition — is dropped rather than misrendered as an ABI-changing opaque pointer; but only a genuinely *by-value* occurrence poisons its owner — a reference solely behind a pointer, byref, or array embeds no layout and renders as an opaque pointer, so it drops nothing — and its referent is left unrecorded, so the decode spells that opaque pointer rather than an uncompilable `UnsafeMutablePointer<Outer.Inner>` naming a type the closure never emits. The collision fixtures assemble their metadata through `WinMDFixture`, an in-target `#~` assembler that interns the heaps and computes the table ranges, with a fluent `WinMDBuilder` facade that assigns rids, links member lists, encodes signatures, and resolves references — so a scenario reads in a dozen declarative lines instead of a hundred of hand-encoded bytes. This slice also reserves the frontier names a signature spells but the closure does not emit, so a reached value type sharing a name with one contends — wrapping and spelling namespace-qualified rather than bare and capturing the frontier's reference. The frontier set is derived once from the emitted-set complement: 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 (its outermost encloser's, so a nested `A.Inner` turns on its root `A`), and a reference is a frontier exactly when that Id is absent from the emitted set. `Storage.collisions(contended:)` bumps a reached value type whose projected name a frontier bears to ambiguous — so an emitted signature naming both a local `A.Point` and an external `Point` wraps `A.Point` rather than leaving both positions bound to the generated bare `Point`. Whether a fabricated namespace would then *shadow* one of those bare references is the following slice; this slice reserves and contends. The reservation reads the metadata resolution, not the rendered text. The decode records every signature reference of every emitted owner in `spelled` — its owner, the local `Id` it resolves to (or nil for an external), and its category — so a reference is a frontier exactly when that referent is unemitted. The template spells names; it does not choose which references the surface bears, so the closure keys renderedness off that graph rather than re-scanning the rendered body for each label. A reached value type still contends with a frontier of its projected name, and a literal identifier a template writes that no signature references is absent from `spelled` and reserves nothing. The synthesized fallback root (`IUnknown`) is not a frontier only when a *top-level* local interface of that name is emitted: the bare `protocol IRoot: IUnknown` resolves at file scope, so a metadata-nested `Outer.IUnknown` does not satisfy the exemption, and with several local `IUnknown`s the reached one is chosen. The exemption is keyed on a fallback flag, not the spelling, so an explicit external `IUnknown` parameter or base stays a frontier. A `known`-bridged reference reserves the frontier under its bridge leaf name, keyed on the identity's leaf component as the decode spells it: a nested `Outer.Inner` bridged `wellknown Inner …` reserves `InnerBridge`, not the enclosing root `Outer`. Keying the bridge on the qualified `Outer.Inner` missed it and reserved `Outer`, so a reached `A.InnerBridge` of the bridge name stayed uncontended and captured the frontier bare rather than wrapping under `enum A`.
1 parent 6cbd5ba commit f93536b

12 files changed

Lines changed: 3586 additions & 258 deletions

File tree

Sources/SQLEngineWinMD/Database+SQL.swift

Lines changed: 50 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)
@@ -1160,6 +1168,40 @@ extension WinMD.Storage {
11601168
return resolver.identities
11611169
}
11621170

1171+
/// The type identities the `MethodDef` at 1-based `method` `Id` names in its
1172+
/// *return* position — the return-spelled subset of `identities(method:)`, so
1173+
/// the closure walk categorizes a method's references as returned or
1174+
/// parameter. A reference named in both positions is in both subsets. An
1175+
/// absent or undecodable row yields the empty set.
1176+
package borrowing func returned(method: Int) -> Set<Referent> {
1177+
guard let table = opened("MethodDef") else { return [] }
1178+
let cursor = WinMD.Cursor(copy self, table)
1179+
guard let tuple = cursor[method - 1],
1180+
let row = Row<Metadata.Tables.MethodDef>(tuple),
1181+
let signature = try? row.prototype,
1182+
let resolver = try? Resolver(of: signature, with: self) else {
1183+
return []
1184+
}
1185+
return resolver.returned
1186+
}
1187+
1188+
/// The type identities the `MethodDef` at 1-based `method` `Id` names in its
1189+
/// *parameter* positions — the parameter-spelled subset of
1190+
/// `identities(method:)`; a reference named in both a return and a parameter
1191+
/// is in `returned(method:)` too. An absent or undecodable row yields the
1192+
/// empty set.
1193+
package borrowing func parameters(method: Int) -> Set<Referent> {
1194+
guard let table = opened("MethodDef") else { return [] }
1195+
let cursor = WinMD.Cursor(copy self, table)
1196+
guard let tuple = cursor[method - 1],
1197+
let row = Row<Metadata.Tables.MethodDef>(tuple),
1198+
let signature = try? row.prototype,
1199+
let resolver = try? Resolver(of: signature, with: self) else {
1200+
return []
1201+
}
1202+
return resolver.parameters
1203+
}
1204+
11631205
/// The distinct type identities named in the signature of the `FieldDef` at
11641206
/// 1-based `field` `Id` — the referenced-type set a struct's field contributes
11651207
/// to the closure walk (edge E6).

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)