Skip to content

Commit 5e22166

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`. 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 on a label it bears, 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), and an unreachable same-named type leaving a reached one bare.
1 parent b02c27a commit 5e22166

6 files changed

Lines changed: 3144 additions & 403 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
@@ -365,6 +365,136 @@ public struct Storage: ~Escapable {
365365
}
366366
}
367367

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

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

0 commit comments

Comments
 (0)