-
Notifications
You must be signed in to change notification settings - Fork 3
winmd-inspect: close over signature-named value types and delegates #349
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,19 @@ | ||
| -- A struct's fields, in declaration order, through the `fields` view bound by | ||
| -- the struct's `Id` — each field's `Id` (for the render to decode its type from | ||
| -- its `FieldDef` signature) and its keyword-escaped `Name`. The render spells | ||
| -- each field's type at render time from the target `Dialect`, so the query | ||
| -- projects only the identity and name, the way `params` does for a method. | ||
| -- its `FieldDef` signature), its keyword-escaped `Name`, its `Flags`, and its | ||
| -- raw (unsanitized) metadata name. The render spells each field's type | ||
| -- at render time from the target `Dialect`, so the query projects the identity | ||
| -- and name the way `params` does for a method; the `Flags` let the struct | ||
| -- render drop a static or literal field (the `fdStatic` bit), which is not | ||
| -- instance storage, while the enum render (which reads the same view for its | ||
| -- members) keeps them. The raw name lets the enum render find its `value__` | ||
| -- storage field by its metadata name, unaffected by a `SANITIZE` override that | ||
| -- would change the escaped spelling the way a member's does. | ||
| SELECT | ||
| Id, | ||
| SANITIZE(Name) AS Name | ||
| f.Id, | ||
| SANITIZE(f.Name) AS Name, | ||
| d.Flags AS Flags, | ||
| f.Name AS Raw | ||
| FROM | ||
| fields | ||
| fields f | ||
| JOIN FieldDef d ON d.Id = f.Id |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| -- Whether the enum `TypeDef` at `:parent` bears a `System.FlagsAttribute` | ||
| -- custom attribute — the `[flags]` marking that makes the enum a bitmask. It | ||
| -- returns the enum's `Id` when the attribute is present and no row otherwise, | ||
| -- so the render projects a `[flags]` enum as an `OptionSet` (its members | ||
| -- combine) and a plain enum as a native fixed-size Swift `enum`. | ||
| -- | ||
| -- The attribute is matched by its (namespace, name) the way `guid.sql` and the | ||
| -- `interfaces` view match `GuidAttribute` — through the three encodings a | ||
| -- `CustomAttribute` names its constructor by: a `MemberRef` to a `TypeRef` (an | ||
| -- external reference, the shape real metadata uses for `System.FlagsAttribute`), | ||
| -- a `MethodDef` on the attribute `TypeDef` (a local definition), and a | ||
| -- `MemberRef` to a `TypeDef` (a `MemberRef` naming a local definition). Only the | ||
| -- namespace and name differ from `guid.sql`: `System.FlagsAttribute`, not the | ||
| -- Win32 metadata `GuidAttribute`. | ||
| -- | ||
| -- `CustomAttribute`/`MemberRef` are present in real metadata; a fixture that | ||
| -- omits the matching rows simply yields no row, so the enum reads as not a | ||
| -- `[flags]` enum — the same graceful non-match the `guid` query relies on. | ||
| SELECT | ||
| t.Id AS flags | ||
| FROM | ||
| TypeDef t | ||
| JOIN CustomAttribute c ON c.Parent_TypeDef = t.Id | ||
| JOIN MemberRef r ON c.Type_MemberRef = r.Id | ||
| JOIN TypeRef g ON r.Class_TypeRef = g.Id | ||
| WHERE | ||
| g.TypeNamespace = 'System' | ||
| AND g.TypeName = 'FlagsAttribute' | ||
| AND t.Id = :parent | ||
| UNION | ||
| SELECT | ||
| t.Id AS flags | ||
| FROM | ||
| TypeDef t | ||
| JOIN CustomAttribute c ON c.Parent_TypeDef = t.Id | ||
| JOIN MethodDef m ON c.Type_MethodDef = m.Id | ||
| JOIN TypeDef g ON m.TypeDef = g.Id | ||
| WHERE | ||
| g.TypeNamespace = 'System' | ||
| AND g.TypeName = 'FlagsAttribute' | ||
| AND t.Id = :parent | ||
| UNION | ||
| SELECT | ||
| t.Id AS flags | ||
| FROM | ||
| TypeDef t | ||
| JOIN CustomAttribute c ON c.Parent_TypeDef = t.Id | ||
| JOIN MemberRef r ON c.Type_MemberRef = r.Id | ||
| JOIN TypeDef g ON r.Class_TypeDef = g.Id | ||
| WHERE | ||
| g.TypeNamespace = 'System' | ||
| AND g.TypeName = 'FlagsAttribute' | ||
| AND t.Id = :parent |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| -- Whether the value type at `:parent` has an ABI layout the `@frozen` struct | ||
| -- projection cannot faithfully reproduce: it returns the type's `Id` when the | ||
| -- type must be rejected, no row when it renders safely. | ||
| -- | ||
| -- Only a sequential layout is safe. The `0x18` LayoutMask (§II.23.1.15) marks a | ||
| -- value type auto (`0x00`), sequential (`0x08` tdSequentialLayout), or explicit | ||
| -- (`0x10` tdExplicitLayout). An auto layout lets the runtime lay the fields out | ||
| -- in whatever order it chooses, so their declaration (FieldDef) order need not | ||
| -- be their memory order — a Swift `struct`, which lays its stored properties out | ||
| -- in source order, would then mismatch. An explicit layout places fields at | ||
| -- declared offsets — an overlapping union or a hand-laid record — which a Swift | ||
| -- `struct` cannot express. Only a sequential layout guarantees the fields lie in | ||
| -- declaration order at their natural offsets, the one arrangement a `struct` | ||
| -- reproduces, so the check admits `0x08` alone and rejects auto, explicit, and | ||
| -- the reserved `0x18`. | ||
| -- | ||
| -- A non-default packing (a `ClassLayout` row whose `PackingSize` is not zero) | ||
| -- tightens the fields below their natural alignment, shifting every offset past | ||
| -- the first. A declared size (a `ClassLayout` row whose `ClassSize` is not zero, | ||
| -- §II.22.8) fixes the total in-memory extent — padding the record out to a | ||
| -- larger size, or forcing tail padding a naturally-laid struct would not carry — | ||
| -- which no `@frozen` `struct` reproduces either. `@frozen` only freezes the | ||
| -- layout Swift itself chooses; it applies neither an explicit offset, a packing, | ||
| -- nor a declared size, so such a type would decode to the wrong size and field | ||
| -- offsets when passed to a native API. The closure walk rejects a type this | ||
| -- query returns a row for, leaving it a frontier the consumer defines rather | ||
| -- than an ABI-incompatible declaration. A `ClassSize` of zero is unspecified | ||
| -- (the natural size stands), so it is safe; any non-zero declared size is | ||
| -- rejected conservatively. | ||
| -- | ||
| -- `ClassLayout` is an optional table (`table(named:)` resolves it to an empty | ||
| -- relation when absent), so a database with no laid-out type reads no packing or | ||
| -- size row and the LEFT JOIN yields NULL, which `COALESCE` treats as the default. | ||
| SELECT | ||
| t.Id | ||
| FROM | ||
| TypeDef t | ||
| LEFT JOIN ClassLayout c ON c.Parent = t.Id | ||
| WHERE | ||
| t.Id = :parent | ||
| AND (BITAND(t.Flags, 24) <> 8 | ||
| OR COALESCE(c.PackingSize, 0) <> 0 | ||
| OR COALESCE(c.ClassSize, 0) <> 0) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,3 +7,69 @@ public protocol {{{name}}}{{#base}}: {{{.}}}{{/base}} { | |
| {{/methods}} | ||
| } | ||
| {{/interface}} | ||
| {{#struct}} | ||
| // @frozen fixes the ABI layout, so the projected struct is the C/C++ record it | ||
| // mirrors field for field — the transparent layout interop passes by value. | ||
| @frozen public struct {{{name}}} { | ||
| {{#fields}} | ||
| public var {{{name}}}: {{{type}}} | ||
| {{/fields}} | ||
|
|
||
| // The synthesized memberwise initializer is internal even for a public | ||
| // struct, so an explicit public one lets a caller outside the generated | ||
| // module construct the value type to pass to a native API. | ||
| public init({{#fields}}{{{name}}}{{#aliased}} {{{local}}}{{/aliased}}: {{{type}}}{{^last}}, {{/last}}{{/fields}}) { | ||
| {{#fields}} | ||
| self.{{{name}}} = {{{local}}} | ||
| {{/fields}} | ||
| } | ||
| } | ||
| {{/struct}} | ||
| {{#enum}} | ||
| {{#flags}} | ||
| {{! A `[flags]` enum projects to an OptionSet the way ClangImporter imports an | ||
| NS_OPTIONS C enum: its members combine as a bitmask, which a native `enum` | ||
| case — a single value — cannot name. `rawValue` is typed as the enum's | ||
| `value__` underlying type directly — the OptionSet `RawValue` associated | ||
| type is inferred from it rather than spelled as an explicit `typealias`, so | ||
| a member legitimately named `RawValue` (not a keyword `SANITIZE` escapes) | ||
| stays a `static var` and does not collide with a support typealias. The set | ||
| 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 { | ||
| public let rawValue: {{{underlying}}} | ||
| @inlinable public init(rawValue: {{{underlying}}}) { self.rawValue = rawValue } | ||
| {{#members}} | ||
| @_transparent public static var {{{name}}}: {{{owner}}} { {{{owner}}}(rawValue: {{{value}}}) } | ||
| {{/members}} | ||
| } | ||
| {{/flags}} | ||
| {{^flags}} | ||
| // A regular enum projects to an explicitly-stored raw-value struct newtype, not | ||
| // a native Swift enum: a native `enum E: {{{underlying}}}` does not carry the ABI | ||
| // layout of its raw type — `@frozen` freezes the compact representation Swift | ||
| // chooses, and the raw type is only a `RawRepresentable` conversion, so a | ||
| // two-case `enum E: UInt32` is one byte, not four, and the signature would pass | ||
| // the wrong width to native code. The newtype stores the `value__` underlying | ||
| // type directly, so it is the ABI-exact width of the C enum; its members are | ||
| // named constants and it holds any ABI value (an undeclared or aliased one) | ||
| // where a native enum would be the wrong size and trap. | ||
| @frozen public struct {{{name}}}: Hashable, Sendable { | ||
| public var rawValue: {{{underlying}}} | ||
| @inlinable public init(rawValue: {{{underlying}}}) { self.rawValue = rawValue } | ||
| {{#members}} | ||
| @_transparent public static var {{{name}}}: {{{owner}}} { {{{owner}}}(rawValue: {{{value}}}) } | ||
| {{/members}} | ||
| } | ||
| {{/flags}} | ||
| {{/enum}} | ||
| {{#delegate}} | ||
| // A delegate is a COM interface — IUnknown plus a single `Invoke` — so `@com` | ||
| // 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}}} { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When Useful? React with 👍 / 👎. |
||
| func Invoke({{#params}}_ {{{name}}}: {{{type}}}{{^last}}, {{/last}}{{/params}}){{#returns}} -> {{{.}}}{{/returns}} | ||
| } | ||
| {{/delegate}} | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When metadata defines a flags enum named
OptionSet, the generated declaration becomesstruct 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 namedHashableorSendable; qualify these support protocols (for example,Swift.OptionSet) or otherwise disambiguate them from the projected type name.Useful? React with 👍 / 👎.