Skip to content

Commit b6b7638

Browse files
committed
winmd-inspect: nest metadata-nested value types into Swift nesting
Fold the flat closure emission into real Swift nesting, so a metadata-nested value type emits as a nested type under its container chain and every signature that names it resolves to that declaration. Disambiguating the names two reached declarations would otherwise share is the next slice. A metadata-nested value type emits as a real nested Swift type under its enclosing-`TypeDef` container chain (`Outer.Inner`), and every signature that names it spells the same dot-path, so the declaration and its uses agree and two same-named nested types under different enclosers stay distinct. The walk emits the enclosing value type too — even when only `Outer.Inner` is named and nothing references `Outer` directly — so the container `Inner` nests inside actually exists; a value type nests only when every enclosing level is an emitted value-type container (`struct`/`enum`), else it is a dropped frontier the consumer defines (an enclosing `protocol` cannot nest a value type, an excluded runtime `class` is not emitted). An interface or delegate is a Swift `protocol`, which cannot nest, so a metadata-nested one is a dropped frontier and only a top-level one is a legal root. The flat emission folds into a containment forest whose roots render in the walk's post-order, so a dependency precedes the type naming it and a top-level type keeps its position. A custom struct/enum template may open with a header comment and append a brace-delimited footer around its main declaration; the child block is spliced before the *main declaration's* closing brace — matched by a brace-depth count over the body's *code* (braces inside a `//` or `/* */` comment or a single- or triple-quoted string literal are skipped, the block-comment and multiline-string states held across lines), from the first code `{` — not the last brace-only line, so the child nests inside the container rather than above the declaration (a header comment's or a multiline literal's braces) or into the footer (the last brace). A nested type's enclosing dot-path strips the CLR arity suffix from every component, not only the leaf, and the strip lives at the single escape seam, before each component is keyword-escaped, so a storage spelling and a directly-resolved one escape identically and a generic instantiation's base (`` `protocol`<…> ``) composes without a second strip that would cut the escape backtick. But a *generic* encloser has no valid unqualified spelling — a member of `Outer`1` is `Outer<T>.Inner`, needing the enclosing specialization the projection does not yet emit, which the arity-stripped `Outer.Inner` misbinds if the consumer supplies the generic `Outer` — so a reference into a type nested under a generic encloser is dropped as an unsupported frontier rather than spelled uncompilable, resolved (like a `TypeSpec` or a null index) to nothing. An interface whose base is a metadata-nested interface names that base through the same enclosing dot-path (`Outer.IChild`) its own nested declaration and a signature naming it both wear, so the refinement resolves rather than reading the bare `TypeName`, which binds no visible declaration. The selected base is resolved to its local definition through the same `requires` scope-chain walk the closure uses, so a base named directly through an `Interface_TypeDef` and one named through an `Interface_TypeRef` — whose nested references the walk follows to the local nested definition — alike carry the enclosing path; the `.render *` batch resolves a `TypeRef` base by the same recursion. Each path component is keyword-escaped separately. A top-level or external base resolves to no enclosing path and spells bare, as does a base a `-I` override names that matches no local definition. Integration tests cover two same-named nested value types each nested under its encloser, an enclosing value type reached only through a nested member, a child nested inside its container despite a header comment's braces, a trailing footer, and a multiline string literal's braces, a reference into a type nested under a generic encloser dropped as an unsupported frontier, a generic instantiation whose already-stripped keyword base keeps its escape, and a top-level interface whose base is a nested interface — named through an `Interface_TypeDef`, and (resolved by the scope-chain walk) through an `Interface_TypeRef` — spelled through its enclosing path.
1 parent d77699e commit b6b7638

8 files changed

Lines changed: 3350 additions & 197 deletions

File tree

Sources/WinMD/Storage.swift

Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,4 +224,319 @@ public struct Storage: ~Escapable {
224224
}
225225
return nil
226226
}
227+
228+
// MARK: - Nesting
229+
230+
/// The lexically enclosing type of the type `tuple` names — its immediate
231+
/// encloser — or `nil` when the type is top-level.
232+
///
233+
/// A `TypeRef` nests through its `ResolutionScope`: a scope that is itself a
234+
/// `TypeRef` is the enclosing reference, and any other scope (a `Module`, a
235+
/// `ModuleRef`, or an `AssemblyRef`) makes the reference top-level. A
236+
/// `TypeDef` nests through the `NestedClass` table (§II.22.32) — the
237+
/// `EnclosingClass` of the row whose `NestedClass` names this definition,
238+
/// found by binary search when the table is stored sorted on its key and by a
239+
/// linear scan otherwise. Any other table has no enclosing and yields `nil`.
240+
/// It is `package` so the synthesis decode and the render adapter compose one
241+
/// nested type's dot-path from the same walk.
242+
@_lifetime(copy self)
243+
package func enclosing(_ tuple: borrowing Tuple)
244+
throws(WinMDError) -> Tuple? {
245+
// The enclosing tuple is opened off `self` (not off the borrowed `tuple`)
246+
// so its lifetime tracks the storage rather than the shorter-lived
247+
// argument, which is what a recursive walk returns through.
248+
switch tuple.table.number {
249+
case Metadata.Tables.TypeRef.number:
250+
guard let scope = tuple.ordinal(for: "ResolutionScope") else {
251+
return nil
252+
}
253+
// Tag 3 of `ResolutionScope` selects `TypeRef`: a scope that is itself a
254+
// `TypeRef` is the enclosing reference; a `Module`/`ModuleRef`/
255+
// `AssemblyRef` scope makes the reference top-level.
256+
let coded = ResolutionScope(rawValue: tuple[scope])
257+
guard coded.tag == 3, coded.row != 0 else { return nil }
258+
return try self.tuple(coded.row - 1,
259+
of: Metadata.Tables.TypeRef.self)
260+
case Metadata.Tables.TypeDef.number:
261+
guard let table = opened(Metadata.Tables.NestedClass.number) else {
262+
return nil
263+
}
264+
// The `NestedClass` (ordinal 0) column holds the 1-based `TypeDef` Id of
265+
// the nested type; `EnclosingClass` (ordinal 1) its encloser.
266+
let child = tuple.row + 1
267+
let count = Int(table.rows)
268+
if sorted & (1 << Metadata.Tables.NestedClass.number) != 0 {
269+
let lower = bound(table, 0, child, count, strict: false)
270+
guard lower < count, Tuple(lower, table, self)[0] == child else {
271+
return nil
272+
}
273+
return try self.tuple(Tuple(lower, table, self)[1] - 1,
274+
of: Metadata.Tables.TypeDef.self)
275+
}
276+
for index in 0 ..< count where Tuple(index, table, self)[0] == child {
277+
return try self.tuple(Tuple(index, table, self)[1] - 1,
278+
of: Metadata.Tables.TypeDef.self)
279+
}
280+
return nil
281+
default:
282+
return nil
283+
}
284+
}
285+
286+
/// The dot-path name of the type `tuple` names, from its outermost encloser —
287+
/// `Foo.Bar` for a `Bar` nested under `Foo`, `Foo.Bar.Baz` for a deeper
288+
/// nesting — or the bare `TypeName` for a top-level type.
289+
///
290+
/// The components are joined raw (unescaped); a caller that spells the path
291+
/// as target source escapes each component separately, so a keyword component
292+
/// is delimited within the path rather than treated as one identifier.
293+
package func qualified(_ tuple: borrowing Tuple)
294+
throws(WinMDError) -> String {
295+
// Each component is a declaration name spelled arity-stripped: a generic
296+
// encloser `Outer``1` nests as `Outer` and the generic decode strips the
297+
// leaf, so strip every component of the enclosing path here — a signature
298+
// spelling `Outer.Inner`, not the unresolved `Outer``1.Inner`. The suffix
299+
// marks only a generic definition, which is never a `known` bridge or
300+
// `System.Guid` (both non-generic), so the identity this feeds still
301+
// matches those lookups, and a top-level component is already projected at
302+
// the seam that qualifies it.
303+
let name = try projected(bare(tuple))
304+
guard let outer = try enclosing(tuple) else { return name }
305+
return try qualified(outer) + "." + name
306+
}
307+
308+
/// Whether any *enclosing* type of `tuple` is generic — its raw `TypeName`
309+
/// carries the CLR arity suffix (a backtick). Such a nesting has no valid
310+
/// unqualified spelling: a member of `Outer``1` is `Outer<T>.Inner`, which
311+
/// needs the enclosing specialization, while both `qualified`'s arity-stripped
312+
/// `Outer.Inner` and appending the generic arguments to the leaf misname it.
313+
/// Projecting the WinRT generic-nesting specialization is a deferred redesign,
314+
/// so a reference into such a type is an unsupported frontier the caller drops
315+
/// rather than spelling. Only an *encloser*'s arity matters: the leaf's own
316+
/// generic arity is stripped and supplied by the decode's own clause.
317+
package func enclosedByGeneric(_ tuple: borrowing Tuple)
318+
throws(WinMDError) -> Bool {
319+
guard let outer = try enclosing(tuple) else { return false }
320+
if try bare(outer).contains("`") { return true }
321+
return try enclosedByGeneric(outer)
322+
}
323+
324+
/// The dot-path name of the `TypeDef` at 1-based `id` — the `qualified`
325+
/// spelling addressed by `Id` rather than by an already-fetched `Tuple`, for
326+
/// a caller (the render's inheritance clause) that holds only the resolved
327+
/// base `Id`. A missing row spells empty, the same absent-name the caller's
328+
/// bare fallback would.
329+
package func qualified(of id: Int) throws(WinMDError) -> String {
330+
guard let tuple = try self.tuple(id - 1, of: Metadata.Tables.TypeDef.self)
331+
else {
332+
return ""
333+
}
334+
return try qualified(tuple)
335+
}
336+
337+
/// The enclosing `TypeDef` chain of the `TypeDef` at 1-based `id`, outermost
338+
/// first — each an `(id, name)` pair — for the render to group a nested type
339+
/// under a container per level. An empty array for a top-level type.
340+
package func nesting(of id: Int)
341+
throws(WinMDError) -> Array<(id: Int, name: String)> {
342+
guard let tuple = try self.tuple(id - 1, of: Metadata.Tables.TypeDef.self),
343+
let outer = try enclosing(tuple) else {
344+
return []
345+
}
346+
return try nesting(of: outer.row + 1) + [(outer.row + 1, bare(outer))]
347+
}
348+
349+
// MARK: - Kind and qualification
350+
351+
/// The projection kind of a named type — how the render spells and nests it.
352+
///
353+
/// The partition is the one the SQL `types` view draws: an `interface` (the
354+
/// `tdInterface` flag), a `delegate`/`structure`/`enumeration` (told apart by
355+
/// the base its `Extends` names — `System.MulticastDelegate`/`System.ValueType`
356+
/// /`System.Enum`), or a runtime `class` (anything else). Only a `structure`
357+
/// or `enumeration` is a value type, spelled fully namespace-qualified and
358+
/// nested; the rest spell by their bare name.
359+
package enum Kind: Sendable, Equatable {
360+
case interface
361+
case delegate
362+
case structure
363+
case enumeration
364+
case `class`
365+
366+
/// Whether the kind is a value type — a `structure` or an `enumeration` —
367+
/// the render namespace-qualifies and nests, as opposed to a `protocol`
368+
/// (`interface`/`delegate`) or a runtime `class` it spells bare.
369+
package var value: Bool {
370+
self == .structure || self == .enumeration
371+
}
372+
}
373+
374+
/// The projection kind of the `TypeDef` the `tuple` names, classified exactly
375+
/// as the SQL `types` view does so the decode spelling and the emit nesting
376+
/// agree from one source.
377+
///
378+
/// The `tdInterface` flag (`0x20`) marks an interface regardless of its base;
379+
/// otherwise the base type the `Extends` coded index names classifies the row
380+
/// — `System.Enum` an enumeration, `System.MulticastDelegate` a delegate,
381+
/// `System.ValueType` a structure, anything else (or no base) a runtime class.
382+
package func kind(_ tuple: borrowing Tuple) throws(WinMDError) -> Kind {
383+
if let flags = tuple.ordinal(for: "Flags"), tuple[flags] & 0x20 == 0x20 {
384+
return .interface
385+
}
386+
guard let extends = tuple.ordinal(for: "Extends") else { return .class }
387+
let base = TypeDefOrRef(rawValue: tuple[extends])
388+
guard let parent = try resolve(base) else { return .class }
389+
switch try names(parent) {
390+
case ("System", "Enum"): return .enumeration
391+
case ("System", "MulticastDelegate"): return .delegate
392+
case ("System", "ValueType"): return .structure
393+
default: return .class
394+
}
395+
}
396+
397+
/// The outermost encloser of the type `tuple` names — the top of its nesting
398+
/// chain, itself when top-level — reached by climbing `enclosing`. The result
399+
/// is opened off `self`, so its lifetime tracks the storage.
400+
@_lifetime(copy self)
401+
private func outermost(_ tuple: borrowing Tuple)
402+
throws(WinMDError) -> Tuple {
403+
guard let up = try enclosing(tuple) else {
404+
return Tuple(tuple.row, tuple.table, self)
405+
}
406+
return try outermost(up)
407+
}
408+
409+
/// The local `TypeDef` the named type `reference` resolves to, or `nil` when
410+
/// it names no local definition.
411+
///
412+
/// A `TypeDef` reference already names a local definition. A `TypeRef` resolves
413+
/// through its `ResolutionScope` chain — a module-scoped reference to the
414+
/// non-nested `TypeDef` of the same (namespace, name), a `TypeRef`-scoped
415+
/// (nested) reference to the nested `TypeDef` under the local definition its
416+
/// enclosing reference resolves to — exactly the walk the render's `references`
417+
/// CTE performs. A reference whose chain terminates at a `ModuleRef` or
418+
/// `AssemblyRef` (an external assembly) resolves to nothing; a `TypeSpec`
419+
/// names no definition.
420+
@_lifetime(copy self)
421+
package func definition(of reference: TypeDefOrRef)
422+
throws(WinMDError) -> Tuple? {
423+
guard let tuple = try resolve(reference) else { return nil }
424+
switch tuple.table.number {
425+
case Metadata.Tables.TypeDef.number:
426+
return tuple
427+
case Metadata.Tables.TypeRef.number:
428+
return try definition(reference: tuple)
429+
default:
430+
return nil
431+
}
432+
}
433+
434+
/// The local `TypeDef` the `TypeRef` `tuple` resolves to through its
435+
/// `ResolutionScope` chain, or `nil` when the reference is external.
436+
@_lifetime(copy self)
437+
private func definition(reference tuple: borrowing Tuple)
438+
throws(WinMDError) -> Tuple? {
439+
guard let ordinal = tuple.ordinal(for: "ResolutionScope") else {
440+
return nil
441+
}
442+
let scope = ResolutionScope(rawValue: tuple[ordinal])
443+
let target = try names(tuple)
444+
// A `TypeRef`-scoped (tag 3) reference is nested: resolve its enclosing
445+
// reference to a local `TypeDef`, then match the nested `TypeDef` directly
446+
// under it by `TypeName` — a nested type's namespace is empty, so the match
447+
// is by name under the encloser, never by namespace.
448+
if scope.tag == 3, scope.row != 0 {
449+
guard let enclosing = try self.tuple(scope.row - 1,
450+
of: Metadata.Tables.TypeRef.self),
451+
let encloser = try definition(reference: enclosing) else {
452+
return nil
453+
}
454+
return try nested(target.name, in: encloser.row + 1)
455+
}
456+
// A `Module`-scoped (tag 0) reference is local and top-level; any other
457+
// scope — a `ModuleRef`/`AssemblyRef`, or a null scope — is external.
458+
guard scope.tag == 0, scope.row != 0 else { return nil }
459+
return try toplevel(target.namespace, target.name)
460+
}
461+
462+
/// The non-nested local `TypeDef` named (`namespace`, `name`), or `nil` — the
463+
/// anchor a module-scoped reference resolves to.
464+
@_lifetime(copy self)
465+
private func toplevel(_ namespace: String, _ name: String)
466+
throws(WinMDError) -> Tuple? {
467+
guard let table = opened(Metadata.Tables.TypeDef.number) else { return nil }
468+
for row in 0 ..< Int(table.rows) {
469+
let tuple = Tuple(row, table, self)
470+
let (space, simple) = try names(tuple)
471+
guard space == namespace, simple == name else { continue }
472+
// A nested type shares a bare (namespace, name) with a top-level one only
473+
// by coincidence, and the empty namespace collapses every nested type's
474+
// pair — so the module-scoped reference names the non-nested definition.
475+
switch try enclosing(tuple) {
476+
case .none: return tuple
477+
case .some: continue
478+
}
479+
}
480+
return nil
481+
}
482+
483+
/// The local nested `TypeDef` named `name` directly under the `TypeDef` at
484+
/// 1-based `encloser` `Id`, or `nil` — the nested reference's resolution step.
485+
@_lifetime(copy self)
486+
private func nested(_ name: String, in encloser: Int)
487+
throws(WinMDError) -> Tuple? {
488+
guard let table = opened(Metadata.Tables.NestedClass.number) else {
489+
return nil
490+
}
491+
// The `NestedClass` column (ordinal 0) is the nested `TypeDef` Id, the
492+
// `EnclosingClass` column (ordinal 1) its encloser.
493+
for index in 0 ..< Int(table.rows) {
494+
let link = Tuple(index, table, self)
495+
guard link[1] == encloser else { continue }
496+
guard let child = try self.tuple(link[0] - 1,
497+
of: Metadata.Tables.TypeDef.self) else {
498+
continue
499+
}
500+
if try bare(child) == name { return child }
501+
}
502+
return nil
503+
}
504+
505+
/// The bare `TypeName` of the type `tuple` names — the empty string when it
506+
/// carries no such column.
507+
private func bare(_ tuple: borrowing Tuple) throws(WinMDError) -> String {
508+
guard let name = tuple.ordinal(for: "TypeName") else { return "" }
509+
return try tuple.string(name)
510+
}
511+
512+
/// A `TypeName` with its CLR generic-arity suffix removed — the projected
513+
/// declaration name the render actually emits and the decode spells. The
514+
/// suffix is a backtick and an arity count (`Foo` backtick `1`), which the
515+
/// projection strips, so a generic `Foo` and a non-generic `Foo` project to
516+
/// the one Swift name and collide. The collision tally and the qualification
517+
/// identity key off this projected name so they match the emission; a name
518+
/// without the suffix is returned unchanged.
519+
private func projected(_ name: String) -> String {
520+
String(name.prefix { $0 != "`" })
521+
}
522+
523+
/// The (namespace, name) the `TypeDef`/`TypeRef` `tuple` names — the empty
524+
/// string for either column it lacks.
525+
private func names(_ tuple: borrowing Tuple)
526+
throws(WinMDError) -> (namespace: String, name: String) {
527+
let name = try bare(tuple)
528+
guard let space = tuple.ordinal(for: "TypeNamespace") else {
529+
return ("", name)
530+
}
531+
return (try tuple.string(space), name)
532+
}
533+
534+
/// The open table numbered `number`, or `nil` when the database omits it —
535+
/// the population-count slot lookup `rows(of:)`/`tuple(_:of:)` share, reduced
536+
/// to the `Table` for a direct row read.
537+
private func opened(_ number: Int) -> Table? {
538+
guard valid & (1 << number) != 0 else { return nil }
539+
let slot = (valid & ((1 << number) - 1)).nonzeroBitCount
540+
return tables[slot]
541+
}
227542
}

Sources/WinMDSynthesis/Decode.swift

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -426,13 +426,39 @@ extension TypeDefOrRef {
426426
if identity == kGuid {
427427
return classification(parameter, dialect: dialect)
428428
}
429-
// A named type's simple name is a bare identifier that may collide with a
430-
// target keyword (`protocol`, `repeat`); escape it as the render escapes a
431-
// declaration name. A well-known spelling is curated and never a keyword.
432-
return dialect.known[identity] ?? dialect.escape(identity.name)
429+
// A well-known spelling (a Win32 primitive, curated and never a keyword)
430+
// wins first, keyed on the `Identity` — so a known value type keeps its
431+
// target name rather than an enclosing dot-path. Otherwise the type spells
432+
// by its `Identity` name, which carries the enclosing dot-path a nested
433+
// type resolves to (`Outer.Inner`) and the bare name a top-level one does.
434+
// Either is a possibly dot-pathed identifier escaped per component
435+
// (`dialect.escape` escapes one identifier, so a keyword component like
436+
// `repeat` is delimited within the path rather than the whole treated as a
437+
// single name); a top-level name has no dot and escapes unchanged.
438+
if let known = dialect.known[identity] { return known }
439+
return qualify(identity.name, dialect)
433440
}
434441
}
435442

443+
/// Escapes a possibly dot-pathed named-type spelling per component through
444+
/// `dialect`, rejoining with `.` — so a nested type (`Foo.repeat`) escapes each
445+
/// identifier in turn (`` Foo.`repeat` ``) rather than the path as a whole. A
446+
/// top-level name (no dot) escapes as the single identifier it is.
447+
///
448+
/// Each component's CLR arity suffix (`` `1 ``) is stripped before it is
449+
/// escaped: the strip must precede the escape, since the suffixed `protocol``1`
450+
/// never matches a keyword while the stripped `protocol` does, and doing it
451+
/// here — the single seam every spelling escapes through — keeps a storage
452+
/// spelling (whose path `Storage.qualified` already stripped) and a
453+
/// directly-resolved one (whose identity still carries the suffix) escaping
454+
/// identically, so a generic instantiation's base needs no separate strip that
455+
/// would cut this escape.
456+
private func qualify(_ name: String, _ dialect: Dialect) -> String {
457+
name.split(separator: ".", omittingEmptySubsequences: false)
458+
.map { dialect.escape(String($0.prefix { $0 != "`" })) }
459+
.joined(separator: ".")
460+
}
461+
436462
/// The `System.Guid` identity that decodes to `IID`/`CLSID`.
437463
private let kGuid = Identity(namespace: "System", name: "Guid")
438464

@@ -467,15 +493,18 @@ extension SignatureType {
467493
// opaque pointer; a generic over it is meaningless, so degrade to that
468494
// opaque pointer rather than emit `UnsafeMutableRawPointer<…>`.
469495
if base == dialect.opaque { return base }
470-
// Strip the CLR arity suffix, THEN escape the base identifier: the full
471-
// `Foo``1` never matches a keyword, so a keyword base (`protocol``1`) must
472-
// be escaped after the strip, not before, to spell `` `protocol`<…> ``.
473-
let name = dialect.escape(String(base.prefix { $0 != "`" }))
496+
// The base already spells the generic definition ready to compose: the
497+
// per-component arity strip lives at the escape seam (`qualify`), before
498+
// the escape, since the suffixed `Foo``1` never matches a keyword — so a
499+
// keyword leaf reads `` `protocol` `` and a namespace-qualified one
500+
// `Outer.`protocol` `. Re-stripping the arity here would scan to that
501+
// escape backtick and empty the leaf (`` `protocol` `` → `<…>`), so the
502+
// base composes with the arguments as it stands.
474503
let arguments = arguments
475504
.map { $0.decode(parameter: parameter, generics: generics,
476505
with: resolver, dialect: dialect) }
477506
.joined(separator: ", ")
478-
return "\(name)\(dialect.generic.open)\(arguments)\(dialect.generic.close)"
507+
return "\(base)\(dialect.generic.open)\(arguments)\(dialect.generic.close)"
479508
}
480509
}
481510

0 commit comments

Comments
 (0)