Skip to content

winmd-inspect: close over signature-named value types and delegates - #349

Open
compnerd wants to merge 1 commit into
compnerd/render-closure-2-templatefrom
compnerd/render-closure-3-values
Open

winmd-inspect: close over signature-named value types and delegates#349
compnerd wants to merge 1 commit into
compnerd/render-closure-2-templatefrom
compnerd/render-closure-3-values

Conversation

@compnerd

Copy link
Copy Markdown
Owner

Extend the --closure walk to pull in the value-type and delegate types a rendered interface names through its method signatures — the E3 value arm, the E6 struct-field types, and the E7 delegate signatures — so the closure emits everything a consumer needs to compile the projection, not the interface surface alone. Each projected type is an ABI-faithful shape, the way the interface projects an @com protocol that lowers to the COM vtable.

emit now dispatches on the row's kind (found[4]): a struct through structure, an enum through enumeration, a delegate through delegation, and an interface through interface (the former inline body, now its own half), each rendered under exactly one of the template's {{#struct}}/{{#enum}}/ {{#delegate}}/{{#interface}} sections. The walk seeds and the requires bases carry the kind at index 4 so the walk and emit dispatch on the same column a references row already has.

The projected shapes match how the target language's import models them. A struct is an @frozen struct, its guaranteed layout the C/C++ record it mirrors field for field, with a public memberwise initialiser (the synthesised one is internal, so a cross-module caller could not otherwise construct it). Only instance fields are storage: a static or literal field (the fdStatic bit) is dropped, and the dependency walk applies the same filter so a type named only by a dropped field is not pulled in. A value type whose ABI layout @frozen cannot reproduce — an explicit layout, a non-default packing, or a declared class size — is rejected as a frontier rather than misprojected; the layout query returns a row only for such a type (ClassLayout is an optional table the adapter now synthesises empty when absent). An enum projects to an explicitly-stored raw-value struct newtype, because a native enum E: <underlying> does not carry its raw type's ABI width; a [flags] enum (a System.FlagsAttribute, detected through the overridable flags query) is the same newtype refined to OptionSet. The value__ storage field is found by its raw metadata name, so a SANITIZE override respelling it does not drop the enum to the i4 fallback width, and each member's value is the constant value(field:) already formats per signedness. A delegate is an @com(interface:) protocol carrying a single Invoke, decoding its runtime IID through the guid query.

Signature adjacency (E3/E6/E7) runs per kind — an interface over its methods, a struct over its instance fields, a delegate over its Invoke — resolving each referenced type through references by the exact row it was named through, never by (namespace, name), so two same-named nested types are not conflated. A resolved runtime class stays a frontier; an external TypeRef-only reference resolves to nothing and drops; a nongeneric interface or delegate bearing no GuidAttribute — whose @com shape would spell a GUID-less "" — is dropped, while a generic one (no static IID by design) is kept and renders through the generic arm. A reached type the language import already provides — a Dialect.known identity such as HRESULT — is a frontier the closure emits no wrapper for, since the consumer has it from the import.

A signature-named value type or delegate emits at top level by its bare name. Metadata nesting (Outer.Inner) and the namespace disambiguation of same-named value types are follow-ups; this change lands the per-kind projections and the signature closure on the unique-name path.

Integration tests over hand-built fixtures cover a struct-returning method (the @frozen struct with its public initialiser, emitted before the interface), an enum parameter (a stored newtype, a duplicate raw value as two constants, and a [flags] OptionSet), a delegate parameter (its @com protocol and decoded IID, plus a parameterised delegate kept through its generic arm and a keyword-named generic parameter escaped), and a signature-named interface enqueued and emitted; further tests pin an auto/explicit/packed/sized struct and a static struct field each dropped, a GUID-less nongeneric interface and a language-provided type each frontiered, and a unique-named value type emitted bare with no namespace.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d77699eaa1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Sources/winmd-inspect/Shell.swift
@compnerd

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 31, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-01T20:02:07.446039Z bdcdb81 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d77699eaa1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Sources/winmd-inspect/Shell.swift Outdated
@compnerd
compnerd force-pushed the compnerd/render-closure-3-values branch from d77699e to 0f3673e Compare August 31, 2026 18:51

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0f3673e47f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Sources/winmd-inspect/Shell.swift
@compnerd
compnerd force-pushed the compnerd/render-closure-3-values branch from 0f3673e to 3fe1bff Compare August 31, 2026 19:50
@compnerd
compnerd force-pushed the compnerd/render-closure-3-values branch from 3fe1bff to 87e3fd4 Compare September 1, 2026 15:34
@compnerd
compnerd force-pushed the compnerd/render-closure-3-values branch from 87e3fd4 to a2fdeba Compare September 1, 2026 15:46

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a2fdebaffa

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Sources/winmd-inspect/Resources/Templates/com.mustache Outdated
Comment thread Sources/winmd-inspect/Resources/Templates/com.mustache Outdated
@compnerd
compnerd force-pushed the compnerd/render-closure-3-values branch from a2fdeba to 7dce41c Compare September 1, 2026 17:46

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7dce41c3e4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Sources/winmd-inspect/Shell.swift
Extend the `--closure` walk to pull in the value-type and delegate types a
rendered interface names through its method signatures — the E3 value arm, the
E6 struct-field types, and the E7 delegate signatures — so the closure emits
everything a consumer needs to compile the projection, not the interface surface
alone. Each projected type is an ABI-faithful shape, the way the interface
projects an `@com` protocol that lowers to the COM vtable.

`emit` now dispatches on the row's kind (`found[4]`): a struct through
`structure`, an enum through `enumeration`, a delegate through `delegation`, and
an interface through `interface` (the former inline body, now its own half),
each rendered under exactly one of the template's `{{#struct}}`/`{{#enum}}`/
`{{#delegate}}`/`{{#interface}}` sections. The `walk` seeds and the `requires`
bases carry the kind at index 4 so the walk and emit dispatch on the same column
a `references` row already has.

The projected shapes match how the target language's import models them. A
struct is an `@frozen` struct, its guaranteed layout the C/C++ record it mirrors
field for field, with a public memberwise initialiser (the synthesised one is
internal, so a cross-module caller could not otherwise construct it). A field
literally named `self` — escaped `` `self` `` for its stored property — takes a
distinct collision-free local as the initialiser parameter's internal name and
the assignment right-hand side (`` init(`self` arg0:) { self.`self` = arg0 } ``);
reusing the field spelling would bind the assignment's leading `self` to the
parameter, not the struct instance, and fail to compile. A field named `_` — the
wildcard, no name — is added to the language spec's reserved words so `SANITIZE`
escapes it to `` `_` `` for a declarable property; unlike `self` it does not
shadow the instance, so it needs no distinct local. Only instance fields are
storage: a static or literal field (the `fdStatic` bit) is dropped, and the
dependency walk applies the same filter so a type named only by a dropped field
is not pulled in. A value type whose ABI layout `@frozen` cannot reproduce — an
explicit layout, a non-default packing, or a declared class size — is rejected
as a frontier rather than misprojected; the `layout` query returns a row only
for such a type (`ClassLayout` is an optional table the adapter now synthesises
empty when absent). An enum projects to an explicitly-stored raw-value struct
newtype, because a native `enum E: <underlying>` does not carry its raw type's
ABI width; a `[flags]` enum (a `System.FlagsAttribute`, detected through the
overridable `flags` query) is the same newtype refined to `OptionSet`, typing
`rawValue` as the underlying type directly so the OptionSet `RawValue` associated
type is inferred rather than spelled as an explicit `typealias` — a member
legitimately named `RawValue` then stays a `static var` and does not collide with
a support typealias. The `value__` storage field is found by its raw metadata
name, so a `SANITIZE` override respelling it does not drop the enum to the `i4`
fallback width, and each member's value is the constant `value(field:)` already
formats per signedness. A delegate is an `@com(interface:)` protocol carrying a
single `Invoke`, decoding its runtime IID through the `guid` query.

Signature adjacency (E3/E6/E7) runs per kind — an interface over its methods, a
struct over its instance fields, a delegate over its `Invoke` — resolving each
referenced type through `references` by the exact row it was named through,
never by (namespace, name), so two same-named nested types are not conflated. A
resolved runtime `class` stays a frontier; an external `TypeRef`-only reference
resolves to nothing and drops; an interface or delegate bearing no
`GuidAttribute` — whose `@com` shape would spell a GUID-less `""` — is dropped. A
reached type the language import already provides — a `Dialect.known` identity
such as `HRESULT` — is a frontier the closure emits no wrapper for, since the
consumer has it from the import.

A signature-named value type or delegate emits at top level by its bare name.
Metadata nesting (`Outer.Inner`) and the namespace disambiguation of same-named
value types are follow-ups; this change lands the per-kind projections and the
signature closure on the unique-name path.

Integration tests over hand-built fixtures cover a struct-returning method (the
`@frozen` struct with its public initialiser, emitted before the interface, a
field named `self` given a distinct initialiser local, and a field named `_`
escaped to `` `_` ``), an enum parameter (a
stored newtype, a duplicate raw value as two constants, a `[flags]` `OptionSet`,
and a flags member named `RawValue` kept distinct from the inferred support
type), a delegate parameter (its `@com` protocol and decoded IID), and a
signature-named interface enqueued and emitted; further tests pin an
auto/explicit/packed/sized struct and a static struct field each dropped, a
GUID-less interface and a language-provided type each frontiered, and a
unique-named value type emitted bare with no namespace.

The `fields` render query gains `Flags` and raw-name columns so the walk and the
struct render drop a static field and the enum render finds its `value__`
storage. Render-query shadowing is a supported extension point, though, so a `-I`
override carrying the former two-column shape (`Id`, `Name`) is tolerated rather
than trapping on the absent columns: a field with no `Flags` counts as instance
storage, as it did before the columns existed, and the raw-name read falls back
to the escaped `Name`.

The `requires` walk tolerates a copied override of the same kind. The bundled
query returns a trailing `kind` column ('interface') the walk and emit read from
`found[4]`; an override copied from the earlier four-column shape (`Id`,
namespace, name, `iid`) omits it. A base row shorter than five columns is padded
to the interface shape before recursion — an `InterfaceImpl` base is always an
interface — so the closure resolves and emits it rather than trapping on the
absent column.

The closure is scoped to pure COM: the walk frontiers a parameterised (generic)
type — its `TypeName` bears an arity suffix — so a WinRT parameterised interface
or delegate is never emitted, only referenced as a consumer-supplied type. A
delegate projects one `@com` protocol (no generic wrapper/ABI-protocol arm), and
`delegation` builds no generic-parameter clause.
@compnerd
compnerd force-pushed the compnerd/render-closure-3-values branch from 7dce41c to bdcdb81 Compare September 1, 2026 19:56

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bdcdb81b2c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

is thus the ABI-exact width of the C enum, and each `@_transparent` member
folds to its raw constant at the use site with no static storage; a repeated
raw value across members is fine here. }}
@frozen public struct {{{name}}}: OptionSet {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Qualify support protocols to avoid enum-name shadowing

When metadata defines a flags enum named OptionSet, the generated declaration becomes struct OptionSet: OptionSet, and Swift resolves the conformance target to the struct being declared, producing “inheritance from non-protocol type.” The regular-enum branch has the same problem for types named Hashable or Sendable; qualify these support protocols (for example, Swift.OptionSet) or otherwise disambiguate them from the projected type name.

Useful? React with 👍 / 👎.

// generates the IUnknown-based vtable from this Invoke-only protocol, the same
// ABI-faithful shape an interface projects, not a Swift closure.
@com(interface: "{{{iid}}}")
public protocol {{{name}}} {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Make projected delegates inherit the COM root

When --closure reaches a delegate, this emits an Invoke-only protocol with no IUnknown refinement, even though the delegate is described here as an IUnknown-based COM interface and the ordinary interface path explicitly adds the language's COM root. Consequently the projected delegate does not model the inherited COM surface and can produce the wrong vtable inheritance for @com; emit the root base for delegates as well.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant