Skip to main content

runmat_hir/
hir.rs

1use crate::{
2    BindingId, ClassId, EntrypointId, ExprId, FunctionId, ModuleId, SourceId, Span, StmtId,
3};
4use serde::{Deserialize, Serialize};
5
6/// Canonical semantic HIR product for one compiled source set.
7///
8/// The assembly owns the tables for modules, functions, classes, bindings, and
9/// entrypoints. Table IDs are local to this assembly; stable identities for
10/// packages/modules/items are represented separately by `DefPath` and qualified
11/// name types.
12#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Default)]
13pub struct HirAssembly {
14    pub modules: Vec<HirModule>,
15    pub functions: Vec<HirFunction>,
16    pub classes: Vec<HirClass>,
17    pub bindings: Vec<HirBinding>,
18    pub entrypoints: Vec<HirEntrypoint>,
19}
20
21/// Source unit metadata plus references to module-owned semantic items.
22///
23/// Top-level functions, classes, and synthetic script entry functions live in
24/// assembly tables and are referenced here by local IDs rather than embedded as
25/// statement variants.
26#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
27pub struct HirModule {
28    pub id: ModuleId,
29    pub name: QualifiedName,
30    pub source_id: SourceId,
31    pub source_unit: SourceUnitKind,
32    pub imports: Vec<HirImport>,
33    pub top_level_functions: Vec<FunctionId>,
34    pub classes: Vec<ClassId>,
35    pub synthetic_entry_function: Option<FunctionId>,
36}
37
38#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
39pub enum SourceUnitKind {
40    ScriptFile,
41    FunctionFile,
42    ClassFile,
43    PackageFunctionFile,
44    ClassFolderMethodFile,
45    ReplSubmission,
46    NotebookCell,
47}
48
49#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
50pub struct HirEntrypoint {
51    pub id: EntrypointId,
52    pub name: Option<EntrypointName>,
53    pub target: FunctionId,
54    pub origin: EntrypointOrigin,
55    pub policy: EntrypointPolicy,
56}
57
58#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
59pub enum EntrypointOrigin {
60    ProjectDeclared,
61    SourcePath,
62    ReplSubmission,
63    NotebookCell,
64    HostSynthetic,
65}
66
67#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
68pub struct EntrypointPolicy {
69    pub workspace_export: WorkspaceExportPolicy,
70    pub top_level_await: bool,
71}
72
73#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
74pub enum WorkspaceExportPolicy {
75    ExportTopLevelBindings,
76    ReturnFunctionOutputs,
77    HostDefined,
78}
79
80/// Uniform executable representation for MATLAB functions and generated entrypoints.
81///
82/// Named functions, nested functions, anonymous functions, class methods, and
83/// synthetic script entrypoints all use this shape. Parameters, outputs, locals,
84/// and captures reference semantic `BindingId`s; VM slots are intentionally not
85/// represented in core HIR.
86#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
87pub struct HirFunction {
88    pub id: FunctionId,
89    pub module: ModuleId,
90    pub parent: Option<FunctionId>,
91    pub enclosing_class: Option<ClassId>,
92    pub name: FunctionName,
93    pub kind: FunctionKind,
94    pub params: Vec<BindingId>,
95    pub outputs: Vec<BindingId>,
96    pub abi: FunctionAbi,
97    pub argument_validations: Vec<FunctionArgumentValidation>,
98    pub locals: Vec<BindingId>,
99    pub captures: Vec<CapturedBinding>,
100    pub modifiers: FunctionModifiers,
101    pub body: HirBlock,
102    pub span: Span,
103}
104
105#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
106pub enum FunctionKind {
107    Named,
108    Anonymous,
109    SyntheticEntrypoint,
110    ClassMethod { is_static: bool },
111}
112
113#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Default)]
114pub struct FunctionModifiers {
115    pub isolated: bool,
116    pub is_async: bool,
117}
118
119#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
120pub struct FunctionAbi {
121    pub fixed_inputs: Vec<BindingId>,
122    pub varargin: Option<BindingId>,
123    pub fixed_outputs: Vec<BindingId>,
124    pub varargout: Option<BindingId>,
125    pub implicit_nargin: Option<BindingId>,
126    pub implicit_nargout: Option<BindingId>,
127}
128
129#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
130pub enum FunctionArgDim {
131    Any,
132    Exact(usize),
133}
134
135#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
136pub struct FunctionArgSizeSpec {
137    pub rows: FunctionArgDim,
138    pub cols: FunctionArgDim,
139}
140
141#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
142pub struct FunctionArgumentValidation {
143    pub binding: BindingId,
144    pub size: Option<FunctionArgSizeSpec>,
145    pub class_name: Option<String>,
146    pub validators: Vec<FunctionArgValidator>,
147    pub default_value: Option<FunctionArgDefaultValue>,
148}
149
150#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
151pub enum FunctionArgValidator {
152    Finite,
153    NumericOrLogical,
154    Text,
155    Nonempty,
156    ScalarOrEmpty,
157    Real,
158    Integer,
159    Positive,
160    Negative,
161    Nonnegative,
162    Nonzero,
163    Nonpositive,
164    GreaterThanOrEqual(f64),
165    LessThanOrEqual(f64),
166    GreaterThan(f64),
167    LessThan(f64),
168}
169
170#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
171pub enum FunctionArgDefaultValue {
172    Number(f64),
173    Bool(bool),
174    String(String),
175    EmptyArray,
176}
177
178#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
179pub struct CapturedBinding {
180    pub binding: BindingId,
181    pub from_function: FunctionId,
182}
183
184/// Semantic binding identity for a name owned by a module or function.
185///
186/// Bindings model MATLAB lexical/global/persistent storage and workspace
187/// visibility. They are not VM slot numbers.
188#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
189pub struct HirBinding {
190    pub id: BindingId,
191    pub owner: BindingOwner,
192    pub name: BindingName,
193    pub role: BindingRole,
194    pub storage: BindingStorage,
195    pub workspace_visibility: WorkspaceVisibility,
196    pub declared_span: Span,
197}
198
199#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
200pub enum BindingOwner {
201    Module(ModuleId),
202    Function(FunctionId),
203}
204
205#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
206pub enum BindingRole {
207    Parameter,
208    Output,
209    Local,
210    ExternalWorkspace,
211    ModuleBinding,
212    ImplicitAns,
213}
214
215#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
216pub enum BindingStorage {
217    Lexical,
218    Global,
219    Persistent,
220}
221
222#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
223pub enum WorkspaceVisibility {
224    Hidden,
225    TopLevel,
226    ModuleVisible,
227    ImplicitAns,
228}
229
230#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
231pub struct HirBlock {
232    pub statements: Vec<HirStmt>,
233}
234
235#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
236pub struct HirStmt {
237    pub id: StmtId,
238    pub kind: HirStmtKind,
239    pub span: Span,
240}
241
242impl HirStmt {
243    pub fn span(&self) -> Span {
244        self.span
245    }
246}
247
248#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
249pub enum HirStmtKind {
250    ExprStmt(HirExpr, bool),
251    Assign(HirPlace, HirExpr, bool),
252    MultiAssign(OutputTargetList, HirExpr, bool),
253    If {
254        cond: HirExpr,
255        then_body: HirBlock,
256        elseif_blocks: Vec<(HirExpr, HirBlock)>,
257        else_body: Option<HirBlock>,
258    },
259    While {
260        cond: HirExpr,
261        body: HirBlock,
262    },
263    For {
264        binding: BindingId,
265        range: HirExpr,
266        body: HirBlock,
267    },
268    Switch {
269        expr: HirExpr,
270        cases: Vec<(HirExpr, HirBlock)>,
271        otherwise: Option<HirBlock>,
272    },
273    TryCatch {
274        try_body: HirBlock,
275        catch_binding: Option<BindingId>,
276        catch_body: HirBlock,
277    },
278    Global(Vec<BindingId>),
279    Persistent(Vec<BindingId>),
280    Break,
281    Continue,
282    Return,
283    Import(HirImport),
284}
285
286#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
287pub struct HirExpr {
288    pub id: ExprId,
289    pub kind: HirExprKind,
290    pub span: Span,
291}
292
293impl HirExpr {
294    pub fn span(&self) -> Span {
295        self.span
296    }
297}
298
299#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
300pub enum HirExprKind {
301    Number(String),
302    String(StringLiteral),
303    Constant(SymbolName),
304    Binding(BindingId),
305    Unary(OperatorKind, Box<HirExpr>),
306    Binary(Box<HirExpr>, OperatorKind, Box<HirExpr>),
307    Tensor(Vec<Vec<HirExpr>>),
308    Cell(Vec<Vec<HirExpr>>),
309    StructLiteral(Vec<(MemberName, HirExpr)>),
310    ObjectLiteral {
311        class_name: QualifiedName,
312        fields: Vec<(MemberName, HirExpr)>,
313    },
314    Range(Box<HirExpr>, Option<Box<HirExpr>>, Box<HirExpr>),
315    Colon,
316    End,
317    Index(Box<HirExpr>, IndexingSemantics),
318    Member(Box<HirExpr>, MemberName),
319    MemberDynamic(Box<HirExpr>, Box<HirExpr>),
320    WorkspaceFirstStaticProperty {
321        workspace_name: SymbolName,
322        class_name: String,
323        property: MemberName,
324    },
325    Call(HirCall),
326    CommandCall(HirCommandCall),
327    FunctionHandle(FunctionHandleTarget),
328    AnonymousFunction(FunctionId),
329    MetaClass(QualifiedName),
330    Await(Box<HirExpr>),
331    Spawn(Box<HirExpr>),
332}
333
334#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
335pub enum HirPlace {
336    Binding(BindingId),
337    Member(Box<HirExpr>, MemberName),
338    MemberDynamic(Box<HirExpr>, Box<HirExpr>),
339    Index(Box<HirExpr>, IndexingSemantics),
340    IndexCell(Box<HirExpr>, IndexingSemantics),
341}
342
343/// Call expression with a semantic callee reference and source syntax marker.
344///
345/// Unresolved or dynamic calls remain explicit, so the HIR no longer relies on a
346/// string-only `FuncCall` variant as the primary call representation.
347#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
348pub struct HirCall {
349    pub callee: HirCallableRef,
350    pub args: Vec<HirExpr>,
351    pub syntax: CallSyntax,
352    pub requested_outputs: RequestedOutputCount,
353    #[serde(default)]
354    pub workspace_first_name: Option<SymbolName>,
355    #[serde(default)]
356    pub bare_identifier: bool,
357}
358
359#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
360pub enum HirCallableRef {
361    Function(FunctionId),
362    Builtin(BuiltinId),
363    Imported(DefPath),
364    SuperConstructor {
365        current_class: SymbolName,
366        super_class: QualifiedName,
367    },
368    SuperMethod {
369        current_class: SymbolName,
370        super_class: QualifiedName,
371        method: SymbolName,
372    },
373    DynamicExpr(Box<HirExpr>),
374    Unresolved(QualifiedName),
375}
376
377impl HirCallableRef {
378    pub fn is_feval_builtin_like(&self) -> bool {
379        match self {
380            HirCallableRef::Builtin(id) => id.0 == FEVAL_BUILTIN_NAME,
381            HirCallableRef::Unresolved(name) if name.0.len() == 1 => {
382                name.0[0].0 == FEVAL_BUILTIN_NAME
383            }
384            _ => false,
385        }
386    }
387
388    pub fn identity(&self) -> Option<CallableIdentity> {
389        match self {
390            HirCallableRef::Function(function) => Some(CallableIdentity::BoundFunction(*function)),
391            HirCallableRef::Builtin(builtin) => Some(CallableIdentity::Builtin(builtin.clone())),
392            HirCallableRef::Imported(path) => Some(CallableIdentity::Imported(path.clone())),
393            HirCallableRef::SuperConstructor { .. } | HirCallableRef::SuperMethod { .. } => None,
394            HirCallableRef::DynamicExpr(_) => None,
395            HirCallableRef::Unresolved(name) => {
396                if name.0.len() == 1 {
397                    Some(CallableIdentity::DynamicName(name.0[0].clone()))
398                } else {
399                    Some(CallableIdentity::ExternalName(name.clone()))
400                }
401            }
402        }
403    }
404}
405
406#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
407pub enum CallableIdentity {
408    BoundFunction(FunctionId),
409    Builtin(BuiltinId),
410    Imported(DefPath),
411    Method(MethodId),
412    AnonymousFunction(FunctionId),
413    DynamicName(SymbolName),
414    ExternalName(QualifiedName),
415}
416
417impl CallableIdentity {
418    pub fn display_name(&self) -> Option<String> {
419        match self {
420            CallableIdentity::BoundFunction(_) | CallableIdentity::AnonymousFunction(_) => None,
421            CallableIdentity::Builtin(id) => (!id.0.is_empty()).then_some(id.0.clone()),
422            CallableIdentity::Imported(path) => path.module.display_name(),
423            CallableIdentity::Method(id) => (!id.0.is_empty()).then_some(id.0.clone()),
424            CallableIdentity::DynamicName(name) => (!name.0.is_empty()).then_some(name.0.clone()),
425            CallableIdentity::ExternalName(name) => name.display_name(),
426        }
427    }
428}
429
430#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, Serialize, Deserialize)]
431pub enum CallableFallbackPolicy {
432    None,
433    RuntimeNameResolution,
434    ObjectDispatch,
435    ExternalBoundary,
436}
437
438impl CallableFallbackPolicy {
439    fn is_well_formed_external_name(name: &QualifiedName) -> bool {
440        name.0.len() > 1 && name.0.iter().all(|segment| !segment.0.trim().is_empty())
441    }
442
443    fn is_well_formed_imported_path(path: &DefPath) -> bool {
444        let Some(module_name) = path.module.display_name() else {
445            return false;
446        };
447        let Some(last_item) = path.item.last() else {
448            return false;
449        };
450        let item_name = last_item.display_name();
451        if item_name.trim().is_empty() {
452            return false;
453        }
454        let Some(last_module_segment) = path.module.0.last() else {
455            return false;
456        };
457        if last_module_segment.0.trim().is_empty() {
458            return false;
459        }
460        // Imported callable identities must keep module leaf and item leaf aligned.
461        // This prevents silently routing a mismatched DefPath through name-shaped fallback.
462        let _ = module_name;
463        last_module_segment.0 == item_name
464    }
465
466    pub fn allows_runtime_name_resolution(self) -> bool {
467        matches!(self, CallableFallbackPolicy::RuntimeNameResolution)
468    }
469
470    pub fn allows_semantic_name_resolution_for(self, identity: &CallableIdentity) -> bool {
471        match identity {
472            CallableIdentity::DynamicName(SymbolName(name))
473            | CallableIdentity::Method(MethodId(name)) => {
474                self.allows_runtime_name_resolution() && !name.trim().is_empty()
475            }
476            CallableIdentity::Imported(path) => {
477                self.allows_runtime_name_resolution() && Self::is_well_formed_imported_path(path)
478            }
479            CallableIdentity::ExternalName(name) => {
480                matches!(self, CallableFallbackPolicy::ExternalBoundary)
481                    && Self::is_well_formed_external_name(name)
482            }
483            _ => false,
484        }
485    }
486
487    pub fn allows_vm_name_fallback_for(self, identity: &CallableIdentity) -> bool {
488        match identity {
489            CallableIdentity::DynamicName(SymbolName(name)) => {
490                self.allows_runtime_name_resolution() && !name.trim().is_empty()
491            }
492            CallableIdentity::Imported(path) => {
493                self.allows_runtime_name_resolution() && Self::is_well_formed_imported_path(path)
494            }
495            CallableIdentity::ExternalName(name) => {
496                matches!(self, CallableFallbackPolicy::ExternalBoundary)
497                    && Self::is_well_formed_external_name(name)
498            }
499            _ => false,
500        }
501    }
502
503    pub fn resolution_name_for(self, identity: &CallableIdentity) -> Option<String> {
504        if !self.allows_semantic_name_resolution_for(identity) {
505            return None;
506        }
507
508        match identity {
509            CallableIdentity::DynamicName(SymbolName(name))
510            | CallableIdentity::Method(MethodId(name)) => {
511                let trimmed = name.trim();
512                (!trimmed.is_empty()).then_some(trimmed.to_string())
513            }
514            CallableIdentity::Imported(path) => path.module.display_name(),
515            CallableIdentity::ExternalName(name) if Self::is_well_formed_external_name(name) => {
516                Some(
517                    name.0
518                        .iter()
519                        .map(|segment| segment.0.as_str())
520                        .collect::<Vec<_>>()
521                        .join("."),
522                )
523            }
524            _ => None,
525        }
526    }
527
528    pub fn vm_fallback_name_for(self, identity: &CallableIdentity) -> Option<String> {
529        if !self.allows_vm_name_fallback_for(identity) {
530            return None;
531        }
532
533        match identity {
534            CallableIdentity::DynamicName(SymbolName(name)) => {
535                let trimmed = name.trim();
536                (!trimmed.is_empty()).then_some(trimmed.to_string())
537            }
538            CallableIdentity::Imported(path) => path.module.display_name(),
539            CallableIdentity::ExternalName(name) if Self::is_well_formed_external_name(name) => {
540                Some(
541                    name.0
542                        .iter()
543                        .map(|segment| segment.0.as_str())
544                        .collect::<Vec<_>>()
545                        .join("."),
546                )
547            }
548            _ => None,
549        }
550    }
551
552    pub fn supports_vm_static_call(self) -> bool {
553        matches!(
554            self,
555            CallableFallbackPolicy::RuntimeNameResolution
556                | CallableFallbackPolicy::ExternalBoundary
557        )
558    }
559
560    pub fn supports_vm_method_or_member_call(self) -> bool {
561        matches!(
562            self,
563            CallableFallbackPolicy::RuntimeNameResolution | CallableFallbackPolicy::ObjectDispatch
564        )
565    }
566
567    pub fn post_object_dispatch(self) -> Self {
568        match self {
569            CallableFallbackPolicy::ObjectDispatch => CallableFallbackPolicy::RuntimeNameResolution,
570            other => other,
571        }
572    }
573}
574
575pub const FEVAL_BUILTIN_NAME: &str = "feval";
576pub const EVAL_BUILTIN_NAME: &str = "eval";
577pub const EVALIN_BUILTIN_NAME: &str = "evalin";
578pub const ASSIGNIN_BUILTIN_NAME: &str = "assignin";
579pub const RUN_BUILTIN_NAME: &str = "run";
580pub const NARGIN_BUILTIN_NAME: &str = "nargin";
581pub const NARGOUT_BUILTIN_NAME: &str = "nargout";
582pub const NARGINCHK_BUILTIN_NAME: &str = "narginchk";
583pub const NARGOUTCHK_BUILTIN_NAME: &str = "nargoutchk";
584pub const AWAIT_EXTENSION_NAME: &str = "await";
585pub const SPAWN_EXTENSION_NAME: &str = "spawn";
586pub const TEST_CLASS_REGISTRATION_BUILTIN_NAME: &str = "__register_test_classes";
587pub const DISCARD_OUTPUT_NAME: &str = "~";
588
589#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
590pub enum CallSyntax {
591    Plain,
592    Method,
593    DottedInvoke,
594    Command,
595}
596
597#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
598pub struct HirCommandCall {
599    pub command: HirCallableRef,
600    pub args: Vec<CommandArgument>,
601}
602
603#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
604pub enum CommandArgument {
605    Word(SymbolName),
606    StringLiteral(StringLiteral),
607}
608
609#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
610pub struct HirImport {
611    pub path: QualifiedName,
612    pub wildcard: bool,
613    pub span: Span,
614}
615
616/// Semantic class metadata owned by the assembly.
617///
618/// Methods reference `HirFunction` entries by `FunctionId`; class definitions are
619/// not durable statement variants in the semantic model.
620#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
621pub struct HirClass {
622    pub id: ClassId,
623    pub module: ModuleId,
624    pub name: QualifiedName,
625    pub super_class: Option<ClassId>,
626    pub kind: ClassKind,
627    pub is_sealed: bool,
628    pub is_abstract: bool,
629    pub properties: Vec<ClassProperty>,
630    pub methods: Vec<ClassMethod>,
631    pub events: Vec<ClassEvent>,
632    pub enumerations: Vec<ClassEnumeration>,
633    pub arguments: Vec<ClassArgumentBlock>,
634    pub span: Span,
635}
636
637#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
638pub enum ClassKind {
639    Value,
640    Handle,
641}
642
643#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
644pub struct ClassProperty {
645    pub name: MemberName,
646    pub attributes: PropertyAttributes,
647    pub default: Option<HirExpr>,
648    pub span: Span,
649}
650
651#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
652pub struct ClassMethod {
653    pub function: FunctionId,
654    pub name: MethodName,
655    pub is_static: bool,
656    pub attributes: MethodAttributes,
657    pub span: Span,
658}
659
660#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
661pub struct ClassEvent {
662    pub name: SymbolName,
663    pub span: Span,
664}
665
666#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
667pub struct ClassEnumeration {
668    pub name: SymbolName,
669    pub span: Span,
670}
671
672#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
673pub struct ClassArgumentBlock {
674    pub span: Span,
675}
676
677#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Default)]
678pub struct PropertyAttributes {
679    pub is_static: bool,
680    pub is_constant: bool,
681    pub is_dependent: bool,
682    pub is_transient: bool,
683    pub is_hidden: bool,
684    pub access: MemberAccess,
685    pub get_access: MemberAccess,
686    pub set_access: MemberAccess,
687}
688
689#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Default)]
690pub struct MethodAttributes {
691    pub access: MemberAccess,
692    pub is_hidden: bool,
693    pub is_abstract: bool,
694    pub is_sealed: bool,
695}
696
697#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Default)]
698pub enum MemberAccess {
699    #[default]
700    Public,
701    Private,
702    Protected,
703}
704
705#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
706pub struct OutputTargetList {
707    pub requested_outputs: RequestedOutputCount,
708    pub targets: Vec<OutputTarget>,
709}
710
711#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
712pub enum OutputTarget {
713    Place(HirPlace),
714    Discard,
715}
716
717#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
718pub enum RequestedOutputCount {
719    Zero,
720    One,
721    Exactly(usize),
722    CurrentFunctionNargout,
723}
724
725impl RequestedOutputCount {
726    pub fn fixed_count(&self) -> usize {
727        match self {
728            RequestedOutputCount::Zero => 0,
729            RequestedOutputCount::One => 1,
730            RequestedOutputCount::Exactly(count) => *count,
731            RequestedOutputCount::CurrentFunctionNargout => 1,
732        }
733    }
734}
735
736#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
737pub enum IndexKind {
738    Paren,
739    Brace,
740}
741
742#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
743pub enum IndexComponent {
744    Colon,
745    End { dim: Option<usize>, offset: isize },
746    Expr(HirExpr),
747    Logical(HirExpr),
748}
749
750#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
751pub struct IndexingSemantics {
752    pub kind: IndexKind,
753    pub components: Vec<IndexComponent>,
754    pub result_context: IndexResultContext,
755}
756
757#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
758pub enum IndexResultContext {
759    ReadSingle,
760    ReadCommaList,
761    AssignmentTarget,
762    DeletionTarget,
763    FunctionArgumentExpansion,
764}
765
766#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
767pub enum FunctionHandleTarget {
768    Function(FunctionId),
769    Builtin(BuiltinId),
770    Anonymous(FunctionId),
771    DefPath(DefPath),
772    DynamicName(SymbolName),
773}
774
775impl FunctionHandleTarget {
776    pub fn identity(&self) -> CallableIdentity {
777        match self {
778            FunctionHandleTarget::Function(function) => CallableIdentity::BoundFunction(*function),
779            FunctionHandleTarget::Builtin(builtin) => CallableIdentity::Builtin(builtin.clone()),
780            FunctionHandleTarget::Anonymous(function) => {
781                CallableIdentity::AnonymousFunction(*function)
782            }
783            FunctionHandleTarget::DefPath(path) => CallableIdentity::Imported(path.clone()),
784            FunctionHandleTarget::DynamicName(name) => CallableIdentity::DynamicName(name.clone()),
785        }
786    }
787}
788
789#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
790pub enum WorkspaceEffect {
791    None,
792    ReadsWorkspace,
793    CreatesBinding,
794    ClearsBinding,
795    ClearsFunctionCache,
796    MutatesGlobal,
797    MutatesPersistent,
798    LoadsExternalBindings,
799    DynamicEval,
800}
801
802#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
803pub enum EnvironmentEffect {
804    PathMutation,
805    WorkingDirectoryMutation,
806    FunctionCacheInvalidation,
807    DynamicLookupInvalidation,
808}
809
810#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
811pub enum EmptyArrayRole {
812    EmptyValue,
813    ConcatenationIdentity,
814    DeletionMarker,
815}
816
817#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
818pub enum ExpansionSemantics {
819    ExactShape,
820    ScalarExpansion,
821    ImplicitExpansion,
822}
823
824#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
825pub enum OperatorKind {
826    UnaryPlus,
827    UnaryMinus,
828    Not,
829    Add,
830    Subtract,
831    MatrixMultiply,
832    ElementwiseMultiply,
833    MatrixPower,
834    ElementwisePower,
835    Mldivide,
836    Mrdivide,
837    ElementwiseDivide,
838    ElementwiseLeftDivide,
839    Equal,
840    NotEqual,
841    Less,
842    LessEqual,
843    Greater,
844    GreaterEqual,
845    ShortCircuitAnd,
846    ShortCircuitOr,
847    ElementwiseAnd,
848    ElementwiseOr,
849    Transpose,
850    ConjugateTranspose,
851}
852
853#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
854pub enum NumericClass {
855    Double,
856    Single,
857    Int8,
858    UInt8,
859    Int16,
860    UInt16,
861    Int32,
862    UInt32,
863    Int64,
864    UInt64,
865}
866
867#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
868pub enum ValueFlowFact {
869    NoValue,
870    Single(TypeFact),
871    CommaList(Vec<TypeFact>),
872    UnknownList,
873}
874
875#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
876pub enum PlaceMutationKind {
877    BindOrAssign,
878    IndexedAssign,
879    CellAssign,
880    MemberAssign,
881    Delete,
882}
883
884#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
885pub struct PlaceMutation {
886    pub place: HirPlace,
887    pub kind: PlaceMutationKind,
888    pub creation_policy: AssignmentCreationPolicy,
889    pub shape_policy: AssignmentShapePolicy,
890}
891
892#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
893pub enum AssignmentCreationPolicy {
894    ExistingOnly,
895    CreateBinding,
896    CreateArrayByIndex,
897    CreateStructFieldPath,
898    Overloaded,
899}
900
901#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
902pub enum AssignmentShapePolicy {
903    Exact,
904    ScalarExpansion,
905    MatlabCompatible,
906    Overloaded,
907}
908
909#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
910pub enum ReferenceKind {
911    Binding(BindingId),
912    Function(FunctionId),
913    Builtin(BuiltinId),
914    Class(ClassId),
915    Package(QualifiedName),
916    Imported(DefPath),
917    RuntimeClass(QualifiedName),
918    Dynamic,
919    Unresolved,
920}
921
922#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
923pub enum CallKind {
924    DirectFunction(FunctionId),
925    Builtin(BuiltinId),
926    Constructor(ClassId),
927    StaticMethod {
928        class: ClassId,
929        method: MethodId,
930    },
931    InstanceMethod {
932        receiver: Box<HirExpr>,
933        method: MethodId,
934    },
935    PackageFunction(DefPath),
936    FunctionHandle,
937    Dynamic,
938    OverloadedOperator,
939    OverloadedIndexing,
940}
941
942#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Default)]
943pub struct HirIndex {
944    pub bindings: Vec<BindingResolution>,
945    pub functions: Vec<FunctionResolution>,
946    pub classes: Vec<ClassResolution>,
947    pub imports: Vec<ImportResolution>,
948    pub references: Vec<ReferenceResolution>,
949    pub calls: Vec<CallResolution>,
950    pub mutations: Vec<PlaceMutation>,
951}
952
953#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
954pub struct BindingResolution {
955    pub name: BindingName,
956    pub binding: BindingId,
957    pub owner: BindingOwner,
958    pub span: Span,
959}
960
961#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
962pub struct FunctionResolution {
963    pub name: FunctionName,
964    pub function: FunctionId,
965    pub parent: Option<FunctionId>,
966    pub span: Span,
967}
968
969#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
970pub struct ClassResolution {
971    pub name: QualifiedName,
972    pub class: ClassId,
973    pub span: Span,
974}
975
976#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
977pub struct ImportResolution {
978    pub import: HirImport,
979}
980
981#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
982pub struct ReferenceResolution {
983    pub name: SymbolName,
984    pub kind: ReferenceKind,
985    pub span: Span,
986}
987
988#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
989pub struct CallResolution {
990    pub name: QualifiedName,
991    pub callee: HirCallableRef,
992    pub kind: CallKind,
993    pub requested_outputs: RequestedOutputCount,
994    pub span: Span,
995}
996
997#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
998pub enum SpawnSafetyFact {
999    SpawnSafe,
1000    RequiresIsolation,
1001    NotSpawnSafe { reason: SpawnSafetyReason },
1002}
1003
1004#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1005pub enum SpawnSafetyReason {
1006    MutableLexicalCapture,
1007    NonSendableRuntimeHandle,
1008    UnsynchronizedSharedMutation,
1009    UnknownDynamicCapture,
1010}
1011
1012#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1013pub enum AsyncValueFact {
1014    Future(FutureFact),
1015    TaskHandle(TaskHandleFact),
1016}
1017
1018#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1019pub struct FutureFact {
1020    pub output: Box<TypeFact>,
1021    pub state: FutureStateFact,
1022}
1023
1024#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1025pub enum FutureStateFact {
1026    Lazy,
1027    Awaited,
1028    Unknown,
1029}
1030
1031#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1032pub struct TaskHandleFact {
1033    pub output: Box<TypeFact>,
1034    pub spawn_safety: SpawnSafetyFact,
1035}
1036
1037#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1038pub enum TypeFact {
1039    Never,
1040    Unknown,
1041    Logical,
1042    Numeric {
1043        class: NumericClass,
1044        domain: NumericDomain,
1045    },
1046    Tensor(TensorTypeFact),
1047    String,
1048    CharArray,
1049    Cell,
1050    Struct,
1051    ClassInstance(ClassId),
1052    ClassRef(ClassId),
1053    Function(FunctionId),
1054}
1055
1056#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1057pub struct TensorTypeFact {
1058    pub element: TensorElementDomainFact,
1059    pub shape: ShapeFact,
1060}
1061
1062#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1063pub enum TensorElementDomainFact {
1064    Unknown,
1065    Logical,
1066    Numeric {
1067        class: NumericClass,
1068        domain: NumericDomain,
1069    },
1070    Char,
1071    Object(ClassId),
1072}
1073
1074#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1075pub enum NumericDomain {
1076    Real,
1077    Complex,
1078}
1079
1080#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1081pub enum ShapeFact {
1082    Unreachable,
1083    Unknown,
1084    Scalar,
1085    Ranked { rank: usize },
1086    Shaped { dims: Vec<DimFact> },
1087}
1088
1089#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1090pub enum DimFact {
1091    Known(usize),
1092    Symbolic(DimSymbol),
1093    Unknown,
1094}
1095
1096#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
1097pub struct DimSymbol(pub String);
1098
1099/// Stable qualified semantic identity for a package/module/item path.
1100///
1101/// Unlike local `ModuleId`/`FunctionId`/`ClassId`/`BindingId` values, a `DefPath`
1102/// is intended to describe the same semantic item across compiler products when
1103/// the source/project identity is unchanged.
1104#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
1105pub struct DefPath {
1106    pub package: PackageName,
1107    pub module: QualifiedName,
1108    pub item: Vec<DefPathSegment>,
1109}
1110
1111impl DefPath {
1112    pub fn display_name(&self) -> Option<String> {
1113        self.item.last().map(DefPathSegment::display_name)
1114    }
1115}
1116
1117#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
1118pub enum DefPathSegment {
1119    Function(SymbolName),
1120}
1121
1122impl DefPathSegment {
1123    pub fn display_name(&self) -> String {
1124        match self {
1125            DefPathSegment::Function(name) => name.0.clone(),
1126        }
1127    }
1128}
1129
1130#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
1131pub struct QualifiedName(pub Vec<SymbolName>);
1132
1133impl QualifiedName {
1134    pub fn display_name(&self) -> Option<String> {
1135        if self.0.is_empty() || self.0.iter().any(|segment| segment.0.is_empty()) {
1136            None
1137        } else {
1138            Some(
1139                self.0
1140                    .iter()
1141                    .map(|segment| segment.0.as_str())
1142                    .collect::<Vec<_>>()
1143                    .join("."),
1144            )
1145        }
1146    }
1147}
1148
1149#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
1150pub struct SymbolName(pub String);
1151
1152#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
1153pub struct BindingName(pub String);
1154
1155#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
1156pub struct FunctionName(pub String);
1157
1158#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
1159pub struct EntrypointName(pub String);
1160
1161#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
1162pub struct MemberName(pub String);
1163
1164#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
1165pub struct MethodName(pub String);
1166
1167#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
1168pub struct PackageName(pub String);
1169
1170#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
1171pub struct BuiltinId(pub String);
1172
1173#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
1174pub struct MethodId(pub String);
1175
1176#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
1177pub struct StringLiteral(pub String);
1178
1179#[derive(Debug, Clone)]
1180pub struct LoweringResult {
1181    pub assembly: HirAssembly,
1182    pub hir_index: HirIndex,
1183}
1184
1185#[cfg(test)]
1186mod tests {
1187    use super::*;
1188
1189    fn span() -> Span {
1190        Span { start: 0, end: 0 }
1191    }
1192
1193    #[test]
1194    fn assembly_owns_core_items() {
1195        let module = ModuleId(0);
1196        let function = FunctionId(0);
1197        let binding = BindingId(0);
1198        let entrypoint = EntrypointId(0);
1199
1200        let assembly = HirAssembly {
1201            modules: vec![HirModule {
1202                id: module,
1203                name: QualifiedName(vec![SymbolName("demo".into())]),
1204                source_id: SourceId(0),
1205                source_unit: SourceUnitKind::ScriptFile,
1206                imports: vec![],
1207                top_level_functions: vec![],
1208                classes: vec![],
1209                synthetic_entry_function: Some(function),
1210            }],
1211            functions: vec![HirFunction {
1212                id: function,
1213                module,
1214                parent: None,
1215                enclosing_class: None,
1216                name: FunctionName("demo_entry".into()),
1217                kind: FunctionKind::SyntheticEntrypoint,
1218                params: vec![],
1219                outputs: vec![],
1220                abi: FunctionAbi {
1221                    fixed_inputs: vec![],
1222                    varargin: None,
1223                    fixed_outputs: vec![],
1224                    varargout: None,
1225                    implicit_nargin: None,
1226                    implicit_nargout: None,
1227                },
1228                locals: vec![binding],
1229                captures: vec![],
1230                argument_validations: vec![],
1231                modifiers: FunctionModifiers::default(),
1232                body: HirBlock { statements: vec![] },
1233                span: span(),
1234            }],
1235            classes: vec![],
1236            bindings: vec![HirBinding {
1237                id: binding,
1238                owner: BindingOwner::Function(function),
1239                name: BindingName("x".into()),
1240                role: BindingRole::Local,
1241                storage: BindingStorage::Lexical,
1242                workspace_visibility: WorkspaceVisibility::TopLevel,
1243                declared_span: span(),
1244            }],
1245            entrypoints: vec![HirEntrypoint {
1246                id: entrypoint,
1247                name: Some(EntrypointName("demo".into())),
1248                target: function,
1249                origin: EntrypointOrigin::SourcePath,
1250                policy: EntrypointPolicy {
1251                    workspace_export: WorkspaceExportPolicy::ExportTopLevelBindings,
1252                    top_level_await: false,
1253                },
1254            }],
1255        };
1256
1257        assert_eq!(assembly.modules[0].synthetic_entry_function, Some(function));
1258        assert_eq!(assembly.entrypoints[0].target, function);
1259        assert_eq!(assembly.functions[0].locals, vec![binding]);
1260        assert!(matches!(
1261            assembly.bindings[0].workspace_visibility,
1262            WorkspaceVisibility::TopLevel
1263        ));
1264    }
1265
1266    #[test]
1267    fn function_abi_can_reuse_shared_input_output_binding() {
1268        let binding = BindingId(0);
1269        let abi = FunctionAbi {
1270            fixed_inputs: vec![binding],
1271            varargin: None,
1272            fixed_outputs: vec![binding],
1273            varargout: None,
1274            implicit_nargin: Some(BindingId(1)),
1275            implicit_nargout: Some(BindingId(2)),
1276        };
1277
1278        assert_eq!(abi.fixed_inputs[0], abi.fixed_outputs[0]);
1279        assert_eq!(abi.implicit_nargin, Some(BindingId(1)));
1280        assert_eq!(abi.implicit_nargout, Some(BindingId(2)));
1281    }
1282
1283    #[test]
1284    fn captures_reference_original_binding_identity() {
1285        let parent = FunctionId(0);
1286        let child = FunctionId(1);
1287        let binding = BindingId(0);
1288
1289        let capture = CapturedBinding {
1290            binding,
1291            from_function: parent,
1292        };
1293
1294        let child_function = HirFunction {
1295            id: child,
1296            module: ModuleId(0),
1297            parent: Some(parent),
1298            enclosing_class: None,
1299            name: FunctionName("inner".into()),
1300            kind: FunctionKind::Named,
1301            params: vec![],
1302            outputs: vec![],
1303            abi: FunctionAbi {
1304                fixed_inputs: vec![],
1305                varargin: None,
1306                fixed_outputs: vec![],
1307                varargout: None,
1308                implicit_nargin: None,
1309                implicit_nargout: None,
1310            },
1311            locals: vec![],
1312            captures: vec![capture],
1313            argument_validations: vec![],
1314            modifiers: FunctionModifiers::default(),
1315            body: HirBlock { statements: vec![] },
1316            span: span(),
1317        };
1318
1319        assert_eq!(child_function.parent, Some(parent));
1320        assert_eq!(child_function.captures[0].binding, binding);
1321        assert_eq!(child_function.captures[0].from_function, parent);
1322    }
1323
1324    #[test]
1325    fn facts_capture_value_flow_and_mutation_context() {
1326        let binding = BindingId(0);
1327        let mutation = PlaceMutation {
1328            place: HirPlace::Binding(binding),
1329            kind: PlaceMutationKind::BindOrAssign,
1330            creation_policy: AssignmentCreationPolicy::CreateBinding,
1331            shape_policy: AssignmentShapePolicy::MatlabCompatible,
1332        };
1333        let value = ValueFlowFact::Single(TypeFact::Tensor(TensorTypeFact {
1334            element: TensorElementDomainFact::Numeric {
1335                class: NumericClass::Double,
1336                domain: NumericDomain::Real,
1337            },
1338            shape: ShapeFact::Shaped {
1339                dims: vec![DimFact::Known(1), DimFact::Symbolic(DimSymbol("n".into()))],
1340            },
1341        }));
1342
1343        assert!(matches!(mutation.place, HirPlace::Binding(id) if id == binding));
1344        assert!(matches!(
1345            value,
1346            ValueFlowFact::Single(TypeFact::Tensor(TensorTypeFact {
1347                shape: ShapeFact::Shaped { .. },
1348                ..
1349            }))
1350        ));
1351    }
1352
1353    #[test]
1354    fn def_path_is_distinct_from_local_table_ids() {
1355        let function_id = FunctionId(0);
1356        let path = DefPath {
1357            package: PackageName("pkg".into()),
1358            module: QualifiedName(vec![SymbolName("pkg".into()), SymbolName("demo".into())]),
1359            item: vec![DefPathSegment::Function(SymbolName("f".into()))],
1360        };
1361
1362        assert_eq!(function_id, FunctionId(0));
1363        assert_eq!(path.package.0, "pkg");
1364        assert!(matches!(path.item[0], DefPathSegment::Function(_)));
1365    }
1366
1367    #[test]
1368    fn imported_callable_identity_prefers_qualified_display_name() {
1369        let path = DefPath {
1370            package: PackageName("pkg".into()),
1371            module: QualifiedName(vec![
1372                SymbolName("pkg".into()),
1373                SymbolName("demo".into()),
1374                SymbolName("f".into()),
1375            ]),
1376            item: vec![DefPathSegment::Function(SymbolName("f".into()))],
1377        };
1378        let identity = CallableIdentity::Imported(path);
1379        assert_eq!(identity.display_name().as_deref(), Some("pkg.demo.f"));
1380
1381        let empty_module_path = DefPath {
1382            package: PackageName("pkg".into()),
1383            module: QualifiedName(vec![]),
1384            item: vec![DefPathSegment::Function(SymbolName("f".into()))],
1385        };
1386        let empty_module_identity = CallableIdentity::Imported(empty_module_path);
1387        assert_eq!(empty_module_identity.display_name(), None);
1388    }
1389
1390    #[test]
1391    fn qualified_name_display_name_rejects_empty_segments() {
1392        let malformed = QualifiedName(vec![
1393            SymbolName("pkg".into()),
1394            SymbolName("".into()),
1395            SymbolName("remote".into()),
1396        ]);
1397        assert_eq!(malformed.display_name(), None);
1398    }
1399
1400    #[test]
1401    fn callable_identity_display_name_rejects_empty_name_fields() {
1402        assert_eq!(
1403            CallableIdentity::Builtin(BuiltinId(String::new())).display_name(),
1404            None
1405        );
1406        assert_eq!(
1407            CallableIdentity::Method(MethodId(String::new())).display_name(),
1408            None
1409        );
1410        assert_eq!(
1411            CallableIdentity::DynamicName(SymbolName(String::new())).display_name(),
1412            None
1413        );
1414    }
1415
1416    #[test]
1417    fn async_facts_distinguish_lazy_futures_from_spawned_tasks() {
1418        let future = AsyncValueFact::Future(FutureFact {
1419            output: Box::new(TypeFact::Unknown),
1420            state: FutureStateFact::Lazy,
1421        });
1422        let task = AsyncValueFact::TaskHandle(TaskHandleFact {
1423            output: Box::new(TypeFact::Unknown),
1424            spawn_safety: SpawnSafetyFact::SpawnSafe,
1425        });
1426
1427        assert!(matches!(
1428            future,
1429            AsyncValueFact::Future(FutureFact {
1430                state: FutureStateFact::Lazy,
1431                ..
1432            })
1433        ));
1434        assert!(matches!(
1435            task,
1436            AsyncValueFact::TaskHandle(TaskHandleFact {
1437                spawn_safety: SpawnSafetyFact::SpawnSafe,
1438                ..
1439            })
1440        ));
1441    }
1442
1443    #[test]
1444    fn callable_name_fallback_policies_require_well_formed_external_names() {
1445        let dynamic = CallableIdentity::DynamicName(SymbolName("sqrt".into()));
1446        let imported = CallableIdentity::Imported(DefPath {
1447            package: PackageName("Point".into()),
1448            module: QualifiedName(vec![
1449                SymbolName("Point".into()),
1450                SymbolName("origin".into()),
1451            ]),
1452            item: vec![DefPathSegment::Function(SymbolName("origin".into()))],
1453        });
1454        let imported_missing_item = CallableIdentity::Imported(DefPath {
1455            package: PackageName("Point".into()),
1456            module: QualifiedName(vec![
1457                SymbolName("Point".into()),
1458                SymbolName("origin".into()),
1459            ]),
1460            item: vec![],
1461        });
1462        let imported_empty_item_name = CallableIdentity::Imported(DefPath {
1463            package: PackageName("Point".into()),
1464            module: QualifiedName(vec![
1465                SymbolName("Point".into()),
1466                SymbolName("origin".into()),
1467            ]),
1468            item: vec![DefPathSegment::Function(SymbolName("".into()))],
1469        });
1470        let imported_mismatched_item = CallableIdentity::Imported(DefPath {
1471            package: PackageName("Point".into()),
1472            module: QualifiedName(vec![
1473                SymbolName("Point".into()),
1474                SymbolName("origin".into()),
1475            ]),
1476            item: vec![DefPathSegment::Function(SymbolName("different".into()))],
1477        });
1478        let method = CallableIdentity::Method(MethodId("deal".into()));
1479        let whitespace_dynamic = CallableIdentity::DynamicName(SymbolName("   ".into()));
1480        let whitespace_method = CallableIdentity::Method(MethodId("   ".into()));
1481        let single_external =
1482            CallableIdentity::ExternalName(QualifiedName(vec![SymbolName("sqrt".into())]));
1483        let whitespace_external = CallableIdentity::ExternalName(QualifiedName(vec![
1484            SymbolName("OverIdx".into()),
1485            SymbolName("   ".into()),
1486        ]));
1487        let qualified_external = CallableIdentity::ExternalName(QualifiedName(vec![
1488            SymbolName("OverIdx".into()),
1489            SymbolName("plus".into()),
1490        ]));
1491        let malformed_external = CallableIdentity::ExternalName(QualifiedName(vec![
1492            SymbolName("OverIdx".into()),
1493            SymbolName("".into()),
1494            SymbolName("plus".into()),
1495        ]));
1496
1497        assert!(CallableFallbackPolicy::RuntimeNameResolution
1498            .allows_semantic_name_resolution_for(&dynamic));
1499        assert!(CallableFallbackPolicy::RuntimeNameResolution
1500            .allows_semantic_name_resolution_for(&imported));
1501        assert!(!CallableFallbackPolicy::RuntimeNameResolution
1502            .allows_semantic_name_resolution_for(&imported_missing_item));
1503        assert!(!CallableFallbackPolicy::RuntimeNameResolution
1504            .allows_semantic_name_resolution_for(&imported_empty_item_name));
1505        assert!(!CallableFallbackPolicy::RuntimeNameResolution
1506            .allows_semantic_name_resolution_for(&imported_mismatched_item));
1507        assert!(CallableFallbackPolicy::RuntimeNameResolution
1508            .allows_semantic_name_resolution_for(&method));
1509        assert!(!CallableFallbackPolicy::RuntimeNameResolution
1510            .allows_semantic_name_resolution_for(&whitespace_dynamic));
1511        assert!(!CallableFallbackPolicy::RuntimeNameResolution
1512            .allows_semantic_name_resolution_for(&whitespace_method));
1513        assert!(
1514            !CallableFallbackPolicy::ExternalBoundary.allows_semantic_name_resolution_for(&dynamic)
1515        );
1516        assert!(!CallableFallbackPolicy::ExternalBoundary
1517            .allows_semantic_name_resolution_for(&imported));
1518        assert!(
1519            !CallableFallbackPolicy::ExternalBoundary.allows_semantic_name_resolution_for(&method)
1520        );
1521        assert!(!CallableFallbackPolicy::ExternalBoundary
1522            .allows_semantic_name_resolution_for(&single_external));
1523        assert!(CallableFallbackPolicy::ExternalBoundary
1524            .allows_semantic_name_resolution_for(&qualified_external));
1525        assert!(!CallableFallbackPolicy::ExternalBoundary
1526            .allows_semantic_name_resolution_for(&whitespace_external));
1527        assert!(!CallableFallbackPolicy::ExternalBoundary
1528            .allows_semantic_name_resolution_for(&malformed_external));
1529
1530        assert!(CallableFallbackPolicy::RuntimeNameResolution.allows_vm_name_fallback_for(&dynamic));
1531        assert!(!CallableFallbackPolicy::RuntimeNameResolution
1532            .allows_vm_name_fallback_for(&whitespace_dynamic));
1533        assert!(
1534            CallableFallbackPolicy::RuntimeNameResolution.allows_vm_name_fallback_for(&imported)
1535        );
1536        assert!(!CallableFallbackPolicy::RuntimeNameResolution
1537            .allows_vm_name_fallback_for(&imported_missing_item));
1538        assert!(!CallableFallbackPolicy::RuntimeNameResolution
1539            .allows_vm_name_fallback_for(&imported_empty_item_name));
1540        assert!(!CallableFallbackPolicy::RuntimeNameResolution
1541            .allows_vm_name_fallback_for(&imported_mismatched_item));
1542        assert!(!CallableFallbackPolicy::RuntimeNameResolution.allows_vm_name_fallback_for(&method));
1543        assert!(!CallableFallbackPolicy::ExternalBoundary.allows_vm_name_fallback_for(&dynamic));
1544        assert!(
1545            !CallableFallbackPolicy::ExternalBoundary.allows_vm_name_fallback_for(&single_external)
1546        );
1547        assert!(CallableFallbackPolicy::ExternalBoundary
1548            .allows_vm_name_fallback_for(&qualified_external));
1549        assert!(!CallableFallbackPolicy::ExternalBoundary
1550            .allows_vm_name_fallback_for(&whitespace_external));
1551        assert!(!CallableFallbackPolicy::ExternalBoundary
1552            .allows_vm_name_fallback_for(&malformed_external));
1553
1554        assert_eq!(
1555            CallableFallbackPolicy::RuntimeNameResolution.vm_fallback_name_for(&dynamic),
1556            Some("sqrt".into())
1557        );
1558        assert_eq!(
1559            CallableFallbackPolicy::ExternalBoundary.vm_fallback_name_for(&dynamic),
1560            None
1561        );
1562        assert_eq!(
1563            CallableFallbackPolicy::RuntimeNameResolution.vm_fallback_name_for(&whitespace_dynamic),
1564            None
1565        );
1566        assert_eq!(
1567            CallableFallbackPolicy::RuntimeNameResolution.vm_fallback_name_for(&imported),
1568            Some("Point.origin".into())
1569        );
1570        assert_eq!(
1571            CallableFallbackPolicy::RuntimeNameResolution
1572                .vm_fallback_name_for(&imported_missing_item),
1573            None
1574        );
1575        assert_eq!(
1576            CallableFallbackPolicy::RuntimeNameResolution
1577                .vm_fallback_name_for(&imported_empty_item_name),
1578            None
1579        );
1580        assert_eq!(
1581            CallableFallbackPolicy::RuntimeNameResolution
1582                .vm_fallback_name_for(&imported_mismatched_item),
1583            None
1584        );
1585        assert_eq!(
1586            CallableFallbackPolicy::RuntimeNameResolution
1587                .resolution_name_for(&imported_missing_item),
1588            None
1589        );
1590        assert_eq!(
1591            CallableFallbackPolicy::RuntimeNameResolution
1592                .resolution_name_for(&imported_empty_item_name),
1593            None
1594        );
1595        assert_eq!(
1596            CallableFallbackPolicy::RuntimeNameResolution
1597                .resolution_name_for(&imported_mismatched_item),
1598            None
1599        );
1600        assert_eq!(
1601            CallableFallbackPolicy::RuntimeNameResolution.vm_fallback_name_for(&method),
1602            None
1603        );
1604        assert_eq!(
1605            CallableFallbackPolicy::RuntimeNameResolution.resolution_name_for(&method),
1606            Some("deal".into())
1607        );
1608        assert_eq!(
1609            CallableFallbackPolicy::RuntimeNameResolution.resolution_name_for(&whitespace_method),
1610            None
1611        );
1612        assert_eq!(
1613            CallableFallbackPolicy::ExternalBoundary.vm_fallback_name_for(&single_external),
1614            None
1615        );
1616        assert_eq!(
1617            CallableFallbackPolicy::ExternalBoundary.resolution_name_for(&single_external),
1618            None
1619        );
1620        assert_eq!(
1621            CallableFallbackPolicy::ExternalBoundary.vm_fallback_name_for(&qualified_external),
1622            Some("OverIdx.plus".into())
1623        );
1624        assert_eq!(
1625            CallableFallbackPolicy::ExternalBoundary.resolution_name_for(&qualified_external),
1626            Some("OverIdx.plus".into())
1627        );
1628        assert_eq!(
1629            CallableFallbackPolicy::ExternalBoundary.vm_fallback_name_for(&whitespace_external),
1630            None
1631        );
1632        assert_eq!(
1633            CallableFallbackPolicy::ExternalBoundary.resolution_name_for(&whitespace_external),
1634            None
1635        );
1636        assert_eq!(
1637            CallableFallbackPolicy::ExternalBoundary.vm_fallback_name_for(&malformed_external),
1638            None
1639        );
1640    }
1641}