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#[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#[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#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
36pub struct RuntimeComputedArtifactInvalidation {
37 pub storage: String,
38 pub dependents: Vec<String>,
39}
40
41#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
45pub struct RuntimeComputedArtifactResourceInvalidation {
46 pub declaration: String,
47 pub dependents: Vec<String>,
48}
49
50#[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#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
65pub struct RuntimeComputedArtifactProgram {
66 pub result: String,
67 pub instructions: Vec<RuntimeComputedArtifactInstruction>,
68}
69
70#[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#[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#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
180pub struct RuntimeComputedArtifactDirtyFlag {
181 pub id: String,
182 pub initial_value: bool,
183}
184
185#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
187#[serde(rename_all = "kebab-case")]
188pub enum RuntimeComputedArtifactSerialization {
189 Serializable,
190 NotSerializable,
191}
192
193#[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(®istry);
205 let evaluations = runtime_evaluations(model, ®istry, 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#[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}