Skip to main content

devscout_rs/graph/
fragment_types.rs

1use serde::{Deserialize, Serialize};
2
3use crate::extract;
4
5use super::ordered::OrderedMap;
6
7// ---------------------------------------------------------------------------
8// Fragments cache schema -- the RAW (unresolved) per-file extraction output,
9// keyed by repo-relative path. Mirrors extract.rs's DefRecord/UsingRecord/
10// RefRecord field-for-field; the serde impls live here rather than on the
11// extractor's own types.
12// ---------------------------------------------------------------------------
13
14/// Field order is significant: id, name, namespace, kind, line, methods, then
15/// the member-fact additions in that exact order -- properties, fields,
16/// methodReturns -- and after those extensionMethods, appended LAST. Each is
17/// omitted entirely when empty, so a type declaring none of them serializes
18/// exactly as it did before those additions.
19#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
20pub struct FragDef {
21    /// The id value.
22    pub id: String,
23    /// The name value.
24    pub name: String,
25    /// The namespace value.
26    pub namespace: String,
27    /// The kind value.
28    pub kind: String,
29    /// The line value.
30    pub line: usize,
31    /// The methods value.
32    pub methods: Vec<String>,
33    #[serde(default, skip_serializing_if = "Vec::is_empty")]
34    /// The properties value.
35    pub properties: Vec<String>,
36    #[serde(default, skip_serializing_if = "Vec::is_empty")]
37    /// The fields value.
38    pub fields: Vec<String>,
39    /// Member name -> declared return type NAME, in FIRST-DECLARATION SOURCE
40    /// ORDER -- deliberately `OrderedMap`, never a `BTreeMap`: this is an
41    /// ordered pair list, and sorting the keys here would break the
42    /// serialized bytes.
43    #[serde(
44        default,
45        rename = "methodReturns",
46        skip_serializing_if = "OrderedMap::is_empty"
47    )]
48    pub method_returns: OrderedMap<String>,
49    /// The extension methods this type declares, in source
50    /// order, deduped by (name, thisType, arityMin, arityMax). Appended after
51    /// `method_returns`, and omitted entirely when empty, so a type declaring
52    /// none keeps its exact prior bytes.
53    #[serde(
54        default,
55        rename = "extensionMethods",
56        skip_serializing_if = "Vec::is_empty"
57    )]
58    pub extension_methods: Vec<FragExtensionMethod>,
59    /// DIRECT base-type identifiers, source
60    /// order, deduped. Appended LAST, after `extension_methods`, omitted when
61    /// empty. A resolution input only: `resolve_graph` strips it (along with
62    /// properties/fields/extensionMethods) before graph.json's def rows, so
63    /// the on-disk def bytes are unchanged.
64    #[serde(default, skip_serializing_if = "Vec::is_empty")]
65    pub bases: Vec<String>,
66    /// The declaring type's own type-parameter names, empty for
67    /// every non-generic declaration. Appended after `bases`, omitted when
68    /// empty. A resolution input, like `bases`: the ctor-DI resolver's
69    /// "is this def itself an open-generic implementation" signal.
70    #[serde(default, rename = "typeParams", skip_serializing_if = "Vec::is_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` (`OrderedMap`
74    /// for the same reason `method_returns` is one: the serialized key order
75    /// is significant). Appended after `type_params`, omitted when
76    /// empty.
77    #[serde(
78        default,
79        rename = "baseGenericArgs",
80        skip_serializing_if = "OrderedMap::is_empty"
81    )]
82    pub base_generic_args: OrderedMap<Vec<String>>,
83    /// The methods a test framework would DISCOVER as
84    /// tests, source order, deduped. Appended LAST, after `baseGenericArgs`,
85    /// omitted when empty. Unlike its neighbours this one is NOT stripped by
86    /// `resolve_graph`: it is what `devscout tests` answers from.
87    #[serde(default, rename = "testMethods", skip_serializing_if = "Vec::is_empty")]
88    pub test_methods: Vec<String>,
89    /// Property name -> declared type fact, in source order and
90    /// under the same dedup as `properties`. An `OrderedMap` for the same
91    /// reason `method_returns` is one: the serialized key order is significant.
92    /// Appended LAST, after `test_methods`, omitted when empty. A
93    /// resolution input only, like `properties`/`fields`.
94    #[serde(
95        default,
96        rename = "propertyTypes",
97        skip_serializing_if = "OrderedMap::is_empty"
98    )]
99    pub property_types: OrderedMap<FragFact>,
100    /// Field name -> declared type fact, in source order and
101    /// under the same dedup as `fields`. Mirrors `propertyTypes` field for
102    /// field: an `OrderedMap` for the same reason (the serialized key order
103    /// is significant), a resolution input only, like `properties`/`fields`,
104    /// and this is a purely additive field -- an absent key reads back as
105    /// "no fields typed", the safe default for every fragment cached before
106    /// this field existed, so it joins the schema with no cache-version
107    /// bump. Appended right after `propertyTypes`, omitted when empty.
108    #[serde(
109        default,
110        rename = "fieldTypes",
111        skip_serializing_if = "OrderedMap::is_empty"
112    )]
113    pub field_types: OrderedMap<FragFact>,
114    /// Per method name `methodReturns` also carries an entry for,
115    /// that return type's top-level generic-arg descriptors -- the same
116    /// capture `baseGenericArgs` keeps beside `bases` (see extract.rs's
117    /// `DefRecord`). `methodReturns` itself is left exactly as it always
118    /// was (the bare return-type identifier, "Task" for
119    /// `Task<Order> GetAsync()`) so an UNAWAITED use of the same callee
120    /// keeps reading "Task" unchanged; this is a purely additive sibling
121    /// field, not a reshaping of `methodReturns`, which is what lets it
122    /// join the schema with no cache-version bump -- an absent key reads
123    /// back as "no generic args", the safe default for every fragment
124    /// cached before this field existed. Appended LAST of all, after
125    /// `fieldTypes`, omitted when empty.
126    #[serde(
127        default,
128        rename = "methodReturnArgs",
129        skip_serializing_if = "OrderedMap::is_empty"
130    )]
131    pub method_return_args: OrderedMap<Vec<String>>,
132    /// Declared method names `methods` does not carry because
133    /// `is_recorded_method` gates that list on a literal `public` modifier
134    /// (or `kind == "interface"`, where this list is always empty). Source
135    /// order, no dedup, same as `methods`. A resolution input only, like
136    /// `properties`/`fields`/`bases`: `resolve_graph` strips it before
137    /// graph.json's def rows. Consulted only for hierarchy-internal
138    /// receivers (`base.` and the `this.` shape's own base walk); the
139    /// scored tier keeps reading `methods` alone. Appended LAST of all,
140    /// after `methodReturnArgs`, omitted when empty -- purely additive, so
141    /// an absent key reads back as "no non-public methods", the safe
142    /// default for every fragment cached before this field existed, which
143    /// is what lets it join the schema with no cache-version bump.
144    #[serde(
145        default,
146        rename = "nonPublicMethods",
147        skip_serializing_if = "Vec::is_empty"
148    )]
149    pub non_public_methods: Vec<String>,
150    /// Method name -> the (min, max) argument-count range every overload
151    /// sharing that name accepts, one tuple per overload, in declaration
152    /// order -- covers every method, public and non-public alike (see
153    /// `extract::DefRecord::method_arities`). `max` is -1 for an unbounded
154    /// `params` overload, the same sentinel `FragExtensionMethod::arity_max`
155    /// already uses. An `OrderedMap` for the same reason `methodReturns` is
156    /// one: the serialized key order is significant. Appended LAST of all,
157    /// after `nonPublicMethods`, omitted when empty -- purely additive, so
158    /// an absent key reads back as "no arity facts", the safe default for
159    /// every fragment cached before this field existed, which is what lets
160    /// it join the schema with no cache-version bump.
161    #[serde(
162        default,
163        rename = "methodArities",
164        skip_serializing_if = "OrderedMap::is_empty"
165    )]
166    pub method_arities: OrderedMap<Vec<(usize, i64)>>,
167    /// Method name -> per-overload parameter type descriptors, one
168    /// `Vec<String>` per overload in declaration order -- covers every
169    /// method, public and non-public alike, same method set as
170    /// `methodArities` (see `extract::DefRecord::method_params`). A
171    /// position naming a type parameter of the enclosing method or class is
172    /// recorded as `"*"`; a parameter carrying the `this` modifier is
173    /// written `"this <descriptor>"`. A `delegate` def carries exactly one
174    /// entry here, keyed `"Invoke"`. An `OrderedMap` for the same reason
175    /// `methodArities` is one: the serialized key order is significant.
176    /// Appended LAST of all, after `methodArities`, omitted when empty.
177    /// Joined the schema with the v18 cache bump: a v17 fragment read back
178    /// carries none, and every overload-shape/`this`-marker/callee-slot
179    /// lookup this field backs would silently see no candidates.
180    #[serde(
181        default,
182        rename = "methodParams",
183        skip_serializing_if = "OrderedMap::is_empty"
184    )]
185    pub method_params: OrderedMap<Vec<Vec<String>>>,
186    /// Declared method names carrying the literal `override` modifier,
187    /// source order, deduped. Appended LAST of all, after `method_params`,
188    /// omitted when empty -- purely additive, so an absent key reads back
189    /// as "no overrides", the safe default for every fragment cached before
190    /// this field existed, which is what lets it join the schema with the
191    /// same v19 cache bump `registrations` does rather than a bump of its
192    /// own. Read by the resolver's member-level `overrides` pass to find
193    /// the nearest in-graph base member of the same name and arity.
194    #[serde(
195        default,
196        rename = "overrideMethods",
197        skip_serializing_if = "Vec::is_empty"
198    )]
199    pub override_methods: Vec<String>,
200    #[serde(default, rename = "endLine", skip_serializing_if = "is_zero")]
201    /// The end line value.
202    pub end_line: usize,
203}
204
205/// One two-type-argument DI service registration a file's invocations
206/// record -- see `extract::RegistrationRecord`.
207///
208/// `service` is the first type argument (the interface), `implementation`
209/// the second (the concrete type). Field order (`service`,
210/// `implementation`, `namespace`, `line`) is significant; neither type name
211/// carries its enclosing-namespace qualification here (that is the
212/// resolver's job, against the registration site's OWN using/alias context,
213/// read from `namespace`).
214#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
215pub struct FragRegistration {
216    /// The service (first type argument) name.
217    pub service: String,
218    /// The implementation (second type argument) name.
219    pub implementation: String,
220    /// The registration call's enclosing namespace.
221    pub namespace: String,
222    /// The registration call's 1-based line.
223    pub line: usize,
224}
225
226/// One declared type fact: the type NAME, plus its top-level
227/// type-argument descriptors when the declaration carried any. Field order
228/// (`type`, `args`) is significant, and `args` is omitted when absent.
229#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
230pub struct FragFact {
231    #[serde(rename = "type")]
232    /// The type name value.
233    pub type_name: String,
234    #[serde(default, skip_serializing_if = "Option::is_none")]
235    /// The args value.
236    pub args: Option<Vec<String>>,
237}
238
239/// One `extensionMethods` entry. Serialized field order (`name`, `thisType`,
240/// `arityMin`, `arityMax`, `thisArgs`) is significant, and serde emits struct
241/// fields in declaration order. The two arity halves are NOT optional: they joined the
242/// schema with the v6 cache rename, so no fragment this reader can meet is
243/// missing them. `arity_max` is signed because -1 is the unbounded-`params`
244/// sentinel.
245#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
246pub struct FragExtensionMethod {
247    /// The name value.
248    pub name: String,
249    #[serde(rename = "thisType")]
250    /// The this type value.
251    pub this_type: String,
252    #[serde(rename = "arityMin")]
253    /// The arity min value.
254    pub arity_min: usize,
255    #[serde(rename = "arityMax")]
256    /// The arity max value.
257    pub arity_max: i64,
258    /// Present only when the this-parameter type is generic (the key is omitted
259    /// key otherwise), so a non-generic entry keeps four fields exactly.
260    #[serde(default, rename = "thisArgs", skip_serializing_if = "Option::is_none")]
261    pub this_args: Option<Vec<String>>,
262}
263
264/// One untyped lambda parameter's callee slot (see extract.rs's `LambdaSlot`).
265///
266/// Field order (`owner`, `member`, `argCount`, `argIndex`,
267/// `arity`, `index`) is significant: serde emits struct fields in
268/// declaration order under `#[serde(rename_all = "camelCase")]`.
269#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
270#[serde(rename_all = "camelCase")]
271pub struct FragLambdaSlot {
272    /// The callee's type-name text.
273    pub owner: String,
274    /// The callee method name.
275    pub member: String,
276    /// The argument count of the callee invocation.
277    pub arg_count: usize,
278    /// The position of the lambda among the callee invocation's arguments.
279    pub arg_index: usize,
280    /// The lambda's own parameter count.
281    pub arity: usize,
282    /// This parameter's position inside the lambda's parameter list.
283    pub index: usize,
284}
285
286#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
287#[serde(untagged)]
288/// Represents `FragUsing`.
289pub enum FragUsing {
290    /// The value value.
291    Alias {
292        /// The alias name.
293        alias: String,
294        /// The aliased target.
295        target: String,
296        /// Whether the directive is global.
297        global: bool,
298    },
299    /// The value value.
300    Plain {
301        /// The imported namespace text.
302        text: String,
303        /// Whether the directive is global.
304        global: bool,
305    },
306}
307
308#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
309/// Represents `FragRef`.
310pub struct FragRef {
311    /// The kind value.
312    pub kind: String,
313    /// The name value.
314    pub name: String,
315    #[serde(default, skip_serializing_if = "Option::is_none")]
316    /// The qualified value.
317    pub qualified: Option<String>,
318    #[serde(default, skip_serializing_if = "Option::is_none")]
319    /// The member value.
320    pub member: Option<String>,
321    /// The line value.
322    pub line: usize,
323    /// Always serialized, including when `null` (imports refs) -- NOT
324    /// `skip_serializing_if`, unlike `qualified`/`member` which are omitted
325    /// entirely when absent. See extract.rs's RefRecord doc comment.
326    pub namespace: Option<String>,
327    #[serde(
328        default,
329        rename = "typeArgCount",
330        skip_serializing_if = "Option::is_none"
331    )]
332    /// The type arg count value.
333    pub type_arg_count: Option<usize>,
334    /// Type-certainty flag (see extract.rs's RefRecord). Serialized last and
335    /// only when `true`; an absent key reads back as false, which also makes
336    /// an older fragment JSON parse safely.
337    #[serde(default, skip_serializing_if = "is_false")]
338    pub generic: bool,
339    /// Receiver fact (see extract.rs's RefRecord). Appended AFTER `generic`,
340    /// and set only when a fact actually fired -- so it serializes last and
341    /// only when present, and an absent key reads back as "no fact", the safe
342    /// default.
343    #[serde(
344        default,
345        rename = "receiverType",
346        skip_serializing_if = "Option::is_none"
347    )]
348    pub receiver_type: Option<String>,
349    /// The callee arg count (see extract.rs's RefRecord). Appended AFTER
350    /// `receiverType`, set only when the member access was the callee of an
351    /// invocation -- so it serializes last and only when present, and an
352    /// absent key reads back as "not a call", which is what keeps a property
353    /// read out of the extension tier.
354    #[serde(default, rename = "argCount", skip_serializing_if = "Option::is_none")]
355    pub arg_count: Option<usize>,
356    /// Receiver generic-arg descriptors (see extract.rs's RefRecord). Appended
357    /// LAST, after `argCount`, set only when the receiver's DECLARED type was
358    /// generic -- an absent key reads back as "not generic", which is what
359    /// makes a generic-vs-non-generic pairing fail to unify.
360    #[serde(
361        default,
362        rename = "receiverArgs",
363        skip_serializing_if = "Option::is_none"
364    )]
365    pub receiver_args: Option<Vec<String>>,
366    /// Enclosing-type stack (see extract.rs's RefRecord). Appended LAST, after
367    /// `receiverArgs`, and set only when non-empty. A Vec rather than an Option
368    /// because empty and absent mean the same thing here -- a ref at namespace
369    /// level and an older cached fragment both read back as "no enclosing
370    /// type", which is what keeps them off the step.
371    #[serde(default, rename = "outerTypes", skip_serializing_if = "Vec::is_empty")]
372    pub outer_types: Vec<String>,
373    /// Generic-arg descriptors for a 'ctor-param' ref (see extract.rs's
374    /// RefRecord). Appended LAST of all, after `outerTypes`, and set only when
375    /// the parameter's type was generic.
376    #[serde(default, skip_serializing_if = "Option::is_none")]
377    pub args: Option<Vec<String>>,
378    /// (See extract.rs's RefRecord.) The type whose PROPERTY the
379    /// qualifier's last segment is, for a two-segment chain whose head the
380    /// enclosing scope could type. Appended after `args`, and never present
381    /// alongside `receiverType`.
382    #[serde(
383        default,
384        rename = "receiverPropertyOwner",
385        skip_serializing_if = "Option::is_none"
386    )]
387    pub receiver_property_owner: Option<String>,
388    /// (See extract.rs's RefRecord.) The type whose METHOD a
389    /// `var x = Q.M(...)` initializer called, and that method's name. Appended
390    /// LAST of all, always as a pair, and never alongside `receiverType`: an
391    /// absent pair reads back as "no call fact", which is what leaves the local
392    /// taken-but-unknown.
393    #[serde(
394        default,
395        rename = "receiverCallOwner",
396        skip_serializing_if = "Option::is_none"
397    )]
398    pub receiver_call_owner: Option<String>,
399    #[serde(
400        default,
401        rename = "receiverCallMember",
402        skip_serializing_if = "Option::is_none"
403    )]
404    /// The receiver call member value.
405    pub receiver_call_member: Option<String>,
406    /// `true` for a `base.` qualifier -- the member lookup starts at the
407    /// enclosing type's bases and never considers the enclosing type
408    /// itself (see `extract.rs`'s `RefRecord`). Appended LAST of all, and
409    /// omitted when `false` -- an absent key reads back as `false`, the
410    /// same as a plain `this.` receiver and as every ref kind that never
411    /// sets it, and also what makes a v15 cached fragment parse safely.
412    #[serde(default, rename = "receiverBase", skip_serializing_if = "is_false")]
413    pub receiver_base: bool,
414    /// `true` when `receiverCallOwner`/`receiverCallMember` came from an
415    /// AWAITED call (see `extract.rs`'s `RefRecord`). Appended LAST of all,
416    /// after `receiverBase`, and omitted when `false` -- an absent key reads
417    /// back as `false`, the same as an unawaited call fact and as every ref
418    /// kind that never sets it, which is also what lets a cached fragment
419    /// from before this field existed parse safely with no cache-version
420    /// bump: it simply reads back as "not awaited", the same answer the
421    /// resolver gave before this field existed.
422    #[serde(default, rename = "receiverAwaited", skip_serializing_if = "is_false")]
423    pub receiver_awaited: bool,
424    /// `true` when this ref's qualifier is a bare identifier for which the
425    /// enclosing MEMBER's own fact table (locals, parameters, lambda
426    /// parameters, patterns, `out` designations) holds ANY entry for the
427    /// name, typed or taken-but-unknown (see `extract.rs`'s `RefRecord` and
428    /// `Scope::has_local_fact`). Read ONLY by the bare-identifier
429    /// field/property fallback: a member-scoped name always shadows a
430    /// same-named field, whether or not anything vouches for its type, so
431    /// that fallback never runs when this is `true`. `false` for every
432    /// dotted or generic qualifier, for `this.`/`base.` (never asked of the
433    /// enclosing scope's local table at all), and for every ref kind but
434    /// `uses-member`. Appended LAST of all, after `receiverAwaited`, and
435    /// omitted when `false` -- an absent key reads back as `false`, the
436    /// same as every ref kind that never sets it, which is also what lets a
437    /// cached fragment from before this field existed parse safely with no
438    /// cache-version bump.
439    #[serde(default, rename = "receiverLocal", skip_serializing_if = "is_false")]
440    pub receiver_local: bool,
441    /// Set when this ref's qualifier is an untyped lambda parameter whose
442    /// type is a delegate parameter of some OTHER callee (see extract.rs's
443    /// `RefRecord`). Never present alongside `receiverType` or
444    /// `receiverCallOwner`. Appended LAST of all, after `receiverLocal`,
445    /// omitted when absent. Joined the schema with the v18 cache bump: a
446    /// v17 fragment read back carries none, and every untyped-lambda
447    /// callee-slot lookup this field backs would silently see no
448    /// candidate.
449    #[serde(
450        default,
451        rename = "receiverLambda",
452        skip_serializing_if = "Option::is_none"
453    )]
454    pub receiver_lambda: Option<FragLambdaSlot>,
455    /// `true` when `receiverType` came off a declaration whose own type
456    /// node carried a `?` (see extract.rs's `RefRecord`). Appended LAST of
457    /// all, after `receiverLambda`, and omitted when `false` -- an absent
458    /// key reads back as `false`, the same as a non-nullable receiver and
459    /// as every ref kind that never sets it. Joined the schema with the
460    /// v20 cache bump: a v19 fragment read back carries none, so a
461    /// `T?` receiver's own `Nullable<T>.Value`/`HasValue`/
462    /// `GetValueOrDefault` unwrap would silently keep resolving against
463    /// `T`'s own same-named member.
464    #[serde(default, rename = "receiverNullable", skip_serializing_if = "is_false")]
465    pub receiver_nullable: bool,
466    /// The parameter count of each delegate-shaped argument of the
467    /// invocation this ref is the callee of -- a lambda literal, or a
468    /// local function passed as a method group (see extract.rs's
469    /// `RefRecord`). Appended LAST of all, after `receiverNullable`,
470    /// omitted when absent. Joined the schema with the v20 cache bump: a
471    /// v19 fragment read back carries none, and a candidate whose delegate
472    /// parameter shape the call's own argument cannot fill would silently
473    /// keep earning a precise edge.
474    #[serde(
475        default,
476        rename = "lambdaArgArity",
477        skip_serializing_if = "Option::is_none"
478    )]
479    pub lambda_arg_arity: Option<Vec<Option<usize>>>,
480}
481
482pub(crate) fn is_false(b: &bool) -> bool {
483    !*b
484}
485
486pub(crate) fn is_zero(n: &usize) -> bool {
487    *n == 0
488}
489
490/// One declared member, with the line its own NAME token sits on.
491/// Field order (`name`, `kind`, `line`, `owner`) is significant, and `owner`
492/// is omitted when empty -- which is how
493/// a markup or resource key, owned by no C# type, serializes with three fields.
494#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
495pub struct FragName {
496    /// The name value.
497    pub name: String,
498    /// The kind value.
499    pub kind: String,
500    /// The line value.
501    pub line: usize,
502    #[serde(default, skip_serializing_if = "String::is_empty")]
503    /// The owner value.
504    pub owner: String,
505}
506
507#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
508/// Represents `Fragment`.
509pub struct Fragment {
510    /// The defs value.
511    pub defs: Vec<FragDef>,
512    /// The usings value.
513    pub usings: Vec<FragUsing>,
514    /// The refs value.
515    pub refs: Vec<FragRef>,
516    /// Appended LAST, after `refs`. Always serialized, like its
517    /// three siblings: a fragment's top-level arrays are a fixed shape, and
518    /// only the fields INSIDE a record follow the omit-when-empty rule.
519    #[serde(default)]
520    pub names: Vec<FragName>,
521    /// The file's two-type-argument DI service registrations -- see
522    /// `FragRegistration`. Appended LAST, after `names`, and omitted when
523    /// empty (unlike its four siblings above, which pre-date the
524    /// omit-when-empty convention): a file recording none serializes exactly
525    /// as it did before this field existed.
526    #[serde(default, skip_serializing_if = "Vec::is_empty")]
527    pub registrations: Vec<FragRegistration>,
528}
529
530/// The two shapes a cached fragment can have. Each rel is keyed to whichever
531/// shape the file's grammar produced; the `ts: 1` tag is what tells them
532/// apart, on disk and at the
533/// resolver's door. Untagged, and TS FIRST: a C#/markup fragment carries no
534/// `ts` key (so the TS arm always fails on it) and a TS fragment carries no
535/// `usings` (so the C# arm always fails on it) -- the discrimination is total
536/// in both directions, never a first-match-wins guess.
537#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
538#[serde(untagged)]
539pub enum AnyFragment {
540    /// Represents `Ts`.
541    Ts(extract::TsFragment),
542    /// Represents `Cs`.
543    Cs(Fragment),
544}
545
546impl From<Fragment> for AnyFragment {
547    fn from(f: Fragment) -> Self {
548        AnyFragment::Cs(f)
549    }
550}
551
552impl From<extract::TsFragment> for AnyFragment {
553    fn from(f: extract::TsFragment) -> Self {
554        AnyFragment::Ts(f)
555    }
556}