Skip to content

Commit 71fe481

Browse files
committed
winmd-inspect: render WinRT generic interfaces ABI-erased
The generic-interface rendering previously projected a generic ABI protocol (`protocol Name<T…>` with `associatedtype`s) plus a wrapper forwarding own methods — the shape-1 foundation, which the non-generic-closed-base wall made a dead end for base inheritance. Pivot the rendering onto the ABI-erased shape windows-rs draws, reusing the merged `SignatureType.abi(…)`/`classification` keystone: - The ABI protocol is now NON-generic. Each method requirement reads every parameter/return through its ABI-erased spelling: a reference-typed slot (an interface, runtime class, delegate, generic-interface instantiation, or `System.Object`) is the opaque interface pointer, a value-typed slot keeps its own ABI. No generic parameters, no associated types. - The public generic `struct` wrapper `Name<T…>` keeps the typed Swift surface: each method takes/returns the decoded typed parameters and casts the typed value to the erased pointer (and the erased result back) as it forwards through `base` — `unsafeBitCast`, the text analogue of windows-rs's `transmute_copy` across the vtable. A value slot needs no cast (its typed and erased spellings coincide), so it forwards unchanged. - Base inheritance becomes trivial and is INCLUDED: a non-generic ABI protocol inherits its base's non-generic ABI protocol by plain protocol inheritance — a generic base inherits `<stripped>ABI` (no arguments, no `where`-constraints), a non-generic base (`IInspectable`) is inherited unchanged. The non-generic-closed-base wall cannot occur. The `Database+SQL` decode layer gains erased-ABI (`abi(return:)`/ `abi(parameter:)`) and classification (`reference(return:)`/ `reference(parameter:)`) accessors beside the existing typed `decode(…)`, all sharing the extracted signature-navigation. `Shell` composes the per-slot erased/typed spellings and the forwarding cast into the render context; the `com.mustache` `{{#generic}}` arm emits the erased protocol + casting wrapper. The non-generic `{{^generic}}` path is byte-identical. The generated Swift is swiftc-typecheck-verified for a simple generic interface (erased/cast element return), a generic interface with a generic base (plain non-generic ABI inheritance, wrapper casts), an out/interface-array method (erased element under the pointer), and a keyword-named generic parameter. Three metadata-shape fixes complete the erased shape: - `SignatureType.classification` now treats a `VAR`/`MVAR` generic type variable as a REFERENCE, so `abi(…)` erases a type-variable slot to the opaque interface pointer rather than spelling its declared name. The ABI protocol is non-generic and declares no associated type, so a requirement `-> Element` would name an undeclared type; the wrapper casts the typed value across the boundary as it does for any reference slot. - The base-ABI rewrite distinguishes a CLR arity backtick (a `` ` `` followed by a DIGIT, the raw `` IVector`1 `` spelling) from a Swift keyword-ESCAPING backtick, which `SANITIZE` wraps around a keyword base (`` `protocol` ``). Only the arity form marks a generic base to rewrite, so an escaped keyword base is inherited unchanged instead of becoming a broken `` `protocolABI` ``. - `bases.sql` resolves a TypeSpec-recorded base: a generic interface inheriting another (`IVector`1 : IIterable`1`) records the base through a `TypeSpec` GENERICINST row, which the plain `Interface_TypeRef`/`_TypeDef` arms miss. A new `GENERICBASE` scalar UDF (over `WinMD.base(decoding:)`) decodes the spec's signature to the base coded-index token; the view splits its tag (`BITAND(token, 3)`) and 1-based row (`token / 4`) to join the base `TypeRef`/`TypeDef` by `Id`, so the ABI rewrite emits `IIterableABI`.
1 parent f160ca0 commit 71fe481

9 files changed

Lines changed: 719 additions & 145 deletions

File tree

Sources/WinMD/Signature.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,3 +525,30 @@ extension Row where Schema == Metadata.Tables.PropertyDef {
525525
}
526526
}
527527
}
528+
529+
// MARK: - TypeSpec generic base
530+
531+
/// The `TypeDefOrRef` coded-index token naming the GENERIC BASE a `TypeSpec`
532+
/// GENERICINST signature instantiates — the row-linkable value the raw `bytes`
533+
/// of a `TypeSpec.Signature` `#Blob` carry — or `nil` when the signature is not
534+
/// a generic instantiation (its base is not a named type).
535+
///
536+
/// This is the escapable, value → value form the SQL adapter's `GENERICBASE`
537+
/// scalar function reads: a caller that has copied a `TypeSpec.Signature` blob
538+
/// out of the borrowed scan decodes it here, returning the base's `rawValue` so
539+
/// SQL can split its tag/row and join to the base `TypeRef`/`TypeDef` by `Id`.
540+
/// A WinRT generic interface inheriting another (`IVector`1 : IIterable`1`)
541+
/// records the base through a `TypeSpec`, so its `bases` row is otherwise
542+
/// empty; this recovers the base name.
543+
///
544+
/// A `TypeSpec` whose signature is not a `GENERICINST` over a named base — a
545+
/// bare array/pointer spec, or a malformed blob — yields `nil` rather than
546+
/// throwing, so the SQL join simply produces no base row for it.
547+
public func base(decoding bytes: Array<UInt8>) -> TypeDefOrRef? {
548+
var decoder = SignatureDecoder(bytes.span.bytes)
549+
guard let type = try? decoder.type(),
550+
case let .instance(.named(_, reference), _) = type else {
551+
return nil
552+
}
553+
return reference
554+
}

Sources/WinMDSynthesis/Decode.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -195,8 +195,11 @@ extension SignatureType {
195195
/// or matrix classifies as its (recursively unwrapped) ELEMENT, so a byref or
196196
/// array of a class reference is itself a reference (its element erases),
197197
/// while a pointer or array of a value stays a value. A `VAR`/`MVAR` generic
198-
/// variable stands for an as-yet-unknown argument and is conservatively a
199-
/// value (its erased spelling falls through to `decode`).
198+
/// variable is a REFERENCE: the ABI protocol is non-generic and declares no
199+
/// associated type, so a type-variable slot cannot keep its declared name
200+
/// (`Element`) — a requirement `-> Element` names an undeclared type. It
201+
/// therefore erases to the opaque interface pointer, exactly as a `CLASS`
202+
/// reference does, and the wrapper casts the typed value across the boundary.
200203
///
201204
/// This is the classification half of the ABI-erasure keystone; `abi(…)`
202205
/// produces the matching erased spelling.
@@ -207,7 +210,7 @@ extension SignatureType {
207210
case .named(kind: .value, _), .primitive, .function:
208211
.value
209212
case .variable:
210-
.value
213+
.reference
211214
case let .instance(base, _):
212215
base.classification
213216
case let .modified(inner, _),

Sources/winmd-inspect/Database+SQL.swift

Lines changed: 133 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -195,8 +195,17 @@ extension Session {
195195
// — and its `[.blob]` parameter contract, which the static type-check
196196
// validates a `GUID(...)` call against. `try!`: the name is a compile-time
197197
// constant and not a protected standard routine, so it never faults.
198-
try! Routines.standard.registering("guid", returns: .text,
199-
parameters: [.blob], Session.guid)
198+
//
199+
// `GENERICBASE` decodes a `TypeSpec.Signature` blob to the coded-index
200+
// token of the generic base it instantiates — an `.integer` over one
201+
// `.blob`, NULL when the spec is not a generic instantiation — so the
202+
// `bases` view can join a `TypeSpec`-recorded base (a generic interface's
203+
// generic base) to its `TypeRef`/`TypeDef` by splitting the token's tag and
204+
// row.
205+
try! Routines.standard
206+
.registering("guid", returns: .text, parameters: [.blob], Session.guid)
207+
.registering("genericbase", returns: .integer, parameters: [.blob],
208+
Session.genericbase)
200209
}
201210

202211
/// `GUID(blob)` — the UUID a `GuidAttribute` `CustomAttribute` value blob
@@ -219,6 +228,32 @@ extension Session {
219228
guard let uuid = try? WinMD.iid(decoding: bytes) else { return .null }
220229
return .text("\(uuid)")
221230
}
231+
232+
/// `GENERICBASE(blob)` — the `TypeDefOrRef` coded-index token naming the
233+
/// generic base a `TypeSpec.Signature` GENERICINST instantiates, as an
234+
/// integer, or `NULL` when the blob is not a generic instantiation over a
235+
/// named base.
236+
///
237+
/// A pure per-row codec over the raw `TypeSpec.Signature` `#Blob`: the
238+
/// `bases` view feeds it the signature of a `TypeSpec`-recorded interface
239+
/// base (a WinRT generic interface inheriting another, `IVector`1 :
240+
/// IIterable`1`) and splits the returned token — `tag = BITAND(token, 3)`,
241+
/// `row = token / 4` — to join the base `TypeRef` (tag 1) or `TypeDef` (tag
242+
/// 0) by its `Id`. A NULL argument propagates to NULL; a non-blob argument is
243+
/// `SQLError.argument`; a non-generic (or malformed) spec yields NULL, so its
244+
/// join produces no base.
245+
private static func genericbase(_ arguments: Array<Value>)
246+
throws(SQLError) -> Value {
247+
guard arguments.count == 1 else {
248+
throw .argument("GENERICBASE takes one argument")
249+
}
250+
if case .null = arguments[0] { return .null }
251+
guard case let .blob(bytes) = arguments[0] else {
252+
throw .argument("GENERICBASE requires a blob argument")
253+
}
254+
guard let base = WinMD.base(decoding: bytes) else { return .null }
255+
return .integer(base.rawValue)
256+
}
222257
}
223258

224259
// MARK: - Table
@@ -764,19 +799,17 @@ extension WinMD.Storage {
764799
return nil
765800
}
766801

767-
/// The decoded type spelling of the return of the `MethodDef` at 1-based
768-
/// `method` `Id`, in `dialect`, or `nil` when the row or its signature
769-
/// does not decode.
802+
/// The `SignatureType` of the return of the `MethodDef` at 1-based `method`
803+
/// `Id`, paired with a `Resolver` over the storage — or `nil` when the row or
804+
/// its signature does not decode.
770805
///
771-
/// This is the signature-navigation the adapter once baked as the
772-
/// `ReturnType` virtual column, relocated so the render can spell a return at
773-
/// render time with a target `Dialect`: it opens the `MethodDef` row, decodes
774-
/// its `prototype` signature, builds a `Resolver` over the storage, and
775-
/// decodes the return. `nil` mirrors the old NULL — an absent row, an
776-
/// undecodable signature, or an unresolvable one.
777-
internal borrowing func decode(return method: Int,
778-
generics: Array<String>? = nil,
779-
in dialect: Dialect) -> String? {
806+
/// This is the shared signature-navigation the render's return spellings
807+
/// (`decode(return:)` for the typed surface, `abi(return:)` for the erased
808+
/// ABI) build on: it opens the `MethodDef` row, decodes its `prototype`
809+
/// signature, and builds the `Resolver`. `nil` mirrors the old NULL — an
810+
/// absent row, an undecodable signature, or an unresolvable one.
811+
private borrowing func returned(_ method: Int)
812+
-> (type: SignatureType, resolver: Resolver)? {
780813
guard let table = opened("MethodDef") else { return nil }
781814
let cursor = WinMD.Cursor(copy self, table)
782815
guard let tuple = cursor[method - 1],
@@ -785,27 +818,24 @@ extension WinMD.Storage {
785818
let resolver = try? Resolver(of: signature, with: self) else {
786819
return nil
787820
}
788-
return signature.returns.decode(generics: generics, with: resolver,
789-
dialect: dialect)
821+
return (signature.returns, resolver)
790822
}
791823

792-
/// The decoded type spelling of the `Param` at 1-based `parameter` `Id`, in
793-
/// `dialect`, navigated through its owning method's signature — or `nil` when
794-
/// it does not decode.
824+
/// The `SignatureType` of the `Param` at 1-based `parameter` `Id`, navigated
825+
/// through its owning method's signature, paired with a `Resolver` and the
826+
/// parameter's own `Name` (the `System.Guid` `IID`/`CLSID` hint) — or `nil`
827+
/// when it does not decode.
795828
///
796-
/// This is the signature-navigation the adapter once baked as the `ParamType`
797-
/// virtual column, relocated so the render can spell a parameter at render
798-
/// time. The `Param.Sequence` cell is the 1-based parameter position:
799-
/// `Sequence == 0` is the return pseudo-parameter and `Sequence >
829+
/// This is the shared signature-navigation the render's parameter spellings
830+
/// (`decode(parameter:)` for the typed surface, `abi(parameter:)` for the
831+
/// erased ABI) build on. The `Param.Sequence` cell is the 1-based parameter
832+
/// position: `Sequence == 0` is the return pseudo-parameter and `Sequence >
800833
/// parameters.count` is out of range, both `nil`. The owning `MethodDef` is
801834
/// found through the `Param` list link — an owner of zero is no parent
802835
/// (malformed/partial metadata), so the parameter is unowned and yields `nil`
803-
/// rather than indexing a negative row. The parameter's own `Name` is the
804-
/// `System.Guid` `IID`/`CLSID` hint; for any other type the decoder ignores
805-
/// it, so threading it is always safe.
806-
internal borrowing func decode(parameter: Int,
807-
generics: Array<String>? = nil,
808-
for dialect: Dialect) -> String? {
836+
/// rather than indexing a negative row.
837+
private borrowing func parametered(_ parameter: Int)
838+
-> (type: SignatureType, resolver: Resolver, name: String?)? {
809839
guard let table = opened("Param") else { return nil }
810840
let params = WinMD.Cursor(copy self, table)
811841
guard let param = params[parameter - 1],
@@ -829,8 +859,79 @@ extension WinMD.Storage {
829859
return nil
830860
}
831861
let name = param.ordinal(for: "Name").flatMap { try? param.string($0) }
832-
return signature.parameters[position - 1]
833-
.decode(parameter: name, generics: generics, with: resolver,
834-
dialect: dialect)
862+
return (signature.parameters[position - 1], resolver, name)
863+
}
864+
865+
/// The typed type spelling of the return of the `MethodDef` at 1-based
866+
/// `method` `Id`, in `dialect` — the wrapper's typed surface — or `nil` when
867+
/// the row or its signature does not decode.
868+
///
869+
/// This is the signature-navigation the adapter once baked as the
870+
/// `ReturnType` virtual column, relocated so the render can spell a return at
871+
/// render time with a target `Dialect`.
872+
internal borrowing func decode(return method: Int,
873+
generics: Array<String>? = nil,
874+
in dialect: Dialect) -> String? {
875+
returned(method).map {
876+
$0.type.decode(generics: generics, with: $0.resolver, dialect: dialect)
877+
}
878+
}
879+
880+
/// The ABI-erased type spelling of the return of the `MethodDef` at 1-based
881+
/// `method` `Id`, in `dialect` — the non-generic ABI protocol's return, a
882+
/// reference erased to the opaque interface pointer — or `nil` when the row
883+
/// or its signature does not decode.
884+
internal borrowing func abi(return method: Int,
885+
generics: Array<String>? = nil,
886+
in dialect: Dialect) -> String? {
887+
returned(method).map {
888+
$0.type.abi(generics: generics, with: $0.resolver, dialect: dialect)
889+
}
890+
}
891+
892+
/// Whether the return of the `MethodDef` at 1-based `method` `Id` crosses the
893+
/// ABI as an erased interface pointer (a reference) — so the wrapper casts
894+
/// the erased result back to its typed spelling — or `nil` when the row does
895+
/// not decode. A value return needs no cast.
896+
internal borrowing func reference(return method: Int) -> Bool? {
897+
returned(method).map { $0.type.classification == .reference }
898+
}
899+
900+
/// The typed type spelling of the `Param` at 1-based `parameter` `Id`, in
901+
/// `dialect`, navigated through its owning method's signature — the wrapper's
902+
/// typed surface — or `nil` when it does not decode. The parameter's own
903+
/// `Name` is the `System.Guid` `IID`/`CLSID` hint; for any other type the
904+
/// decoder ignores it, so threading it is always safe.
905+
///
906+
/// This is the signature-navigation the adapter once baked as the `ParamType`
907+
/// virtual column, relocated so the render can spell a parameter at render
908+
/// time.
909+
internal borrowing func decode(parameter: Int,
910+
generics: Array<String>? = nil,
911+
for dialect: Dialect) -> String? {
912+
parametered(parameter).map {
913+
$0.type.decode(parameter: $0.name, generics: generics,
914+
with: $0.resolver, dialect: dialect)
915+
}
916+
}
917+
918+
/// The ABI-erased type spelling of the `Param` at 1-based `parameter` `Id`,
919+
/// in `dialect` — the non-generic ABI protocol's parameter, a reference
920+
/// erased to the opaque interface pointer — or `nil` when it does not decode.
921+
internal borrowing func abi(parameter: Int,
922+
generics: Array<String>? = nil,
923+
for dialect: Dialect) -> String? {
924+
parametered(parameter).map {
925+
$0.type.abi(parameter: $0.name, generics: generics,
926+
with: $0.resolver, dialect: dialect)
927+
}
928+
}
929+
930+
/// Whether the `Param` at 1-based `parameter` `Id` crosses the ABI as an
931+
/// erased interface pointer (a reference) — so the wrapper casts the typed
932+
/// argument to the erased pointer before forwarding — or `nil` when it does
933+
/// not decode. A value parameter needs no cast.
934+
internal borrowing func reference(parameter: Int) -> Bool? {
935+
parametered(parameter).map { $0.type.classification == .reference }
835936
}
836937
}

Sources/winmd-inspect/Resources/Queries/bases.sql

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,28 @@ FROM
1414
JOIN TypeDef d ON i.Interface_TypeDef = d.Id
1515
WHERE
1616
i.Class = :parent
17+
UNION
18+
-- A generic interface's base is recorded through a TypeSpec (a GENERICINST
19+
-- signature) rather than a TypeRef/TypeDef, so join the TypeSpec, decode its
20+
-- signature to the base coded-index token (GENERICBASE), and resolve that token
21+
-- to the base's TypeName by splitting its tag (BITAND(token, 3)) and 1-based
22+
-- row (token / 4): tag 1 names a TypeRef, tag 0 a TypeDef.
23+
SELECT
24+
b.TypeName AS base
25+
FROM
26+
InterfaceImpl i
27+
JOIN TypeSpec s ON i.Interface_TypeSpec = s.Id
28+
JOIN TypeRef b ON b.Id = GENERICBASE(s.Signature) / 4
29+
WHERE
30+
i.Class = :parent
31+
AND BITAND(GENERICBASE(s.Signature), 3) = 1
32+
UNION
33+
SELECT
34+
d.TypeName AS base
35+
FROM
36+
InterfaceImpl i
37+
JOIN TypeSpec s ON i.Interface_TypeSpec = s.Id
38+
JOIN TypeDef d ON d.Id = GENERICBASE(s.Signature) / 4
39+
WHERE
40+
i.Class = :parent
41+
AND BITAND(GENERICBASE(s.Signature), 3) = 0

Sources/winmd-inspect/Resources/Templates/com.mustache

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,28 @@
11
{{! language: swift }}
22
{{#generic}}
3-
// A WinRT parameterised interface has no static IID: its IID is a
4-
// per-instantiation PIID computed at runtime from the type
5-
// arguments, so no `@com(interface:)` is emitted on the ABI protocol
6-
// or the generic wrapper — the runtime projection supplies it.
7-
internal protocol {{{abi}}}<{{#generics}}{{{name}}}{{^last}}, {{/last}}{{/generics}}>{{#base}}: {{{.}}}{{/base}} {
8-
{{#generics}}
9-
associatedtype {{{name}}}
10-
{{/generics}}
3+
// A WinRT parameterised interface erases to a NON-generic ABI protocol:
4+
// every reference-typed slot crosses the vtable as the opaque interface
5+
// pointer, so the protocol carries no generic parameters and no
6+
// associated types, and a generic base is inherited by PLAIN protocol
7+
// inheritance. It has no static IID: a parameterised interface's IID is a
8+
// per-instantiation PIID computed at runtime from the type arguments, so
9+
// no `@com(interface:)` is emitted on the ABI protocol or the wrapper —
10+
// the runtime projection supplies it.
11+
internal protocol {{{abi}}}{{#base}}: {{{.}}}{{/base}} {
1112
{{#methods}}
1213
func {{{name}}}({{#params}}_ {{{name}}}: {{{type}}}{{^last}}, {{/last}}{{/params}}){{#returns}} -> {{{.}}}{{/returns}}
1314
{{/methods}}
1415
}
1516

17+
// The public generic wrapper keeps the typed Swift surface: each method
18+
// takes and returns the decoded typed parameters, casting between the
19+
// typed value and the erased interface pointer when it forwards through
20+
// `base` to the non-generic ABI protocol.
1621
public struct {{{name}}}<{{#generics}}{{{name}}}{{^last}}, {{/last}}{{/generics}}> {
17-
internal let base: any {{{abi}}}<{{#generics}}{{{name}}}{{^last}}, {{/last}}{{/generics}}>
22+
internal let base: any {{{abi}}}
1823
{{#methods}}
19-
public func {{{name}}}({{#params}}_ {{{local}}}: {{{type}}}{{^last}}, {{/last}}{{/params}}){{#returns}} -> {{{.}}}{{/returns}} {
20-
base.{{{name}}}({{#params}}{{{local}}}{{^last}}, {{/last}}{{/params}})
24+
public func {{{name}}}({{#params}}_ {{{local}}}: {{{typed}}}{{^last}}, {{/last}}{{/params}}){{#typed}} -> {{{.}}}{{/typed}} {
25+
{{{call}}}
2126
}
2227
{{/methods}}
2328
}

0 commit comments

Comments
 (0)