Skip to main content

engine_input_producers/
lib.rs

1//! Typed producers for source, resolution, and semantic engine-input facts.
2
3use std::collections::BTreeMap;
4
5use serde::{Deserialize, Deserializer, Serialize, de};
6
7pub mod engine_contract_v2_idl_generated;
8mod expression_domain;
9mod expression_semantics;
10mod query_plan;
11mod selector_usage;
12mod semantic;
13mod source_resolution;
14mod source_side;
15#[cfg(test)]
16mod test_support;
17mod type_facts;
18
19pub use expression_domain::collect_expression_domain_flow_graphs;
20pub use expression_domain::summarize_expression_domain_call_site_flow_analysis_input;
21pub use expression_domain::summarize_expression_domain_candidates_input;
22pub use expression_domain::summarize_expression_domain_canonical_candidate_bundle_input;
23pub use expression_domain::summarize_expression_domain_canonical_producer_signal_input;
24pub use expression_domain::summarize_expression_domain_control_flow_analysis_input;
25pub use expression_domain::summarize_expression_domain_evaluator_candidates_input;
26pub use expression_domain::summarize_expression_domain_flow_analysis_input;
27pub use expression_domain::summarize_expression_domain_fragments_input;
28pub use expression_domain::summarize_expression_domain_plan_input;
29pub use expression_domain::summarize_expression_domain_provenance_explanations_input;
30pub use expression_domain::summarize_expression_domain_reduced_product_iteration_input;
31pub use expression_semantics::summarize_expression_semantics_candidates_input;
32pub use expression_semantics::summarize_expression_semantics_canonical_candidate_bundle_input;
33pub use expression_semantics::summarize_expression_semantics_canonical_producer_signal_input;
34pub use expression_semantics::summarize_expression_semantics_evaluator_candidates_input;
35pub use expression_semantics::summarize_expression_semantics_fragments_input;
36pub use expression_semantics::summarize_expression_semantics_match_fragments_input;
37pub use expression_semantics::summarize_expression_semantics_query_fragments_input;
38pub use query_plan::summarize_query_plan_input;
39pub use selector_usage::summarize_selector_usage_candidates_input;
40pub use selector_usage::summarize_selector_usage_canonical_candidate_bundle_input;
41pub use selector_usage::summarize_selector_usage_canonical_producer_signal_input;
42pub use selector_usage::summarize_selector_usage_evaluator_candidates_input;
43pub use selector_usage::summarize_selector_usage_fragments_input;
44pub use selector_usage::summarize_selector_usage_plan_input;
45pub use selector_usage::summarize_selector_usage_query_fragments_input;
46pub use semantic::summarize_semantic_canonical_candidate_bundle_input;
47pub use semantic::summarize_semantic_canonical_producer_signal_input;
48pub use semantic::summarize_semantic_evaluator_candidates_input;
49pub use source_resolution::summarize_source_resolution_candidates_input;
50pub use source_resolution::summarize_source_resolution_canonical_candidate_bundle_input;
51pub use source_resolution::summarize_source_resolution_canonical_producer_signal_input;
52pub use source_resolution::summarize_source_resolution_evaluator_candidates_input;
53pub use source_resolution::summarize_source_resolution_fragments_input;
54pub use source_resolution::summarize_source_resolution_match_fragments_input;
55pub use source_resolution::summarize_source_resolution_plan_input;
56pub use source_resolution::summarize_source_resolution_query_fragments_input;
57pub use source_side::summarize_source_side_canonical_candidate_bundle_input;
58pub use source_side::summarize_source_side_canonical_producer_signal_input;
59pub use source_side::summarize_source_side_evaluator_candidates_input;
60pub use type_facts::summarize_type_fact_input;
61pub type EngineInputWireV2 = engine_contract_v2_idl_generated::EngineInputV2Json;
62pub type StringTypeFactsV2 = engine_contract_v2_idl_generated::StringTypeFactsV2Json;
63pub type Utf16CodeUnitLengthV2 = engine_contract_v2_idl_generated::Utf16CodeUnitLengthV2Json;
64pub type TypeFactControlFlowBlockV2 =
65    engine_contract_v2_idl_generated::TypeFactControlFlowBlockV2Json;
66pub type TypeFactControlFlowGraphV2 =
67    engine_contract_v2_idl_generated::TypeFactControlFlowGraphV2Json;
68pub type TypeFactEntryV2 = engine_contract_v2_idl_generated::TypeFactEntryV2Json;
69
70#[derive(Debug)]
71pub struct EngineInputV2 {
72    pub version: String,
73    pub sources: Vec<SourceAnalysisInputV2>,
74    pub styles: Vec<StyleAnalysisInputV2>,
75    pub type_facts: Vec<TypeFactEntryV2>,
76}
77
78impl<'de> Deserialize<'de> for EngineInputV2 {
79    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
80    where
81        D: Deserializer<'de>,
82    {
83        EngineInputWireV2::deserialize(deserializer)?
84            .try_into()
85            .map_err(de::Error::custom)
86    }
87}
88
89impl TryFrom<EngineInputWireV2> for EngineInputV2 {
90    type Error = serde_json::Error;
91
92    fn try_from(value: EngineInputWireV2) -> Result<Self, Self::Error> {
93        Ok(Self {
94            version: value.version,
95            sources: value
96                .sources
97                .into_iter()
98                .map(SourceAnalysisInputV2::try_from)
99                .collect::<Result<Vec<_>, _>>()?,
100            styles: value
101                .styles
102                .into_iter()
103                .map(StyleAnalysisInputV2::try_from)
104                .collect::<Result<Vec<_>, _>>()?,
105            type_facts: value.type_facts,
106        })
107    }
108}
109
110#[derive(Debug, Deserialize)]
111#[serde(rename_all = "camelCase")]
112pub struct SourceAnalysisInputV2 {
113    pub document: SourceDocumentV2,
114}
115
116impl TryFrom<engine_contract_v2_idl_generated::SourceAnalysisInputV2Json>
117    for SourceAnalysisInputV2
118{
119    type Error = serde_json::Error;
120
121    fn try_from(
122        value: engine_contract_v2_idl_generated::SourceAnalysisInputV2Json,
123    ) -> Result<Self, Self::Error> {
124        Ok(Self {
125            document: serde_json::from_value(value.document)?,
126        })
127    }
128}
129
130#[derive(Debug, Deserialize)]
131#[serde(rename_all = "camelCase")]
132pub struct SourceDocumentV2 {
133    pub class_expressions: Vec<ClassExpressionInputV2>,
134}
135
136#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord)]
137#[serde(rename_all = "camelCase")]
138pub struct PositionV2 {
139    pub line: usize,
140    pub character: usize,
141}
142
143#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord)]
144#[serde(rename_all = "camelCase")]
145pub struct RangeV2 {
146    pub start: PositionV2,
147    pub end: PositionV2,
148}
149
150#[derive(Debug, Deserialize)]
151#[serde(rename_all = "camelCase")]
152pub struct BemSuffixInfoV2 {
153    pub raw_token_range: RangeV2,
154}
155
156#[derive(Debug, Deserialize)]
157#[serde(rename_all = "camelCase")]
158pub struct ClassExpressionInputV2 {
159    pub id: String,
160    pub kind: String,
161    pub scss_module_path: String,
162    pub range: RangeV2,
163    pub class_name: Option<String>,
164    pub root_binding_decl_id: Option<String>,
165    pub access_path: Option<Vec<String>>,
166}
167
168#[derive(Debug, Deserialize)]
169#[serde(rename_all = "camelCase")]
170pub struct StyleAnalysisInputV2 {
171    pub file_path: String,
172    #[serde(default)]
173    pub source: Option<String>,
174    pub document: StyleDocumentV2,
175}
176
177impl TryFrom<engine_contract_v2_idl_generated::StyleAnalysisInputV2Json> for StyleAnalysisInputV2 {
178    type Error = serde_json::Error;
179
180    fn try_from(
181        value: engine_contract_v2_idl_generated::StyleAnalysisInputV2Json,
182    ) -> Result<Self, Self::Error> {
183        Ok(Self {
184            file_path: value.file_path,
185            source: value.source,
186            document: serde_json::from_value(value.document)?,
187        })
188    }
189}
190
191#[derive(Debug, Deserialize)]
192#[serde(rename_all = "camelCase")]
193pub struct StyleDocumentV2 {
194    pub selectors: Vec<StyleSelectorV2>,
195}
196
197#[derive(Debug, Deserialize)]
198#[serde(rename_all = "camelCase")]
199pub struct StyleSelectorV2 {
200    pub name: String,
201    pub view_kind: String,
202    pub canonical_name: Option<String>,
203    pub range: RangeV2,
204    pub nested_safety: Option<String>,
205    pub composes: Option<Vec<serde_json::Value>>,
206    pub bem_suffix: Option<BemSuffixInfoV2>,
207}
208
209#[derive(Debug, Serialize)]
210#[serde(rename_all = "camelCase")]
211pub struct TypeFactInputSummaryV0 {
212    pub schema_version: &'static str,
213    pub input_version: String,
214    pub type_fact_count: usize,
215    pub distinct_fact_files: usize,
216    pub by_kind: BTreeMap<String, usize>,
217    pub constrained_kinds: BTreeMap<String, usize>,
218    pub finite_value_count: usize,
219}
220
221#[derive(Debug, Serialize)]
222#[serde(rename_all = "camelCase")]
223pub struct QueryPlanSummaryV0 {
224    schema_version: &'static str,
225    input_version: String,
226    expression_semantics_ids: Vec<String>,
227    source_expression_resolution_ids: Vec<String>,
228    selector_usage_ids: Vec<String>,
229    total_query_count: usize,
230}
231
232#[derive(Debug, Serialize, Clone)]
233#[serde(rename_all = "camelCase")]
234pub struct ExpressionDomainPlanSummaryV0 {
235    schema_version: &'static str,
236    input_version: String,
237    planned_expression_ids: Vec<String>,
238    value_domain_kinds: BTreeMap<String, usize>,
239    value_constraint_kinds: BTreeMap<String, usize>,
240    constraint_detail_counts: ConstraintDetailCounts,
241    finite_value_count: usize,
242}
243
244#[derive(Debug, Serialize, Clone)]
245#[serde(rename_all = "camelCase")]
246pub struct ExpressionDomainFragmentV0 {
247    pub expression_id: String,
248    pub file_path: String,
249    pub value_domain_kind: String,
250    #[serde(skip_serializing_if = "Option::is_none")]
251    pub value_constraint_kind: Option<String>,
252    #[serde(skip_serializing_if = "Option::is_none")]
253    pub value_prefix: Option<String>,
254    #[serde(skip_serializing_if = "Option::is_none")]
255    pub value_suffix: Option<String>,
256    #[serde(skip_serializing_if = "Option::is_none")]
257    pub value_min_len: Option<Utf16CodeUnitLengthV2>,
258    #[serde(skip_serializing_if = "Option::is_none")]
259    pub value_max_len: Option<Utf16CodeUnitLengthV2>,
260    #[serde(skip_serializing_if = "Option::is_none")]
261    pub value_char_must: Option<String>,
262    #[serde(skip_serializing_if = "Option::is_none")]
263    pub value_char_may: Option<String>,
264    #[serde(skip_serializing_if = "Option::is_none")]
265    pub value_may_include_other_chars: Option<bool>,
266    pub finite_value_count: usize,
267}
268
269#[derive(Debug, Serialize)]
270#[serde(rename_all = "camelCase")]
271pub struct ExpressionDomainFragmentsV0 {
272    pub schema_version: &'static str,
273    pub input_version: String,
274    pub fragments: Vec<ExpressionDomainFragmentV0>,
275}
276
277#[derive(Debug, Serialize, Clone)]
278#[serde(rename_all = "camelCase")]
279pub struct ExpressionDomainCandidateV0 {
280    pub expression_id: String,
281    pub file_path: String,
282    pub value_domain_kind: String,
283    #[serde(skip_serializing_if = "Option::is_none")]
284    pub value_constraint_kind: Option<String>,
285    #[serde(skip_serializing_if = "Option::is_none")]
286    pub value_prefix: Option<String>,
287    #[serde(skip_serializing_if = "Option::is_none")]
288    pub value_suffix: Option<String>,
289    #[serde(skip_serializing_if = "Option::is_none")]
290    pub value_min_len: Option<Utf16CodeUnitLengthV2>,
291    #[serde(skip_serializing_if = "Option::is_none")]
292    pub value_max_len: Option<Utf16CodeUnitLengthV2>,
293    #[serde(skip_serializing_if = "Option::is_none")]
294    pub value_char_must: Option<String>,
295    #[serde(skip_serializing_if = "Option::is_none")]
296    pub value_char_may: Option<String>,
297    #[serde(skip_serializing_if = "Option::is_none")]
298    pub value_may_include_other_chars: Option<bool>,
299    pub finite_value_count: usize,
300}
301
302#[derive(Debug, Serialize)]
303#[serde(rename_all = "camelCase")]
304pub struct ExpressionDomainCandidatesV0 {
305    pub schema_version: &'static str,
306    pub input_version: String,
307    pub candidates: Vec<ExpressionDomainCandidateV0>,
308}
309
310#[derive(Debug, Serialize)]
311#[serde(rename_all = "camelCase")]
312pub struct ExpressionDomainCanonicalCandidateBundleV0 {
313    pub schema_version: &'static str,
314    pub input_version: String,
315    pub plan_summary: ExpressionDomainPlanSummaryV0,
316    pub fragments: Vec<ExpressionDomainFragmentV0>,
317    pub candidates: Vec<ExpressionDomainCandidateV0>,
318}
319
320#[derive(Debug, Serialize)]
321#[serde(rename_all = "camelCase")]
322pub struct ExpressionDomainEvaluatorCandidatePayloadV0 {
323    pub expression_id: String,
324    pub value_domain_kind: String,
325    #[serde(skip_serializing_if = "Option::is_none")]
326    pub value_constraint_kind: Option<String>,
327    #[serde(skip_serializing_if = "Option::is_none")]
328    pub value_prefix: Option<String>,
329    #[serde(skip_serializing_if = "Option::is_none")]
330    pub value_suffix: Option<String>,
331    #[serde(skip_serializing_if = "Option::is_none")]
332    pub value_min_len: Option<Utf16CodeUnitLengthV2>,
333    #[serde(skip_serializing_if = "Option::is_none")]
334    pub value_max_len: Option<Utf16CodeUnitLengthV2>,
335    #[serde(skip_serializing_if = "Option::is_none")]
336    pub value_char_must: Option<String>,
337    #[serde(skip_serializing_if = "Option::is_none")]
338    pub value_char_may: Option<String>,
339    #[serde(skip_serializing_if = "Option::is_none")]
340    pub value_may_include_other_chars: Option<bool>,
341    pub finite_value_count: usize,
342    pub value_domain_derivation: omena_abstract_value::ReducedClassValueDerivationV0,
343    pub value_domain_provenance_tree: omena_abstract_value::AbstractClassValueProvenanceTreeV0,
344}
345
346#[derive(Debug, Serialize)]
347#[serde(rename_all = "camelCase")]
348pub struct ExpressionDomainEvaluatorCandidateV0 {
349    pub kind: &'static str,
350    pub file_path: String,
351    pub query_id: String,
352    pub payload: ExpressionDomainEvaluatorCandidatePayloadV0,
353}
354
355#[derive(Debug, Serialize)]
356#[serde(rename_all = "camelCase")]
357pub struct ExpressionDomainEvaluatorCandidatesV0 {
358    pub schema_version: &'static str,
359    pub input_version: String,
360    pub results: Vec<ExpressionDomainEvaluatorCandidateV0>,
361}
362
363#[derive(Debug, Serialize)]
364#[serde(rename_all = "camelCase")]
365pub struct ExpressionDomainCanonicalProducerSignalV0 {
366    pub schema_version: &'static str,
367    pub input_version: String,
368    pub canonical_bundle: ExpressionDomainCanonicalCandidateBundleV0,
369    pub evaluator_candidates: ExpressionDomainEvaluatorCandidatesV0,
370}
371
372#[derive(Debug, Serialize)]
373#[serde(rename_all = "camelCase")]
374pub struct ExpressionDomainProvenanceExplanationsV0 {
375    pub schema_version: &'static str,
376    pub product: &'static str,
377    pub input_version: String,
378    pub explanation_count: usize,
379    pub explanations: Vec<ExpressionDomainProvenanceExplanationV0>,
380}
381
382#[derive(Debug, Serialize)]
383#[serde(rename_all = "camelCase")]
384pub struct ExpressionDomainProvenanceExplanationV0 {
385    pub expression_id: String,
386    pub file_path: String,
387    pub input_fact_kind: String,
388    #[serde(skip_serializing_if = "Option::is_none")]
389    pub input_constraint_kind: Option<String>,
390    pub reduced_kind: &'static str,
391    pub derivation: omena_abstract_value::ReducedClassValueDerivationV0,
392    pub provenance_tree: omena_abstract_value::AbstractClassValueProvenanceTreeV0,
393}
394
395#[derive(Debug, Serialize)]
396#[serde(rename_all = "camelCase")]
397pub struct ExpressionDomainFlowAnalysisV0 {
398    pub schema_version: &'static str,
399    pub product: &'static str,
400    pub input_version: String,
401    pub analyses: Vec<ExpressionDomainFlowAnalysisEntryV0>,
402}
403
404#[derive(Debug, Serialize)]
405#[serde(rename_all = "camelCase")]
406pub struct ExpressionDomainFlowAnalysisEntryV0 {
407    pub graph_id: String,
408    pub file_path: String,
409    pub analysis: omena_abstract_value::ClassValueFlowAnalysisV0,
410}
411
412#[derive(Debug, Clone, PartialEq, Eq)]
413pub struct ExpressionDomainFlowGraphEntryV0 {
414    pub graph_id: String,
415    pub file_path: String,
416    pub graph: omena_abstract_value::ClassValueFlowGraphV0,
417}
418
419#[derive(Debug, Serialize)]
420#[serde(rename_all = "camelCase")]
421pub struct ExpressionDomainControlFlowAnalysisV0 {
422    pub schema_version: &'static str,
423    pub product: &'static str,
424    pub input_version: String,
425    pub analyses: Vec<ExpressionDomainControlFlowAnalysisEntryV0>,
426}
427
428#[derive(Debug, Serialize)]
429#[serde(rename_all = "camelCase")]
430pub struct ExpressionDomainControlFlowAnalysisEntryV0 {
431    pub graph_id: String,
432    pub file_path: String,
433    pub analysis: omena_abstract_value::ClassValueControlFlowAnalysisV0,
434}
435
436#[derive(Debug, Serialize)]
437#[serde(rename_all = "camelCase")]
438pub struct ExpressionDomainCallSiteFlowAnalysisV0 {
439    pub schema_version: &'static str,
440    pub product: &'static str,
441    pub input_version: String,
442    pub zero_cfa: omena_abstract_value::KLimitedCallSiteFlowAnalysisV0,
443    pub one_cfa: omena_abstract_value::KLimitedCallSiteFlowAnalysisV0,
444}
445
446#[derive(Debug, Serialize)]
447#[serde(rename_all = "camelCase")]
448pub struct ExpressionDomainReducedProductIterationV0 {
449    pub schema_version: &'static str,
450    pub product: &'static str,
451    pub input_version: String,
452    pub iteration_count: usize,
453    pub iterations: Vec<ExpressionDomainReducedProductIterationEntryV0>,
454}
455
456#[derive(Debug, Serialize)]
457#[serde(rename_all = "camelCase")]
458pub struct ExpressionDomainReducedProductIterationEntryV0 {
459    pub expression_id: String,
460    pub file_path: String,
461    pub input_value_kind: String,
462    pub axis_constraint_count: usize,
463    pub iteration: omena_abstract_value::ReducedClassValueProductIterationV0,
464}
465
466#[derive(Debug, Serialize)]
467#[serde(rename_all = "camelCase")]
468pub struct SelectorUsagePlanSummaryV0 {
469    schema_version: &'static str,
470    input_version: String,
471    canonical_selector_names: Vec<String>,
472    view_kind_counts: BTreeMap<String, usize>,
473    nested_safety_counts: BTreeMap<String, usize>,
474    composed_selector_count: usize,
475    total_composes_refs: usize,
476}
477
478#[derive(Debug, Clone, Serialize)]
479#[serde(rename_all = "camelCase")]
480pub struct SelectorUsageFragmentV0 {
481    pub ordinal: usize,
482    pub view_kind: String,
483    #[serde(skip_serializing_if = "Option::is_none")]
484    pub canonical_name: Option<String>,
485    #[serde(skip_serializing_if = "Option::is_none")]
486    pub nested_safety: Option<String>,
487    pub composes_count: usize,
488}
489
490#[derive(Debug, Serialize)]
491#[serde(rename_all = "camelCase")]
492pub struct SelectorUsageFragmentsV0 {
493    pub schema_version: &'static str,
494    pub input_version: String,
495    pub fragments: Vec<SelectorUsageFragmentV0>,
496}
497
498#[derive(Debug, Clone, Serialize)]
499#[serde(rename_all = "camelCase")]
500pub struct SelectorUsageQueryFragmentV0 {
501    pub query_id: String,
502    pub canonical_name: String,
503    #[serde(skip_serializing_if = "Option::is_none")]
504    pub nested_safety: Option<String>,
505    pub composes_count: usize,
506}
507
508#[derive(Debug, Serialize)]
509#[serde(rename_all = "camelCase")]
510pub struct SelectorUsageQueryFragmentsV0 {
511    pub schema_version: &'static str,
512    pub input_version: String,
513    pub fragments: Vec<SelectorUsageQueryFragmentV0>,
514}
515
516#[derive(Debug, Serialize, Clone)]
517#[serde(rename_all = "camelCase")]
518pub struct SelectorUsageCandidateV0 {
519    pub query_id: String,
520    pub canonical_name: String,
521    pub file_path: String,
522    pub total_references: usize,
523    pub direct_reference_count: usize,
524    pub editable_direct_reference_count: usize,
525    pub exact_reference_count: usize,
526    pub inferred_or_better_reference_count: usize,
527    pub has_expanded_references: bool,
528    pub has_style_dependency_references: bool,
529    pub has_any_references: bool,
530}
531
532#[derive(Debug, Serialize)]
533#[serde(rename_all = "camelCase")]
534pub struct SelectorUsageCandidatesV0 {
535    pub schema_version: &'static str,
536    pub input_version: String,
537    pub candidates: Vec<SelectorUsageCandidateV0>,
538}
539
540#[derive(Debug, Serialize, Clone)]
541#[serde(rename_all = "camelCase")]
542pub struct SelectorUsageEvaluatorCandidatePayloadV0 {
543    pub canonical_name: String,
544    pub total_references: usize,
545    pub direct_reference_count: usize,
546    pub editable_direct_reference_count: usize,
547    pub exact_reference_count: usize,
548    pub inferred_or_better_reference_count: usize,
549    pub has_expanded_references: bool,
550    pub has_style_dependency_references: bool,
551    pub has_any_references: bool,
552    pub all_sites: Vec<SelectorUsageReferenceSiteV0>,
553    pub editable_direct_sites: Vec<SelectorUsageEditableDirectSiteV0>,
554}
555
556#[derive(Debug, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord)]
557#[serde(rename_all = "camelCase")]
558pub struct SelectorUsageReferenceSiteV0 {
559    pub file_path: String,
560    pub range: RangeV2,
561    pub expansion: String,
562    pub reference_kind: String,
563}
564
565#[derive(Debug, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord)]
566#[serde(rename_all = "camelCase")]
567pub struct SelectorUsageEditableDirectSiteV0 {
568    pub file_path: String,
569    pub range: RangeV2,
570    pub class_name: String,
571}
572
573#[derive(Debug, Serialize, Clone)]
574#[serde(rename_all = "camelCase")]
575pub struct SelectorUsageEvaluatorCandidateV0 {
576    pub kind: &'static str,
577    pub file_path: String,
578    pub query_id: String,
579    pub payload: SelectorUsageEvaluatorCandidatePayloadV0,
580}
581
582#[derive(Debug, Serialize)]
583#[serde(rename_all = "camelCase")]
584pub struct SelectorUsageEvaluatorCandidatesV0 {
585    pub schema_version: &'static str,
586    pub input_version: String,
587    pub results: Vec<SelectorUsageEvaluatorCandidateV0>,
588}
589
590#[derive(Debug, Serialize)]
591#[serde(rename_all = "camelCase")]
592pub struct SelectorUsageCanonicalCandidateBundleV0 {
593    pub schema_version: &'static str,
594    pub input_version: String,
595    pub query_fragments: Vec<SelectorUsageQueryFragmentV0>,
596    pub fragments: Vec<SelectorUsageFragmentV0>,
597    pub candidates: Vec<SelectorUsageCandidateV0>,
598}
599
600#[derive(Debug, Serialize)]
601#[serde(rename_all = "camelCase")]
602pub struct SelectorUsageCanonicalProducerSignalV0 {
603    pub schema_version: &'static str,
604    pub input_version: String,
605    pub canonical_bundle: SelectorUsageCanonicalCandidateBundleV0,
606    pub evaluator_candidates: SelectorUsageEvaluatorCandidatesV0,
607}
608
609#[derive(Debug, Serialize)]
610#[serde(rename_all = "camelCase")]
611pub struct SourceResolutionPlanSummaryV0 {
612    schema_version: &'static str,
613    input_version: String,
614    planned_expression_ids: Vec<String>,
615    expression_kind_counts: BTreeMap<String, usize>,
616    distinct_style_file_paths: Vec<String>,
617    symbol_ref_with_binding_count: usize,
618    style_access_count: usize,
619    style_access_path_depth_sum: usize,
620}
621
622#[derive(Debug, Serialize, Clone)]
623#[serde(rename_all = "camelCase")]
624pub struct SourceResolutionQueryFragmentV0 {
625    pub query_id: String,
626    pub expression_id: String,
627    pub expression_kind: String,
628    pub style_file_path: String,
629}
630
631#[derive(Debug, Serialize)]
632#[serde(rename_all = "camelCase")]
633pub struct SourceResolutionQueryFragmentsV0 {
634    pub schema_version: &'static str,
635    pub input_version: String,
636    pub fragments: Vec<SourceResolutionQueryFragmentV0>,
637}
638
639#[derive(Debug, Serialize, Clone)]
640#[serde(rename_all = "camelCase")]
641pub struct SourceResolutionMatchFragmentV0 {
642    pub query_id: String,
643    pub expression_id: String,
644    pub style_file_path: String,
645    pub selector_names: Vec<String>,
646    #[serde(skip_serializing_if = "Option::is_none")]
647    pub finite_values: Option<Vec<String>>,
648}
649
650#[derive(Debug, Serialize)]
651#[serde(rename_all = "camelCase")]
652pub struct SourceResolutionMatchFragmentsV0 {
653    pub schema_version: &'static str,
654    pub input_version: String,
655    pub fragments: Vec<SourceResolutionMatchFragmentV0>,
656}
657
658#[derive(Debug, Serialize, Clone)]
659#[serde(rename_all = "camelCase")]
660pub struct SourceResolutionCandidateV0 {
661    pub query_id: String,
662    pub expression_id: String,
663    pub style_file_path: String,
664    pub selector_names: Vec<String>,
665    #[serde(skip_serializing_if = "Option::is_none")]
666    pub finite_values: Option<Vec<String>>,
667    pub selector_certainty: String,
668    #[serde(skip_serializing_if = "Option::is_none")]
669    pub value_certainty: Option<String>,
670    pub selector_certainty_shape_kind: String,
671    pub selector_certainty_shape_label: String,
672    pub value_certainty_shape_kind: String,
673    pub value_certainty_shape_label: String,
674    #[serde(skip_serializing_if = "Option::is_none")]
675    pub selector_constraint_kind: Option<String>,
676    #[serde(skip_serializing_if = "Option::is_none")]
677    pub value_certainty_constraint_kind: Option<String>,
678    #[serde(skip_serializing_if = "Option::is_none")]
679    pub value_prefix: Option<String>,
680    #[serde(skip_serializing_if = "Option::is_none")]
681    pub value_suffix: Option<String>,
682    #[serde(skip_serializing_if = "Option::is_none")]
683    pub value_min_len: Option<Utf16CodeUnitLengthV2>,
684    #[serde(skip_serializing_if = "Option::is_none")]
685    pub value_max_len: Option<Utf16CodeUnitLengthV2>,
686    #[serde(skip_serializing_if = "Option::is_none")]
687    pub value_char_must: Option<String>,
688    #[serde(skip_serializing_if = "Option::is_none")]
689    pub value_char_may: Option<String>,
690    #[serde(skip_serializing_if = "Option::is_none")]
691    pub value_may_include_other_chars: Option<bool>,
692}
693
694#[derive(Debug, Serialize)]
695#[serde(rename_all = "camelCase")]
696pub struct SourceResolutionCandidatesV0 {
697    pub schema_version: &'static str,
698    pub input_version: String,
699    pub candidates: Vec<SourceResolutionCandidateV0>,
700}
701
702#[derive(Debug, Serialize)]
703#[serde(rename_all = "camelCase")]
704pub struct SourceResolutionCanonicalCandidateBundleV0 {
705    pub schema_version: &'static str,
706    pub input_version: String,
707    pub query_fragments: Vec<SourceResolutionQueryFragmentV0>,
708    pub fragments: Vec<SourceResolutionFragmentV0>,
709    pub match_fragments: Vec<SourceResolutionMatchFragmentV0>,
710    pub candidates: Vec<SourceResolutionCandidateV0>,
711}
712
713#[derive(Debug, Serialize)]
714#[serde(rename_all = "camelCase")]
715pub struct SourceResolutionEvaluatorCandidatePayloadV0 {
716    pub expression_id: String,
717    pub style_file_path: String,
718    pub selector_names: Vec<String>,
719    #[serde(skip_serializing_if = "Option::is_none")]
720    pub finite_values: Option<Vec<String>>,
721    pub selector_certainty: String,
722    #[serde(skip_serializing_if = "Option::is_none")]
723    pub value_certainty: Option<String>,
724    pub selector_certainty_shape_kind: String,
725    pub selector_certainty_shape_label: String,
726    pub value_certainty_shape_kind: String,
727    pub value_certainty_shape_label: String,
728    #[serde(skip_serializing_if = "Option::is_none")]
729    pub selector_constraint_kind: Option<String>,
730    #[serde(skip_serializing_if = "Option::is_none")]
731    pub value_certainty_constraint_kind: Option<String>,
732    #[serde(skip_serializing_if = "Option::is_none")]
733    pub value_prefix: Option<String>,
734    #[serde(skip_serializing_if = "Option::is_none")]
735    pub value_suffix: Option<String>,
736    #[serde(skip_serializing_if = "Option::is_none")]
737    pub value_min_len: Option<Utf16CodeUnitLengthV2>,
738    #[serde(skip_serializing_if = "Option::is_none")]
739    pub value_max_len: Option<Utf16CodeUnitLengthV2>,
740    #[serde(skip_serializing_if = "Option::is_none")]
741    pub value_char_must: Option<String>,
742    #[serde(skip_serializing_if = "Option::is_none")]
743    pub value_char_may: Option<String>,
744    #[serde(skip_serializing_if = "Option::is_none")]
745    pub value_may_include_other_chars: Option<bool>,
746}
747
748#[derive(Debug, Serialize)]
749#[serde(rename_all = "camelCase")]
750pub struct SourceResolutionEvaluatorCandidateV0 {
751    pub kind: &'static str,
752    pub file_path: String,
753    pub query_id: String,
754    pub payload: SourceResolutionEvaluatorCandidatePayloadV0,
755}
756
757#[derive(Debug, Serialize)]
758#[serde(rename_all = "camelCase")]
759pub struct SourceResolutionEvaluatorCandidatesV0 {
760    pub schema_version: &'static str,
761    pub input_version: String,
762    pub results: Vec<SourceResolutionEvaluatorCandidateV0>,
763}
764
765#[derive(Debug, Serialize)]
766#[serde(rename_all = "camelCase")]
767pub struct SourceResolutionCanonicalProducerSignalV0 {
768    pub schema_version: &'static str,
769    pub input_version: String,
770    pub canonical_bundle: SourceResolutionCanonicalCandidateBundleV0,
771    pub evaluator_candidates: SourceResolutionEvaluatorCandidatesV0,
772}
773
774#[derive(Debug, Serialize)]
775#[serde(rename_all = "camelCase")]
776pub struct SourceSideCanonicalCandidateBundleV0 {
777    pub schema_version: &'static str,
778    pub input_version: String,
779    pub expression_semantics: ExpressionSemanticsCanonicalCandidateBundleV0,
780    pub source_resolution: SourceResolutionCanonicalCandidateBundleV0,
781}
782
783#[derive(Debug, Serialize)]
784#[serde(rename_all = "camelCase")]
785pub struct SourceSideEvaluatorCandidatesV0 {
786    pub schema_version: &'static str,
787    pub input_version: String,
788    pub expression_semantics: ExpressionSemanticsEvaluatorCandidatesV0,
789    pub source_resolution: SourceResolutionEvaluatorCandidatesV0,
790}
791
792#[derive(Debug, Serialize)]
793#[serde(rename_all = "camelCase")]
794pub struct SourceSideCanonicalProducerSignalV0 {
795    pub schema_version: &'static str,
796    pub input_version: String,
797    pub canonical_bundle: SourceSideCanonicalCandidateBundleV0,
798    pub evaluator_candidates: SourceSideEvaluatorCandidatesV0,
799}
800
801#[derive(Debug, Serialize)]
802#[serde(rename_all = "camelCase")]
803pub struct SemanticCanonicalCandidateBundleV0 {
804    pub schema_version: &'static str,
805    pub input_version: String,
806    pub source_side: SourceSideCanonicalCandidateBundleV0,
807    pub expression_domain: ExpressionDomainCanonicalCandidateBundleV0,
808}
809
810#[derive(Debug, Serialize)]
811#[serde(rename_all = "camelCase")]
812pub struct SemanticEvaluatorCandidatesV0 {
813    pub schema_version: &'static str,
814    pub input_version: String,
815    pub source_side: SourceSideEvaluatorCandidatesV0,
816    pub expression_domain: ExpressionDomainEvaluatorCandidatesV0,
817}
818
819#[derive(Debug, Serialize)]
820#[serde(rename_all = "camelCase")]
821pub struct SemanticCanonicalProducerSignalV0 {
822    pub schema_version: &'static str,
823    pub input_version: String,
824    pub canonical_bundle: SemanticCanonicalCandidateBundleV0,
825    pub evaluator_candidates: SemanticEvaluatorCandidatesV0,
826}
827
828#[derive(Debug, Serialize, Clone)]
829#[serde(rename_all = "camelCase")]
830pub struct ExpressionSemanticsFragmentV0 {
831    query_id: String,
832    expression_id: String,
833    expression_kind: String,
834    style_file_path: String,
835    value_domain_kind: String,
836    #[serde(skip_serializing_if = "Option::is_none")]
837    value_constraint_kind: Option<String>,
838    #[serde(skip_serializing_if = "Option::is_none")]
839    value_prefix: Option<String>,
840    #[serde(skip_serializing_if = "Option::is_none")]
841    value_suffix: Option<String>,
842    #[serde(skip_serializing_if = "Option::is_none")]
843    value_min_len: Option<Utf16CodeUnitLengthV2>,
844    #[serde(skip_serializing_if = "Option::is_none")]
845    value_max_len: Option<Utf16CodeUnitLengthV2>,
846    #[serde(skip_serializing_if = "Option::is_none")]
847    value_char_must: Option<String>,
848    #[serde(skip_serializing_if = "Option::is_none")]
849    value_char_may: Option<String>,
850    #[serde(skip_serializing_if = "Option::is_none")]
851    value_may_include_other_chars: Option<bool>,
852}
853
854#[derive(Debug, Serialize)]
855#[serde(rename_all = "camelCase")]
856pub struct ExpressionSemanticsFragmentsV0 {
857    schema_version: &'static str,
858    input_version: String,
859    fragments: Vec<ExpressionSemanticsFragmentV0>,
860}
861
862#[derive(Debug, Serialize, Clone)]
863#[serde(rename_all = "camelCase")]
864pub struct ExpressionSemanticsQueryFragmentV0 {
865    pub query_id: String,
866    pub expression_id: String,
867    pub expression_kind: String,
868    pub style_file_path: String,
869}
870
871#[derive(Debug, Serialize)]
872#[serde(rename_all = "camelCase")]
873pub struct ExpressionSemanticsQueryFragmentsV0 {
874    pub schema_version: &'static str,
875    pub input_version: String,
876    pub fragments: Vec<ExpressionSemanticsQueryFragmentV0>,
877}
878
879#[derive(Debug, Serialize, Clone)]
880#[serde(rename_all = "camelCase")]
881pub struct ExpressionSemanticsMatchFragmentV0 {
882    pub query_id: String,
883    pub expression_id: String,
884    pub style_file_path: String,
885    pub selector_names: Vec<String>,
886    pub candidate_names: Vec<String>,
887    #[serde(skip_serializing_if = "Option::is_none")]
888    pub finite_values: Option<Vec<String>>,
889}
890
891#[derive(Debug, Serialize)]
892#[serde(rename_all = "camelCase")]
893pub struct ExpressionSemanticsMatchFragmentsV0 {
894    pub schema_version: &'static str,
895    pub input_version: String,
896    pub fragments: Vec<ExpressionSemanticsMatchFragmentV0>,
897}
898
899#[derive(Debug, Serialize, Clone)]
900#[serde(rename_all = "camelCase")]
901pub struct ExpressionSemanticsCandidateV0 {
902    pub query_id: String,
903    pub expression_id: String,
904    pub expression_kind: String,
905    pub style_file_path: String,
906    pub selector_names: Vec<String>,
907    pub candidate_names: Vec<String>,
908    #[serde(skip_serializing_if = "Option::is_none")]
909    pub finite_values: Option<Vec<String>>,
910    pub value_domain_kind: String,
911    pub selector_certainty: String,
912    #[serde(skip_serializing_if = "Option::is_none")]
913    pub value_certainty: Option<String>,
914    pub selector_certainty_shape_kind: String,
915    pub selector_certainty_shape_label: String,
916    pub value_certainty_shape_kind: String,
917    pub value_certainty_shape_label: String,
918    #[serde(skip_serializing_if = "Option::is_none")]
919    pub selector_constraint_kind: Option<String>,
920    #[serde(skip_serializing_if = "Option::is_none")]
921    pub value_certainty_constraint_kind: Option<String>,
922    #[serde(skip_serializing_if = "Option::is_none")]
923    pub value_constraint_kind: Option<String>,
924    #[serde(skip_serializing_if = "Option::is_none")]
925    pub value_prefix: Option<String>,
926    #[serde(skip_serializing_if = "Option::is_none")]
927    pub value_suffix: Option<String>,
928    #[serde(skip_serializing_if = "Option::is_none")]
929    pub value_min_len: Option<Utf16CodeUnitLengthV2>,
930    #[serde(skip_serializing_if = "Option::is_none")]
931    pub value_max_len: Option<Utf16CodeUnitLengthV2>,
932    #[serde(skip_serializing_if = "Option::is_none")]
933    pub value_char_must: Option<String>,
934    #[serde(skip_serializing_if = "Option::is_none")]
935    pub value_char_may: Option<String>,
936    #[serde(skip_serializing_if = "Option::is_none")]
937    pub value_may_include_other_chars: Option<bool>,
938}
939
940#[derive(Debug, Serialize)]
941#[serde(rename_all = "camelCase")]
942pub struct ExpressionSemanticsCandidatesV0 {
943    pub schema_version: &'static str,
944    pub input_version: String,
945    pub candidates: Vec<ExpressionSemanticsCandidateV0>,
946}
947
948#[derive(Debug, Serialize)]
949#[serde(rename_all = "camelCase")]
950pub struct ExpressionSemanticsCanonicalCandidateBundleV0 {
951    pub schema_version: &'static str,
952    pub input_version: String,
953    pub query_fragments: Vec<ExpressionSemanticsQueryFragmentV0>,
954    pub fragments: Vec<ExpressionSemanticsFragmentV0>,
955    pub match_fragments: Vec<ExpressionSemanticsMatchFragmentV0>,
956    pub candidates: Vec<ExpressionSemanticsCandidateV0>,
957}
958
959#[derive(Debug, Serialize)]
960#[serde(rename_all = "camelCase")]
961pub struct ExpressionSemanticsEvaluatorCandidatePayloadV0 {
962    pub expression_id: String,
963    pub expression_kind: String,
964    pub style_file_path: String,
965    pub selector_names: Vec<String>,
966    pub candidate_names: Vec<String>,
967    #[serde(skip_serializing_if = "Option::is_none")]
968    pub finite_values: Option<Vec<String>>,
969    pub value_domain_kind: String,
970    pub selector_certainty: String,
971    #[serde(skip_serializing_if = "Option::is_none")]
972    pub value_certainty: Option<String>,
973    pub selector_certainty_shape_kind: String,
974    pub selector_certainty_shape_label: String,
975    pub value_certainty_shape_kind: String,
976    pub value_certainty_shape_label: String,
977    #[serde(skip_serializing_if = "Option::is_none")]
978    pub selector_constraint_kind: Option<String>,
979    #[serde(skip_serializing_if = "Option::is_none")]
980    pub value_certainty_constraint_kind: Option<String>,
981    #[serde(skip_serializing_if = "Option::is_none")]
982    pub value_constraint_kind: Option<String>,
983    #[serde(skip_serializing_if = "Option::is_none")]
984    pub value_prefix: Option<String>,
985    #[serde(skip_serializing_if = "Option::is_none")]
986    pub value_suffix: Option<String>,
987    #[serde(skip_serializing_if = "Option::is_none")]
988    pub value_min_len: Option<Utf16CodeUnitLengthV2>,
989    #[serde(skip_serializing_if = "Option::is_none")]
990    pub value_max_len: Option<Utf16CodeUnitLengthV2>,
991    #[serde(skip_serializing_if = "Option::is_none")]
992    pub value_char_must: Option<String>,
993    #[serde(skip_serializing_if = "Option::is_none")]
994    pub value_char_may: Option<String>,
995    #[serde(skip_serializing_if = "Option::is_none")]
996    pub value_may_include_other_chars: Option<bool>,
997    pub value_domain_derivation: omena_abstract_value::ReducedClassValueDerivationV0,
998    pub value_domain_provenance_tree: omena_abstract_value::AbstractClassValueProvenanceTreeV0,
999}
1000
1001#[derive(Debug, Serialize)]
1002#[serde(rename_all = "camelCase")]
1003pub struct ExpressionSemanticsEvaluatorCandidateV0 {
1004    pub kind: &'static str,
1005    pub file_path: String,
1006    pub query_id: String,
1007    pub payload: ExpressionSemanticsEvaluatorCandidatePayloadV0,
1008}
1009
1010#[derive(Debug, Serialize)]
1011#[serde(rename_all = "camelCase")]
1012pub struct ExpressionSemanticsEvaluatorCandidatesV0 {
1013    pub schema_version: &'static str,
1014    pub input_version: String,
1015    pub results: Vec<ExpressionSemanticsEvaluatorCandidateV0>,
1016}
1017
1018#[derive(Debug, Serialize)]
1019#[serde(rename_all = "camelCase")]
1020pub struct ExpressionSemanticsCanonicalProducerSignalV0 {
1021    pub schema_version: &'static str,
1022    pub input_version: String,
1023    pub canonical_bundle: ExpressionSemanticsCanonicalCandidateBundleV0,
1024    pub evaluator_candidates: ExpressionSemanticsEvaluatorCandidatesV0,
1025}
1026
1027#[derive(Debug, Serialize, Clone)]
1028#[serde(rename_all = "camelCase")]
1029pub struct SourceResolutionFragmentV0 {
1030    query_id: String,
1031    expression_id: String,
1032    style_file_path: String,
1033    value_certainty_shape_kind: String,
1034    #[serde(skip_serializing_if = "Option::is_none")]
1035    value_certainty_constraint_kind: Option<String>,
1036    #[serde(skip_serializing_if = "Option::is_none")]
1037    value_prefix: Option<String>,
1038    #[serde(skip_serializing_if = "Option::is_none")]
1039    value_suffix: Option<String>,
1040    #[serde(skip_serializing_if = "Option::is_none")]
1041    value_min_len: Option<Utf16CodeUnitLengthV2>,
1042    #[serde(skip_serializing_if = "Option::is_none")]
1043    value_max_len: Option<Utf16CodeUnitLengthV2>,
1044    #[serde(skip_serializing_if = "Option::is_none")]
1045    value_char_must: Option<String>,
1046    #[serde(skip_serializing_if = "Option::is_none")]
1047    value_char_may: Option<String>,
1048    #[serde(skip_serializing_if = "Option::is_none")]
1049    value_may_include_other_chars: Option<bool>,
1050}
1051
1052#[derive(Debug, Serialize)]
1053#[serde(rename_all = "camelCase")]
1054pub struct SourceResolutionFragmentsV0 {
1055    schema_version: &'static str,
1056    input_version: String,
1057    fragments: Vec<SourceResolutionFragmentV0>,
1058}
1059
1060#[derive(Debug, Serialize, Default, Clone)]
1061#[serde(rename_all = "camelCase")]
1062pub struct ConstraintDetailCounts {
1063    pub prefix_count: usize,
1064    pub suffix_count: usize,
1065    pub min_len_count: usize,
1066    pub min_len_sum: usize,
1067    pub max_len_count: usize,
1068    pub max_len_sum: usize,
1069    pub char_must_count: usize,
1070    pub char_must_len_sum: usize,
1071    pub char_may_count: usize,
1072    pub char_may_len_sum: usize,
1073    pub may_include_other_chars_count: usize,
1074}
1075
1076fn collect_constraint_detail_counts(
1077    counts: &mut ConstraintDetailCounts,
1078    details: ConstraintDetailInput<'_>,
1079) {
1080    if details.prefix.is_some() {
1081        counts.prefix_count += 1;
1082    }
1083    if details.suffix.is_some() {
1084        counts.suffix_count += 1;
1085    }
1086    if let Some(value) = details.min_len {
1087        counts.min_len_count += 1;
1088        counts.min_len_sum += value;
1089    }
1090    if let Some(value) = details.max_len {
1091        counts.max_len_count += 1;
1092        counts.max_len_sum += value;
1093    }
1094    if let Some(value) = details.char_must {
1095        counts.char_must_count += 1;
1096        counts.char_must_len_sum += value.len();
1097    }
1098    if let Some(value) = details.char_may {
1099        counts.char_may_count += 1;
1100        counts.char_may_len_sum += value.len();
1101    }
1102    if details.may_include_other_chars == Some(true) {
1103        counts.may_include_other_chars_count += 1;
1104    }
1105}
1106
1107pub(crate) struct ConstraintDetailInput<'a> {
1108    pub(crate) prefix: Option<&'a String>,
1109    pub(crate) suffix: Option<&'a String>,
1110    pub(crate) min_len: Option<Utf16CodeUnitLengthV2>,
1111    pub(crate) max_len: Option<Utf16CodeUnitLengthV2>,
1112    pub(crate) char_must: Option<&'a String>,
1113    pub(crate) char_may: Option<&'a String>,
1114    pub(crate) may_include_other_chars: Option<bool>,
1115}
1116
1117pub(crate) fn map_expression_value_domain_kind(facts: &StringTypeFactsV2) -> String {
1118    omena_abstract_value::expression_value_domain_kind_from_facts(&abstract_value_facts(facts))
1119}
1120
1121pub(crate) fn map_reduced_expression_value_domain_kind(facts: &StringTypeFactsV2) -> String {
1122    omena_abstract_value::reduced_value_domain_kind_from_facts(&abstract_value_facts(facts))
1123        .to_string()
1124}
1125
1126pub(crate) fn map_reduced_expression_value_domain_derivation(
1127    facts: &StringTypeFactsV2,
1128) -> omena_abstract_value::ReducedClassValueDerivationV0 {
1129    omena_abstract_value::reduced_class_value_derivation_from_facts(&abstract_value_facts(facts))
1130}
1131
1132pub(crate) fn map_reduced_expression_value_domain_provenance_tree(
1133    facts: &StringTypeFactsV2,
1134) -> omena_abstract_value::AbstractClassValueProvenanceTreeV0 {
1135    let value =
1136        omena_abstract_value::reduced_abstract_class_value_from_facts(&abstract_value_facts(facts));
1137    omena_abstract_value::summarize_abstract_class_value_provenance_tree(&value)
1138}
1139
1140pub(crate) fn map_value_certainty(facts: &StringTypeFactsV2) -> Option<String> {
1141    omena_abstract_value::value_certainty_from_facts(&abstract_value_facts(facts))
1142        .map(str::to_string)
1143}
1144
1145pub(crate) fn map_value_certainty_shape_kind(facts: &StringTypeFactsV2) -> String {
1146    omena_abstract_value::value_certainty_shape_kind_from_facts(&abstract_value_facts(facts))
1147        .to_string()
1148}
1149
1150pub(crate) fn map_value_certainty_shape_label(facts: &StringTypeFactsV2) -> String {
1151    omena_abstract_value::value_certainty_shape_label_from_facts(&abstract_value_facts(facts))
1152}
1153
1154#[derive(Debug, Clone, PartialEq, Eq)]
1155pub(crate) struct SelectorCertaintyProjectionV0 {
1156    pub(crate) certainty: String,
1157    pub(crate) shape_kind: String,
1158    pub(crate) shape_label: String,
1159}
1160
1161/// Consumes the already-published flow-analysis signals when deriving selector certainty.
1162pub(crate) fn hedge_selector_certainty_for_flow(
1163    base: omena_abstract_value::SelectorProjectionCertaintyV0,
1164    graph_converged: bool,
1165    contains_flow_iteration_limit: bool,
1166) -> omena_abstract_value::SelectorProjectionCertaintyV0 {
1167    if graph_converged && !contains_flow_iteration_limit {
1168        base
1169    } else {
1170        omena_abstract_value::SelectorProjectionCertaintyV0::Possible
1171    }
1172}
1173
1174pub(crate) fn map_selector_certainty_projection(
1175    facts: &StringTypeFactsV2,
1176    matched_selector_count: usize,
1177    selector_universe_count: usize,
1178    flow_hedge: Option<&expression_domain::ExpressionDomainSelectorCertaintyFlowHedgeV0>,
1179) -> SelectorCertaintyProjectionV0 {
1180    use omena_abstract_value::SelectorProjectionCertaintyV0;
1181
1182    let abstract_facts = abstract_value_facts(facts);
1183    let base = match omena_abstract_value::selector_certainty_from_facts(
1184        &abstract_facts,
1185        matched_selector_count,
1186        selector_universe_count,
1187    ) {
1188        "exact" => SelectorProjectionCertaintyV0::Exact,
1189        "inferred" => SelectorProjectionCertaintyV0::Inferred,
1190        _ => SelectorProjectionCertaintyV0::Possible,
1191    };
1192    let certainty = flow_hedge.map_or(base, |hedge| {
1193        hedge_selector_certainty_for_flow(
1194            base,
1195            hedge.graph_converged,
1196            hedge.contains_flow_iteration_limit,
1197        )
1198    });
1199    let flow_demoted = base != SelectorProjectionCertaintyV0::Possible
1200        && certainty == SelectorProjectionCertaintyV0::Possible;
1201
1202    if flow_demoted {
1203        return SelectorCertaintyProjectionV0 {
1204            certainty: "possible".to_string(),
1205            shape_kind: "unknown".to_string(),
1206            shape_label: "unknown".to_string(),
1207        };
1208    }
1209
1210    SelectorCertaintyProjectionV0 {
1211        certainty: match certainty {
1212            SelectorProjectionCertaintyV0::Exact => "exact",
1213            SelectorProjectionCertaintyV0::Inferred => "inferred",
1214            SelectorProjectionCertaintyV0::Possible => "possible",
1215        }
1216        .to_string(),
1217        shape_kind: omena_abstract_value::selector_certainty_shape_kind_from_facts(
1218            &abstract_facts,
1219            matched_selector_count,
1220            selector_universe_count,
1221        )
1222        .to_string(),
1223        shape_label: omena_abstract_value::selector_certainty_shape_label_from_facts(
1224            &abstract_facts,
1225            matched_selector_count,
1226            selector_universe_count,
1227        ),
1228    }
1229}
1230
1231pub(crate) fn finite_values_for_facts(facts: &StringTypeFactsV2) -> Option<Vec<String>> {
1232    omena_abstract_value::finite_values_from_facts(&abstract_value_facts(facts))
1233}
1234
1235pub(crate) fn abstract_value_facts(
1236    facts: &StringTypeFactsV2,
1237) -> omena_abstract_value::ExternalStringTypeFactsV0 {
1238    omena_abstract_value::ExternalStringTypeFactsV0 {
1239        kind: facts.kind.clone(),
1240        constraint_kind: facts.constraint_kind.clone(),
1241        values: facts.values.clone(),
1242        prefix: facts.prefix.clone(),
1243        suffix: facts.suffix.clone(),
1244        min_len: facts.min_len,
1245        max_len: facts.max_len,
1246        char_must: facts.char_must.clone(),
1247        char_may: facts.char_may.clone(),
1248        may_include_other_chars: facts.may_include_other_chars,
1249    }
1250}
1251
1252pub(crate) fn resolve_selector_names(
1253    style: &StyleAnalysisInputV2,
1254    facts: &StringTypeFactsV2,
1255) -> Vec<String> {
1256    match facts.kind.as_str() {
1257        "unknown" => Vec::new(),
1258        "top" => canonical_selector_names(style),
1259        "exact" | "finiteSet" => {
1260            let mut names = Vec::new();
1261            for value in facts.values.as_ref().into_iter().flatten() {
1262                if !matches_external_utf16_length_bounds(value, facts) {
1263                    continue;
1264                }
1265                push_canonical_match(style, value, &mut names);
1266            }
1267            names
1268        }
1269        "constrained" => resolve_constrained_selector_names(style, facts),
1270        _ => Vec::new(),
1271    }
1272}
1273
1274fn resolve_constrained_selector_names(
1275    style: &StyleAnalysisInputV2,
1276    facts: &StringTypeFactsV2,
1277) -> Vec<String> {
1278    let mut names = Vec::new();
1279
1280    for selector in &style.document.selectors {
1281        if !matches_selector_constraints(selector, facts) {
1282            continue;
1283        }
1284        let canonical_name = canonical_name_for_selector(style, selector);
1285        if let Some(canonical_name) = canonical_name
1286            && !names.contains(&canonical_name)
1287        {
1288            names.push(canonical_name);
1289        }
1290    }
1291
1292    names
1293}
1294
1295fn matches_selector_constraints(selector: &StyleSelectorV2, facts: &StringTypeFactsV2) -> bool {
1296    if !matches_external_utf16_length_bounds(&selector.name, facts) {
1297        return false;
1298    }
1299
1300    match facts.constraint_kind.as_deref() {
1301        Some("prefix") => facts
1302            .prefix
1303            .as_ref()
1304            .is_some_and(|prefix| selector.name.starts_with(prefix)),
1305        Some("suffix") => facts
1306            .suffix
1307            .as_ref()
1308            .is_some_and(|suffix| selector.name.ends_with(suffix)),
1309        Some("prefixSuffix") => {
1310            let prefix_ok = facts
1311                .prefix
1312                .as_ref()
1313                .is_none_or(|prefix| selector.name.starts_with(prefix));
1314            let suffix_ok = facts
1315                .suffix
1316                .as_ref()
1317                .is_none_or(|suffix| selector.name.ends_with(suffix));
1318            prefix_ok && suffix_ok
1319        }
1320        Some("charInclusion") => matches_char_constraints(
1321            &selector.name,
1322            facts.char_must.as_deref().unwrap_or(""),
1323            facts.char_may.as_deref().unwrap_or(""),
1324            facts.may_include_other_chars.unwrap_or(false),
1325        ),
1326        Some("composite") => {
1327            let prefix_ok = facts
1328                .prefix
1329                .as_ref()
1330                .is_none_or(|prefix| selector.name.starts_with(prefix));
1331            let suffix_ok = facts
1332                .suffix
1333                .as_ref()
1334                .is_none_or(|suffix| selector.name.ends_with(suffix));
1335            prefix_ok
1336                && suffix_ok
1337                && matches_char_constraints(
1338                    &selector.name,
1339                    facts.char_must.as_deref().unwrap_or(""),
1340                    facts.char_may.as_deref().unwrap_or(""),
1341                    facts.may_include_other_chars.unwrap_or(false),
1342                )
1343        }
1344        _ => false,
1345    }
1346}
1347
1348fn matches_external_utf16_length_bounds(value: &str, facts: &StringTypeFactsV2) -> bool {
1349    let value_length = omena_abstract_value::external_utf16_code_unit_length(value);
1350    facts.min_len.is_none_or(|min_len| value_length >= min_len)
1351        && facts.max_len.is_none_or(|max_len| value_length <= max_len)
1352}
1353
1354fn matches_char_constraints(
1355    value: &str,
1356    must_chars: &str,
1357    may_chars: &str,
1358    may_include_other_chars: bool,
1359) -> bool {
1360    let value_chars: std::collections::BTreeSet<char> = value.chars().collect();
1361    let must_set: std::collections::BTreeSet<char> = must_chars.chars().collect();
1362    let may_set: std::collections::BTreeSet<char> = may_chars.chars().collect();
1363
1364    if must_set.iter().any(|char| !value_chars.contains(char)) {
1365        return false;
1366    }
1367    if !may_include_other_chars && value_chars.iter().any(|char| !may_set.contains(char)) {
1368        return false;
1369    }
1370    true
1371}
1372
1373fn push_canonical_match(style: &StyleAnalysisInputV2, view_name: &str, names: &mut Vec<String>) {
1374    if let Some(canonical_name) = canonical_name_for_view_name(style, view_name)
1375        && !names.contains(&canonical_name)
1376    {
1377        names.push(canonical_name);
1378    }
1379}
1380
1381fn canonical_selector_names(style: &StyleAnalysisInputV2) -> Vec<String> {
1382    let mut names = Vec::new();
1383    for selector in &style.document.selectors {
1384        if selector.view_kind == "canonical"
1385            && let Some(canonical_name) = selector.canonical_name.clone()
1386            && !names.contains(&canonical_name)
1387        {
1388            names.push(canonical_name);
1389        }
1390    }
1391    names
1392}
1393
1394pub(crate) fn canonical_selector_count(style: &StyleAnalysisInputV2) -> usize {
1395    canonical_selector_names(style).len()
1396}
1397
1398fn canonical_name_for_selector(
1399    style: &StyleAnalysisInputV2,
1400    selector: &StyleSelectorV2,
1401) -> Option<String> {
1402    canonical_name_for_view_name(style, &selector.name)
1403}
1404
1405fn canonical_name_for_view_name(style: &StyleAnalysisInputV2, view_name: &str) -> Option<String> {
1406    let matched = style
1407        .document
1408        .selectors
1409        .iter()
1410        .find(|selector| selector.name == view_name)?;
1411    let canonical = style.document.selectors.iter().find(|selector| {
1412        selector.view_kind == "canonical" && selector.canonical_name == matched.canonical_name
1413    });
1414    canonical
1415        .and_then(|selector| selector.canonical_name.clone())
1416        .or_else(|| matched.canonical_name.clone())
1417        .or_else(|| Some(matched.name.clone()))
1418}
1419
1420#[cfg(test)]
1421pub(crate) fn configure_nonconvergent_selector_certainty_fixture(
1422    entry: &mut TypeFactEntryV2,
1423    exact_value: &str,
1424) {
1425    entry.facts = StringTypeFactsV2 {
1426        kind: "exact".to_string(),
1427        constraint_kind: None,
1428        values: Some(vec![exact_value.to_string()]),
1429        prefix: None,
1430        suffix: None,
1431        min_len: None,
1432        max_len: None,
1433        char_must: None,
1434        char_may: None,
1435        may_include_other_chars: None,
1436        provenance: None,
1437    };
1438    entry.control_flow_graph = Some(nonconvergent_selector_certainty_control_flow_graph());
1439}
1440
1441#[cfg(test)]
1442fn nonconvergent_selector_certainty_control_flow_graph() -> TypeFactControlFlowGraphV2 {
1443    TypeFactControlFlowGraphV2 {
1444        entry_block_id: "seed".to_string(),
1445        blocks: vec![
1446            TypeFactControlFlowBlockV2 {
1447                id: "seed".to_string(),
1448                kind: "assignment".to_string(),
1449                transfer_kind: "assignFacts".to_string(),
1450                successor_block_ids: vec!["loop".to_string()],
1451                symbol_ordinal: None,
1452                variable_name: None,
1453                expression_kind: None,
1454                boundary_effect: "unknownBoundary".to_string(),
1455                facts: Some(StringTypeFactsV2 {
1456                    kind: "finiteSet".to_string(),
1457                    constraint_kind: None,
1458                    values: Some(vec!["a".to_string(), "b".to_string()]),
1459                    prefix: None,
1460                    suffix: None,
1461                    min_len: None,
1462                    max_len: None,
1463                    char_must: None,
1464                    char_may: None,
1465                    may_include_other_chars: None,
1466                    provenance: None,
1467                }),
1468            },
1469            TypeFactControlFlowBlockV2 {
1470                id: "loop".to_string(),
1471                kind: "loopBody".to_string(),
1472                transfer_kind: "concatFacts".to_string(),
1473                successor_block_ids: vec!["loop".to_string()],
1474                symbol_ordinal: None,
1475                variable_name: None,
1476                expression_kind: None,
1477                boundary_effect: "unknownBoundary".to_string(),
1478                facts: None,
1479            },
1480        ],
1481    }
1482}
1483
1484#[cfg(test)]
1485mod selector_certainty_flow_tests {
1486    use super::{
1487        StringTypeFactsV2, hedge_selector_certainty_for_flow, map_selector_certainty_projection,
1488    };
1489    use omena_abstract_value::SelectorProjectionCertaintyV0;
1490
1491    #[test]
1492    fn shared_flow_hedge_demotes_inferred_certainty_to_possible() {
1493        let certainty =
1494            hedge_selector_certainty_for_flow(SelectorProjectionCertaintyV0::Inferred, false, true);
1495
1496        println!("selector-certainty shared hedge inferred->possible");
1497        assert_eq!(certainty, SelectorProjectionCertaintyV0::Possible);
1498    }
1499
1500    #[test]
1501    fn no_flow_hedge_preserves_base_possible_certainty_shape() {
1502        let facts = StringTypeFactsV2 {
1503            kind: "exact".to_string(),
1504            constraint_kind: None,
1505            values: Some(vec!["absent".to_string()]),
1506            prefix: None,
1507            suffix: None,
1508            min_len: None,
1509            max_len: None,
1510            char_must: None,
1511            char_may: None,
1512            may_include_other_chars: None,
1513            provenance: None,
1514        };
1515
1516        let projection = map_selector_certainty_projection(&facts, 0, 1, None);
1517
1518        assert_eq!(projection.certainty, "possible");
1519        assert_eq!(projection.shape_kind, "unknown");
1520        assert_eq!(projection.shape_label, "unknown");
1521    }
1522}