Skip to main content

presolve_compiler/
runtime_computed_artifact.rs

1use std::collections::{BTreeMap, BTreeSet};
2
3use serde::Serialize;
4
5use crate::{
6    build_runtime_computed_registry, ApplicationSemanticModel, IntermediateRepresentation,
7    IrBinaryOperation, IrConstant, IrInstructionKind, IrOperand, IrStorageId, IrUnaryOperation,
8    SemanticId, SerializableValue, SerializationCompatibility,
9};
10
11pub const RUNTIME_COMPUTED_ARTIFACT_SCHEMA_VERSION: u32 = 12;
12
13/// Versioned runtime metadata and executable programs emitted from canonical IR.
14#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
15pub struct RuntimeComputedArtifact {
16    pub schema_version: u32,
17    pub state: Vec<RuntimeComputedArtifactState>,
18    pub invalidations: Vec<RuntimeComputedArtifactInvalidation>,
19    pub resource_invalidations: Vec<RuntimeComputedArtifactResourceInvalidation>,
20    pub evaluations: Vec<RuntimeComputedArtifactEvaluation>,
21    pub evaluation_order: Vec<String>,
22    pub update_batches: Vec<Vec<String>>,
23}
24
25/// Compiler-lowered storage initialization available to computed programs.
26#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
27pub struct RuntimeComputedArtifactState {
28    pub component: String,
29    pub field: String,
30    pub storage: String,
31    pub initial_value: Option<SerializableValue>,
32}
33
34/// Compiler-generated transitive computed invalidations for one state storage.
35#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
36pub struct RuntimeComputedArtifactInvalidation {
37    pub storage: String,
38    pub dependents: Vec<String>,
39}
40
41/// Compiler-generated transitive computed invalidations for one exact Resource
42/// declaration. The browser runtime still resolves its activation through the
43/// active component instance; this artifact never exposes a dynamic lookup.
44#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
45pub struct RuntimeComputedArtifactResourceInvalidation {
46    pub declaration: String,
47    pub dependents: Vec<String>,
48}
49
50/// Runtime metadata and canonical instruction program for one computed value.
51#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
52pub struct RuntimeComputedArtifactEvaluation {
53    pub computed: String,
54    pub component: String,
55    pub cache_slot: String,
56    pub dirty_flag: RuntimeComputedArtifactDirtyFlag,
57    pub dependencies: Vec<String>,
58    pub evaluation_function: String,
59    pub serialization: RuntimeComputedArtifactSerialization,
60    pub program: RuntimeComputedArtifactProgram,
61}
62
63/// One compiler-lowered evaluation result and its authored-order instructions.
64#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
65pub struct RuntimeComputedArtifactProgram {
66    pub result: String,
67    pub instructions: Vec<RuntimeComputedArtifactInstruction>,
68}
69
70/// A runtime operand still tied to a canonical IR value or constant.
71#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
72#[serde(tag = "kind", rename_all = "kebab-case")]
73pub enum RuntimeComputedArtifactOperand {
74    Value { value: String },
75    Constant { value: SerializableValue },
76    Storage { storage: String },
77}
78
79/// One supported compiler-lowered computed instruction.
80#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
81#[serde(tag = "kind", rename_all = "kebab-case")]
82pub enum RuntimeComputedArtifactInstruction {
83    Constant {
84        result: String,
85        value: SerializableValue,
86    },
87    LoadState {
88        result: String,
89        storage: String,
90    },
91    LoadComputed {
92        result: String,
93        computed: String,
94    },
95    LoadResource {
96        result: String,
97        declaration: String,
98    },
99    GetMember {
100        result: String,
101        object: RuntimeComputedArtifactOperand,
102        property: String,
103        optional: bool,
104    },
105    GetIndex {
106        result: String,
107        object: RuntimeComputedArtifactOperand,
108        index: RuntimeComputedArtifactOperand,
109    },
110    Select {
111        result: String,
112        condition: RuntimeComputedArtifactOperand,
113        when_true: RuntimeComputedArtifactOperand,
114        when_false: RuntimeComputedArtifactOperand,
115    },
116    Template {
117        result: String,
118        quasis: Vec<String>,
119        expressions: Vec<String>,
120    },
121    PurePackageCall {
122        result: String,
123        package: String,
124        version: String,
125        integrity: String,
126        export: String,
127        runtime_module: String,
128        resume_policy: String,
129        operation: crate::semantic_package::SemanticPackagePureOperation,
130        arguments: Vec<String>,
131    },
132    Binary {
133        result: String,
134        operation: RuntimeComputedArtifactBinaryOperation,
135        left: RuntimeComputedArtifactOperand,
136        right: RuntimeComputedArtifactOperand,
137    },
138    Unary {
139        result: String,
140        operation: RuntimeComputedArtifactUnaryOperation,
141        operand: RuntimeComputedArtifactOperand,
142    },
143}
144
145#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
146#[serde(rename_all = "kebab-case")]
147pub enum RuntimeComputedArtifactBinaryOperation {
148    Add,
149    Subtract,
150    Multiply,
151    Divide,
152    Remainder,
153    Equal,
154    NotEqual,
155    LessThan,
156    LessThanOrEqual,
157    GreaterThan,
158    GreaterThanOrEqual,
159    And,
160    Or,
161    NullishCoalesce,
162    Min,
163    Max,
164}
165
166#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
167#[serde(rename_all = "kebab-case")]
168pub enum RuntimeComputedArtifactUnaryOperation {
169    Not,
170    Identity,
171    Negate,
172    Abs,
173    Floor,
174    Ceil,
175    Round,
176}
177
178/// Compiler-provided dirty state for one emitted computed evaluation.
179#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
180pub struct RuntimeComputedArtifactDirtyFlag {
181    pub id: String,
182    pub initial_value: bool,
183}
184
185/// Runtime-facing spelling for the compiler's serialization contract.
186#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
187#[serde(rename_all = "kebab-case")]
188pub enum RuntimeComputedArtifactSerialization {
189    Serializable,
190    NotSerializable,
191}
192
193/// Emit deterministic runtime metadata and E10 programs from canonical ASM and IR.
194///
195/// The artifact contains no source expressions and requires no runtime dependency
196/// discovery. E15 executes its explicit E9 order once, populating compiler-owned
197/// cache slots without invalidation or re-planning.
198#[must_use]
199pub fn build_runtime_computed_artifact(
200    model: &ApplicationSemanticModel,
201    ir: &IntermediateRepresentation,
202) -> RuntimeComputedArtifact {
203    let registry = build_runtime_computed_registry(model, ir);
204    let available = registry_ids(&registry);
205    let evaluations = runtime_evaluations(model, &registry, ir);
206    let emitted = evaluations
207        .iter()
208        .map(|evaluation| evaluation.computed.clone())
209        .collect::<BTreeSet<_>>();
210    let state = runtime_state(model);
211
212    RuntimeComputedArtifact {
213        schema_version: RUNTIME_COMPUTED_ARTIFACT_SCHEMA_VERSION,
214        invalidations: runtime_invalidations(model, &state, &emitted),
215        resource_invalidations: runtime_resource_invalidations(model, &emitted),
216        state,
217        evaluations,
218        evaluation_order: planned_evaluations(model, &available, &emitted),
219        update_batches: planned_batches(model, &available, &emitted),
220    }
221}
222
223fn registry_ids(registry: &crate::RuntimeComputedRegistry) -> BTreeSet<String> {
224    registry
225        .records
226        .keys()
227        .map(|computed| computed.as_str().to_string())
228        .collect()
229}
230
231fn runtime_evaluations(
232    model: &ApplicationSemanticModel,
233    registry: &crate::RuntimeComputedRegistry,
234    ir: &IntermediateRepresentation,
235) -> Vec<RuntimeComputedArtifactEvaluation> {
236    let programs = computed_programs(ir);
237    registry
238        .records
239        .values()
240        .filter_map(|record| {
241            let component = model
242                .components
243                .iter()
244                .find(|component| record.computed.as_str().starts_with(component.id.as_str()))?;
245            let program = programs.get(&record.computed)?.clone();
246            Some(RuntimeComputedArtifactEvaluation {
247                computed: record.computed.as_str().to_string(),
248                component: component.class_name.clone(),
249                cache_slot: record.cache_slot.as_str().to_string(),
250                dirty_flag: RuntimeComputedArtifactDirtyFlag {
251                    id: record.dirty_flag.id.as_str().to_string(),
252                    initial_value: record.dirty_flag.initial_value,
253                },
254                dependencies: record
255                    .dependencies
256                    .iter()
257                    .map(|dependency| dependency.as_str().to_string())
258                    .collect(),
259                evaluation_function: record.evaluation_function.as_str().to_string(),
260                serialization: serialization(record.serialization),
261                program,
262            })
263        })
264        .collect()
265}
266
267fn runtime_state(model: &ApplicationSemanticModel) -> Vec<RuntimeComputedArtifactState> {
268    model
269        .components
270        .iter()
271        .flat_map(|component| {
272            component
273                .state_fields
274                .iter()
275                .map(move |field| RuntimeComputedArtifactState {
276                    component: component.class_name.clone(),
277                    field: field.name.clone(),
278                    storage: IrStorageId::for_semantic_origin(&field.id)
279                        .as_str()
280                        .to_string(),
281                    initial_value: field.initial_value.clone(),
282                })
283        })
284        .collect()
285}
286
287fn runtime_invalidations(
288    model: &ApplicationSemanticModel,
289    state: &[RuntimeComputedArtifactState],
290    emitted: &BTreeSet<String>,
291) -> Vec<RuntimeComputedArtifactInvalidation> {
292    state
293        .iter()
294        .filter_map(|state| {
295            let field = model.components.iter().find_map(|component| {
296                (component.class_name == state.component)
297                    .then(|| {
298                        component
299                            .state_fields
300                            .iter()
301                            .find(|field| field.name == state.field)
302                    })
303                    .flatten()
304            })?;
305            Some(RuntimeComputedArtifactInvalidation {
306                storage: state.storage.clone(),
307                dependents: model
308                    .reactive_transitive_analysis
309                    .dependents_of(field.id.as_str())
310                    .iter()
311                    .filter(|computed| emitted.contains(computed.as_str()))
312                    .cloned()
313                    .collect(),
314            })
315        })
316        .collect()
317}
318
319fn runtime_resource_invalidations(
320    model: &ApplicationSemanticModel,
321    emitted: &BTreeSet<String>,
322) -> Vec<RuntimeComputedArtifactResourceInvalidation> {
323    model
324        .resource_declarations
325        .values()
326        .map(|resource| RuntimeComputedArtifactResourceInvalidation {
327            declaration: resource.id.as_str().to_string(),
328            dependents: model
329                .reactive_transitive_analysis
330                .dependents_of(resource.id.as_str())
331                .iter()
332                .filter(|computed| emitted.contains(computed.as_str()))
333                .cloned()
334                .collect(),
335        })
336        .collect()
337}
338
339fn planned_evaluations(
340    model: &ApplicationSemanticModel,
341    available: &BTreeSet<String>,
342    emitted: &BTreeSet<String>,
343) -> Vec<String> {
344    model
345        .computed_evaluation_plan
346        .evaluation_order
347        .iter()
348        .filter(|computed| {
349            available.contains(computed.as_str()) && emitted.contains(computed.as_str())
350        })
351        .cloned()
352        .collect()
353}
354
355fn planned_batches(
356    model: &ApplicationSemanticModel,
357    available: &BTreeSet<String>,
358    emitted: &BTreeSet<String>,
359) -> Vec<Vec<String>> {
360    model
361        .computed_evaluation_plan
362        .update_batches
363        .iter()
364        .map(|batch| {
365            batch
366                .iter()
367                .filter(|computed| {
368                    available.contains(computed.as_str()) && emitted.contains(computed.as_str())
369                })
370                .cloned()
371                .collect::<Vec<_>>()
372        })
373        .filter(|batch| !batch.is_empty())
374        .collect()
375}
376
377/// Serialize emitted computed runtime metadata as deterministic, pretty JSON.
378///
379/// # Panics
380///
381/// Panics when the compiler-owned runtime metadata cannot serialize.
382#[must_use]
383pub fn runtime_computed_artifact_json(artifact: &RuntimeComputedArtifact) -> String {
384    serde_json::to_string_pretty(artifact).expect("computed runtime artifact should serialize")
385        + "\n"
386}
387
388fn computed_programs(
389    ir: &IntermediateRepresentation,
390) -> BTreeMap<SemanticId, RuntimeComputedArtifactProgram> {
391    ir.modules
392        .iter()
393        .flat_map(|module| {
394            module.computed_evaluations.iter().filter_map(|evaluation| {
395                let function = module
396                    .functions
397                    .iter()
398                    .find(|function| function.id == evaluation.function)?;
399                let instructions = function
400                    .blocks
401                    .iter()
402                    .flat_map(|block| block.instructions.iter())
403                    .map(runtime_instruction)
404                    .collect::<Option<Vec<_>>>()?;
405                Some((
406                    evaluation.computed.clone(),
407                    RuntimeComputedArtifactProgram {
408                        result: evaluation.result.as_str().to_string(),
409                        instructions,
410                    },
411                ))
412            })
413        })
414        .collect()
415}
416
417pub(crate) fn runtime_instruction(
418    instruction: &crate::IrInstruction,
419) -> Option<RuntimeComputedArtifactInstruction> {
420    let result = instruction.result.as_ref()?.as_str().to_string();
421    match &instruction.kind {
422        IrInstructionKind::Constant { value } => {
423            Some(RuntimeComputedArtifactInstruction::Constant {
424                result,
425                value: runtime_constant(value),
426            })
427        }
428        IrInstructionKind::LoadStorage { storage } => {
429            Some(RuntimeComputedArtifactInstruction::LoadState {
430                result,
431                storage: storage.as_str().to_string(),
432            })
433        }
434        IrInstructionKind::LoadComputed { computed } => {
435            Some(RuntimeComputedArtifactInstruction::LoadComputed {
436                result,
437                computed: computed.as_str().to_string(),
438            })
439        }
440        IrInstructionKind::LoadResource { declaration } => {
441            Some(RuntimeComputedArtifactInstruction::LoadResource {
442                result,
443                declaration: declaration.as_str().to_string(),
444            })
445        }
446        IrInstructionKind::GetMember {
447            object,
448            property,
449            optional,
450        } => Some(RuntimeComputedArtifactInstruction::GetMember {
451            result,
452            object: runtime_operand(object),
453            property: property.clone(),
454            optional: *optional,
455        }),
456        IrInstructionKind::GetIndex { object, index } => {
457            Some(RuntimeComputedArtifactInstruction::GetIndex {
458                result,
459                object: runtime_operand(object),
460                index: runtime_operand(index),
461            })
462        }
463        IrInstructionKind::Select {
464            condition,
465            when_true,
466            when_false,
467        } => Some(RuntimeComputedArtifactInstruction::Select {
468            result,
469            condition: runtime_operand(condition),
470            when_true: runtime_operand(when_true),
471            when_false: runtime_operand(when_false),
472        }),
473        IrInstructionKind::Template {
474            quasis,
475            expressions,
476        } => Some(RuntimeComputedArtifactInstruction::Template {
477            result,
478            quasis: quasis.clone(),
479            expressions: expressions
480                .iter()
481                .map(|expression| expression.as_str().to_string())
482                .collect(),
483        }),
484        IrInstructionKind::PurePackageCall {
485            package,
486            version,
487            integrity,
488            export,
489            runtime_module,
490            resume_policy,
491            operation,
492            arguments,
493        } => Some(RuntimeComputedArtifactInstruction::PurePackageCall {
494            result,
495            package: package.clone(),
496            version: version.clone(),
497            integrity: integrity.clone(),
498            export: export.clone(),
499            runtime_module: runtime_module.clone(),
500            resume_policy: resume_policy.clone(),
501            operation: *operation,
502            arguments: arguments
503                .iter()
504                .map(|argument| argument.as_str().to_string())
505                .collect(),
506        }),
507        IrInstructionKind::Binary {
508            operation,
509            left,
510            right,
511        } => Some(RuntimeComputedArtifactInstruction::Binary {
512            result,
513            operation: binary_operation(*operation),
514            left: runtime_operand(left),
515            right: runtime_operand(right),
516        }),
517        IrInstructionKind::Unary { operation, operand } => {
518            Some(RuntimeComputedArtifactInstruction::Unary {
519                result,
520                operation: unary_operation(*operation),
521                operand: runtime_operand(operand),
522            })
523        }
524        IrInstructionKind::Nop
525        | IrInstructionKind::Copy { .. }
526        | IrInstructionKind::InitializeStorage { .. }
527        | IrInstructionKind::InitializeContextSlot { .. }
528        | IrInstructionKind::StoreStorage { .. }
529        | IrInstructionKind::LoadContextSlot { .. }
530        | IrInstructionKind::CapabilityCall { .. }
531        | IrInstructionKind::CapabilityAssign { .. } => None,
532    }
533}
534
535fn runtime_operand(operand: &IrOperand) -> RuntimeComputedArtifactOperand {
536    match operand {
537        IrOperand::Value(value) => RuntimeComputedArtifactOperand::Value {
538            value: value.as_str().to_string(),
539        },
540        IrOperand::Constant(value) => RuntimeComputedArtifactOperand::Constant {
541            value: runtime_constant(value),
542        },
543        IrOperand::Storage(storage) => RuntimeComputedArtifactOperand::Storage {
544            storage: storage.as_str().to_string(),
545        },
546    }
547}
548
549fn runtime_constant(constant: &IrConstant) -> SerializableValue {
550    match constant {
551        IrConstant::Null => SerializableValue::Null,
552        IrConstant::Boolean(value) => SerializableValue::Boolean(*value),
553        IrConstant::Number(value) => SerializableValue::Number(value.clone()),
554        IrConstant::String(value) => SerializableValue::String(value.clone()),
555        IrConstant::Array(value) => SerializableValue::Array(value.clone()),
556        IrConstant::Object(value) => SerializableValue::Object(value.clone()),
557    }
558}
559
560const fn binary_operation(operation: IrBinaryOperation) -> RuntimeComputedArtifactBinaryOperation {
561    match operation {
562        IrBinaryOperation::Add => RuntimeComputedArtifactBinaryOperation::Add,
563        IrBinaryOperation::Subtract => RuntimeComputedArtifactBinaryOperation::Subtract,
564        IrBinaryOperation::Multiply => RuntimeComputedArtifactBinaryOperation::Multiply,
565        IrBinaryOperation::Divide => RuntimeComputedArtifactBinaryOperation::Divide,
566        IrBinaryOperation::Remainder => RuntimeComputedArtifactBinaryOperation::Remainder,
567        IrBinaryOperation::Equal => RuntimeComputedArtifactBinaryOperation::Equal,
568        IrBinaryOperation::NotEqual => RuntimeComputedArtifactBinaryOperation::NotEqual,
569        IrBinaryOperation::LessThan => RuntimeComputedArtifactBinaryOperation::LessThan,
570        IrBinaryOperation::LessThanOrEqual => {
571            RuntimeComputedArtifactBinaryOperation::LessThanOrEqual
572        }
573        IrBinaryOperation::GreaterThan => RuntimeComputedArtifactBinaryOperation::GreaterThan,
574        IrBinaryOperation::GreaterThanOrEqual => {
575            RuntimeComputedArtifactBinaryOperation::GreaterThanOrEqual
576        }
577        IrBinaryOperation::And => RuntimeComputedArtifactBinaryOperation::And,
578        IrBinaryOperation::Or => RuntimeComputedArtifactBinaryOperation::Or,
579        IrBinaryOperation::NullishCoalesce => {
580            RuntimeComputedArtifactBinaryOperation::NullishCoalesce
581        }
582        IrBinaryOperation::Min => RuntimeComputedArtifactBinaryOperation::Min,
583        IrBinaryOperation::Max => RuntimeComputedArtifactBinaryOperation::Max,
584    }
585}
586
587const fn unary_operation(operation: IrUnaryOperation) -> RuntimeComputedArtifactUnaryOperation {
588    match operation {
589        IrUnaryOperation::Not => RuntimeComputedArtifactUnaryOperation::Not,
590        IrUnaryOperation::Identity => RuntimeComputedArtifactUnaryOperation::Identity,
591        IrUnaryOperation::Negate => RuntimeComputedArtifactUnaryOperation::Negate,
592        IrUnaryOperation::Abs => RuntimeComputedArtifactUnaryOperation::Abs,
593        IrUnaryOperation::Floor => RuntimeComputedArtifactUnaryOperation::Floor,
594        IrUnaryOperation::Ceil => RuntimeComputedArtifactUnaryOperation::Ceil,
595        IrUnaryOperation::Round => RuntimeComputedArtifactUnaryOperation::Round,
596    }
597}
598
599const fn serialization(
600    compatibility: SerializationCompatibility,
601) -> RuntimeComputedArtifactSerialization {
602    match compatibility {
603        SerializationCompatibility::Serializable => {
604            RuntimeComputedArtifactSerialization::Serializable
605        }
606        SerializationCompatibility::NotSerializable => {
607            RuntimeComputedArtifactSerialization::NotSerializable
608        }
609    }
610}
611
612#[cfg(test)]
613mod tests {
614    use super::RuntimeComputedArtifactInstruction;
615    use crate::{
616        build_application_semantic_model, build_runtime_computed_artifact, lower_components_to_ir,
617        runtime_computed_artifact_json, RUNTIME_COMPUTED_ARTIFACT_SCHEMA_VERSION,
618    };
619
620    #[test]
621    fn emits_deterministic_runtime_programs_from_canonical_ir() {
622        let parsed = presolve_parser::parse_file(
623            "src/RuntimeComputedArtifact.tsx",
624            r#"
625@component("x-runtime-computed-artifact")
626class RuntimeComputedArtifact extends Component {
627  count = state(1);
628
629  @computed()
630  get doubled() { return this.count * 2; }
631
632  @computed()
633  get label() { return this.doubled + 1; }
634}
635"#,
636        );
637        let model = build_application_semantic_model(&parsed);
638        let component = &model.components[0];
639        let count = component.id.state_field("count");
640        let doubled = component.id.computed("doubled");
641        let label = component.id.computed("label");
642        let ir = lower_components_to_ir(&model);
643        let artifact = build_runtime_computed_artifact(&model, &ir);
644
645        assert_eq!(
646            artifact.schema_version,
647            RUNTIME_COMPUTED_ARTIFACT_SCHEMA_VERSION
648        );
649        assert_eq!(artifact.state.len(), 1);
650        assert_eq!(artifact.state[0].field, "count");
651        assert_eq!(artifact.invalidations.len(), 1);
652        assert_eq!(
653            artifact.invalidations[0].dependents,
654            vec![doubled.to_string(), label.to_string()]
655        );
656        assert_eq!(artifact.evaluations.len(), 2);
657        assert_eq!(artifact.evaluations[0].computed, doubled.as_str());
658        assert_eq!(
659            artifact.evaluations[0].dependencies,
660            vec![count.to_string()]
661        );
662        assert_eq!(artifact.evaluations[1].computed, label.as_str());
663        assert_eq!(
664            artifact.evaluations[1].dependencies,
665            vec![doubled.to_string()]
666        );
667        assert_eq!(
668            artifact.evaluation_order,
669            vec![doubled.to_string(), label.to_string()]
670        );
671        assert!(artifact.evaluations.iter().all(|evaluation| {
672            !evaluation.program.instructions.is_empty()
673                && evaluation.program.result.contains("/value:")
674                && evaluation.dirty_flag.initial_value
675        }));
676
677        let first = runtime_computed_artifact_json(&artifact);
678        let second = runtime_computed_artifact_json(&build_runtime_computed_artifact(&model, &ir));
679        assert_eq!(first, second);
680        let json: serde_json::Value = serde_json::from_str(&first).expect("artifact JSON");
681        assert_eq!(json["schema_version"], 12);
682        assert_eq!(
683            json["evaluations"][0]["program"]["instructions"][0]["kind"],
684            "load-state"
685        );
686        assert_eq!(
687            json["evaluations"][1]["program"]["instructions"][0]["kind"],
688            "load-computed"
689        );
690    }
691
692    #[test]
693    fn emits_compiler_lowered_identity_package_call_with_contract_provenance() {
694        let unit = crate::CompilationUnit::parse_sources([(
695            "src/PackageComputed.tsx",
696            r#"
697import { identity as visible } from "value-kit";
698
699@component("x-package-computed")
700class PackageComputed extends Component {
701  count = state(1);
702
703  @computed()
704  get visibleCount() { return visible(this.count); }
705
706  render() { return <output>Visible count</output>; }
707}
708"#,
709        )]);
710        let contract = crate::semantic_package::parse_semantic_package_contract(
711            r#"{"schema_version":1,"package":"value-kit","version":"1.0.0","integrity":"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","exports":{"identity":{"kind":"pure","type_signature":"<T>(T)->T","runtime_module":"dist/identity.js","resume_policy":"input_only","pure_operation":"identity"}}}"#,
712        )
713        .expect("valid package contract");
714        let mut packages = crate::semantic_package::SemanticPackageResolutionTable::default();
715        packages.insert("value-kit".into(), contract).unwrap();
716
717        let model = crate::application_semantic_model::build_application_semantic_model_for_unit_with_packages(&unit, &packages);
718        assert!(
719            model.diagnostics.is_empty(),
720            "package model diagnostics: {:?}",
721            model.diagnostics
722        );
723        assert!(model
724            .diagnostics
725            .iter()
726            .all(|diagnostic| diagnostic.code != "PSC3015"));
727        let ir = lower_components_to_ir(&model);
728        let artifact = build_runtime_computed_artifact(&model, &ir);
729        let instruction = artifact.evaluations[0]
730            .program
731            .instructions
732            .iter()
733            .find(|instruction| {
734                matches!(
735                    instruction,
736                    RuntimeComputedArtifactInstruction::PurePackageCall { .. }
737                )
738            })
739            .expect("lowered pure package call");
740        let RuntimeComputedArtifactInstruction::PurePackageCall {
741            package,
742            version,
743            integrity,
744            export,
745            operation,
746            arguments,
747            ..
748        } = instruction
749        else {
750            unreachable!();
751        };
752        assert_eq!(package, "value-kit");
753        assert_eq!(version, "1.0.0");
754        assert_eq!(
755            integrity,
756            "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
757        );
758        assert_eq!(export, "identity");
759        assert_eq!(
760            *operation,
761            crate::semantic_package::SemanticPackagePureOperation::Identity
762        );
763        assert_eq!(arguments.len(), 1);
764    }
765
766    #[test]
767    fn emits_template_interpolation_program_with_cooked_segments() {
768        let parsed = presolve_parser::parse_file(
769            "src/TemplateComputed.tsx",
770            r#"
771@component("x-template-computed")
772class TemplateComputed extends Component {
773  count = state(2);
774  @computed()
775  get label() { return `Count: ${this.count}!`; }
776  render() { return <output>Count</output>; }
777}
778"#,
779        );
780        let model = build_application_semantic_model(&parsed);
781        assert!(model.diagnostics.is_empty(), "{:?}", model.diagnostics);
782        let artifact = build_runtime_computed_artifact(&model, &lower_components_to_ir(&model));
783        let instruction = artifact.evaluations[0]
784            .program
785            .instructions
786            .iter()
787            .find(|instruction| {
788                matches!(
789                    instruction,
790                    RuntimeComputedArtifactInstruction::Template { .. }
791                )
792            })
793            .expect("lowered template instruction");
794        let RuntimeComputedArtifactInstruction::Template {
795            quasis,
796            expressions,
797            ..
798        } = instruction
799        else {
800            unreachable!();
801        };
802        assert_eq!(quasis, &["Count: ".to_string(), "!".to_string()]);
803        assert_eq!(expressions.len(), 1);
804    }
805
806    #[test]
807    fn emits_static_index_access_program_for_tuple_state() {
808        let parsed = presolve_parser::parse_file(
809            "src/IndexedComputed.tsx",
810            r#"
811@component("x-indexed-computed")
812class IndexedComputed extends Component {
813  labels = state(["zero", "one"]);
814  record = state({ label: "object" });
815  @computed()
816  get selected() { return this.labels[1]; }
817  @computed()
818  get title() { return this.record["label"]; }
819  render() { return <output>Indexed</output>; }
820}
821"#,
822        );
823        let model = build_application_semantic_model(&parsed);
824        assert!(model.diagnostics.is_empty(), "{:?}", model.diagnostics);
825        let artifact = build_runtime_computed_artifact(&model, &lower_components_to_ir(&model));
826        let instructions = artifact
827            .evaluations
828            .iter()
829            .flat_map(|evaluation| evaluation.program.instructions.iter())
830            .filter(|instruction| {
831                matches!(
832                    instruction,
833                    RuntimeComputedArtifactInstruction::GetIndex { .. }
834                )
835            })
836            .collect::<Vec<_>>();
837        assert_eq!(instructions.len(), 2);
838        let instruction = instructions[0];
839        let RuntimeComputedArtifactInstruction::GetIndex { index, .. } = instruction else {
840            unreachable!();
841        };
842        assert!(matches!(
843            index,
844            super::RuntimeComputedArtifactOperand::Value { .. }
845        ));
846    }
847
848    #[test]
849    fn emits_boolean_conditional_select_program() {
850        let parsed = presolve_parser::parse_file(
851            "src/ConditionalComputed.tsx",
852            r#"
853@component("x-conditional-computed")
854class ConditionalComputed extends Component {
855  enabled: boolean = state(true);
856  @computed()
857  get label() { return this.enabled ? "enabled" : "disabled"; }
858  render() { return <output>Conditional</output>; }
859}
860"#,
861        );
862        let model = build_application_semantic_model(&parsed);
863        assert!(model.diagnostics.is_empty(), "{:?}", model.diagnostics);
864        let artifact = build_runtime_computed_artifact(&model, &lower_components_to_ir(&model));
865        assert!(artifact.evaluations[0]
866            .program
867            .instructions
868            .iter()
869            .any(|instruction| {
870                matches!(
871                    instruction,
872                    RuntimeComputedArtifactInstruction::Select { .. }
873                )
874            }));
875    }
876
877    #[test]
878    fn rejects_non_boolean_computed_conditional() {
879        let parsed = presolve_parser::parse_file(
880            "src/InvalidConditionalComputed.tsx",
881            r#"
882@component("x-invalid-conditional-computed")
883class InvalidConditionalComputed extends Component {
884  count: number = state(1);
885  @computed()
886  get label() { return this.count ? "one" : "zero"; }
887  render() { return <output>Conditional</output>; }
888}
889"#,
890        );
891        let model = build_application_semantic_model(&parsed);
892        assert!(
893            model
894                .diagnostics
895                .iter()
896                .any(|diagnostic| diagnostic.code == "PSC1029"),
897            "{:?}",
898            model.diagnostics
899        );
900    }
901
902    #[test]
903    fn emits_optional_member_read_with_compiler_retained_optionality() {
904        let parsed = presolve_parser::parse_file(
905            "src/OptionalComputed.tsx",
906            r#"
907@component("x-optional-computed")
908class OptionalComputed extends Component {
909  profile = state({ label: "Presolve" });
910  @computed()
911  get label() { return this.profile?.label; }
912  render() { return <output>Optional</output>; }
913}
914"#,
915        );
916        let model = build_application_semantic_model(&parsed);
917        assert!(model.diagnostics.is_empty(), "{:?}", model.diagnostics);
918        let artifact = build_runtime_computed_artifact(&model, &lower_components_to_ir(&model));
919        assert!(artifact.evaluations[0]
920            .program
921            .instructions
922            .iter()
923            .any(|instruction| {
924                matches!(
925                    instruction,
926                    RuntimeComputedArtifactInstruction::GetMember { optional: true, .. }
927                )
928            }));
929    }
930
931    #[test]
932    fn emits_compiler_registered_math_abs_as_unary_program() {
933        let parsed = presolve_parser::parse_file(
934            "src/AbsComputed.tsx",
935            r#"
936@component("x-abs-computed")
937class AbsComputed extends Component {
938  count = state(-2);
939  @computed()
940  get magnitude() { return Math.abs(this.count); }
941  render() { return <output>Abs</output>; }
942}
943"#,
944        );
945        let model = build_application_semantic_model(&parsed);
946        assert!(model.diagnostics.is_empty(), "{:?}", model.diagnostics);
947        let artifact = build_runtime_computed_artifact(&model, &lower_components_to_ir(&model));
948        assert!(artifact.evaluations[0]
949            .program
950            .instructions
951            .iter()
952            .any(|instruction| {
953                matches!(
954                    instruction,
955                    RuntimeComputedArtifactInstruction::Unary {
956                        operation: super::RuntimeComputedArtifactUnaryOperation::Abs,
957                        ..
958                    }
959                )
960            }));
961    }
962
963    #[test]
964    fn emits_compiler_registered_math_rounding_as_unary_programs() {
965        let parsed = presolve_parser::parse_file(
966            "src/RoundingComputed.tsx",
967            r#"
968@component("x-rounding-computed")
969class RoundingComputed extends Component {
970  value = state(-1.5);
971  @computed() get floorValue() { return Math.floor(this.value); }
972  @computed() get ceilValue() { return Math.ceil(this.value); }
973  @computed() get roundValue() { return Math.round(this.value); }
974  render() { return <output>Rounding</output>; }
975}
976"#,
977        );
978        let model = build_application_semantic_model(&parsed);
979        assert!(model.diagnostics.is_empty(), "{:?}", model.diagnostics);
980        let artifact = build_runtime_computed_artifact(&model, &lower_components_to_ir(&model));
981        let operations = artifact
982            .evaluations
983            .iter()
984            .flat_map(|evaluation| evaluation.program.instructions.iter())
985            .filter_map(|instruction| match instruction {
986                RuntimeComputedArtifactInstruction::Unary { operation, .. } => Some(*operation),
987                _ => None,
988            })
989            .collect::<Vec<_>>();
990        assert!(operations.contains(&super::RuntimeComputedArtifactUnaryOperation::Floor));
991        assert!(operations.contains(&super::RuntimeComputedArtifactUnaryOperation::Ceil));
992        assert!(operations.contains(&super::RuntimeComputedArtifactUnaryOperation::Round));
993    }
994
995    #[test]
996    fn emits_compiler_registered_math_min_and_max_as_binary_programs() {
997        let parsed = presolve_parser::parse_file(
998            "src/MinMaxComputed.tsx",
999            r#"
1000@component("x-min-max-computed")
1001class MinMaxComputed extends Component {
1002  left = state(-2);
1003  right = state(5);
1004  @computed()
1005  get minimum() { return Math.min(this.left, this.right); }
1006  @computed()
1007  get maximum() { return Math.max(this.left, this.right); }
1008  render() { return <output>MinMax</output>; }
1009}
1010"#,
1011        );
1012        let model = build_application_semantic_model(&parsed);
1013        assert!(model.diagnostics.is_empty(), "{:?}", model.diagnostics);
1014        let artifact = build_runtime_computed_artifact(&model, &lower_components_to_ir(&model));
1015        let operations = artifact
1016            .evaluations
1017            .iter()
1018            .flat_map(|evaluation| evaluation.program.instructions.iter())
1019            .filter_map(|instruction| match instruction {
1020                RuntimeComputedArtifactInstruction::Binary { operation, .. } => Some(*operation),
1021                _ => None,
1022            })
1023            .collect::<Vec<_>>();
1024        assert!(operations.contains(&super::RuntimeComputedArtifactBinaryOperation::Min));
1025        assert!(operations.contains(&super::RuntimeComputedArtifactBinaryOperation::Max));
1026    }
1027}