1use std::collections::{BTreeMap, BTreeSet};
2use std::sync::{Arc, Mutex, OnceLock};
3
4use serde::{Deserialize, Serialize};
5use sha2::{Digest, Sha256};
6use thiserror::Error;
7
8use crate::ast::{
9 AssignPathStep, BinaryOp, Declaration, Expr, LabelMetadata, ListComprehensionClause,
10 ProcessDecl, Program, ResourceRefExpr, TypeExpr, UnaryOp,
11};
12use crate::linker::{
13 LashlangAbilities, LashlangHostCatalog, LashlangLanguageFeatures, ResourceOperationBinding,
14};
15use crate::trigger_manifest::{
16 CurrentTriggerKeyManifest, TriggerKeyManifest, TriggerManifestReplacement,
17};
18
19pub const LASHLANG_SEMANTIC_HASH_VERSION: &str = "lashlang-semantic-v2";
20pub const LASHLANG_COMPILER_VERSION: &str = env!("CARGO_PKG_VERSION");
21pub const LASHLANG_VM_ABI_VERSION: &str = "lashlang-vm-abi-v1";
22
23#[derive(
32 Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
33)]
34#[serde(rename_all = "snake_case")]
35pub enum DurabilityTier {
36 #[default]
37 Inline,
38 Durable,
39}
40
41#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
42#[serde(transparent)]
43pub struct ContentHash(String);
44
45impl ContentHash {
46 pub fn new(hex: impl Into<String>) -> Self {
47 Self(hex.into())
48 }
49
50 pub fn as_str(&self) -> &str {
51 &self.0
52 }
53}
54
55impl std::fmt::Display for ContentHash {
56 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57 f.write_str(&self.0)
58 }
59}
60
61#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
62#[serde(transparent)]
63pub struct ModuleRef(String);
64
65impl ModuleRef {
66 pub fn new(hash: &ContentHash) -> Self {
67 Self(format!("lashlang:v1:sha256:{hash}"))
68 }
69
70 pub fn as_str(&self) -> &str {
71 &self.0
72 }
73
74 pub fn hash_hex(&self) -> Option<&str> {
75 self.0.strip_prefix("lashlang:v1:sha256:")
76 }
77}
78
79impl std::fmt::Display for ModuleRef {
80 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81 f.write_str(&self.0)
82 }
83}
84
85#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
86pub struct ProcessRef {
87 pub component: ContentHash,
88 pub pos: u32,
89}
90
91impl ProcessRef {
92 pub fn new(component: ContentHash, pos: u32) -> Self {
93 Self { component, pos }
94 }
95}
96
97#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
98#[serde(transparent)]
99pub struct HostRequirementsRef(String);
100
101impl HostRequirementsRef {
102 pub fn new(hash: &ContentHash) -> Self {
103 Self(format!("lashlang-host-requirements:v1:sha256:{hash}"))
104 }
105
106 pub fn as_str(&self) -> &str {
107 &self.0
108 }
109}
110
111impl std::fmt::Display for HostRequirementsRef {
112 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113 f.write_str(&self.0)
114 }
115}
116
117#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
118pub struct HostRequirements {
119 #[serde(default)]
120 pub resources: LashlangHostCatalog,
121 #[serde(default)]
122 pub abilities: LashlangAbilities,
123 #[serde(default)]
124 pub language_features: LashlangLanguageFeatures,
125}
126
127#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
128pub struct ModuleExports {
129 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
130 pub processes: BTreeMap<String, ProcessRef>,
131}
132
133#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
134pub struct ModuleArtifact {
135 pub module_ref: ModuleRef,
136 pub host_requirements_ref: HostRequirementsRef,
137 pub host_requirements: HostRequirements,
138 pub exports: ModuleExports,
139 #[serde(default)]
140 pub trigger_key_manifest: TriggerKeyManifest,
141 pub canonical_ir: Program,
142 #[serde(default, skip_serializing_if = "Vec::is_empty")]
143 pub dependencies: Vec<ModuleRef>,
144}
145
146impl ModuleArtifact {
147 pub fn from_program(program: Program) -> Result<Self, ModuleArtifactError> {
148 let canonical_ir = canonical_program_ir(program);
149 let requirements = host_requirements_for_program(&canonical_ir);
150 Self::from_canonical_ir_and_requirements(canonical_ir, requirements)
151 }
152
153 pub(crate) fn from_program_with_requirements(
154 program: Program,
155 requirements: HostRequirements,
156 ) -> Result<Self, ModuleArtifactError> {
157 let canonical_ir = canonical_program_ir(program);
158 Self::from_canonical_ir_and_requirements(canonical_ir, requirements)
159 }
160
161 fn from_canonical_ir_and_requirements(
162 canonical_ir: Program,
163 requirements: HostRequirements,
164 ) -> Result<Self, ModuleArtifactError> {
165 let host_requirements_ref = host_requirements_ref(&requirements);
166 let exports = module_exports(&canonical_ir);
167 let trigger_key_manifest = TriggerKeyManifest::from_program(&canonical_ir);
168 let module_ref = module_ref(&canonical_ir, &host_requirements_ref, &exports);
169 Ok(Self {
170 module_ref,
171 host_requirements_ref,
172 host_requirements: requirements,
173 exports,
174 trigger_key_manifest,
175 canonical_ir,
176 dependencies: Vec::new(),
177 })
178 }
179
180 pub fn process_ref(&self, process_name: &str) -> Option<&ProcessRef> {
181 self.exports.processes.get(process_name)
182 }
183
184 pub fn process_name_for_ref(&self, process_ref: &ProcessRef) -> Option<&str> {
185 self.exports
186 .processes
187 .iter()
188 .find_map(|(name, candidate)| (candidate == process_ref).then_some(name.as_str()))
189 }
190
191 pub fn canonical_source(&self) -> Result<String, crate::CanonicalSourceError> {
197 crate::canonical_program_source_with_requirements(
198 &self.canonical_ir,
199 &self.host_requirements,
200 )
201 }
202
203 pub fn canonical_process_source(
209 &self,
210 process_ref: &ProcessRef,
211 ) -> Result<Option<String>, crate::CanonicalSourceError> {
212 let Some(process_name) = self.process_name_for_ref(process_ref) else {
213 return Ok(None);
214 };
215 self.canonical_process_source_by_name(process_name)
216 }
217
218 pub fn canonical_process_source_by_name(
222 &self,
223 process_name: &str,
224 ) -> Result<Option<String>, crate::CanonicalSourceError> {
225 let Some(process) = self.canonical_ir.process(process_name) else {
226 return Ok(None);
227 };
228 crate::canonical_process_source_with_requirements(process, &self.host_requirements)
229 .map(Some)
230 }
231
232 pub fn introspect(
233 &self,
234 ) -> Result<crate::ModuleIntrospection, crate::ModuleIntrospectionError> {
235 crate::ModuleIntrospection::from_artifact(self)
236 }
237
238 pub fn verify(&self) -> Result<(), ModuleArtifactError> {
239 let rebuilt = Self::from_program_with_requirements(
240 self.canonical_ir.clone(),
241 self.host_requirements.clone(),
242 )?;
243 if rebuilt.module_ref != self.module_ref {
244 return Err(ModuleArtifactError::HashMismatch {
245 field: "module_ref",
246 expected: rebuilt.module_ref.to_string(),
247 actual: self.module_ref.to_string(),
248 });
249 }
250 if rebuilt.host_requirements_ref != self.host_requirements_ref {
251 return Err(ModuleArtifactError::HashMismatch {
252 field: "host_requirements_ref",
253 expected: rebuilt.host_requirements_ref.to_string(),
254 actual: self.host_requirements_ref.to_string(),
255 });
256 }
257 if rebuilt.exports != self.exports {
258 return Err(ModuleArtifactError::HashMismatch {
259 field: "exports",
260 expected: "canonical exports".to_string(),
261 actual: "artifact exports".to_string(),
262 });
263 }
264 if rebuilt.trigger_key_manifest != self.trigger_key_manifest {
265 return Err(ModuleArtifactError::HashMismatch {
266 field: "trigger_key_manifest",
267 expected: "canonical trigger key manifest".to_string(),
268 actual: "artifact trigger key manifest".to_string(),
269 });
270 }
271 Ok(())
272 }
273
274 pub fn to_store_bytes(&self) -> Result<Vec<u8>, ModuleArtifactError> {
275 self.verify()?;
276 serde_json::to_vec(self).map_err(|err| ModuleArtifactError::Codec(err.to_string()))
277 }
278
279 pub fn from_store_bytes(bytes: &[u8]) -> Result<Self, ModuleArtifactError> {
280 let artifact: Self = serde_json::from_slice(bytes)
281 .map_err(|err| ModuleArtifactError::Codec(err.to_string()))?;
282 artifact.verify()?;
283 Ok(artifact)
284 }
285}
286
287#[derive(Clone, Debug, Error, PartialEq, Eq)]
288pub enum ModuleArtifactError {
289 #[error("failed to encode module artifact: {0}")]
290 Codec(String),
291 #[error("module artifact {field} mismatch: expected {expected}, got {actual}")]
292 HashMismatch {
293 field: &'static str,
294 expected: String,
295 actual: String,
296 },
297}
298
299#[derive(Debug, Error)]
300pub enum ArtifactStoreError {
301 #[error("failed to encode lashlang artifact: {0}")]
302 Encode(String),
303 #[error("failed to decode lashlang artifact: {0}")]
304 Decode(String),
305 #[error("artifact store backend error: {0}")]
306 Backend(String),
307}
308
309impl From<ModuleArtifactError> for ArtifactStoreError {
310 fn from(value: ModuleArtifactError) -> Self {
311 match value {
312 ModuleArtifactError::Codec(message) => Self::Decode(message),
313 ModuleArtifactError::HashMismatch { .. } => Self::Decode(value.to_string()),
314 }
315 }
316}
317
318#[async_trait::async_trait]
319pub trait LashlangArtifactStore: Send + Sync {
320 fn durability_tier(&self) -> DurabilityTier {
322 DurabilityTier::Inline
323 }
324
325 async fn put_module_artifact(
326 &self,
327 artifact: &ModuleArtifact,
328 ) -> Result<(), ArtifactStoreError>;
329
330 async fn get_module_artifact(
331 &self,
332 module_ref: &ModuleRef,
333 ) -> Result<Option<Arc<ModuleArtifact>>, ArtifactStoreError>;
334
335 async fn replace_current_trigger_manifest(
338 &self,
339 owner_namespace: &str,
340 artifact: &ModuleArtifact,
341 ) -> Result<TriggerManifestReplacement, ArtifactStoreError>;
342
343 async fn get_current_trigger_manifest(
344 &self,
345 owner_namespace: &str,
346 ) -> Result<Option<CurrentTriggerKeyManifest>, ArtifactStoreError>;
347
348 async fn put_artifact_bytes(
349 &self,
350 artifact_ref: &str,
351 descriptor: &str,
352 bytes: &[u8],
353 ) -> Result<(), ArtifactStoreError>;
354
355 async fn get_artifact_bytes(
356 &self,
357 artifact_ref: &str,
358 ) -> Result<Option<Vec<u8>>, ArtifactStoreError>;
359}
360
361#[derive(Clone, Default)]
362pub struct InMemoryLashlangArtifactStore {
363 modules: Arc<Mutex<BTreeMap<ModuleRef, Arc<ModuleArtifact>>>>,
364 current_trigger_manifests: Arc<Mutex<BTreeMap<String, CurrentTriggerKeyManifest>>>,
365 artifacts: Arc<Mutex<BTreeMap<String, Vec<u8>>>>,
366}
367
368impl InMemoryLashlangArtifactStore {
369 pub fn new() -> Self {
370 Self::default()
371 }
372}
373
374pub fn global_in_memory_lashlang_artifact_store() -> Arc<InMemoryLashlangArtifactStore> {
375 static STORE: OnceLock<Arc<InMemoryLashlangArtifactStore>> = OnceLock::new();
376 STORE
377 .get_or_init(|| Arc::new(InMemoryLashlangArtifactStore::new()))
378 .clone()
379}
380
381#[async_trait::async_trait]
382impl LashlangArtifactStore for InMemoryLashlangArtifactStore {
383 async fn put_module_artifact(
384 &self,
385 artifact: &ModuleArtifact,
386 ) -> Result<(), ArtifactStoreError> {
387 let mut modules = self
388 .modules
389 .lock()
390 .map_err(|_| ArtifactStoreError::Backend("artifact store lock poisoned".to_string()))?;
391 modules.insert(artifact.module_ref.clone(), Arc::new(artifact.clone()));
392 Ok(())
393 }
394
395 async fn get_module_artifact(
396 &self,
397 module_ref: &ModuleRef,
398 ) -> Result<Option<Arc<ModuleArtifact>>, ArtifactStoreError> {
399 let modules = self
400 .modules
401 .lock()
402 .map_err(|_| ArtifactStoreError::Backend("artifact store lock poisoned".to_string()))?;
403 Ok(modules.get(module_ref).cloned())
404 }
405
406 async fn replace_current_trigger_manifest(
407 &self,
408 owner_namespace: &str,
409 artifact: &ModuleArtifact,
410 ) -> Result<TriggerManifestReplacement, ArtifactStoreError> {
411 let mut manifests = self.current_trigger_manifests.lock().map_err(|_| {
412 ArtifactStoreError::Backend("current trigger manifest lock poisoned".to_string())
413 })?;
414 let previous = manifests.insert(
415 owner_namespace.to_string(),
416 CurrentTriggerKeyManifest {
417 module_ref: artifact.module_ref.clone(),
418 manifest: artifact.trigger_key_manifest.clone(),
419 },
420 );
421 Ok(TriggerManifestReplacement {
422 previous_module_ref: previous.as_ref().map(|entry| entry.module_ref.clone()),
423 current_module_ref: artifact.module_ref.clone(),
424 diff: previous
425 .map(|entry| entry.manifest.diff(&artifact.trigger_key_manifest))
426 .unwrap_or_default(),
427 })
428 }
429
430 async fn get_current_trigger_manifest(
431 &self,
432 owner_namespace: &str,
433 ) -> Result<Option<CurrentTriggerKeyManifest>, ArtifactStoreError> {
434 Ok(self
435 .current_trigger_manifests
436 .lock()
437 .map_err(|_| {
438 ArtifactStoreError::Backend("current trigger manifest lock poisoned".to_string())
439 })?
440 .get(owner_namespace)
441 .cloned())
442 }
443
444 async fn put_artifact_bytes(
445 &self,
446 artifact_ref: &str,
447 _descriptor: &str,
448 bytes: &[u8],
449 ) -> Result<(), ArtifactStoreError> {
450 self.artifacts
451 .lock()
452 .map_err(|_| ArtifactStoreError::Backend("artifact store lock poisoned".to_string()))?
453 .insert(artifact_ref.to_string(), bytes.to_vec());
454 Ok(())
455 }
456
457 async fn get_artifact_bytes(
458 &self,
459 artifact_ref: &str,
460 ) -> Result<Option<Vec<u8>>, ArtifactStoreError> {
461 Ok(self
462 .artifacts
463 .lock()
464 .map_err(|_| ArtifactStoreError::Backend("artifact store lock poisoned".to_string()))?
465 .get(artifact_ref)
466 .cloned())
467 }
468}
469
470#[derive(Clone)]
471pub(crate) struct CompiledModuleContext {
472 pub(crate) module_ref: ModuleRef,
473 pub(crate) host_requirements_ref: HostRequirementsRef,
474 pub(crate) process_refs: BTreeMap<String, ProcessRef>,
475}
476
477impl From<&ModuleArtifact> for CompiledModuleContext {
478 fn from(value: &ModuleArtifact) -> Self {
479 Self {
480 module_ref: value.module_ref.clone(),
481 host_requirements_ref: value.host_requirements_ref.clone(),
482 process_refs: value.exports.processes.clone(),
483 }
484 }
485}
486
487pub fn canonical_program_ir(mut program: Program) -> Program {
488 program.declaration_spans.clear();
489 program.expression_spans.clear();
490 program.expression_source_spans.clear();
491 program
492}
493
494pub fn host_requirements_for_program(program: &Program) -> HostRequirements {
495 RequirementsCollector::new(program).collect()
496}
497
498pub(crate) fn host_requirements_for_program_with_catalog(
499 program: &Program,
500 catalog: &LashlangHostCatalog,
501) -> HostRequirements {
502 RequirementsCollector::new(program)
503 .with_resource_catalog(catalog)
504 .collect()
505}
506
507fn module_exports(program: &Program) -> ModuleExports {
508 let mut exports = ModuleExports::default();
509 let mut process_pos = 0u32;
510 for declaration in &program.declarations {
511 if let Declaration::Process(process) = declaration {
512 exports.processes.insert(
513 process.name.to_string(),
514 ProcessRef::new(process_component_hash(process), process_pos),
515 );
516 process_pos += 1;
517 }
518 }
519 exports
520}
521
522fn module_ref(
523 program: &Program,
524 host_requirements_ref: &HostRequirementsRef,
525 exports: &ModuleExports,
526) -> ModuleRef {
527 let mut writer = HashWriter::new();
528 writer.atom(LASHLANG_SEMANTIC_HASH_VERSION);
529 writer.atom("module");
530 writer.atom(host_requirements_ref.as_str());
531 write_exports(&mut writer, exports);
532 write_program(&mut writer, program);
533 ModuleRef::new(&writer.finish())
534}
535
536fn host_requirements_ref(requirements: &HostRequirements) -> HostRequirementsRef {
537 let mut writer = HashWriter::new();
538 writer.atom(LASHLANG_SEMANTIC_HASH_VERSION);
539 writer.atom("host-requirements");
540 write_host_requirements(&mut writer, requirements);
541 HostRequirementsRef::new(&writer.finish())
542}
543
544fn process_component_hash(process: &ProcessDecl) -> ContentHash {
545 let mut writer = HashWriter::new();
546 writer.atom(LASHLANG_SEMANTIC_HASH_VERSION);
547 writer.atom("process");
548 write_process(&mut writer, process);
549 writer.finish()
550}
551
552fn write_exports(writer: &mut HashWriter, exports: &ModuleExports) {
553 writer.atom("exports");
554 writer.usize(exports.processes.len());
555 for (name, process_ref) in &exports.processes {
556 writer.atom("process-export");
557 writer.atom(name);
558 writer.atom(process_ref.component.as_str());
559 writer.u32(process_ref.pos);
560 }
561}
562
563fn write_host_requirements(writer: &mut HashWriter, requirements: &HostRequirements) {
564 writer.atom("abilities");
565 writer.bool(requirements.abilities.processes);
566 writer.bool(requirements.abilities.sleep);
567 writer.bool(requirements.abilities.process_signals);
568 writer.bool(requirements.abilities.triggers);
569 if requirements.language_features.label_annotations {
570 writer.atom("language-features");
571 writer.atom("label-annotations");
572 }
573 writer.atom("resources");
574 writer.atom("modules");
575 writer.usize(requirements.resources.module_instances().count());
576 for (module_path, module) in requirements.resources.module_instances() {
577 writer.atom(module_path);
578 writer.atom(&module.resource_type);
579 writer.atom(&module.alias);
580 writer.atom("operations");
581 writer.usize(module.operations.len());
582 for (operation, binding) in &module.operations {
583 writer.atom(operation);
584 writer.atom(&binding.host_operation);
585 }
586 }
587 writer.usize(requirements.resources.resource_types().count());
588 for (resource_type, catalog) in requirements.resources.resource_types() {
589 writer.atom(resource_type);
590 writer.atom("operations");
591 writer.usize(catalog.operations.len());
592 for (operation, binding) in &catalog.operations {
593 writer.atom(operation);
594 write_type(writer, &binding.input_ty);
595 write_type(writer, &binding.output_ty);
596 if let Some(output_from_input) = &binding.output_from_input {
597 writer.atom("output-from-input");
598 writer.atom(&output_from_input.input_field);
599 if let Some(default_schema) = &output_from_input.default_schema {
600 writer.atom("default-schema");
601 write_type(writer, default_schema);
602 }
603 }
604 }
605 }
606 writer.atom("named-data-types");
607 writer.usize(requirements.resources.named_data_types().count());
608 for (name, data_type) in requirements.resources.named_data_types() {
609 writer.atom(name);
610 write_type(writer, data_type.ty());
611 }
612 writer.atom("constructors");
613 writer.usize(requirements.resources.value_constructors().count());
614 for (path, constructor) in requirements.resources.value_constructors() {
615 writer.atom(path);
616 writer.atom(&constructor.type_name);
617 write_type(writer, &constructor.input_ty);
618 write_type(writer, &constructor.output_ty);
619 }
620 writer.atom("trigger-sources");
621 writer.usize(requirements.resources.trigger_sources().count());
622 for (source_ty, binding) in requirements.resources.trigger_sources() {
623 writer.atom(source_ty);
624 writer.atom(binding.event_type_name());
625 }
626}
627
628fn write_program(writer: &mut HashWriter, program: &Program) {
629 writer.atom("program");
630 writer.usize(program.declarations.len());
631 for declaration in &program.declarations {
632 write_declaration(writer, declaration);
633 }
634 let mut normalizer = NameNormalizer::default();
635 normalizer.collect_expr(&program.main);
636 write_expr(writer, &program.main, &normalizer);
637}
638
639fn write_declaration(writer: &mut HashWriter, declaration: &Declaration) {
640 match declaration {
641 Declaration::Type(type_decl) => {
642 writer.atom("type-decl");
643 writer.atom(type_decl.name.as_str());
644 write_type(writer, &type_decl.ty);
645 }
646 Declaration::Process(process) => write_process(writer, process),
647 }
648}
649
650fn write_process(writer: &mut HashWriter, process: &ProcessDecl) {
651 writer.atom("process-decl");
652 writer.atom(process.name.as_str());
653 writer.usize(process.params.len());
654 for param in &process.params {
655 writer.atom(param.name.as_str());
656 write_type(writer, ¶m.ty);
657 }
658 writer.atom("signals");
659 writer.usize(process.signals.len());
660 for signal in &process.signals {
661 writer.atom(signal.name.as_str());
662 write_type(writer, &signal.ty);
663 }
664 match &process.return_ty {
665 Some(ty) => {
666 writer.atom("return");
667 write_type(writer, ty);
668 }
669 None => writer.atom("no-return"),
670 }
671 if let Some(label) = &process.label {
672 write_label_metadata(writer, label);
673 }
674 let mut normalizer = NameNormalizer::default();
675 for param in &process.params {
676 normalizer.bind_abi(param.name.as_str());
677 }
678 normalizer.bind_abi("input");
679 normalizer.bind_abi("inputs");
680 normalizer.collect_expr(&process.body);
681 write_expr(writer, &process.body, &normalizer);
682}
683
684fn write_type(writer: &mut HashWriter, ty: &TypeExpr) {
685 match ty {
686 TypeExpr::Any => writer.atom("type:any"),
687 TypeExpr::Str => writer.atom("type:str"),
688 TypeExpr::Int => writer.atom("type:int"),
689 TypeExpr::Float => writer.atom("type:float"),
690 TypeExpr::Bool => writer.atom("type:bool"),
691 TypeExpr::Dict => writer.atom("type:dict"),
692 TypeExpr::Null => writer.atom("type:null"),
693 TypeExpr::Enum(values) => {
694 writer.atom("type:enum");
695 writer.usize(values.len());
696 for value in values {
697 writer.atom(value.as_str());
698 }
699 }
700 TypeExpr::List(item) => {
701 writer.atom("type:list");
702 write_type(writer, item);
703 }
704 TypeExpr::Object(fields) => {
705 writer.atom("type:object");
706 writer.usize(fields.len());
707 for field in fields {
708 writer.atom(field.name.as_str());
709 writer.bool(field.optional);
710 write_type(writer, &field.ty);
711 }
712 }
713 TypeExpr::Ref(name) => {
714 writer.atom("type:ref");
715 writer.atom(name.as_str());
716 }
717 TypeExpr::Process {
718 input,
719 output,
720 input_count,
721 } => {
722 writer.atom("type:process");
723 writer.usize(*input_count);
724 write_type(writer, input);
725 write_type(writer, output);
726 }
727 TypeExpr::TriggerHandle(event) => {
728 writer.atom("type:trigger-handle");
729 write_type(writer, event);
730 }
731 TypeExpr::Union(items) => {
732 writer.atom("type:union");
733 writer.usize(items.len());
734 for item in items {
735 write_type(writer, item);
736 }
737 }
738 }
739}
740
741fn write_expr(writer: &mut HashWriter, expr: &Expr, normalizer: &NameNormalizer) {
742 match expr {
743 Expr::Block(expressions) => {
744 writer.atom("block");
745 writer.usize(expressions.len());
746 for expression in expressions {
747 write_expr(writer, expression, normalizer);
748 }
749 }
750 Expr::LabelAnnotated { label, expr } => {
751 writer.atom("label-annotated");
752 write_label_metadata(writer, label);
753 write_expr(writer, expr, normalizer);
754 }
755 Expr::Null => writer.atom("null"),
756 Expr::Bool(value) => {
757 writer.atom("bool");
758 writer.bool(*value);
759 }
760 Expr::Number(value) => {
761 writer.atom("number");
762 writer.u64(if *value == 0.0 { 0 } else { value.to_bits() });
763 }
764 Expr::String(value) => {
765 writer.atom("string");
766 writer.atom(value.as_str());
767 }
768 Expr::Variable(name) => {
769 writer.atom("variable");
770 writer.atom(&normalizer.name_token(name.as_str()));
771 }
772 Expr::Tuple(items) => {
773 writer.atom("tuple");
774 writer.usize(items.len());
775 for item in items {
776 write_expr(writer, item, normalizer);
777 }
778 }
779 Expr::List(items) => {
780 writer.atom("list");
781 writer.usize(items.len());
782 for item in items {
783 write_expr(writer, item, normalizer);
784 }
785 }
786 Expr::ListComprehension { element, clauses } => {
787 writer.atom("list-comprehension");
788 writer.usize(clauses.len());
789 for clause in clauses {
790 match clause {
791 ListComprehensionClause::For { binding, iterable } => {
792 writer.atom("for");
793 writer.atom(&normalizer.name_token(binding.as_str()));
794 write_expr(writer, iterable, normalizer);
795 }
796 ListComprehensionClause::If { condition } => {
797 writer.atom("if");
798 write_expr(writer, condition, normalizer);
799 }
800 }
801 }
802 write_expr(writer, element, normalizer);
803 }
804 Expr::Record(entries) => {
805 writer.atom("record");
806 writer.usize(entries.len());
807 for (key, value) in entries {
808 writer.atom(key.as_str());
809 write_expr(writer, value, normalizer);
810 }
811 }
812 Expr::Assign { target, expr } => {
813 writer.atom("assign");
814 writer.atom(&normalizer.name_token(target.root.as_str()));
815 writer.usize(target.steps.len());
816 for step in &target.steps {
817 match step {
818 AssignPathStep::Field(field) => {
819 writer.atom("field");
820 writer.atom(field.as_str());
821 }
822 AssignPathStep::Index(index) => {
823 writer.atom("index");
824 write_expr(writer, index, normalizer);
825 }
826 }
827 }
828 write_expr(writer, expr, normalizer);
829 }
830 Expr::If {
831 condition,
832 then_block,
833 else_block,
834 } => {
835 writer.atom("if");
836 write_expr(writer, condition, normalizer);
837 write_expr(writer, then_block, normalizer);
838 write_expr(writer, else_block, normalizer);
839 }
840 Expr::For {
841 binding,
842 iterable,
843 body,
844 } => {
845 writer.atom("for");
846 writer.atom(&normalizer.name_token(binding.as_str()));
847 write_expr(writer, iterable, normalizer);
848 write_expr(writer, body, normalizer);
849 }
850 Expr::While { condition, body } => {
851 writer.atom("while");
852 write_expr(writer, condition, normalizer);
853 write_expr(writer, body, normalizer);
854 }
855 Expr::Break => writer.atom("break"),
856 Expr::Continue => writer.atom("continue"),
857 Expr::StartProcess(start) => {
858 writer.atom("start-process");
859 writer.atom(start.process.as_str());
860 writer.usize(start.args.len());
861 for (key, value) in &start.args {
862 writer.atom(key.as_str());
863 write_expr(writer, value, normalizer);
864 }
865 }
866 Expr::ProcessRef { process } => {
867 writer.atom("process-ref");
868 writer.atom(process.as_str());
869 }
870 Expr::HostDescriptorConstructor { type_name, input } => {
871 writer.atom("host-value-constructor");
872 writer.atom(type_name.as_str());
873 write_expr(writer, input, normalizer);
874 }
875 Expr::ResourceRef(resource) => {
876 writer.atom("resource-ref");
877 write_resource_ref(writer, resource);
878 }
879 Expr::ReceiverCall {
880 receiver,
881 operation,
882 args,
883 } => {
884 writer.atom("receiver-call");
885 write_expr(writer, receiver, normalizer);
886 writer.atom(operation.as_str());
887 writer.usize(args.len());
888 for arg in args {
889 write_expr(writer, arg, normalizer);
890 }
891 }
892 Expr::Await(expr) => write_unary_expr(writer, "await", expr, normalizer),
893 Expr::SleepFor(expr) => write_unary_expr(writer, "sleep-for", expr, normalizer),
894 Expr::SleepUntil(expr) => write_unary_expr(writer, "sleep-until", expr, normalizer),
895 Expr::WaitSignal { name } => {
896 writer.atom("wait-signal");
897 writer.atom(name.as_str());
898 }
899 Expr::SignalRun { run, name, payload } => {
900 writer.atom("signal-run");
901 writer.atom(name.as_str());
902 write_expr(writer, run, normalizer);
903 write_expr(writer, payload, normalizer);
904 }
905 Expr::ResultUnwrap(expr) => write_unary_expr(writer, "unwrap", expr, normalizer),
906 Expr::Cancel(expr) => write_unary_expr(writer, "cancel", expr, normalizer),
907 Expr::Print(expr) => write_unary_expr(writer, "print", expr, normalizer),
908 Expr::Yield(expr) => write_unary_expr(writer, "yield", expr, normalizer),
909 Expr::Wake(expr) => write_unary_expr(writer, "wake", expr, normalizer),
910 Expr::Finish(expr) => write_unary_expr(writer, "finish", expr, normalizer),
911 Expr::Fail(expr) => write_unary_expr(writer, "fail", expr, normalizer),
912 Expr::BuiltinCall { name, args } => {
913 writer.atom("builtin-call");
914 writer.atom(name.as_str());
915 writer.usize(args.len());
916 for arg in args {
917 write_expr(writer, arg, normalizer);
918 }
919 }
920 Expr::Field { target, field } => {
921 writer.atom("field-access");
922 write_expr(writer, target, normalizer);
923 writer.atom(field.as_str());
924 }
925 Expr::Index { target, index } => {
926 writer.atom("index-access");
927 write_expr(writer, target, normalizer);
928 write_expr(writer, index, normalizer);
929 }
930 Expr::Unary { op, expr } => {
931 writer.atom("unary");
932 write_unary_op(writer, *op);
933 write_expr(writer, expr, normalizer);
934 }
935 Expr::Binary { left, op, right } => {
936 writer.atom("binary");
937 write_binary_op(writer, *op);
938 write_expr(writer, left, normalizer);
939 write_expr(writer, right, normalizer);
940 }
941 Expr::TypeLiteral(ty) => {
942 writer.atom("type-literal");
943 write_type(writer, ty);
944 }
945 }
946}
947
948fn write_label_metadata(writer: &mut HashWriter, label: &LabelMetadata) {
949 writer.atom("label");
950 writer.atom(label.title.as_str());
951 match &label.description {
952 Some(description) => {
953 writer.atom("description");
954 writer.atom(description.as_str());
955 }
956 None => writer.atom("no-description"),
957 }
958}
959
960fn write_unary_expr(
961 writer: &mut HashWriter,
962 tag: &'static str,
963 expr: &Expr,
964 normalizer: &NameNormalizer,
965) {
966 writer.atom(tag);
967 write_expr(writer, expr, normalizer);
968}
969
970fn write_resource_ref(writer: &mut HashWriter, resource: &ResourceRefExpr) {
971 writer.atom("path");
972 writer.usize(resource.path.len());
973 for segment in &resource.path {
974 writer.atom(segment.as_str());
975 }
976 writer.atom("handle");
977 writer.atom(resource.resource_type.as_str());
978 writer.atom(resource.alias.as_str());
979}
980
981fn write_unary_op(writer: &mut HashWriter, op: UnaryOp) {
982 writer.atom(match op {
983 UnaryOp::Negate => "negate",
984 UnaryOp::Not => "not",
985 });
986}
987
988fn write_binary_op(writer: &mut HashWriter, op: BinaryOp) {
989 writer.atom(match op {
990 BinaryOp::Add => "add",
991 BinaryOp::Subtract => "subtract",
992 BinaryOp::Multiply => "multiply",
993 BinaryOp::Divide => "divide",
994 BinaryOp::Modulo => "modulo",
995 BinaryOp::Equal => "equal",
996 BinaryOp::NotEqual => "not-equal",
997 BinaryOp::Less => "less",
998 BinaryOp::LessEqual => "less-equal",
999 BinaryOp::Greater => "greater",
1000 BinaryOp::GreaterEqual => "greater-equal",
1001 BinaryOp::And => "and",
1002 BinaryOp::Or => "or",
1003 });
1004}
1005
1006#[derive(Default)]
1007struct NameNormalizer {
1008 names: BTreeMap<String, String>,
1009 abi_names: BTreeSet<String>,
1010 next_local: u32,
1011}
1012
1013impl NameNormalizer {
1014 fn bind_abi(&mut self, name: &str) {
1015 self.abi_names.insert(name.to_string());
1016 self.names.insert(name.to_string(), format!("abi:{name}"));
1017 }
1018
1019 fn bind_local(&mut self, name: &str) {
1020 if self.abi_names.contains(name) || self.names.contains_key(name) {
1021 return;
1022 }
1023 let token = format!("local:{}", self.next_local);
1024 self.next_local += 1;
1025 self.names.insert(name.to_string(), token);
1026 }
1027
1028 fn name_token(&self, name: &str) -> String {
1029 self.names
1030 .get(name)
1031 .cloned()
1032 .unwrap_or_else(|| format!("global:{name}"))
1033 }
1034
1035 fn collect_expr(&mut self, expr: &Expr) {
1036 match expr {
1042 Expr::Assign { target, expr } => {
1043 self.bind_local(target.root.as_str());
1044 for step in &target.steps {
1045 if let AssignPathStep::Index(index) = step {
1046 self.collect_expr(index);
1047 }
1048 }
1049 self.collect_expr(expr);
1050 }
1051 Expr::For {
1052 binding,
1053 iterable,
1054 body,
1055 } => {
1056 self.collect_expr(iterable);
1057 self.bind_local(binding.as_str());
1058 self.collect_expr(body);
1059 }
1060 Expr::ListComprehension { element, clauses } => {
1061 for clause in clauses {
1062 match clause {
1063 ListComprehensionClause::For { binding, iterable } => {
1064 self.collect_expr(iterable);
1065 self.bind_local(binding.as_str());
1066 }
1067 ListComprehensionClause::If { condition } => {
1068 self.collect_expr(condition);
1069 }
1070 }
1071 }
1072 self.collect_expr(element);
1073 }
1074 _ => {
1075 for child in expr.children() {
1076 self.collect_expr(child);
1077 }
1078 }
1079 }
1080 }
1081}
1082
1083#[derive(Default)]
1084struct HashWriter {
1085 bytes: Vec<u8>,
1086}
1087
1088impl HashWriter {
1089 fn new() -> Self {
1090 Self::default()
1091 }
1092
1093 fn atom(&mut self, value: &str) {
1094 self.bytes
1095 .extend_from_slice(value.len().to_string().as_bytes());
1096 self.bytes.push(b':');
1097 self.bytes.extend_from_slice(value.as_bytes());
1098 self.bytes.push(b';');
1099 }
1100
1101 fn bool(&mut self, value: bool) {
1102 self.atom(if value { "true" } else { "false" });
1103 }
1104
1105 fn usize(&mut self, value: usize) {
1106 self.atom(&value.to_string());
1107 }
1108
1109 fn u32(&mut self, value: u32) {
1110 self.atom(&value.to_string());
1111 }
1112
1113 fn u64(&mut self, value: u64) {
1114 self.atom(&value.to_string());
1115 }
1116
1117 fn finish(self) -> ContentHash {
1118 ContentHash::new(hex_digest(&Sha256::digest(self.bytes)))
1119 }
1120}
1121
1122fn hex_digest(bytes: &[u8]) -> String {
1123 const HEX: &[u8; 16] = b"0123456789abcdef";
1124 let mut out = String::with_capacity(bytes.len() * 2);
1125 for byte in bytes {
1126 out.push(HEX[(byte >> 4) as usize] as char);
1127 out.push(HEX[(byte & 0x0f) as usize] as char);
1128 }
1129 out
1130}
1131
1132#[derive(Clone, Debug)]
1133enum RequirementBinding {
1134 Value,
1135 Resource {
1136 resource_type: String,
1137 path: Option<Vec<String>>,
1138 },
1139}
1140
1141struct RequirementsCollector<'program> {
1142 program: &'program Program,
1143 resource_catalog: Option<&'program LashlangHostCatalog>,
1144 type_names: BTreeSet<String>,
1145 requirements: HostRequirements,
1146}
1147
1148impl<'program> RequirementsCollector<'program> {
1149 fn new(program: &'program Program) -> Self {
1150 let type_names = program
1151 .declarations
1152 .iter()
1153 .filter_map(|declaration| match declaration {
1154 Declaration::Type(type_decl) => Some(type_decl.name.to_string()),
1155 _ => None,
1156 })
1157 .collect();
1158 Self {
1159 program,
1160 resource_catalog: None,
1161 type_names,
1162 requirements: HostRequirements::default(),
1163 }
1164 }
1165
1166 fn with_resource_catalog(mut self, catalog: &'program LashlangHostCatalog) -> Self {
1167 self.resource_catalog = Some(catalog);
1168 self
1169 }
1170
1171 fn collect(mut self) -> HostRequirements {
1172 for declaration in &self.program.declarations {
1173 match declaration {
1174 Declaration::Type(type_decl) => self.collect_type(&type_decl.ty),
1175 Declaration::Process(process) => {
1176 self.requirements.abilities.processes = true;
1177 if process.label.is_some() {
1178 self.requirements.language_features.label_annotations = true;
1179 }
1180 let mut scope = BTreeMap::new();
1181 for param in &process.params {
1182 self.collect_type(¶m.ty);
1183 if let TypeExpr::Ref(name) = ¶m.ty
1184 && !self.type_names.contains(name.as_str())
1185 && self.is_resource_type_name(name)
1186 {
1187 self.requirements
1188 .resources
1189 .ensure_resource_type(name.to_string());
1190 scope.insert(
1191 param.name.to_string(),
1192 RequirementBinding::Resource {
1193 resource_type: name.to_string(),
1194 path: None,
1195 },
1196 );
1197 } else {
1198 scope.insert(param.name.to_string(), RequirementBinding::Value);
1199 }
1200 }
1201 if let Some(return_ty) = &process.return_ty {
1202 self.collect_type(return_ty);
1203 }
1204 scope.insert("input".to_string(), RequirementBinding::Value);
1205 scope.insert("inputs".to_string(), RequirementBinding::Value);
1206 self.collect_expr(&process.body, &mut scope);
1207 }
1208 }
1209 }
1210 let mut top_level = BTreeMap::new();
1211 self.collect_expr(&self.program.main, &mut top_level);
1212 self.requirements
1213 }
1214
1215 fn collect_type(&mut self, ty: &TypeExpr) {
1216 match ty {
1217 TypeExpr::List(item) => self.collect_type(item),
1218 TypeExpr::Object(fields) => {
1219 for field in fields {
1220 self.collect_type(&field.ty);
1221 }
1222 }
1223 TypeExpr::Union(items) => {
1224 for item in items {
1225 self.collect_type(item);
1226 }
1227 }
1228 TypeExpr::Process { input, output, .. } => {
1229 self.collect_type(input);
1230 self.collect_type(output);
1231 }
1232 TypeExpr::TriggerHandle(event) => self.collect_type(event),
1233 TypeExpr::Ref(name)
1234 if !self.type_names.contains(name.as_str())
1235 && self.is_host_data_type_name(name) =>
1236 {
1237 let data_type = self
1238 .resource_catalog
1239 .and_then(|catalog| catalog.resolve_named_data_type(name.as_str()))
1240 .expect("checked host data type presence")
1241 .clone();
1242 self.requirements
1243 .resources
1244 .add_named_data_type(data_type)
1245 .expect("host data type requirement came from host catalog");
1246 }
1247 TypeExpr::Ref(name)
1248 if !self.type_names.contains(name.as_str()) && self.is_resource_type_name(name) =>
1249 {
1250 self.requirements
1251 .resources
1252 .ensure_resource_type(name.to_string());
1253 }
1254 TypeExpr::Any
1255 | TypeExpr::Str
1256 | TypeExpr::Int
1257 | TypeExpr::Float
1258 | TypeExpr::Bool
1259 | TypeExpr::Dict
1260 | TypeExpr::Null
1261 | TypeExpr::Enum(_)
1262 | TypeExpr::Ref(_) => {}
1263 }
1264 }
1265
1266 fn is_host_data_type_name(&self, name: &str) -> bool {
1267 self.resource_catalog
1268 .map(|catalog| catalog.has_named_data_type(name))
1269 .unwrap_or(false)
1270 }
1271
1272 fn is_resource_type_name(&self, name: &str) -> bool {
1273 self.resource_catalog
1274 .map(|catalog| catalog.has_resource_type(name))
1275 .unwrap_or(true)
1276 }
1277
1278 fn collect_expr(
1279 &mut self,
1280 expr: &Expr,
1281 scope: &mut BTreeMap<String, RequirementBinding>,
1282 ) -> Option<RequirementBinding> {
1283 match expr {
1284 Expr::Block(expressions) => {
1285 let mut last = None;
1286 for expression in expressions {
1287 last = self.collect_expr(expression, scope);
1288 }
1289 last
1290 }
1291 Expr::LabelAnnotated { expr, .. } => {
1292 self.requirements.language_features.label_annotations = true;
1293 self.collect_expr(expr, scope)
1294 }
1295 Expr::Variable(name) => scope.get(name.as_str()).cloned(),
1296 Expr::Tuple(items) => {
1297 for item in items {
1298 self.collect_expr(item, scope);
1299 }
1300 Some(RequirementBinding::Value)
1301 }
1302 Expr::List(items) => {
1303 for item in items {
1304 self.collect_expr(item, scope);
1305 }
1306 Some(RequirementBinding::Value)
1307 }
1308 Expr::ListComprehension { element, clauses } => {
1309 let mut previous = Vec::new();
1310 for clause in clauses {
1311 match clause {
1312 ListComprehensionClause::For { binding, iterable } => {
1313 self.collect_expr(iterable, scope);
1314 previous.push((
1315 binding.to_string(),
1316 scope.insert(binding.to_string(), RequirementBinding::Value),
1317 ));
1318 }
1319 ListComprehensionClause::If { condition } => {
1320 self.collect_expr(condition, scope);
1321 }
1322 }
1323 }
1324 self.collect_expr(element, scope);
1325 for (binding, previous) in previous.into_iter().rev() {
1326 if let Some(previous) = previous {
1327 scope.insert(binding, previous);
1328 } else {
1329 scope.remove(binding.as_str());
1330 }
1331 }
1332 Some(RequirementBinding::Value)
1333 }
1334 Expr::Record(entries) => {
1335 for (_, value) in entries {
1336 self.collect_expr(value, scope);
1337 }
1338 Some(RequirementBinding::Value)
1339 }
1340 Expr::Assign { target, expr } => {
1341 for step in &target.steps {
1342 if let AssignPathStep::Index(index) = step {
1343 self.collect_expr(index, scope);
1344 }
1345 }
1346 let binding = self
1347 .collect_expr(expr, scope)
1348 .unwrap_or(RequirementBinding::Value);
1349 if target.steps.is_empty() {
1350 scope.insert(target.root.to_string(), binding);
1351 }
1352 Some(RequirementBinding::Value)
1353 }
1354 Expr::If {
1355 condition,
1356 then_block,
1357 else_block,
1358 } => {
1359 self.collect_expr(condition, scope);
1360 let mut then_scope = scope.clone();
1361 self.collect_expr(then_block, &mut then_scope);
1362 let mut else_scope = scope.clone();
1363 self.collect_expr(else_block, &mut else_scope);
1364 for (name, binding) in then_scope.into_iter().chain(else_scope) {
1365 scope.entry(name).or_insert(binding);
1366 }
1367 Some(RequirementBinding::Value)
1368 }
1369 Expr::For {
1370 binding,
1371 iterable,
1372 body,
1373 } => {
1374 self.collect_expr(iterable, scope);
1375 let previous = scope.insert(binding.to_string(), RequirementBinding::Value);
1376 self.collect_expr(body, scope);
1377 if let Some(previous) = previous {
1378 scope.insert(binding.to_string(), previous);
1379 } else {
1380 scope.remove(binding.as_str());
1381 }
1382 Some(RequirementBinding::Value)
1383 }
1384 Expr::While { condition, body } => {
1385 self.collect_expr(condition, scope);
1386 self.collect_expr(body, scope);
1387 Some(RequirementBinding::Value)
1388 }
1389 Expr::StartProcess(start) => {
1390 self.requirements.abilities.processes = true;
1391 for (_, value) in &start.args {
1392 self.collect_expr(value, scope);
1393 }
1394 Some(RequirementBinding::Value)
1395 }
1396 Expr::ProcessRef { .. } => {
1397 self.requirements.abilities.processes = true;
1398 Some(RequirementBinding::Value)
1399 }
1400 Expr::HostDescriptorConstructor { type_name, input } => {
1401 if let Some(catalog) = self.resource_catalog
1402 && let Some(constructor) = catalog
1403 .value_constructors()
1404 .map(|(_, constructor)| constructor)
1405 .find(|constructor| constructor.type_name == type_name.as_str())
1406 {
1407 self.requirements.resources.add_value_constructor(
1408 constructor.path.iter().map(String::as_str),
1409 constructor.input_ty.clone(),
1410 constructor.output_ty.clone(),
1411 );
1412 }
1413 if let Some(catalog) = self.resource_catalog
1414 && let Some(binding) = catalog.resolve_trigger_source(type_name.as_str())
1415 {
1416 self.requirements
1417 .resources
1418 .add_trigger_source_type(
1419 type_name.to_string(),
1420 binding.event_type().clone(),
1421 )
1422 .expect("trigger source requirement came from host catalog");
1423 }
1424 self.collect_expr(input, scope);
1425 Some(RequirementBinding::Value)
1426 }
1427 Expr::ResourceRef(resource) => {
1428 self.require_resource_ref(resource);
1429 Some(RequirementBinding::Resource {
1430 resource_type: resource.resource_type.to_string(),
1431 path: Some(resource.path.iter().map(ToString::to_string).collect()),
1432 })
1433 }
1434 Expr::ReceiverCall {
1435 receiver,
1436 operation,
1437 args,
1438 } => {
1439 let receiver = self.collect_expr(receiver, scope);
1440 if let Some(RequirementBinding::Resource {
1441 resource_type,
1442 path,
1443 }) = receiver
1444 {
1445 self.require_resource_operation(resource_type, path, operation.as_str());
1446 }
1447 for arg in args {
1448 self.collect_expr(arg, scope);
1449 }
1450 Some(RequirementBinding::Value)
1451 }
1452 Expr::SleepFor(expr) | Expr::SleepUntil(expr) => {
1453 self.requirements.abilities.sleep = true;
1454 self.collect_expr(expr, scope);
1455 Some(RequirementBinding::Value)
1456 }
1457 Expr::WaitSignal { .. } => {
1458 self.requirements.abilities.process_signals = true;
1459 Some(RequirementBinding::Value)
1460 }
1461 Expr::SignalRun { run, payload, .. } => {
1462 self.requirements.abilities.process_signals = true;
1463 self.collect_expr(run, scope);
1464 self.collect_expr(payload, scope);
1465 Some(RequirementBinding::Value)
1466 }
1467 Expr::Await(expr)
1468 | Expr::ResultUnwrap(expr)
1469 | Expr::Cancel(expr)
1470 | Expr::Print(expr)
1471 | Expr::Yield(expr)
1472 | Expr::Wake(expr)
1473 | Expr::Fail(expr)
1474 | Expr::Unary { expr, .. } => {
1475 self.collect_expr(expr, scope);
1476 Some(RequirementBinding::Value)
1477 }
1478 Expr::Finish(expr) => {
1479 self.collect_expr(expr, scope);
1480 Some(RequirementBinding::Value)
1481 }
1482 Expr::BuiltinCall { args, .. } => {
1483 for arg in args {
1484 self.collect_expr(arg, scope);
1485 }
1486 Some(RequirementBinding::Value)
1487 }
1488 Expr::Field { target, .. } => {
1489 self.collect_expr(target, scope);
1490 Some(RequirementBinding::Value)
1491 }
1492 Expr::Index { target, index } => {
1493 self.collect_expr(target, scope);
1494 self.collect_expr(index, scope);
1495 Some(RequirementBinding::Value)
1496 }
1497 Expr::Binary { left, right, .. } => {
1498 self.collect_expr(left, scope);
1499 self.collect_expr(right, scope);
1500 Some(RequirementBinding::Value)
1501 }
1502 Expr::TypeLiteral(ty) => {
1503 self.collect_type(ty);
1504 Some(RequirementBinding::Value)
1505 }
1506 Expr::Null
1507 | Expr::Bool(_)
1508 | Expr::Number(_)
1509 | Expr::String(_)
1510 | Expr::Break
1511 | Expr::Continue => Some(RequirementBinding::Value),
1512 }
1513 }
1514
1515 fn require_resource_ref(&mut self, resource: &ResourceRefExpr) {
1516 self.requirements
1517 .resources
1518 .add_module_instance(
1519 resource.path.iter().map(|segment| segment.as_str()),
1520 resource.resource_type.to_string(),
1521 )
1522 .expect("resolved resource references cannot conflict");
1523 }
1524
1525 fn require_resource_operation(
1526 &mut self,
1527 resource_type: String,
1528 path: Option<Vec<String>>,
1529 operation: &str,
1530 ) {
1531 let (operation, binding) = self.resource_operation_requirement(&resource_type, operation);
1532 if let (Some(catalog), Some(path)) = (self.resource_catalog, path.as_ref()) {
1533 let alias = path.join(".");
1534 if let Some(module_binding) =
1535 catalog.resolve_module_operation(&resource_type, &alias, &operation)
1536 {
1537 self.requirements.resources.add_module_operation_binding(
1538 path.iter().map(String::as_str),
1539 resource_type,
1540 operation,
1541 module_binding.host_operation.clone(),
1542 binding,
1543 );
1544 return;
1545 }
1546 }
1547 self.requirements
1548 .resources
1549 .add_operation_binding(resource_type, operation, binding);
1550 }
1551
1552 fn resource_operation_requirement(
1553 &self,
1554 resource_type: &str,
1555 operation: &str,
1556 ) -> (String, ResourceOperationBinding) {
1557 if let Some(catalog) = self.resource_catalog
1558 && let Some(binding) = catalog.resolve_operation(resource_type, operation)
1559 {
1560 return (operation.to_string(), binding.clone());
1561 }
1562 (
1563 operation.to_string(),
1564 ResourceOperationBinding {
1565 input_ty: TypeExpr::Any,
1566 output_ty: TypeExpr::Any,
1567 output_from_input: None,
1568 },
1569 )
1570 }
1571}