Skip to content

Commit 1f99012

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.
1 parent b6b7638 commit 1f99012

9 files changed

Lines changed: 3270 additions & 170 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/WinMD/Storage.swift

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,136 @@ public struct Storage: ~Escapable {
394394
}
395395
}
396396

397+
/// The CLR namespace the value type at 1-based `id` nests under — the
398+
/// namespace segments its fully-qualified spelling and its fabricated
399+
/// namespace `enum` containers share.
400+
///
401+
/// A nested type carries an empty `TypeNamespace`; the CLR namespace lives on
402+
/// its outermost encloser, so the walk climbs the enclosing chain to that
403+
/// outermost `TypeDef` and reads its namespace. A top-level type is its own
404+
/// outermost, so this reads its own namespace. The empty string is the global
405+
/// namespace, which fabricates no container.
406+
package func namespace(of id: Int) throws(WinMDError) -> String {
407+
let chain = try nesting(of: id)
408+
let outermost = chain.first?.id ?? id
409+
guard let tuple = try self.tuple(outermost - 1,
410+
of: Metadata.Tables.TypeDef.self),
411+
let space = tuple.ordinal(for: "TypeNamespace") else {
412+
return ""
413+
}
414+
return try tuple.string(space)
415+
}
416+
417+
/// The namespace-qualified render spelling of the named type `reference`
418+
/// names, or `nil` when it spells by its bare (or enclosing) name — the
419+
/// fallback a caller already applies for an unresolvable reference or a
420+
/// non-ambiguous type.
421+
///
422+
/// Qualification is collision-only: a value type spells its full CLR
423+
/// namespace, enclosing-`TypeDef` dot-path, and own name
424+
/// (`Windows.Win32.Foundation.Point`, `A.B.Outer.Inner`) — matching the
425+
/// fabricated namespace `enum` and real container nesting the emit builds —
426+
/// only when its simple `TypeName` is ambiguous, which the caller-supplied
427+
/// `qualifying` set names (see `collisions()`).
428+
///
429+
/// Qualification is applied to a value-type *identity*, not to a bare name:
430+
/// only a value type is ever wrapped in a namespace container, so a reference
431+
/// that shares the ambiguous name yet names an interface/delegate (a bare
432+
/// `protocol`), a runtime `class`, or an external type (the consumer supplies
433+
/// it bare) yields `nil` and keeps its bare name — else it would spell
434+
/// `NS.Point` for a type no `NS.Point` declaration is emitted for. So it gates
435+
/// on the reference's full `namespace.name`: `collisions()` records that
436+
/// identity only for an ambiguous *value* type, so a same-named non-value
437+
/// reference's identity is simply absent and the reference spells bare — with
438+
/// no per-reference resolution.
439+
///
440+
/// The bare-name membership test is the fast path, applied *before* forming the
441+
/// identity: a reference whose bare (or outermost) name is not ambiguous
442+
/// returns `nil` at once. The components join raw; a caller escapes each
443+
/// separately.
444+
package func spelling(of reference: TypeDefOrRef, qualifying: Set<String>)
445+
throws(WinMDError) -> String? {
446+
guard let resolved = try resolve(reference) else { return nil }
447+
let (space, raw) = try names(resolved)
448+
// The projected (arity-stripped) name, so a generic reference tests and
449+
// spells the one name the emission carries — matching the collision tally.
450+
let name = projected(raw)
451+
// A top-level reference is its own outermost encloser: its own CLR namespace
452+
// and name spell it, read straight off the resolved row — the fast path,
453+
// with no enclosing walk. (A Module-scoped `TypeRef` shares its target's
454+
// `(namespace, name)`, so the spelling agrees with the emit.) The bare name
455+
// fast-rejects; the `namespace.rawName` identity confirms a value type of
456+
// that *raw* name is ambiguous — keyed on the raw name, not the projected
457+
// one, so a same-namespace generic protocol whose raw name carries an arity
458+
// suffix does not borrow the value type's wrap; and `local` — following the
459+
// `ResolutionScope` — confirms *this* reference resolves to that local
460+
// definition, not an external `AssemblyRef`-scoped type of the same
461+
// identity (which the closure drops as nonlocal, and must not spell as the
462+
// wrapper).
463+
if !space.isEmpty {
464+
guard qualifying.contains(name),
465+
qualifying.contains(space + "." + raw),
466+
try local(reference) else { return nil }
467+
return space + "." + name
468+
}
469+
// A nested (or global) reference carries an empty own namespace. It is
470+
// already disambiguated by its enclosing dot-path, so qualify only when its
471+
// outermost encloser's name is ambiguous — climbing the reference's own
472+
// enclosing chain (cheap along a `TypeRef` scope chain). The outermost's
473+
// namespace prefixes the reference's enclosing dot-path, and its
474+
// `namespace.rawName` identity confirms the outermost is a value type —
475+
// keyed on the raw name, so a same-named generic type does not borrow it.
476+
let outer = try outermost(resolved)
477+
let (root, outerRaw) = try names(outer)
478+
let outerName = projected(outerRaw)
479+
let identity = root.isEmpty ? outerRaw : root + "." + outerRaw
480+
guard qualifying.contains(outerName), qualifying.contains(identity),
481+
try local(reference) else { return nil }
482+
let path = try qualified(resolved)
483+
return root.isEmpty ? path : root + "." + path
484+
}
485+
486+
/// Whether `reference` resolves to a definition in this module — a `TypeDef`,
487+
/// or a `TypeRef` whose `ResolutionScope` chain terminates at the `Module`,
488+
/// not a `ModuleRef`/`AssemblyRef` (an external assembly). A qualified
489+
/// spelling is a local ambiguous value type's namespace path, so an external
490+
/// reference sharing that `(namespace, name)` — which the closure's SQL drops
491+
/// as nonlocal — must not take it, or the signature would bind the unrelated
492+
/// local wrapper (or an undefined name) instead of the consumer-supplied
493+
/// external type. This follows the scope chain only, not the `toplevel`/
494+
/// `nested` definition scan, so it confirms locality in O(depth) without the
495+
/// per-reference table walk.
496+
private func local(_ reference: TypeDefOrRef) throws(WinMDError) -> Bool {
497+
guard let tuple = try resolve(reference) else { return false }
498+
switch tuple.table.number {
499+
case Metadata.Tables.TypeDef.number:
500+
return true
501+
case Metadata.Tables.TypeRef.number:
502+
return try local(reference: tuple)
503+
default:
504+
return false
505+
}
506+
}
507+
508+
/// Whether the `TypeRef` `tuple`'s `ResolutionScope` chain is local — a
509+
/// `TypeRef`-scoped (tag 3) nested reference whose enclosing reference is
510+
/// local, or a `Module`-scoped (tag 0) top-level reference. A `ModuleRef`/
511+
/// `AssemblyRef`, or a null scope, is external.
512+
private func local(reference tuple: borrowing Tuple)
513+
throws(WinMDError) -> Bool {
514+
guard let ordinal = tuple.ordinal(for: "ResolutionScope") else {
515+
return false
516+
}
517+
let scope = ResolutionScope(rawValue: tuple[ordinal])
518+
if scope.tag == 3, scope.row != 0 {
519+
guard let enclosing = try self.tuple(scope.row - 1,
520+
of: Metadata.Tables.TypeRef.self)
521+
else { return false }
522+
return try local(reference: enclosing)
523+
}
524+
return scope.tag == 0 && scope.row != 0
525+
}
526+
397527
/// The outermost encloser of the type `tuple` names — the top of its nesting
398528
/// chain, itself when top-level — reached by climbing `enclosing`. The result
399529
/// is opened off `self`, so its lifetime tracks the storage.
@@ -406,6 +536,135 @@ public struct Storage: ~Escapable {
406536
return try outermost(up)
407537
}
408538

539+
/// The namespace-qualification sets a collision-only render keys off, computed
540+
/// in one scan so neither the decode nor the emit repeats the work per
541+
/// reference.
542+
///
543+
/// The projection is one flat top-level Swift scope: an interface or delegate
544+
/// is a bare `protocol`, a runtime `class` and an external reference are bare
545+
/// frontiers the consumer supplies, and only a value type
546+
/// (`structure`/`enumeration`) can be wrapped — in a fabricated namespace
547+
/// `enum` — to disambiguate. A value type's simple `TypeName` is therefore
548+
/// ambiguous when two or more distinct top-level `TypeDef`s bear it *of any
549+
/// kind* — a second value type, an interface/delegate, or a runtime class —
550+
/// because spelled bare it would clash with that other top-level declaration.
551+
/// When it is, the value type — and any type nested under it — is spelled and
552+
/// emitted namespace-qualified, while the colliding protocol/class stays bare
553+
/// (a bare `protocol Point` and a wrapped `NS.Point` no longer clash).
554+
///
555+
/// Only top-level definitions are counted: a nested type is already
556+
/// disambiguated by its enclosing-type dot-path (`Foo.Bar`) and never occupies
557+
/// the top-level scope, so qualification keys off the outermost encloser's
558+
/// name, which the nested count would only pollute. This also keeps the
559+
/// ubiquitous anonymous nested record names — Win32 gives every one the same
560+
/// generated `_Anonymous_e__…`, yet each is unique under its distinct
561+
/// encloser — out of the tally, so a nested record stays bare
562+
/// (`VARIANT._Anonymous_e__Struct`) rather than drowning the projection in
563+
/// namespace paths.
564+
///
565+
/// The scan tallies every top-level `TypeDef` name and derives from a name's
566+
/// multiplicity:
567+
/// - `names`: the ambiguous top-level `TypeName`s, so the decode gates its
568+
/// namespace-qualification on an O(1) membership test of a reference's own
569+
/// (or outermost encloser's) name before it resolves the reference's kind;
570+
/// - `ids`: the ambiguous *value-type* `TypeDef` `Id`s alone — the only kind
571+
/// the emit wraps in a namespace `enum` — so a colliding protocol or class,
572+
/// which the emit leaves bare, is absent (a nested value type nests under
573+
/// its encloser regardless).
574+
///
575+
/// `reached`, when non-nil, restricts the tally to the `TypeDef` `Id`s in
576+
/// it — the declarations a `--closure` render actually emits — so a name is
577+
/// ambiguous only among the reached set. An unreachable definition never
578+
/// becomes a Swift declaration, so it cannot clash with one: leaving it out
579+
/// keeps a closure from wrapping (and possibly faulting) a value type on
580+
/// account of a same-named type the closure does not emit. A nil `reached`
581+
/// (the flat render, which emits the whole assembly) tallies every top-level
582+
/// `TypeDef`.
583+
///
584+
/// `contended` names top-level types a `--closure` render spells but does not
585+
/// emit — a frontier such as an external or runtime-class `B.Point` a
586+
/// signature references. The `reached` tally sees only the emitted
587+
/// definitions, so a local value type `A.Point` reached alongside such a
588+
/// frontier reads as
589+
/// the sole bearer of `Point` and would spell bare, capturing the frontier's
590+
/// reference. A frontier is a distinct top-level type bearing that name, so a
591+
/// reached value type whose projected name is `contended` is bumped to
592+
/// ambiguous — wrapped and qualified — even when it is the only *emitted*
593+
/// bearer. A `contended` name no reached value type bears wraps nothing
594+
/// (there is no emitted definition to disambiguate).
595+
package func collisions(among reached: Set<Int>? = nil,
596+
contended: Set<String> = [])
597+
throws(WinMDError) -> (names: Set<String>, ids: Set<Int>) {
598+
guard let table = opened(Metadata.Tables.TypeDef.number) else {
599+
return ([], [])
600+
}
601+
// The nested `TypeDef` Ids, read once from `NestedClass` (its ordinal-0
602+
// column is the nested type's Id), so a top-level test is an O(1) membership
603+
// check rather than a per-row `enclosing` scan of a relation that need not be
604+
// sorted — which would make this whole scan quadratic.
605+
var nested = Set<Int>()
606+
if let relation = opened(Metadata.Tables.NestedClass.number) {
607+
for row in 0 ..< Int(relation.rows) {
608+
nested.insert(Tuple(row, relation, self)[0])
609+
}
610+
}
611+
// Each top-level value `TypeDef` as its `Id`, CLR namespace, and simple
612+
// name, with a tally of how many distinct top-level types — of any kind —
613+
// bear each name.
614+
var values = Array<(id: Int, space: String, name: String, raw: String)>()
615+
var counts = Dictionary<String, Int>()
616+
for row in 0 ..< Int(table.rows) {
617+
let tuple = Tuple(row, table, self)
618+
guard !nested.contains(tuple.row + 1) else { continue }
619+
// A closure render tallies only the declarations it emits: an unreachable
620+
// top-level type is skipped, so it cannot make a reached name ambiguous.
621+
if let reached, !reached.contains(tuple.row + 1) { continue }
622+
let (space, raw) = try names(tuple)
623+
// Tally the projected (arity-stripped) name, not the raw `TypeName`, so a
624+
// generic `Foo` backtick `1` collides with a non-generic `Foo` exactly as
625+
// the two project to the one emitted `Foo`.
626+
let name = projected(raw)
627+
counts[name, default: 0] += 1
628+
if try kind(tuple).value {
629+
values.append((tuple.row + 1, space, name, raw))
630+
}
631+
}
632+
// A frontier the render spells but does not emit is a second distinct
633+
// top-level bearer of its name: bump a reached value type it collides with
634+
// to ambiguous, so the local definition wraps and the frontier's reference
635+
// no longer binds to it. A name no reached type bears is skipped — there is
636+
// no emitted value type to disambiguate.
637+
for name in contended where counts[name] != nil {
638+
counts[name]! += 1
639+
}
640+
// A name two or more top-level types bear is ambiguous: its value-type
641+
// bearers gate the decode spelling and the emit wrap, while a colliding
642+
// protocol or class stays bare. `names` carries two kinds of token, disjoint
643+
// by shape so one set threads to every seam: a bare ambiguous `TypeName` (no
644+
// dot) drives the decode's O(1) fast-reject and name lookups, and an
645+
// ambiguous value type's full `namespace.name` identity (dotted) gates the
646+
// value-aware qualification — a same-named protocol, class, or external
647+
// reference, whose identity is absent, spells bare with no per-reference
648+
// resolution.
649+
var names = Set<String>()
650+
var ids = Set<Int>()
651+
for value in values where (counts[value.name] ?? 0) >= 2 {
652+
names.insert(value.name)
653+
// The identity keys off the *raw* `TypeName`, not the projected one, so a
654+
// value type `A.Foo` and a same-namespace generic protocol `A.Foo` +
655+
// arity — which the projected name collapses to one identity — stay
656+
// distinct: only the value type's raw identity is here, so a reference to
657+
// the generic protocol (whose raw name carries the arity suffix) does not
658+
// match and stays bare, while the value type qualifies. The counting
659+
// above still uses the projected name, so the two collide and the value
660+
// type wraps.
661+
names.insert(value.space.isEmpty ? value.raw
662+
: value.space + "." + value.raw)
663+
ids.insert(value.id)
664+
}
665+
return (names, ids)
666+
}
667+
409668
/// The local `TypeDef` the named type `reference` resolves to, or `nil` when
410669
/// it names no local definition.
411670
///

0 commit comments

Comments
 (0)