Skip to content

Commit 0dbb2c2

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.
1 parent f160ca0 commit 0dbb2c2

4 files changed

Lines changed: 409 additions & 137 deletions

File tree

Sources/winmd-inspect/Database+SQL.swift

Lines changed: 96 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -764,19 +764,17 @@ extension WinMD.Storage {
764764
return nil
765765
}
766766

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.
767+
/// The `SignatureType` of the return of the `MethodDef` at 1-based `method`
768+
/// `Id`, paired with a `Resolver` over the storage — or `nil` when the row or
769+
/// its signature does not decode.
770770
///
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? {
771+
/// This is the shared signature-navigation the render's return spellings
772+
/// (`decode(return:)` for the typed surface, `abi(return:)` for the erased
773+
/// ABI) build on: it opens the `MethodDef` row, decodes its `prototype`
774+
/// signature, and builds the `Resolver`. `nil` mirrors the old NULL — an
775+
/// absent row, an undecodable signature, or an unresolvable one.
776+
private borrowing func returned(_ method: Int)
777+
-> (type: SignatureType, resolver: Resolver)? {
780778
guard let table = opened("MethodDef") else { return nil }
781779
let cursor = WinMD.Cursor(copy self, table)
782780
guard let tuple = cursor[method - 1],
@@ -785,27 +783,24 @@ extension WinMD.Storage {
785783
let resolver = try? Resolver(of: signature, with: self) else {
786784
return nil
787785
}
788-
return signature.returns.decode(generics: generics, with: resolver,
789-
dialect: dialect)
786+
return (signature.returns, resolver)
790787
}
791788

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.
789+
/// The `SignatureType` of the `Param` at 1-based `parameter` `Id`, navigated
790+
/// through its owning method's signature, paired with a `Resolver` and the
791+
/// parameter's own `Name` (the `System.Guid` `IID`/`CLSID` hint) — or `nil`
792+
/// when it does not decode.
795793
///
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 >
794+
/// This is the shared signature-navigation the render's parameter spellings
795+
/// (`decode(parameter:)` for the typed surface, `abi(parameter:)` for the
796+
/// erased ABI) build on. The `Param.Sequence` cell is the 1-based parameter
797+
/// position: `Sequence == 0` is the return pseudo-parameter and `Sequence >
800798
/// parameters.count` is out of range, both `nil`. The owning `MethodDef` is
801799
/// found through the `Param` list link — an owner of zero is no parent
802800
/// (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? {
801+
/// rather than indexing a negative row.
802+
private borrowing func parametered(_ parameter: Int)
803+
-> (type: SignatureType, resolver: Resolver, name: String?)? {
809804
guard let table = opened("Param") else { return nil }
810805
let params = WinMD.Cursor(copy self, table)
811806
guard let param = params[parameter - 1],
@@ -829,8 +824,79 @@ extension WinMD.Storage {
829824
return nil
830825
}
831826
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)
827+
return (signature.parameters[position - 1], resolver, name)
828+
}
829+
830+
/// The typed type spelling of the return of the `MethodDef` at 1-based
831+
/// `method` `Id`, in `dialect` — the wrapper's typed surface — or `nil` when
832+
/// the row or its signature does not decode.
833+
///
834+
/// This is the signature-navigation the adapter once baked as the
835+
/// `ReturnType` virtual column, relocated so the render can spell a return at
836+
/// render time with a target `Dialect`.
837+
internal borrowing func decode(return method: Int,
838+
generics: Array<String>? = nil,
839+
in dialect: Dialect) -> String? {
840+
returned(method).map {
841+
$0.type.decode(generics: generics, with: $0.resolver, dialect: dialect)
842+
}
843+
}
844+
845+
/// The ABI-erased type spelling of the return of the `MethodDef` at 1-based
846+
/// `method` `Id`, in `dialect` — the non-generic ABI protocol's return, a
847+
/// reference erased to the opaque interface pointer — or `nil` when the row
848+
/// or its signature does not decode.
849+
internal borrowing func abi(return method: Int,
850+
generics: Array<String>? = nil,
851+
in dialect: Dialect) -> String? {
852+
returned(method).map {
853+
$0.type.abi(generics: generics, with: $0.resolver, dialect: dialect)
854+
}
855+
}
856+
857+
/// Whether the return of the `MethodDef` at 1-based `method` `Id` crosses the
858+
/// ABI as an erased interface pointer (a reference) — so the wrapper casts
859+
/// the erased result back to its typed spelling — or `nil` when the row does
860+
/// not decode. A value return needs no cast.
861+
internal borrowing func reference(return method: Int) -> Bool? {
862+
returned(method).map { $0.type.classification == .reference }
863+
}
864+
865+
/// The typed type spelling of the `Param` at 1-based `parameter` `Id`, in
866+
/// `dialect`, navigated through its owning method's signature — the wrapper's
867+
/// typed surface — or `nil` when it does not decode. The parameter's own
868+
/// `Name` is the `System.Guid` `IID`/`CLSID` hint; for any other type the
869+
/// decoder ignores it, so threading it is always safe.
870+
///
871+
/// This is the signature-navigation the adapter once baked as the `ParamType`
872+
/// virtual column, relocated so the render can spell a parameter at render
873+
/// time.
874+
internal borrowing func decode(parameter: Int,
875+
generics: Array<String>? = nil,
876+
for dialect: Dialect) -> String? {
877+
parametered(parameter).map {
878+
$0.type.decode(parameter: $0.name, generics: generics,
879+
with: $0.resolver, dialect: dialect)
880+
}
881+
}
882+
883+
/// The ABI-erased type spelling of the `Param` at 1-based `parameter` `Id`,
884+
/// in `dialect` — the non-generic ABI protocol's parameter, a reference
885+
/// erased to the opaque interface pointer — or `nil` when it does not decode.
886+
internal borrowing func abi(parameter: Int,
887+
generics: Array<String>? = nil,
888+
for dialect: Dialect) -> String? {
889+
parametered(parameter).map {
890+
$0.type.abi(parameter: $0.name, generics: generics,
891+
with: $0.resolver, dialect: dialect)
892+
}
893+
}
894+
895+
/// Whether the `Param` at 1-based `parameter` `Id` crosses the ABI as an
896+
/// erased interface pointer (a reference) — so the wrapper casts the typed
897+
/// argument to the erased pointer before forwarding — or `nil` when it does
898+
/// not decode. A value parameter needs no cast.
899+
internal borrowing func reference(parameter: Int) -> Bool? {
900+
parametered(parameter).map { $0.type.classification == .reference }
835901
}
836902
}

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)