Skip to main content

omena_query_core/
lib.rs

1//! Core query runtime primitives below the public `omena-query` facade.
2//!
3//! This crate owns producer-fragment summaries and expression-domain runtime
4//! state. `omena-query` re-exports these surfaces, but no longer needs to depend
5//! directly on each lower-level producer crate for this part of the dataflow.
6
7use std::collections::{BTreeMap, BTreeSet};
8
9pub use engine_input_producers::{
10    ClassExpressionInputV2, EngineInputV2, ExpressionDomainCallSiteFlowAnalysisV0,
11    ExpressionDomainControlFlowAnalysisV0, ExpressionDomainFlowAnalysisV0,
12    ExpressionDomainProvenanceExplanationsV0, ExpressionDomainReducedProductIterationV0,
13    ExpressionSemanticsCanonicalProducerSignalV0, ExpressionSemanticsQueryFragmentsV0, PositionV2,
14    RangeV2, SelectorUsageCanonicalProducerSignalV0, SelectorUsageQueryFragmentsV0,
15    SourceAnalysisInputV2, SourceDocumentV2, SourceResolutionCanonicalProducerSignalV0,
16    SourceResolutionQueryFragmentsV0, StringTypeFactsV2, StyleAnalysisInputV2, StyleDocumentV2,
17    StyleSelectorV2, TypeFactEntryV2,
18};
19use engine_input_producers::{
20    collect_expression_domain_flow_graphs,
21    summarize_expression_domain_call_site_flow_analysis_input,
22    summarize_expression_domain_control_flow_analysis_input,
23    summarize_expression_domain_flow_analysis_input,
24    summarize_expression_domain_provenance_explanations_input,
25    summarize_expression_domain_reduced_product_iteration_input,
26    summarize_expression_semantics_canonical_producer_signal_input,
27    summarize_expression_semantics_query_fragments_input,
28    summarize_selector_usage_canonical_producer_signal_input,
29    summarize_selector_usage_query_fragments_input,
30};
31pub use omena_abstract_value::{
32    AbstractClassValueV0, AbstractPropertyValueCandidateV0, AbstractPropertyValueNarrowingV0,
33    AbstractPropertyValueV0, AbstractValueDomainSummaryV0, CascadeContextV0,
34    CascadeValueFamilyMemberV0, ClassValueFlowAnalysisV0, ClassValueFlowIncrementalAnalysisV0,
35    CssValueValidationClassV0, ExternalStringTypeFactsV0, FactPrecision, Lin01ProvenanceSemiringV0,
36    LinearProvenancePathV0, LinearProvenanceV0, NaturalCountProvenanceSemiringV0,
37    OmenaAbstractValueCoverageDirectionV0, OmenaAbstractValuePrecisionBasisV0,
38    OmenaAbstractValuePrecisionWitnessV0, PolynomialProvenanceProjectionV0,
39    PolynomialProvenanceTermV0, PolynomialProvenanceV0, PolynomialProvenanceVariableV0,
40    ProvenanceSemiringLawReportV0, ReducedClassValueProductIterationV0, ReducedClassValueProductV0,
41    SelectorProjectionCertaintyV0, abstract_class_value_from_facts, abstract_class_value_kind,
42    derive_cascade_restriction_maps_v0, fact_precision_from_class_value,
43    fact_precision_from_class_value_with_witness, iterate_reduced_class_value_product_constraints,
44    join_abstract_class_values, narrow_abstract_property_value_for_cascade_branch,
45    narrow_abstract_property_value_for_pseudo_state, prefix_suffix_class_value,
46    summarize_cascade_value_family_v0, summarize_polynomial_provenance_from_linear_v0,
47    top_class_value, validate_registered_property_value_v0,
48    verify_provenance_semiring_laws_on_fixtures,
49};
50use omena_abstract_value::{
51    analyze_class_value_flow_incremental_with_database, project_abstract_value_selectors,
52    summarize_omena_abstract_value_domain, summarize_reduced_class_value_product,
53};
54pub use omena_incremental::{
55    IncrementalEditDistancePriorityInputV0, IncrementalGraphInputV0,
56    IncrementalInvalidationPriorityPlanV0, IncrementalNodeInputV0, IncrementalRevisionV0,
57    OmenaIncrementalDatabaseV0, OmenaSalsaDatabaseV0, OmenaWorkspaceSnapshotIdV0,
58    snapshot_from_graph_input,
59};
60pub use omena_refinement::{
61    CascadeDimensionalRefinementBridgeV0, RefinementPropertyPredicateV0,
62    summarize_cascade_dimensional_refinement_bridge_v0,
63};
64pub use omena_resolver::OmenaResolverSourceResolutionRuntimeIndexV0;
65use omena_resolver::{
66    summarize_omena_resolver_canonical_producer_signal, summarize_omena_resolver_query_fragments,
67    summarize_omena_resolver_source_resolution_runtime,
68};
69pub use omena_value_lattice::{
70    canonicalize_css_value, split_top_level_value_arguments,
71    split_top_level_whitespace_value_components,
72};
73use serde::{Deserialize, Serialize};
74
75pub const OMENA_QUERY_CURRENT_SCHEMA_VERSION: &str = "0";
76pub const OMENA_QUERY_CURRENT_SCHEMA_VERSION_LABEL: &str = "V0";
77
78#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
79#[serde(rename_all = "camelCase")]
80pub struct OmenaQueryAnalysisPrecisionV0 {
81    pub product: String,
82    pub value_domain: String,
83    pub flow_sensitivity: String,
84    pub context_sensitivity: String,
85    pub revision_axis: String,
86}
87
88const OMENA_QUERY_ANALYSIS_FACT_PRECISION_BY_VALUE_DOMAIN: &[(&str, FactPrecision)] = &[
89    ("cascadeAtPosition", FactPrecision::Exact),
90    ("styleModuleResolution", FactPrecision::Exact),
91    ("classValueResolution", FactPrecision::Conservative),
92    ("classValueUniverse", FactPrecision::Conservative),
93    ("classValueFlow", FactPrecision::Heuristic),
94    ("unknown", FactPrecision::Unknown),
95];
96
97pub fn fact_precision_from_analysis_precision(
98    precision: &OmenaQueryAnalysisPrecisionV0,
99) -> FactPrecision {
100    OMENA_QUERY_ANALYSIS_FACT_PRECISION_BY_VALUE_DOMAIN
101        .iter()
102        .find_map(|(value_domain, mapped)| {
103            (*value_domain == precision.value_domain).then_some(*mapped)
104        })
105        .unwrap_or(FactPrecision::Unknown)
106}
107
108#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
109#[serde(rename_all = "camelCase")]
110pub struct OmenaQueryAnalysisResultV0<TValue> {
111    pub schema_version: String,
112    pub product: String,
113    pub value: TValue,
114    pub precision: OmenaQueryAnalysisPrecisionV0,
115    pub provenance: Vec<String>,
116    pub revision: u64,
117}
118
119impl<TValue> OmenaQueryAnalysisResultV0<TValue> {
120    pub fn new(
121        value: TValue,
122        precision: OmenaQueryAnalysisPrecisionV0,
123        provenance: Vec<String>,
124        revision: u64,
125    ) -> Self {
126        Self {
127            schema_version: OMENA_QUERY_CURRENT_SCHEMA_VERSION.to_string(),
128            product: "omena-query.analysis-result".to_string(),
129            value,
130            precision,
131            provenance,
132            revision,
133        }
134    }
135}
136
137#[derive(Debug, Serialize)]
138#[serde(rename_all = "camelCase")]
139pub struct OmenaQueryFragmentBundleV0 {
140    pub schema_version: &'static str,
141    pub product: &'static str,
142    pub input_version: String,
143    pub expression_semantics: ExpressionSemanticsQueryFragmentsV0,
144    pub source_resolution: SourceResolutionQueryFragmentsV0,
145    pub selector_usage: SelectorUsageQueryFragmentsV0,
146}
147
148#[derive(Debug, Serialize)]
149#[serde(rename_all = "camelCase")]
150pub struct OmenaQueryExpressionDomainIncrementalFlowAnalysisV0 {
151    pub schema_version: &'static str,
152    pub product: &'static str,
153    pub input_version: String,
154    pub revision: u64,
155    pub graph_count: usize,
156    pub dirty_graph_count: usize,
157    pub reused_graph_count: usize,
158    pub analyses: Vec<OmenaQueryExpressionDomainIncrementalFlowAnalysisEntryV0>,
159}
160
161#[derive(Debug, Serialize)]
162#[serde(rename_all = "camelCase")]
163pub struct OmenaQueryExpressionDomainIncrementalFlowAnalysisEntryV0 {
164    pub graph_id: String,
165    pub file_path: String,
166    pub analysis: ClassValueFlowIncrementalAnalysisV0,
167}
168
169#[derive(Debug, Serialize)]
170#[serde(rename_all = "camelCase")]
171pub struct OmenaQueryExpressionDomainSelectorProjectionV0 {
172    pub schema_version: &'static str,
173    pub product: &'static str,
174    pub input_version: String,
175    pub projection_count: usize,
176    pub projections: Vec<OmenaQueryExpressionDomainSelectorProjectionEntryV0>,
177}
178
179#[derive(Debug, Serialize)]
180#[serde(rename_all = "camelCase")]
181pub struct OmenaQueryExpressionDomainSelectorProjectionEntryV0 {
182    pub graph_id: String,
183    pub file_path: String,
184    pub node_id: String,
185    pub target_style_paths: Vec<String>,
186    pub value_kind: &'static str,
187    #[serde(skip_serializing_if = "Option::is_none")]
188    pub reduced_product: Option<ReducedClassValueProductV0>,
189    pub selector_names: Vec<String>,
190    pub certainty: SelectorProjectionCertaintyV0,
191}
192
193#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
194#[serde(rename_all = "camelCase")]
195pub struct OmenaQueryExpressionDomainSelectorPrecisionV0 {
196    pub graph_id: String,
197    pub node_id: String,
198    pub precision: FactPrecision,
199}
200
201#[derive(Default)]
202pub struct OmenaQueryExpressionDomainFlowRuntimeV0 {
203    revision: u64,
204    databases_by_graph_id: BTreeMap<String, OmenaIncrementalDatabaseV0>,
205    previous_analyses_by_graph_id: BTreeMap<String, ClassValueFlowAnalysisV0>,
206}
207
208impl OmenaQueryExpressionDomainFlowRuntimeV0 {
209    pub fn revision(&self) -> u64 {
210        self.revision
211    }
212
213    pub fn graph_count(&self) -> usize {
214        self.databases_by_graph_id.len()
215    }
216
217    pub fn analyze_input(
218        &mut self,
219        input: &EngineInputV2,
220    ) -> OmenaQueryExpressionDomainIncrementalFlowAnalysisV0 {
221        self.revision += 1;
222        let revision = self.revision;
223        let flow_graphs = collect_expression_domain_flow_graphs(input);
224        let live_graph_ids = flow_graphs
225            .iter()
226            .map(|entry| entry.graph_id.clone())
227            .collect::<BTreeSet<_>>();
228
229        self.databases_by_graph_id
230            .retain(|graph_id, _| live_graph_ids.contains(graph_id));
231        self.previous_analyses_by_graph_id
232            .retain(|graph_id, _| live_graph_ids.contains(graph_id));
233
234        let analyses = flow_graphs
235            .into_iter()
236            .map(|entry| {
237                let database = self
238                    .databases_by_graph_id
239                    .entry(entry.graph_id.clone())
240                    .or_default();
241                let previous_analysis = self.previous_analyses_by_graph_id.get(&entry.graph_id);
242                let analysis = analyze_class_value_flow_incremental_with_database(
243                    &entry.graph,
244                    database,
245                    previous_analysis,
246                    revision,
247                );
248                self.previous_analyses_by_graph_id
249                    .insert(entry.graph_id.clone(), analysis.analysis.clone());
250
251                OmenaQueryExpressionDomainIncrementalFlowAnalysisEntryV0 {
252                    graph_id: entry.graph_id,
253                    file_path: entry.file_path,
254                    analysis,
255                }
256            })
257            .collect::<Vec<_>>();
258
259        let dirty_graph_count = analyses
260            .iter()
261            .filter(|entry| entry.analysis.incremental_plan.dirty_node_count > 0)
262            .count();
263        let reused_graph_count = analyses
264            .iter()
265            .filter(|entry| entry.analysis.reused_previous_analysis)
266            .count();
267
268        OmenaQueryExpressionDomainIncrementalFlowAnalysisV0 {
269            schema_version: OMENA_QUERY_CURRENT_SCHEMA_VERSION,
270            product: "omena-query.expression-domain-incremental-flow-analysis",
271            input_version: input.version.clone(),
272            revision,
273            graph_count: analyses.len(),
274            dirty_graph_count,
275            reused_graph_count,
276            analyses,
277        }
278    }
279}
280
281pub fn summarize_omena_query_core_abstract_value_domain() -> AbstractValueDomainSummaryV0 {
282    summarize_omena_abstract_value_domain()
283}
284
285pub fn summarize_omena_query_fragment_bundle(input: &EngineInputV2) -> OmenaQueryFragmentBundleV0 {
286    OmenaQueryFragmentBundleV0 {
287        schema_version: OMENA_QUERY_CURRENT_SCHEMA_VERSION,
288        product: "omena-query.fragment-bundle",
289        input_version: input.version.clone(),
290        expression_semantics: summarize_omena_query_expression_semantics_query_fragments(input),
291        source_resolution: summarize_omena_query_source_resolution_query_fragments(input),
292        selector_usage: summarize_omena_query_selector_usage_query_fragments(input),
293    }
294}
295
296pub fn summarize_omena_query_expression_semantics_query_fragments(
297    input: &EngineInputV2,
298) -> ExpressionSemanticsQueryFragmentsV0 {
299    summarize_expression_semantics_query_fragments_input(input)
300}
301
302pub fn summarize_omena_query_expression_domain_flow_analysis(
303    input: &EngineInputV2,
304) -> ExpressionDomainFlowAnalysisV0 {
305    summarize_expression_domain_flow_analysis_input(input)
306}
307
308pub fn summarize_omena_query_expression_domain_control_flow_analysis(
309    input: &EngineInputV2,
310) -> ExpressionDomainControlFlowAnalysisV0 {
311    summarize_expression_domain_control_flow_analysis_input(input)
312}
313
314pub fn summarize_omena_query_expression_domain_call_site_flow_analysis(
315    input: &EngineInputV2,
316) -> ExpressionDomainCallSiteFlowAnalysisV0 {
317    summarize_expression_domain_call_site_flow_analysis_input(input)
318}
319
320pub fn summarize_omena_query_expression_domain_provenance_explanations(
321    input: &EngineInputV2,
322) -> ExpressionDomainProvenanceExplanationsV0 {
323    summarize_expression_domain_provenance_explanations_input(input)
324}
325
326pub fn summarize_omena_query_expression_domain_reduced_product_iteration(
327    input: &EngineInputV2,
328) -> ExpressionDomainReducedProductIterationV0 {
329    summarize_expression_domain_reduced_product_iteration_input(input)
330}
331
332pub fn summarize_omena_query_expression_domain_incremental_flow_analysis(
333    input: &EngineInputV2,
334    runtime: &mut OmenaQueryExpressionDomainFlowRuntimeV0,
335) -> OmenaQueryExpressionDomainIncrementalFlowAnalysisV0 {
336    runtime.analyze_input(input)
337}
338
339pub fn summarize_omena_query_expression_domain_incremental_flow_analysis_result(
340    input: &EngineInputV2,
341    runtime: &mut OmenaQueryExpressionDomainFlowRuntimeV0,
342) -> OmenaQueryAnalysisResultV0<OmenaQueryExpressionDomainIncrementalFlowAnalysisV0> {
343    let value = runtime.analyze_input(input);
344    let revision = value.revision;
345    OmenaQueryAnalysisResultV0::new(
346        value,
347        OmenaQueryAnalysisPrecisionV0 {
348            product: "omena-query.analysis-precision".to_string(),
349            value_domain: "classValueFlow".to_string(),
350            flow_sensitivity: "incrementalDataflow".to_string(),
351            context_sensitivity: "perExpressionGraph".to_string(),
352            revision_axis: "OmenaQueryExpressionDomainFlowRuntimeV0.revision".to_string(),
353        },
354        vec![
355            "omena-query-core.expression-domain-runtime".to_string(),
356            "omena-abstract-value.incremental-class-value-flow".to_string(),
357        ],
358        revision,
359    )
360}
361
362pub fn summarize_omena_query_expression_domain_selector_projection(
363    input: &EngineInputV2,
364) -> OmenaQueryExpressionDomainSelectorProjectionV0 {
365    summarize_omena_query_expression_domain_selector_projection_with_precision(input).0
366}
367
368pub fn summarize_omena_query_expression_domain_selector_projection_with_precision(
369    input: &EngineInputV2,
370) -> (
371    OmenaQueryExpressionDomainSelectorProjectionV0,
372    Vec<OmenaQueryExpressionDomainSelectorPrecisionV0>,
373) {
374    let style_selectors_by_path = style_selector_universe_by_path(input);
375    let expression_targets = expression_target_style_paths(input);
376    let flow_analysis = summarize_omena_query_expression_domain_flow_analysis(input);
377    let mut projections = Vec::new();
378    let mut precisions = Vec::new();
379
380    for graph in flow_analysis.analyses {
381        for node in graph.analysis.nodes {
382            let target_style_paths = target_style_paths_for_flow_node(
383                node.id.as_str(),
384                node.predecessor_ids.as_slice(),
385                &expression_targets,
386            );
387            let selector_universe = selector_universe_for_targets(
388                target_style_paths.as_slice(),
389                &style_selectors_by_path,
390            );
391            let projection = project_abstract_value_selectors(&node.value, &selector_universe);
392            precisions.push(OmenaQueryExpressionDomainSelectorPrecisionV0 {
393                graph_id: graph.graph_id.clone(),
394                node_id: node.id.clone(),
395                precision: fact_precision_from_class_value(&node.value),
396            });
397            projections.push(OmenaQueryExpressionDomainSelectorProjectionEntryV0 {
398                graph_id: graph.graph_id.clone(),
399                file_path: graph.file_path.clone(),
400                node_id: node.id,
401                target_style_paths,
402                value_kind: node.value_kind,
403                reduced_product: summarize_reduced_class_value_product(&node.value),
404                selector_names: projection.selector_names,
405                certainty: projection.certainty,
406            });
407        }
408    }
409
410    (
411        OmenaQueryExpressionDomainSelectorProjectionV0 {
412            schema_version: OMENA_QUERY_CURRENT_SCHEMA_VERSION,
413            product: "omena-query.expression-domain-selector-projection",
414            input_version: input.version.clone(),
415            projection_count: projections.len(),
416            projections,
417        },
418        precisions,
419    )
420}
421
422fn expression_target_style_paths(input: &EngineInputV2) -> BTreeMap<String, String> {
423    input
424        .sources
425        .iter()
426        .flat_map(|source| source.document.class_expressions.iter())
427        .map(|expression| (expression.id.clone(), expression.scss_module_path.clone()))
428        .collect()
429}
430
431fn style_selector_universe_by_path(input: &EngineInputV2) -> BTreeMap<String, Vec<String>> {
432    input
433        .styles
434        .iter()
435        .map(|style| {
436            let selector_names = style
437                .document
438                .selectors
439                .iter()
440                .map(|selector| {
441                    selector
442                        .canonical_name
443                        .clone()
444                        .unwrap_or_else(|| selector.name.clone())
445                })
446                .collect::<BTreeSet<_>>()
447                .into_iter()
448                .collect::<Vec<_>>();
449            (style.file_path.clone(), selector_names)
450        })
451        .collect()
452}
453
454fn target_style_paths_for_flow_node(
455    node_id: &str,
456    predecessor_ids: &[String],
457    expression_targets: &BTreeMap<String, String>,
458) -> Vec<String> {
459    let mut targets = BTreeSet::new();
460    if let Some(target) = expression_targets.get(node_id) {
461        targets.insert(target.clone());
462    }
463    for predecessor_id in predecessor_ids {
464        if let Some(target) = expression_targets.get(predecessor_id) {
465            targets.insert(target.clone());
466        }
467    }
468    targets.into_iter().collect()
469}
470
471fn selector_universe_for_targets(
472    target_style_paths: &[String],
473    style_selectors_by_path: &BTreeMap<String, Vec<String>>,
474) -> Vec<String> {
475    let mut selectors = BTreeSet::new();
476    if target_style_paths.is_empty() {
477        for selector_names in style_selectors_by_path.values() {
478            selectors.extend(selector_names.iter().cloned());
479        }
480    } else {
481        for target_style_path in target_style_paths {
482            if let Some(selector_names) = style_selectors_by_path.get(target_style_path) {
483                selectors.extend(selector_names.iter().cloned());
484            }
485        }
486    }
487    selectors.into_iter().collect()
488}
489
490pub fn summarize_omena_query_source_resolution_query_fragments(
491    input: &EngineInputV2,
492) -> SourceResolutionQueryFragmentsV0 {
493    summarize_omena_resolver_query_fragments(input)
494}
495
496pub fn summarize_omena_query_selector_usage_query_fragments(
497    input: &EngineInputV2,
498) -> SelectorUsageQueryFragmentsV0 {
499    summarize_selector_usage_query_fragments_input(input)
500}
501
502pub fn summarize_omena_query_source_resolution_canonical_producer_signal(
503    input: &EngineInputV2,
504) -> SourceResolutionCanonicalProducerSignalV0 {
505    summarize_omena_resolver_canonical_producer_signal(input)
506}
507
508pub fn summarize_omena_query_source_resolution_runtime(
509    input: &EngineInputV2,
510) -> OmenaResolverSourceResolutionRuntimeIndexV0 {
511    summarize_omena_resolver_source_resolution_runtime(input)
512}
513
514pub fn summarize_omena_query_expression_semantics_canonical_producer_signal(
515    input: &EngineInputV2,
516) -> ExpressionSemanticsCanonicalProducerSignalV0 {
517    summarize_expression_semantics_canonical_producer_signal_input(input)
518}
519
520pub fn summarize_omena_query_selector_usage_canonical_producer_signal(
521    input: &EngineInputV2,
522) -> SelectorUsageCanonicalProducerSignalV0 {
523    summarize_selector_usage_canonical_producer_signal_input(input)
524}
525
526#[cfg(test)]
527mod tests {
528    use super::*;
529
530    #[test]
531    fn expression_domain_runtime_reuses_graph_databases_across_revisions() {
532        let input = EngineInputV2 {
533            version: "core-runtime".to_string(),
534            sources: Vec::new(),
535            styles: Vec::new(),
536            type_facts: Vec::new(),
537        };
538        let mut runtime = OmenaQueryExpressionDomainFlowRuntimeV0::default();
539
540        let first =
541            summarize_omena_query_expression_domain_incremental_flow_analysis(&input, &mut runtime);
542        let second =
543            summarize_omena_query_expression_domain_incremental_flow_analysis(&input, &mut runtime);
544
545        assert_eq!(first.revision, 1);
546        assert_eq!(second.revision, 2);
547        assert_eq!(runtime.revision(), 2);
548    }
549
550    #[test]
551    fn analysis_precision_view_maps_known_producers_and_fails_closed() {
552        let precision = |value_domain: &str| OmenaQueryAnalysisPrecisionV0 {
553            product: "omena-query.analysis-precision".to_string(),
554            value_domain: value_domain.to_string(),
555            flow_sensitivity: "fixture".to_string(),
556            context_sensitivity: "fixture".to_string(),
557            revision_axis: "fixture".to_string(),
558        };
559
560        assert_eq!(
561            fact_precision_from_analysis_precision(&precision("cascadeAtPosition")),
562            FactPrecision::Exact
563        );
564        assert_eq!(
565            fact_precision_from_analysis_precision(&precision("styleModuleResolution")),
566            FactPrecision::Exact
567        );
568        assert_eq!(
569            fact_precision_from_analysis_precision(&precision("classValueResolution")),
570            FactPrecision::Conservative
571        );
572        assert_eq!(
573            fact_precision_from_analysis_precision(&precision("classValueUniverse")),
574            FactPrecision::Conservative
575        );
576        assert_eq!(
577            fact_precision_from_analysis_precision(&precision("classValueFlow")),
578            FactPrecision::Heuristic
579        );
580        assert_eq!(
581            fact_precision_from_analysis_precision(&precision("unknown")),
582            FactPrecision::Unknown
583        );
584        assert_eq!(
585            fact_precision_from_analysis_precision(&precision("unregistered")),
586            FactPrecision::Unknown
587        );
588    }
589}