Skip to main content

devscout_rs/extract/
types.rs

1// ---------------------------------------------------------------------------
2// Graph fragment extraction and its helpers. Scope: defs =
3// namespace-level+nested types with public method names plus enum members;
4// refs = using directives, base-list types,
5// object-creation types, field/property/parameter/return types, generic
6// type arguments, and member-access qualifier.member candidates. No
7// call-graph resolution.
8// ---------------------------------------------------------------------------
9
10/// Represents `DefRecord`.
11pub struct DefRecord {
12    /// The id value.
13    pub id: String,
14    /// The name value.
15    pub name: String,
16    /// The namespace value.
17    pub namespace: String,
18    /// The kind value.
19    pub kind: String,
20    /// The line value.
21    pub line: usize,
22    /// The methods value.
23    pub methods: Vec<String>,
24    /// Declared property names, source order, deduped
25    /// (indexers excluded by construction: `indexer_declaration` is a
26    /// different grammar node). NO accessibility filter, deliberately
27    /// asymmetric with `methods`: static/const/readonly/expression-bodied all
28    /// count and only indexers are excluded, so a private member is still a
29    /// member of the type. Empty for every
30    /// enum-member def.
31    pub properties: Vec<String>,
32    /// Declared field names, source order, deduped, every
33    /// declarator of every `field_declaration` ("private int a, b;"
34    /// contributes both). `event_field_declaration` is a distinct node type
35    /// and is NOT a field for this purpose. Same no-accessibility-filter
36    /// rule as `properties`.
37    pub fields: Vec<String>,
38    /// (method name, declared return type NAME) pairs in
39    /// FIRST-declaration source order, parallel to `methods` (same
40    /// `is_recorded_method` predicate, so the two can never drift). The
41    /// first declaration of a name claims the slot outright: a later
42    /// overload with a different return type is ignored, and a first
43    /// declaration whose return type yields no fact (void, var, a
44    /// predefined type) BLOCKS the name rather than letting a later
45    /// overload stand in for it. A Vec of pairs, not a map: the serialized
46    /// key order is significant (see graph.rs's `FragDef`).
47    pub method_returns: Vec<(String, String)>,
48    /// The extension methods this type declares: every method
49    /// whose FIRST parameter carries the `this` modifier, in source order,
50    /// deduped by (name, thisType, arityMin, arityMax). Appended LAST, after
51    /// `method_returns`, and omitted at serialization when empty. Unlike
52    /// `methods` there is NO accessibility filter (`internal static class
53    /// FooExtensions` is the shape this feature exists for) and no `static`
54    /// check on either the method or its class -- C# already disallows a
55    /// `this` parameter anywhere else, so the parameter modifier IS the
56    /// discriminator.
57    pub extension_methods: Vec<ExtensionMethod>,
58    /// The DIRECT base-type identifiers this
59    /// declaration lists, in source order, deduped. Same `base_list` traversal
60    /// `record_base_list` walks for its `inherits` refs, reduced to a base
61    /// IDENTIFIER because the resolver re-RESOLVES these names through the
62    /// ordinary ladder rather than matching them; the resulting closure is
63    /// what lets the extension tier see an inherited instance member and
64    /// decline. Appended after `extension_methods` and omitted when empty.
65    pub bases: Vec<String>,
66    /// The declaring type's OWN type-parameter names (`class
67    /// MongoRepository<T>` records `["T"]`), empty for every non-generic
68    /// declaration. This is the ctor-DI resolver's "is this def itself an
69    /// open-generic implementation" signal. Appended after `bases`, omitted
70    /// when empty.
71    pub type_params: Vec<String>,
72    /// Per base name that carried a type-argument list, that
73    /// list's generic-arg descriptors relative to `type_params` (a `"*"`
74    /// wildcard marks a position that is a pass-through of the declaring
75    /// type's own parameter). A `Vec` of pairs, not a map, for the same
76    /// reason `method_returns` is: the serialized key order is significant.
77    /// Appended after `type_params`, omitted when empty; a base
78    /// with no type-argument list at all contributes no entry.
79    pub base_generic_args: Vec<(String, Vec<String>)>,
80    /// The methods this type declares that a test
81    /// framework would DISCOVER as tests, in source order, deduped. Appended
82    /// LAST, after `base_generic_args`, and omitted at serialization when
83    /// empty. The attribute IS the fact, which is why this is the one member
84    /// fact with no accessibility filter at all and why no file, folder or
85    /// type-name convention is read anywhere.
86    pub test_methods: Vec<String>,
87    /// (property name, declared type fact) pairs for exactly the
88    /// properties `properties` records, in the same source order and under the
89    /// same dedup. A property whose declared type yields no fact (a predefined
90    /// type) has no entry, so the two lists are parallel but not equal in
91    /// length. A `Vec` of pairs, not a map, for the same reason
92    /// `method_returns` is one: the serialized key order is significant.
93    /// Appended LAST, after `test_methods`.
94    pub property_types: Vec<(String, Fact)>,
95    /// (field name, declared type fact) pairs for exactly the
96    /// fields `fields` records, in the same source order and under the same
97    /// dedup. A field whose declared type yields no fact (a predefined type)
98    /// has no entry, so the two lists are parallel but not equal in length.
99    /// Every declarator of one `field_declaration` shares that declaration's
100    /// own type node, so `private int a, b;` gives `a` and `b` the SAME
101    /// fact -- the same declarator sharing `fields`'s own collection loop
102    /// already relies on. A `Vec` of pairs, not a map, for the same reason
103    /// `property_types` is one: the serialized key order is significant.
104    /// Appended LAST, after `property_types`.
105    pub field_types: Vec<(String, Fact)>,
106    /// Per method name that `method_returns` also records an
107    /// entry for, that return type's top-level generic-arg descriptors --
108    /// the same capture `base_generic_args` keeps beside `bases`, for the
109    /// same reason: `method_returns` records only the return type's bare
110    /// identifier ("Task" for `Task<Order> GetAsync()`), and this is what
111    /// lets the resolver unwrap ONE `Task<...>`/`ValueTask<...>` layer off
112    /// an AWAITED call's callee without ever touching `method_returns`
113    /// itself, which an unawaited use of the very same callee reads
114    /// unchanged. A `Vec` of pairs, not a map, for the same reason
115    /// `method_returns` is one: the serialized key order is significant.
116    /// Appended LAST of all, after `field_types`; a method whose return
117    /// type carries no type-argument list at all contributes no entry.
118    pub method_return_args: Vec<(String, Vec<String>)>,
119    /// Declared METHOD names this type does NOT record in `methods`,
120    /// because `is_recorded_method` gates that list on a literal `public`
121    /// modifier (or `kind == "interface"`, where every method already
122    /// counts as public and this list is always empty). Same predicate,
123    /// same `method_declaration` node kind, same source order and dedup as
124    /// `methods` -- the two lists partition a type's method declarations
125    /// with no overlap and no gap. Unlike `properties`/`fields`, which
126    /// already carry every accessibility with no filter at all, `methods`
127    /// needed a counterpart list for a non-public method (protected,
128    /// internal, private or no-modifier/private-by-default) to be
129    /// recorded anywhere. Consulted by the resolver ONLY for
130    /// hierarchy-internal receivers -- a `base.` lookup and the `this.`
131    /// shape's own typed-receiver walk -- never by the scored tier's
132    /// vouching or veto, which keep reading `methods` alone. Appended
133    /// LAST of all, after `method_return_args`.
134    pub non_public_methods: Vec<String>,
135    /// Method name -> the (min, max) argument-count RANGE every overload
136    /// sharing that name accepts, one tuple per overload, in declaration
137    /// order -- covers EVERY `method_declaration`, public and non-public
138    /// alike (unlike `methods`/`non_public_methods`, which only say a name
139    /// exists, this says what a CALL of that name needs to look like).
140    /// `min` excludes a parameter carrying a default value or the `params`
141    /// modifier; `max` is the total parameter count, or -1 (unbounded) when
142    /// the trailing parameter is a `params` array -- the same sentinel
143    /// `ExtensionMethod::arity_max` already uses. A `Vec` of pairs, not a
144    /// map, for the same reason `method_returns` is one: the serialized key
145    /// order is significant. Consulted by the resolver's arity-aware call
146    /// vouching: a ref carrying an `argCount` binds to `methods`/
147    /// `non_public_methods` only when SOME overload's range admits it: a
148    /// same-named instance member at the wrong arity does not shadow the
149    /// extension tier. A read (no `argCount`) never consults this table.
150    /// Appended LAST of all, after `non_public_methods`.
151    pub method_arities: Vec<(String, Vec<(usize, i64)>)>,
152    /// Method name -> per-overload parameter type descriptors (see
153    /// `type_descriptor`), one `Vec<String>` per overload in declaration
154    /// order -- covers EVERY `method_declaration`, public and non-public
155    /// alike, same method set as `method_arities`. A position naming the
156    /// enclosing method's or type's own type parameter is recorded as `"*"`
157    /// (wildcard); a parameter carrying the `this` modifier is written
158    /// `"this <descriptor>"` (the extension-method marker). For a
159    /// `delegate` def this carries exactly one entry, keyed `"Invoke"`,
160    /// built from the delegate's own parameter list (a delegate has no
161    /// body, so it is not one of the `method_declaration` overloads this
162    /// otherwise iterates). A `Vec` of pairs, not a map, for the same
163    /// reason `method_returns` is one: the serialized key order is
164    /// significant. Appended LAST of all, after `method_arities`.
165    pub method_params: Vec<(String, Vec<Vec<String>>)>,
166    /// Declared method names carrying the literal `override` modifier,
167    /// source order, deduped. Appended LAST of all, after `method_params`.
168    /// Consulted only by the resolver's member-level `overrides` pass,
169    /// itself run only for a type the resolver already knows is a
170    /// registered DI implementation -- see `graph.rs`'s `FragDef`.
171    pub override_methods: Vec<String>,
172    /// Per base name that carries a top-level type argument which is
173    /// ITSELF generic, that argument list rendered by `type_descriptor`
174    /// (nested structure intact) rather than the flattened bare
175    /// identifiers `base_generic_args` carries -- `IConsumer<Batch<
176    /// LoanRequested>>` records `[("IConsumer", ["Batch<LoanRequested>"])]`
177    /// here, so the inner message type survives extraction instead of
178    /// collapsing to `Batch`. A base whose arguments are all non-generic
179    /// contributes no entry: `base_generic_args` already carries that
180    /// shape, so a second copy would add no fact. Appended LAST of all,
181    /// after `override_methods`.
182    pub base_type_args: Vec<(String, Vec<String>)>,
183    /// Every distinct single type argument the type's own properties wrap
184    /// (`Event<LoanRequested>` records `LoanRequested`), in declaration
185    /// order. A long-running handler names the further messages it binds
186    /// this way rather than on its base list, so the base list alone would
187    /// miss them. Recorded for every type; the resolver keeps these only
188    /// for a type it already holds to be a handler. Appended LAST of all,
189    /// after `base_type_args`.
190    pub property_message_args: Vec<String>,
191    /// Base names (a subset of `bases`) whose message-position argument --
192    /// `base_generic_args`' own position 0 -- is itself an array type
193    /// (`IConsumer<M[]>`). `base_generic_args`/`generic_arg_descriptors`
194    /// both already look THROUGH an `array_type` to its element (the same
195    /// unwrapping `base_type_identifier` performs generally), so this is
196    /// the only place left that still knows the wrapper was there at all --
197    /// the lane-owned sibling of the array bit `extract::receivers::type_fact`
198    /// keeps for a declared local/parameter. A base whose nested argument
199    /// already carries its own array suffix through `base_type_args`
200    /// (`IConsumer<Batch<M[]>>`) needs no entry here: that reading is
201    /// structural, off the rendered descriptor, and never loses the
202    /// bracket. Appended LAST of all, after `property_message_args`.
203    pub array_message_bases: Vec<String>,
204    /// 1-based last line of the complete declaration node.
205    pub end_line: usize,
206}
207
208/// One message-bus publish-site fact: an invocation handing a message
209/// instance to a bus, mediator or scheduler.
210///
211/// `message` is a RESOLVED type descriptor -- read off a generic type
212/// argument, a constructed type, or a declared local/parameter type
213/// through the ordinary scope ladder, never a bare identifier merely
214/// co-located on the same source line as the call. See `extract/bus.rs`.
215#[derive(Debug, Clone, PartialEq)]
216pub struct PublishRecord {
217    /// The invoked method name (`Publish`, `PublishAsync`, `SubmitJob`,
218    /// `Reply`, `Send`).
219    pub verb: String,
220    /// The resolved message-type descriptor the call hands off.
221    pub message: String,
222    /// The call's enclosing namespace.
223    pub namespace: String,
224    /// The call's 1-based line -- the SAME line no matter how many source
225    /// lines the call itself spans, so a split call still records exactly
226    /// one site.
227    pub line: usize,
228    /// The walk's own `type_stack` (see `RefRecord::outer_types`).
229    pub outer_types: Vec<String>,
230    /// The name of the method this call sits in, recorded ONLY when the
231    /// message is an unbound type parameter. That pairing is the signature
232    /// of a forwarding wrapper -- a method that hands its own caller's
233    /// message on to a bus -- and the method name is what the resolver
234    /// needs to recognize this repository's own calls to it. An ordinary
235    /// publish site names a concrete message and records nothing here.
236    /// Appended LAST, after `outer_types`.
237    pub enclosing_method: Option<String>,
238    /// The call's own argument count -- see `extract::refs::invocation_arg_count`.
239    /// Appended LAST, after `enclosing_method`.
240    pub arg_count: usize,
241    /// Set when the call sits inside a lambda body that is itself an
242    /// argument of an ENCLOSING invocation (a test-double setup/verify
243    /// lambda, or an ordinary delegate lambda -- this fact alone does not
244    /// distinguish the two; see `extract::bus::enclosing_call_through_lambda`).
245    /// Appended LAST, after `arg_count`.
246    pub enclosing_call: Option<EnclosingCallFact>,
247}
248
249/// The enclosing invocation a publish call's own lambda sits inside, when
250/// it sits inside one at all. See `PublishRecord::enclosing_call`.
251#[derive(Debug, Clone, PartialEq)]
252pub struct EnclosingCallFact {
253    /// The enclosing invocation's own callee bare name.
254    pub verb: String,
255    /// The 0-based position the lambda occupies among the enclosing
256    /// invocation's arguments.
257    pub arg_position: usize,
258    /// The enclosing invocation's own total argument count.
259    pub arg_count: usize,
260}
261
262/// One `extensionMethods` entry. Field order (`name`, `thisType`, `arityMin`,
263/// `arityMax`, `thisArgs`) is significant: it fixes the serialized field
264/// order.
265#[derive(Debug, Clone, PartialEq)]
266pub struct ExtensionMethod {
267    /// The name value.
268    pub name: String,
269    /// The this type value.
270    pub this_type: String,
271    /// Non-this parameters a caller cannot
272    /// leave out: no default value AND no `params` modifier.
273    pub arity_min: usize,
274    /// Total non-this parameter count, or -1 when the trailing parameter is a
275    /// `params` array (unbounded above). Signed precisely so -1 can be the
276    /// sentinel written as a JSON number.
277    pub arity_max: i64,
278    /// The this-parameter type's TOP-LEVEL
279    /// type-argument descriptors, present only when that type is generic.
280    /// A position naming one of the method's or the enclosing class's own type
281    /// parameters is recorded as "*" (wildcard).
282    pub this_args: Option<Vec<String>>,
283}
284
285/// Represents `RefRecord`.
286pub struct RefRecord {
287    /// The kind value.
288    pub kind: String,
289    /// The name value.
290    pub name: String,
291    /// The qualified value.
292    pub qualified: Option<String>,
293    /// The member value.
294    pub member: Option<String>,
295    /// The line value.
296    pub line: usize,
297    /// `None` only for 'imports' refs -- a using directive is not
298    /// namespace-scoped, so `ns` is deliberately `null`. Every other ref
299    /// carries `Some(ns)`, where `ns`
300    /// may itself be the empty string at file scope.
301    pub namespace: Option<String>,
302    /// The type arg count value.
303    pub type_arg_count: Option<usize>,
304    /// `true` when a uses-member qualifier carried a
305    /// type-argument list anywhere (`Cache<T>.x`, `Ns.Cache<T>.x`): syntax
306    /// only a TYPE can carry, so the resolver treats it as type-certainty
307    /// for non-enum member emission. Always `false` for every other ref
308    /// kind.
309    pub generic: bool,
310    /// The declared type NAME of the receiver a uses-member
311    /// qualifier names, when the enclosing scope holds exactly one fact for
312    /// it (a declared local, a `var x = new T()` local, a parameter, a
313    /// field, or a primary-constructor parameter). Set ONLY for a BARE,
314    /// non-generic qualifier -- see the `member_access_expression` arm's
315    /// guard, which is what makes the chain-tail hazard structurally
316    /// impossible. Always `None` for every other ref kind.
317    pub receiver_type: Option<String>,
318    /// The argument count of the call this
319    /// member access is the CALLEE of (`argument_list` named-child count),
320    /// recorded only when the access is the `function` field of an
321    /// `invocation_expression`. A property read (`x.P`) carries `None`, which
322    /// is what keeps it out of the arity-matched extension tier entirely.
323    /// Appended AFTER `receiver_type`. Always `None` for every other ref kind.
324    pub arg_count: Option<usize>,
325    /// The DECLARED receiver type's top-level
326    /// type-argument descriptors, present only when that type is generic. A
327    /// position naming a type parameter of the enclosing method or class is
328    /// recorded as "*" (wildcard): nothing at the fact site knows its binding.
329    /// Travels with `receiver_type` -- the fact carries both or neither.
330    /// Appended LAST, after `arg_count`.
331    pub receiver_args: Option<Vec<String>>,
332    /// The walk's own `type_stack` verbatim: the enclosing type simple
333    /// names, OUTERMOST first, the same order `type_id` joins with "+" to build
334    /// a nested def id. Empty at namespace level, and empty is exactly what an
335    /// absent key deserializes to. Appended LAST, after `receiver_args`.
336    pub outer_types: Vec<String>,
337    /// Generic-arg descriptors for a 'ctor-param' ref's
338    /// parameter type (same descriptor shape as `receiver_args`: a `"*"`
339    /// wildcard for a pass-through of the enclosing type's own type
340    /// parameter), present only when that type is generic. `None` for every
341    /// other ref kind. Appended LAST of all, after `outer_types` -- 'ctor-param'
342    /// is a ref kind no other caller touches, so putting it after that
343    /// "last of all" field costs no other ref kind a single byte.
344    pub args: Option<Vec<String>>,
345    /// The type whose PROPERTY the qualifier's last segment is, for
346    /// a two-segment chain whose head the enclosing scope has a fact for
347    /// ("a.Settings" in `a.Settings.Reload()`). Never travels with
348    /// `receiver_type`: that one is bare-qualifier-only and this one is
349    /// dotted-qualifier-only, which is what keeps the chain-tail hazard
350    /// structurally impossible -- the resolver reaches the tail's type through
351    /// the head type's RECORDED property types, never by inheriting the head's
352    /// own. Appended after `args`.
353    pub receiver_property_owner: Option<String>,
354    /// The type whose METHOD a `var x = Q.M(...)` initializer
355    /// called, and that method's name. Always set as a pair, and never
356    /// alongside `receiver_type`: the local's type is whatever `M` returns, a
357    /// lookup only the resolver can do. Appended LAST of all.
358    pub receiver_call_owner: Option<String>,
359    /// The receiver call member value.
360    pub receiver_call_member: Option<String>,
361    /// `true` for a `base.M` qualifier -- the member lookup starts at the
362    /// enclosing type's bases and never considers the enclosing type
363    /// itself. `false` for every other ref kind, including a plain `this.M`
364    /// qualifier (which resolves through the ordinary typed-receiver path
365    /// against the enclosing type itself). Appended LAST of all.
366    pub receiver_base: bool,
367    /// `true` when this ref's `receiver_call_owner`/`receiver_call_member`
368    /// pair came from an AWAITED call (`var x = await Q.M()`, `_client` a
369    /// field: `var order = await _client.FetchAsync()`) -- the resolver reads
370    /// it to decide whether to unwrap exactly one `Task<...>`/`ValueTask<...>`
371    /// layer off `M`'s recorded return type before typing the local. `false`
372    /// for an unawaited call fact and for every ref carrying no call fact at
373    /// all, which is what keeps `var t = x.FetchAsync(); t.Wait();` typed as
374    /// `Task` rather than unwrapped. Appended LAST of all, after
375    /// `receiver_base`.
376    pub receiver_awaited: bool,
377    /// `true` when this ref's qualifier is a bare identifier for which the
378    /// enclosing MEMBER's own fact table -- `Scope::has_local_fact`, the
379    /// SAME `member_facts` table `receiver_fact_for` reads first -- holds
380    /// ANY entry for the name: a local, a parameter, an explicitly-typed
381    /// lambda parameter, a pattern designation, or an `out` designation,
382    /// typed or taken-but-unknown. A member-scoped name always shadows a
383    /// same-named field regardless of whether anything vouches for its
384    /// type, so the resolver's bare-identifier field/property fallback
385    /// (Unit B) reads this to refuse running at all when it is `true` --
386    /// the one shape a resolved `receiver_type`/`receiver_call_owner` of
387    /// `None` cannot itself distinguish from "no fact anywhere for this
388    /// name". `false` for a dotted or generic qualifier, for `this.`/
389    /// `base.` (never asked of the enclosing scope's local table), and for
390    /// every ref kind but `uses-member`. Appended LAST of all, after
391    /// `receiver_awaited`.
392    pub receiver_local: bool,
393    /// Set when this ref's qualifier is an untyped lambda parameter whose
394    /// type is a delegate parameter of some OTHER callee -- a lookup only
395    /// the resolver can do. Never set alongside `receiver_type` or
396    /// `receiver_call_owner`: the three are mutually exclusive on one ref.
397    /// `receiver_local` still reads `true` for such a ref (the parameter
398    /// IS a member-scoped name), and `receiver_type` stays `None`. Appended
399    /// LAST of all, after `receiver_local`.
400    pub receiver_lambda: Option<LambdaSlot>,
401    /// `true` when `receiver_type` came off a declaration whose own type
402    /// node was a `nullable_type` (`Widget? w`) -- see `Fact::nullable`,
403    /// which this carries verbatim off the SAME receiver fact
404    /// `receiver_type`/`receiver_args` already came off. `false` for every
405    /// ref kind, and for a `receiver_type` that came from anywhere but a
406    /// plain declared-type fact (a call-hop return, a lambda-slot type, a
407    /// bare field/property fallback: none of those can be nullable-
408    /// annotated, since none of them read a type node directly). Appended
409    /// LAST of all, after `receiver_lambda`.
410    pub receiver_nullable: bool,
411    /// The parameter count of each delegate-shaped argument of the
412    /// invocation this ref is the callee of -- a lambda literal, or a bare
413    /// identifier naming one unshadowed local function in scope -- one
414    /// entry per argument position (`None` at every other position), or
415    /// `None` entirely when the ref is not invocation-shaped or names no
416    /// such argument at all. `x => ...` counts 1 parameter, `(a, b) => ...`
417    /// counts 2, `() => ...` counts 0, a local function counts its own
418    /// declared parameters -- the argument's OWN arity, never the delegate
419    /// type it will end up bound to (that binding is exactly what this fact
420    /// lets the resolver judge, rather than assume). Appended LAST of all,
421    /// after `receiver_nullable`.
422    pub lambda_arg_arity: Option<Vec<Option<usize>>>,
423}
424
425/// Represents `UsingRecord`.
426pub enum UsingRecord {
427    /// The value value.
428    Alias {
429        /// The alias name.
430        alias: String,
431        /// The aliased target.
432        target: String,
433        /// Whether the directive is global.
434        global: bool,
435    },
436    /// The value value.
437    Plain {
438        /// The imported namespace text.
439        text: String,
440        /// Whether the directive is global.
441        global: bool,
442    },
443}
444
445/// Represents `Extraction`.
446pub struct Extraction {
447    /// The purpose value.
448    pub purpose: Option<String>,
449    /// The defs value.
450    pub defs: Vec<DefRecord>,
451    /// The usings value.
452    pub usings: Vec<UsingRecord>,
453    /// The refs value.
454    pub refs: Vec<RefRecord>,
455    /// Every member the file's types declare, appended LAST after
456    /// `refs` in both the fragment and the `extract-dump` shape.
457    pub names: Vec<NameRecord>,
458    /// Every two-type-argument DI service registration the file's
459    /// invocations record, appended LAST after `names`. See
460    /// `RegistrationRecord`.
461    pub registrations: Vec<RegistrationRecord>,
462    /// Every message-bus publish-site fact the file's invocations record,
463    /// appended LAST after `registrations`. See `PublishRecord`.
464    pub publishes: Vec<PublishRecord>,
465    /// Every one-type-argument handler registration the file's invocations
466    /// record, appended LAST after `publishes`. See
467    /// `HandlerRegistrationRecord`.
468    pub handler_registrations: Vec<HandlerRegistrationRecord>,
469}
470
471/// One one-type-argument handler registration: an invocation whose name
472/// reads as an installation and which names exactly one type.
473///
474/// This is the fact a repository's own message vocabulary is derived from.
475/// The named type's base list says which generic base that repository
476/// treats as a handler shape and at which position it carries its message,
477/// so a bus whose type names appear nowhere in this engine is still
478/// recognized from the repository's own registrations. `handler` carries
479/// the type argument's descriptor as written; resolving it is the
480/// resolver's job, not the extractor's.
481#[derive(Debug, Clone, PartialEq)]
482pub struct HandlerRegistrationRecord {
483    /// The registered type's descriptor, as written.
484    pub handler: String,
485    /// The registration call's enclosing namespace.
486    pub namespace: String,
487    /// The registration call's 1-based line.
488    pub line: usize,
489}
490
491/// One two-type-argument DI service registration.
492///
493/// The shape is an invocation whose method name begins `Add` or `TryAdd`
494/// and ends `Singleton`, `Scoped` or `Transient`, carrying exactly two type
495/// arguments -- the first the service type, the second the implementation
496/// type. `service`/`implementation` carry the type argument's raw text
497/// verbatim (dotted when the source wrote it qualified); splitting a dotted
498/// name into its bare tail plus its full qualified form, the way an
499/// ordinary type reference does, is the resolver's job, not the
500/// extractor's.
501#[derive(Debug, Clone, PartialEq)]
502pub struct RegistrationRecord {
503    /// The service (first type argument) name, as written.
504    pub service: String,
505    /// The implementation (second type argument) name, as written.
506    pub implementation: String,
507    /// The registration call's enclosing namespace.
508    pub namespace: String,
509    /// The registration call's 1-based line.
510    pub line: usize,
511}
512
513/// One declared name and the line its own NAME TOKEN sits on. Deliberately
514/// not the declaration node's start row: a member's span begins
515/// at its attribute list, so an attributed member would point a reader at the
516/// `[` line instead of the line carrying the name they searched for.
517///
518/// Serialized field order (`name`, `kind`, `line`, `owner`) is significant;
519/// `owner` is omitted when empty, which is how a markup or resource
520/// key -- built by `markup.rs`, owned by no C# type -- serializes with three
521/// fields.
522#[derive(Debug, Clone, PartialEq)]
523pub struct NameRecord {
524    /// The name value.
525    pub name: String,
526    /// The kind value.
527    pub kind: String,
528    /// The line value.
529    pub line: usize,
530    /// The owner value.
531    pub owner: String,
532}
533
534/// One untyped lambda parameter's callee slot.
535///
536/// The name's type is the delegate parameter of the invocation the lambda
537/// sits inside as an argument -- a lookup only the resolver can do (it has
538/// to look up the callee's own delegate-typed parameter at `arg_index` and
539/// read ITS `index`-th parameter type). `owner`/`member` name the callee like
540/// a call fact's `type_name`/`call` do; `arg_count`/`arg_index` locate the
541/// lambda among the callee's arguments; `arity`/`index` locate this
542/// parameter inside the lambda itself.
543#[derive(Debug, Clone, PartialEq, Eq)]
544pub struct LambdaSlot {
545    /// The type-name text the callee is invoked on: the identifier's
546    /// in-file fact type, the identifier itself when the name is not taken
547    /// (a static class), or the innermost enclosing type for `this.M(...)`
548    /// / bare `M(...)`.
549    pub owner: String,
550    /// The callee method name.
551    pub member: String,
552    /// The argument count of the callee invocation.
553    pub arg_count: usize,
554    /// The position of the lambda among the callee invocation's arguments.
555    pub arg_index: usize,
556    /// The lambda's own parameter count.
557    pub arity: usize,
558    /// This parameter's position inside the lambda's parameter list.
559    pub index: usize,
560}
561
562/// One receiver fact: the declared type NAME plus that type's top-level
563/// type-argument descriptors when it carried any. Two facts agree only when
564/// BOTH halves do, so `Box<int> a` and `Box<string> a` in sibling blocks
565/// conflict exactly like two different type names would.
566#[derive(Clone, PartialEq, Eq)]
567pub struct Fact {
568    /// The type name value.
569    pub type_name: String,
570    /// The args value.
571    pub args: Option<Vec<String>>,
572    /// When set, the name's type is whatever the method of this name returns
573    /// on the type `type_name` stands for, a lookup only the resolver can do.
574    /// Part of the equality the table compares, so `var x = A.Make()` and
575    /// `var x = A.Build()` in sibling blocks conflict exactly like two
576    /// different type names would.
577    pub call: Option<String>,
578    /// `true` when this is a call fact (`call.is_some()`) built from an
579    /// AWAITED invocation (`var x = await Q.M()`); meaningless -- and always
580    /// `false` -- when `call` is `None`. Part of the equality the table
581    /// compares, so `var x = Q.M()` and `var x = await Q.M()` in sibling
582    /// blocks conflict exactly like two different callees would, rather than
583    /// silently picking one awaited-ness for a name the source gives two.
584    pub awaited: bool,
585    /// `true` when the declaration's own type node was, at its TOP level, an
586    /// `array_type` (`Widget[]`) -- set ONLY by `type_fact`, `false`
587    /// everywhere else (a call fact, a receiver-args unwrap, `this`/`base`'s
588    /// own fact: none of those can be an array). `base_type_identifier`
589    /// already collapses `Widget[]` to the same `type_name`/`args` shape a
590    /// plain `Widget` field would carry (the element type's own base
591    /// identifier, no type-argument list), so this bit is the ONLY signal
592    /// left that distinguishes "one Widget" from "an array of Widget" --
593    /// needed by the lambda-parameter element-typing rule, which must tell
594    /// `Widget[]` apart from `Widget` even though both facts otherwise read
595    /// identically. Part of the equality the table compares.
596    pub is_array: bool,
597    /// `true` when the declaration's own type node was, at its TOP level, a
598    /// `nullable_type` (`Widget?`) -- set ONLY by `type_fact`, `false`
599    /// everywhere else, mirroring `is_array`: a call fact, a receiver-args
600    /// unwrap and `this`/`base`'s own fact are never nullable-annotated.
601    /// `base_type_identifier` already unwraps `Widget?` to the same
602    /// `type_name` a plain `Widget` field would carry, so this bit is the
603    /// only signal left that a `System.Nullable<T>` sits between the
604    /// declared name and the value -- needed so the resolver can refuse a
605    /// member the wrapper itself owns (`.Value`, `.HasValue`,
606    /// `.GetValueOrDefault`) rather than binding `T`'s own same-named
607    /// member. Part of the equality the table compares.
608    pub nullable: bool,
609    /// When set, the name is an untyped lambda parameter whose type is the
610    /// corresponding delegate parameter of the callee this slot names -- a
611    /// lookup only the resolver can do; `type_name` is empty and `call` is
612    /// `None`. Part of the equality the table compares, so two sibling
613    /// lambdas binding the same parameter name to different callees
614    /// conflict exactly like two different type names would.
615    pub lambda: Option<LambdaSlot>,
616}