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    ExternalFunction {
363        function: FunctionId,
364        display_name: String,
365    },
366    Builtin(BuiltinId),
367    Imported(DefPath),
368    SuperConstructor {
369        current_class: SymbolName,
370        super_class: QualifiedName,
371    },
372    SuperMethod {
373        current_class: SymbolName,
374        super_class: QualifiedName,
375        method: SymbolName,
376    },
377    DynamicExpr(Box<HirExpr>),
378    Unresolved(QualifiedName),
379}
380
381impl HirCallableRef {
382    pub fn is_feval_builtin_like(&self) -> bool {
383        match self {
384            HirCallableRef::Builtin(id) => id.0 == FEVAL_BUILTIN_NAME,
385            HirCallableRef::Unresolved(name) if name.0.len() == 1 => {
386                name.0[0].0 == FEVAL_BUILTIN_NAME
387            }
388            _ => false,
389        }
390    }
391
392    pub fn identity(&self) -> Option<CallableIdentity> {
393        match self {
394            HirCallableRef::Function(function) => Some(CallableIdentity::BoundFunction(*function)),
395            HirCallableRef::ExternalFunction {
396                function,
397                display_name,
398            } => Some(CallableIdentity::ExternalFunction {
399                function: *function,
400                display_name: display_name.clone(),
401            }),
402            HirCallableRef::Builtin(builtin) => Some(CallableIdentity::Builtin(builtin.clone())),
403            HirCallableRef::Imported(path) => Some(CallableIdentity::Imported(path.clone())),
404            HirCallableRef::SuperConstructor { .. } | HirCallableRef::SuperMethod { .. } => None,
405            HirCallableRef::DynamicExpr(_) => None,
406            HirCallableRef::Unresolved(name) => {
407                if name.0.len() == 1 {
408                    Some(CallableIdentity::DynamicName(name.0[0].clone()))
409                } else {
410                    Some(CallableIdentity::ExternalName(name.clone()))
411                }
412            }
413        }
414    }
415}
416
417#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
418pub enum CallableIdentity {
419    BoundFunction(FunctionId),
420    ExternalFunction {
421        function: FunctionId,
422        display_name: String,
423    },
424    Builtin(BuiltinId),
425    Imported(DefPath),
426    Method(MethodId),
427    AnonymousFunction(FunctionId),
428    DynamicName(SymbolName),
429    ExternalName(QualifiedName),
430}
431
432impl CallableIdentity {
433    pub fn display_name(&self) -> Option<String> {
434        match self {
435            CallableIdentity::BoundFunction(_) | CallableIdentity::AnonymousFunction(_) => None,
436            CallableIdentity::ExternalFunction { display_name, .. } => {
437                (!display_name.is_empty()).then_some(display_name.clone())
438            }
439            CallableIdentity::Builtin(id) => (!id.0.is_empty()).then_some(id.0.clone()),
440            CallableIdentity::Imported(path) => path.module.display_name(),
441            CallableIdentity::Method(id) => (!id.0.is_empty()).then_some(id.0.clone()),
442            CallableIdentity::DynamicName(name) => (!name.0.is_empty()).then_some(name.0.clone()),
443            CallableIdentity::ExternalName(name) => name.display_name(),
444        }
445    }
446}
447
448#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, Serialize, Deserialize)]
449pub enum CallableFallbackPolicy {
450    None,
451    RuntimeNameResolution,
452    ObjectDispatch,
453    ExternalBoundary,
454}
455
456impl CallableFallbackPolicy {
457    fn is_well_formed_external_name(name: &QualifiedName) -> bool {
458        name.0.len() > 1 && name.0.iter().all(|segment| !segment.0.trim().is_empty())
459    }
460
461    fn is_well_formed_imported_path(path: &DefPath) -> bool {
462        let Some(module_name) = path.module.display_name() else {
463            return false;
464        };
465        let Some(last_item) = path.item.last() else {
466            return false;
467        };
468        let item_name = last_item.display_name();
469        if item_name.trim().is_empty() {
470            return false;
471        }
472        let Some(last_module_segment) = path.module.0.last() else {
473            return false;
474        };
475        if last_module_segment.0.trim().is_empty() {
476            return false;
477        }
478        // Imported callable identities must keep module leaf and item leaf aligned.
479        // This prevents silently routing a mismatched DefPath through name-shaped fallback.
480        let _ = module_name;
481        last_module_segment.0 == item_name
482    }
483
484    pub fn allows_runtime_name_resolution(self) -> bool {
485        matches!(self, CallableFallbackPolicy::RuntimeNameResolution)
486    }
487
488    pub fn allows_semantic_name_resolution_for(self, identity: &CallableIdentity) -> bool {
489        match identity {
490            CallableIdentity::DynamicName(SymbolName(name))
491            | CallableIdentity::Method(MethodId(name)) => {
492                self.allows_runtime_name_resolution() && !name.trim().is_empty()
493            }
494            CallableIdentity::Imported(path) => {
495                self.allows_runtime_name_resolution() && Self::is_well_formed_imported_path(path)
496            }
497            CallableIdentity::ExternalName(name) => {
498                matches!(self, CallableFallbackPolicy::ExternalBoundary)
499                    && Self::is_well_formed_external_name(name)
500            }
501            _ => false,
502        }
503    }
504
505    pub fn allows_vm_name_fallback_for(self, identity: &CallableIdentity) -> bool {
506        match identity {
507            CallableIdentity::DynamicName(SymbolName(name)) => {
508                self.allows_runtime_name_resolution() && !name.trim().is_empty()
509            }
510            CallableIdentity::Imported(path) => {
511                self.allows_runtime_name_resolution() && Self::is_well_formed_imported_path(path)
512            }
513            CallableIdentity::ExternalName(name) => {
514                matches!(self, CallableFallbackPolicy::ExternalBoundary)
515                    && Self::is_well_formed_external_name(name)
516            }
517            _ => false,
518        }
519    }
520
521    pub fn resolution_name_for(self, identity: &CallableIdentity) -> Option<String> {
522        if !self.allows_semantic_name_resolution_for(identity) {
523            return None;
524        }
525
526        match identity {
527            CallableIdentity::DynamicName(SymbolName(name))
528            | CallableIdentity::Method(MethodId(name)) => {
529                let trimmed = name.trim();
530                (!trimmed.is_empty()).then_some(trimmed.to_string())
531            }
532            CallableIdentity::Imported(path) => path.module.display_name(),
533            CallableIdentity::ExternalName(name) if Self::is_well_formed_external_name(name) => {
534                Some(
535                    name.0
536                        .iter()
537                        .map(|segment| segment.0.as_str())
538                        .collect::<Vec<_>>()
539                        .join("."),
540                )
541            }
542            _ => None,
543        }
544    }
545
546    pub fn vm_fallback_name_for(self, identity: &CallableIdentity) -> Option<String> {
547        if !self.allows_vm_name_fallback_for(identity) {
548            return None;
549        }
550
551        match identity {
552            CallableIdentity::DynamicName(SymbolName(name)) => {
553                let trimmed = name.trim();
554                (!trimmed.is_empty()).then_some(trimmed.to_string())
555            }
556            CallableIdentity::Imported(path) => path.module.display_name(),
557            CallableIdentity::ExternalName(name) if Self::is_well_formed_external_name(name) => {
558                Some(
559                    name.0
560                        .iter()
561                        .map(|segment| segment.0.as_str())
562                        .collect::<Vec<_>>()
563                        .join("."),
564                )
565            }
566            _ => None,
567        }
568    }
569
570    pub fn supports_vm_static_call(self) -> bool {
571        matches!(
572            self,
573            CallableFallbackPolicy::RuntimeNameResolution
574                | CallableFallbackPolicy::ExternalBoundary
575        )
576    }
577
578    pub fn supports_vm_method_or_member_call(self) -> bool {
579        matches!(
580            self,
581            CallableFallbackPolicy::RuntimeNameResolution | CallableFallbackPolicy::ObjectDispatch
582        )
583    }
584
585    pub fn post_object_dispatch(self) -> Self {
586        match self {
587            CallableFallbackPolicy::ObjectDispatch => CallableFallbackPolicy::RuntimeNameResolution,
588            other => other,
589        }
590    }
591}
592
593pub const FEVAL_BUILTIN_NAME: &str = "feval";
594pub const EVAL_BUILTIN_NAME: &str = "eval";
595pub const EVALIN_BUILTIN_NAME: &str = "evalin";
596pub const ASSIGNIN_BUILTIN_NAME: &str = "assignin";
597pub const RUN_BUILTIN_NAME: &str = "run";
598pub const NARGIN_BUILTIN_NAME: &str = "nargin";
599pub const NARGOUT_BUILTIN_NAME: &str = "nargout";
600pub const NARGINCHK_BUILTIN_NAME: &str = "narginchk";
601pub const NARGOUTCHK_BUILTIN_NAME: &str = "nargoutchk";
602pub const AWAIT_EXTENSION_NAME: &str = "await";
603pub const SPAWN_EXTENSION_NAME: &str = "spawn";
604pub const TEST_CLASS_REGISTRATION_BUILTIN_NAME: &str = "__register_test_classes";
605pub const DISCARD_OUTPUT_NAME: &str = "~";
606
607#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
608pub enum CallSyntax {
609    Plain,
610    Method,
611    DottedInvoke,
612    Command,
613}
614
615#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
616pub struct HirCommandCall {
617    pub command: HirCallableRef,
618    pub args: Vec<CommandArgument>,
619}
620
621#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
622pub enum CommandArgument {
623    Word(SymbolName),
624    StringLiteral(StringLiteral),
625}
626
627#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
628pub struct HirImport {
629    pub path: QualifiedName,
630    pub wildcard: bool,
631    pub span: Span,
632}
633
634/// Semantic class metadata owned by the assembly.
635///
636/// Methods reference `HirFunction` entries by `FunctionId`; class definitions are
637/// not durable statement variants in the semantic model.
638#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
639pub struct HirClass {
640    pub id: ClassId,
641    pub module: ModuleId,
642    pub name: QualifiedName,
643    pub super_class: Option<ClassId>,
644    pub kind: ClassKind,
645    pub is_sealed: bool,
646    pub is_abstract: bool,
647    pub properties: Vec<ClassProperty>,
648    pub methods: Vec<ClassMethod>,
649    pub events: Vec<ClassEvent>,
650    pub enumerations: Vec<ClassEnumeration>,
651    pub arguments: Vec<ClassArgumentBlock>,
652    pub span: Span,
653}
654
655#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
656pub enum ClassKind {
657    Value,
658    Handle,
659}
660
661#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
662pub struct ClassProperty {
663    pub name: MemberName,
664    pub attributes: PropertyAttributes,
665    pub default: Option<HirExpr>,
666    pub span: Span,
667}
668
669#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
670pub struct ClassMethod {
671    pub function: FunctionId,
672    pub name: MethodName,
673    pub is_static: bool,
674    pub attributes: MethodAttributes,
675    pub span: Span,
676}
677
678#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
679pub struct ClassEvent {
680    pub name: SymbolName,
681    pub span: Span,
682}
683
684#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
685pub struct ClassEnumeration {
686    pub name: SymbolName,
687    pub span: Span,
688}
689
690#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
691pub struct ClassArgumentBlock {
692    pub span: Span,
693}
694
695#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Default)]
696pub struct PropertyAttributes {
697    pub is_static: bool,
698    pub is_constant: bool,
699    pub is_dependent: bool,
700    pub is_transient: bool,
701    pub is_hidden: bool,
702    pub access: MemberAccess,
703    pub get_access: MemberAccess,
704    pub set_access: MemberAccess,
705}
706
707#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Default)]
708pub struct MethodAttributes {
709    pub access: MemberAccess,
710    pub is_hidden: bool,
711    pub is_abstract: bool,
712    pub is_sealed: bool,
713}
714
715#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Default)]
716pub enum MemberAccess {
717    #[default]
718    Public,
719    Private,
720    Protected,
721}
722
723#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
724pub struct OutputTargetList {
725    pub requested_outputs: RequestedOutputCount,
726    pub targets: Vec<OutputTarget>,
727}
728
729#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
730pub enum OutputTarget {
731    Place(HirPlace),
732    Discard,
733}
734
735#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
736pub enum RequestedOutputCount {
737    Zero,
738    One,
739    Exactly(usize),
740    CurrentFunctionNargout,
741}
742
743impl RequestedOutputCount {
744    pub fn fixed_count(&self) -> usize {
745        match self {
746            RequestedOutputCount::Zero => 0,
747            RequestedOutputCount::One => 1,
748            RequestedOutputCount::Exactly(count) => *count,
749            RequestedOutputCount::CurrentFunctionNargout => 1,
750        }
751    }
752}
753
754#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
755pub enum IndexKind {
756    Paren,
757    Brace,
758}
759
760#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
761pub enum IndexComponent {
762    Colon,
763    End { dim: Option<usize>, offset: isize },
764    Expr(HirExpr),
765    Logical(HirExpr),
766}
767
768#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
769pub struct IndexingSemantics {
770    pub kind: IndexKind,
771    pub components: Vec<IndexComponent>,
772    pub result_context: IndexResultContext,
773}
774
775#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
776pub enum IndexResultContext {
777    ReadSingle,
778    ReadCommaList,
779    AssignmentTarget,
780    DeletionTarget,
781    FunctionArgumentExpansion,
782}
783
784#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
785pub enum FunctionHandleTarget {
786    Function(FunctionId),
787    ExternalFunction {
788        function: FunctionId,
789        display_name: String,
790    },
791    Builtin(BuiltinId),
792    Anonymous(FunctionId),
793    DefPath(DefPath),
794    DynamicName(SymbolName),
795}
796
797impl FunctionHandleTarget {
798    pub fn identity(&self) -> CallableIdentity {
799        match self {
800            FunctionHandleTarget::Function(function) => CallableIdentity::BoundFunction(*function),
801            FunctionHandleTarget::ExternalFunction {
802                function,
803                display_name,
804            } => CallableIdentity::ExternalFunction {
805                function: *function,
806                display_name: display_name.clone(),
807            },
808            FunctionHandleTarget::Builtin(builtin) => CallableIdentity::Builtin(builtin.clone()),
809            FunctionHandleTarget::Anonymous(function) => {
810                CallableIdentity::AnonymousFunction(*function)
811            }
812            FunctionHandleTarget::DefPath(path) => CallableIdentity::Imported(path.clone()),
813            FunctionHandleTarget::DynamicName(name) => CallableIdentity::DynamicName(name.clone()),
814        }
815    }
816}
817
818#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
819pub enum WorkspaceEffect {
820    None,
821    ReadsWorkspace,
822    CreatesBinding,
823    ClearsBinding,
824    ClearsFunctionCache,
825    MutatesGlobal,
826    MutatesPersistent,
827    LoadsExternalBindings,
828    DynamicEval,
829}
830
831#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
832pub enum EnvironmentEffect {
833    PathMutation,
834    WorkingDirectoryMutation,
835    FunctionCacheInvalidation,
836    DynamicLookupInvalidation,
837}
838
839#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
840pub enum EmptyArrayRole {
841    EmptyValue,
842    ConcatenationIdentity,
843    DeletionMarker,
844}
845
846#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
847pub enum ExpansionSemantics {
848    ExactShape,
849    ScalarExpansion,
850    ImplicitExpansion,
851}
852
853#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
854pub enum OperatorKind {
855    UnaryPlus,
856    UnaryMinus,
857    Not,
858    Add,
859    Subtract,
860    MatrixMultiply,
861    ElementwiseMultiply,
862    MatrixPower,
863    ElementwisePower,
864    Mldivide,
865    Mrdivide,
866    ElementwiseDivide,
867    ElementwiseLeftDivide,
868    Equal,
869    NotEqual,
870    Less,
871    LessEqual,
872    Greater,
873    GreaterEqual,
874    ShortCircuitAnd,
875    ShortCircuitOr,
876    ElementwiseAnd,
877    ElementwiseOr,
878    Transpose,
879    ConjugateTranspose,
880}
881
882#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
883pub enum NumericClass {
884    Double,
885    Single,
886    Int8,
887    UInt8,
888    Int16,
889    UInt16,
890    Int32,
891    UInt32,
892    Int64,
893    UInt64,
894}
895
896#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
897pub enum ValueFlowFact {
898    NoValue,
899    Single(TypeFact),
900    CommaList(Vec<TypeFact>),
901    UnknownList,
902}
903
904#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
905pub enum PlaceMutationKind {
906    BindOrAssign,
907    IndexedAssign,
908    CellAssign,
909    MemberAssign,
910    Delete,
911}
912
913#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
914pub struct PlaceMutation {
915    pub place: HirPlace,
916    pub kind: PlaceMutationKind,
917    pub creation_policy: AssignmentCreationPolicy,
918    pub shape_policy: AssignmentShapePolicy,
919}
920
921#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
922pub enum AssignmentCreationPolicy {
923    ExistingOnly,
924    CreateBinding,
925    CreateArrayByIndex,
926    CreateStructFieldPath,
927    Overloaded,
928}
929
930#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
931pub enum AssignmentShapePolicy {
932    Exact,
933    ScalarExpansion,
934    MatlabCompatible,
935    Overloaded,
936}
937
938#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
939pub enum ReferenceKind {
940    Binding(BindingId),
941    Function(FunctionId),
942    Builtin(BuiltinId),
943    Class(ClassId),
944    Package(QualifiedName),
945    Imported(DefPath),
946    RuntimeClass(QualifiedName),
947    Dynamic,
948    Unresolved,
949}
950
951#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
952pub enum CallKind {
953    DirectFunction(FunctionId),
954    Builtin(BuiltinId),
955    Constructor(ClassId),
956    StaticMethod {
957        class: ClassId,
958        method: MethodId,
959    },
960    InstanceMethod {
961        receiver: Box<HirExpr>,
962        method: MethodId,
963    },
964    PackageFunction(DefPath),
965    FunctionHandle,
966    Dynamic,
967    OverloadedOperator,
968    OverloadedIndexing,
969}
970
971#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Default)]
972pub struct HirIndex {
973    pub bindings: Vec<BindingResolution>,
974    pub functions: Vec<FunctionResolution>,
975    pub classes: Vec<ClassResolution>,
976    pub imports: Vec<ImportResolution>,
977    pub references: Vec<ReferenceResolution>,
978    pub calls: Vec<CallResolution>,
979    pub mutations: Vec<PlaceMutation>,
980}
981
982#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
983pub struct BindingResolution {
984    pub name: BindingName,
985    pub binding: BindingId,
986    pub owner: BindingOwner,
987    pub span: Span,
988}
989
990#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
991pub struct FunctionResolution {
992    pub name: FunctionName,
993    pub function: FunctionId,
994    pub parent: Option<FunctionId>,
995    pub span: Span,
996}
997
998#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
999pub struct ClassResolution {
1000    pub name: QualifiedName,
1001    pub class: ClassId,
1002    pub span: Span,
1003}
1004
1005#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1006pub struct ImportResolution {
1007    pub import: HirImport,
1008}
1009
1010#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1011pub struct ReferenceResolution {
1012    pub name: SymbolName,
1013    pub kind: ReferenceKind,
1014    pub span: Span,
1015}
1016
1017#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1018pub struct CallResolution {
1019    pub name: QualifiedName,
1020    pub callee: HirCallableRef,
1021    pub kind: CallKind,
1022    pub requested_outputs: RequestedOutputCount,
1023    pub span: Span,
1024}
1025
1026#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1027pub enum SpawnSafetyFact {
1028    SpawnSafe,
1029    RequiresIsolation,
1030    NotSpawnSafe { reason: SpawnSafetyReason },
1031}
1032
1033#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1034pub enum SpawnSafetyReason {
1035    MutableLexicalCapture,
1036    NonSendableRuntimeHandle,
1037    UnsynchronizedSharedMutation,
1038    UnknownDynamicCapture,
1039}
1040
1041#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1042pub enum AsyncValueFact {
1043    Future(FutureFact),
1044    TaskHandle(TaskHandleFact),
1045}
1046
1047#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1048pub struct FutureFact {
1049    pub output: Box<TypeFact>,
1050    pub state: FutureStateFact,
1051}
1052
1053#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1054pub enum FutureStateFact {
1055    Lazy,
1056    Awaited,
1057    Unknown,
1058}
1059
1060#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1061pub struct TaskHandleFact {
1062    pub output: Box<TypeFact>,
1063    pub spawn_safety: SpawnSafetyFact,
1064}
1065
1066#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1067pub enum TypeFact {
1068    Never,
1069    Unknown,
1070    Logical,
1071    Numeric {
1072        class: NumericClass,
1073        domain: NumericDomain,
1074    },
1075    Tensor(TensorTypeFact),
1076    String,
1077    CharArray,
1078    Cell,
1079    Struct,
1080    ClassInstance(ClassId),
1081    ClassRef(ClassId),
1082    Function(FunctionId),
1083}
1084
1085#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1086pub struct TensorTypeFact {
1087    pub element: TensorElementDomainFact,
1088    pub shape: ShapeFact,
1089}
1090
1091#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1092pub enum TensorElementDomainFact {
1093    Unknown,
1094    Logical,
1095    Numeric {
1096        class: NumericClass,
1097        domain: NumericDomain,
1098    },
1099    Char,
1100    Object(ClassId),
1101}
1102
1103#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1104pub enum NumericDomain {
1105    Real,
1106    Complex,
1107}
1108
1109#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1110pub enum ShapeFact {
1111    Unreachable,
1112    Unknown,
1113    Scalar,
1114    Ranked { rank: usize },
1115    Shaped { dims: Vec<DimFact> },
1116}
1117
1118#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1119pub enum DimFact {
1120    Known(usize),
1121    Symbolic(DimSymbol),
1122    Unknown,
1123}
1124
1125#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
1126pub struct DimSymbol(pub String);
1127
1128/// Stable qualified semantic identity for a package/module/item path.
1129///
1130/// Unlike local `ModuleId`/`FunctionId`/`ClassId`/`BindingId` values, a `DefPath`
1131/// is intended to describe the same semantic item across compiler products when
1132/// the source/project identity is unchanged.
1133#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
1134pub struct DefPath {
1135    pub package: PackageName,
1136    pub module: QualifiedName,
1137    pub item: Vec<DefPathSegment>,
1138}
1139
1140impl DefPath {
1141    pub fn display_name(&self) -> Option<String> {
1142        self.item.last().map(DefPathSegment::display_name)
1143    }
1144}
1145
1146#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
1147pub enum DefPathSegment {
1148    Function(SymbolName),
1149}
1150
1151impl DefPathSegment {
1152    pub fn display_name(&self) -> String {
1153        match self {
1154            DefPathSegment::Function(name) => name.0.clone(),
1155        }
1156    }
1157}
1158
1159#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
1160pub struct QualifiedName(pub Vec<SymbolName>);
1161
1162impl QualifiedName {
1163    pub fn display_name(&self) -> Option<String> {
1164        if self.0.is_empty() || self.0.iter().any(|segment| segment.0.is_empty()) {
1165            None
1166        } else {
1167            Some(
1168                self.0
1169                    .iter()
1170                    .map(|segment| segment.0.as_str())
1171                    .collect::<Vec<_>>()
1172                    .join("."),
1173            )
1174        }
1175    }
1176}
1177
1178#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
1179pub struct SymbolName(pub String);
1180
1181#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
1182pub struct BindingName(pub String);
1183
1184#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
1185pub struct FunctionName(pub String);
1186
1187#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
1188pub struct EntrypointName(pub String);
1189
1190#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
1191pub struct MemberName(pub String);
1192
1193#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
1194pub struct MethodName(pub String);
1195
1196#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
1197pub struct PackageName(pub String);
1198
1199#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
1200pub struct BuiltinId(pub String);
1201
1202#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
1203pub struct MethodId(pub String);
1204
1205#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
1206pub struct StringLiteral(pub String);
1207
1208#[derive(Debug, Clone)]
1209pub struct LoweringResult {
1210    pub assembly: HirAssembly,
1211    pub hir_index: HirIndex,
1212}
1213
1214#[cfg(test)]
1215mod tests {
1216    use super::*;
1217
1218    fn span() -> Span {
1219        Span { start: 0, end: 0 }
1220    }
1221
1222    #[test]
1223    fn assembly_owns_core_items() {
1224        let module = ModuleId(0);
1225        let function = FunctionId(0);
1226        let binding = BindingId(0);
1227        let entrypoint = EntrypointId(0);
1228
1229        let assembly = HirAssembly {
1230            modules: vec![HirModule {
1231                id: module,
1232                name: QualifiedName(vec![SymbolName("demo".into())]),
1233                source_id: SourceId(0),
1234                source_unit: SourceUnitKind::ScriptFile,
1235                imports: vec![],
1236                top_level_functions: vec![],
1237                classes: vec![],
1238                synthetic_entry_function: Some(function),
1239            }],
1240            functions: vec![HirFunction {
1241                id: function,
1242                module,
1243                parent: None,
1244                enclosing_class: None,
1245                name: FunctionName("demo_entry".into()),
1246                kind: FunctionKind::SyntheticEntrypoint,
1247                params: vec![],
1248                outputs: vec![],
1249                abi: FunctionAbi {
1250                    fixed_inputs: vec![],
1251                    varargin: None,
1252                    fixed_outputs: vec![],
1253                    varargout: None,
1254                    implicit_nargin: None,
1255                    implicit_nargout: None,
1256                },
1257                locals: vec![binding],
1258                captures: vec![],
1259                argument_validations: vec![],
1260                modifiers: FunctionModifiers::default(),
1261                body: HirBlock { statements: vec![] },
1262                span: span(),
1263            }],
1264            classes: vec![],
1265            bindings: vec![HirBinding {
1266                id: binding,
1267                owner: BindingOwner::Function(function),
1268                name: BindingName("x".into()),
1269                role: BindingRole::Local,
1270                storage: BindingStorage::Lexical,
1271                workspace_visibility: WorkspaceVisibility::TopLevel,
1272                declared_span: span(),
1273            }],
1274            entrypoints: vec![HirEntrypoint {
1275                id: entrypoint,
1276                name: Some(EntrypointName("demo".into())),
1277                target: function,
1278                origin: EntrypointOrigin::SourcePath,
1279                policy: EntrypointPolicy {
1280                    workspace_export: WorkspaceExportPolicy::ExportTopLevelBindings,
1281                    top_level_await: false,
1282                },
1283            }],
1284        };
1285
1286        assert_eq!(assembly.modules[0].synthetic_entry_function, Some(function));
1287        assert_eq!(assembly.entrypoints[0].target, function);
1288        assert_eq!(assembly.functions[0].locals, vec![binding]);
1289        assert!(matches!(
1290            assembly.bindings[0].workspace_visibility,
1291            WorkspaceVisibility::TopLevel
1292        ));
1293    }
1294
1295    #[test]
1296    fn function_abi_can_reuse_shared_input_output_binding() {
1297        let binding = BindingId(0);
1298        let abi = FunctionAbi {
1299            fixed_inputs: vec![binding],
1300            varargin: None,
1301            fixed_outputs: vec![binding],
1302            varargout: None,
1303            implicit_nargin: Some(BindingId(1)),
1304            implicit_nargout: Some(BindingId(2)),
1305        };
1306
1307        assert_eq!(abi.fixed_inputs[0], abi.fixed_outputs[0]);
1308        assert_eq!(abi.implicit_nargin, Some(BindingId(1)));
1309        assert_eq!(abi.implicit_nargout, Some(BindingId(2)));
1310    }
1311
1312    #[test]
1313    fn captures_reference_original_binding_identity() {
1314        let parent = FunctionId(0);
1315        let child = FunctionId(1);
1316        let binding = BindingId(0);
1317
1318        let capture = CapturedBinding {
1319            binding,
1320            from_function: parent,
1321        };
1322
1323        let child_function = HirFunction {
1324            id: child,
1325            module: ModuleId(0),
1326            parent: Some(parent),
1327            enclosing_class: None,
1328            name: FunctionName("inner".into()),
1329            kind: FunctionKind::Named,
1330            params: vec![],
1331            outputs: vec![],
1332            abi: FunctionAbi {
1333                fixed_inputs: vec![],
1334                varargin: None,
1335                fixed_outputs: vec![],
1336                varargout: None,
1337                implicit_nargin: None,
1338                implicit_nargout: None,
1339            },
1340            locals: vec![],
1341            captures: vec![capture],
1342            argument_validations: vec![],
1343            modifiers: FunctionModifiers::default(),
1344            body: HirBlock { statements: vec![] },
1345            span: span(),
1346        };
1347
1348        assert_eq!(child_function.parent, Some(parent));
1349        assert_eq!(child_function.captures[0].binding, binding);
1350        assert_eq!(child_function.captures[0].from_function, parent);
1351    }
1352
1353    #[test]
1354    fn facts_capture_value_flow_and_mutation_context() {
1355        let binding = BindingId(0);
1356        let mutation = PlaceMutation {
1357            place: HirPlace::Binding(binding),
1358            kind: PlaceMutationKind::BindOrAssign,
1359            creation_policy: AssignmentCreationPolicy::CreateBinding,
1360            shape_policy: AssignmentShapePolicy::MatlabCompatible,
1361        };
1362        let value = ValueFlowFact::Single(TypeFact::Tensor(TensorTypeFact {
1363            element: TensorElementDomainFact::Numeric {
1364                class: NumericClass::Double,
1365                domain: NumericDomain::Real,
1366            },
1367            shape: ShapeFact::Shaped {
1368                dims: vec![DimFact::Known(1), DimFact::Symbolic(DimSymbol("n".into()))],
1369            },
1370        }));
1371
1372        assert!(matches!(mutation.place, HirPlace::Binding(id) if id == binding));
1373        assert!(matches!(
1374            value,
1375            ValueFlowFact::Single(TypeFact::Tensor(TensorTypeFact {
1376                shape: ShapeFact::Shaped { .. },
1377                ..
1378            }))
1379        ));
1380    }
1381
1382    #[test]
1383    fn def_path_is_distinct_from_local_table_ids() {
1384        let function_id = FunctionId(0);
1385        let path = DefPath {
1386            package: PackageName("pkg".into()),
1387            module: QualifiedName(vec![SymbolName("pkg".into()), SymbolName("demo".into())]),
1388            item: vec![DefPathSegment::Function(SymbolName("f".into()))],
1389        };
1390
1391        assert_eq!(function_id, FunctionId(0));
1392        assert_eq!(path.package.0, "pkg");
1393        assert!(matches!(path.item[0], DefPathSegment::Function(_)));
1394    }
1395
1396    #[test]
1397    fn imported_callable_identity_prefers_qualified_display_name() {
1398        let path = DefPath {
1399            package: PackageName("pkg".into()),
1400            module: QualifiedName(vec![
1401                SymbolName("pkg".into()),
1402                SymbolName("demo".into()),
1403                SymbolName("f".into()),
1404            ]),
1405            item: vec![DefPathSegment::Function(SymbolName("f".into()))],
1406        };
1407        let identity = CallableIdentity::Imported(path);
1408        assert_eq!(identity.display_name().as_deref(), Some("pkg.demo.f"));
1409
1410        let empty_module_path = DefPath {
1411            package: PackageName("pkg".into()),
1412            module: QualifiedName(vec![]),
1413            item: vec![DefPathSegment::Function(SymbolName("f".into()))],
1414        };
1415        let empty_module_identity = CallableIdentity::Imported(empty_module_path);
1416        assert_eq!(empty_module_identity.display_name(), None);
1417    }
1418
1419    #[test]
1420    fn qualified_name_display_name_rejects_empty_segments() {
1421        let malformed = QualifiedName(vec![
1422            SymbolName("pkg".into()),
1423            SymbolName("".into()),
1424            SymbolName("remote".into()),
1425        ]);
1426        assert_eq!(malformed.display_name(), None);
1427    }
1428
1429    #[test]
1430    fn callable_identity_display_name_rejects_empty_name_fields() {
1431        assert_eq!(
1432            CallableIdentity::Builtin(BuiltinId(String::new())).display_name(),
1433            None
1434        );
1435        assert_eq!(
1436            CallableIdentity::Method(MethodId(String::new())).display_name(),
1437            None
1438        );
1439        assert_eq!(
1440            CallableIdentity::DynamicName(SymbolName(String::new())).display_name(),
1441            None
1442        );
1443    }
1444
1445    #[test]
1446    fn async_facts_distinguish_lazy_futures_from_spawned_tasks() {
1447        let future = AsyncValueFact::Future(FutureFact {
1448            output: Box::new(TypeFact::Unknown),
1449            state: FutureStateFact::Lazy,
1450        });
1451        let task = AsyncValueFact::TaskHandle(TaskHandleFact {
1452            output: Box::new(TypeFact::Unknown),
1453            spawn_safety: SpawnSafetyFact::SpawnSafe,
1454        });
1455
1456        assert!(matches!(
1457            future,
1458            AsyncValueFact::Future(FutureFact {
1459                state: FutureStateFact::Lazy,
1460                ..
1461            })
1462        ));
1463        assert!(matches!(
1464            task,
1465            AsyncValueFact::TaskHandle(TaskHandleFact {
1466                spawn_safety: SpawnSafetyFact::SpawnSafe,
1467                ..
1468            })
1469        ));
1470    }
1471
1472    #[test]
1473    fn callable_name_fallback_policies_require_well_formed_external_names() {
1474        let dynamic = CallableIdentity::DynamicName(SymbolName("sqrt".into()));
1475        let imported = CallableIdentity::Imported(DefPath {
1476            package: PackageName("Point".into()),
1477            module: QualifiedName(vec![
1478                SymbolName("Point".into()),
1479                SymbolName("origin".into()),
1480            ]),
1481            item: vec![DefPathSegment::Function(SymbolName("origin".into()))],
1482        });
1483        let imported_missing_item = CallableIdentity::Imported(DefPath {
1484            package: PackageName("Point".into()),
1485            module: QualifiedName(vec![
1486                SymbolName("Point".into()),
1487                SymbolName("origin".into()),
1488            ]),
1489            item: vec![],
1490        });
1491        let imported_empty_item_name = CallableIdentity::Imported(DefPath {
1492            package: PackageName("Point".into()),
1493            module: QualifiedName(vec![
1494                SymbolName("Point".into()),
1495                SymbolName("origin".into()),
1496            ]),
1497            item: vec![DefPathSegment::Function(SymbolName("".into()))],
1498        });
1499        let imported_mismatched_item = CallableIdentity::Imported(DefPath {
1500            package: PackageName("Point".into()),
1501            module: QualifiedName(vec![
1502                SymbolName("Point".into()),
1503                SymbolName("origin".into()),
1504            ]),
1505            item: vec![DefPathSegment::Function(SymbolName("different".into()))],
1506        });
1507        let method = CallableIdentity::Method(MethodId("deal".into()));
1508        let whitespace_dynamic = CallableIdentity::DynamicName(SymbolName("   ".into()));
1509        let whitespace_method = CallableIdentity::Method(MethodId("   ".into()));
1510        let single_external =
1511            CallableIdentity::ExternalName(QualifiedName(vec![SymbolName("sqrt".into())]));
1512        let whitespace_external = CallableIdentity::ExternalName(QualifiedName(vec![
1513            SymbolName("OverIdx".into()),
1514            SymbolName("   ".into()),
1515        ]));
1516        let qualified_external = CallableIdentity::ExternalName(QualifiedName(vec![
1517            SymbolName("OverIdx".into()),
1518            SymbolName("plus".into()),
1519        ]));
1520        let malformed_external = CallableIdentity::ExternalName(QualifiedName(vec![
1521            SymbolName("OverIdx".into()),
1522            SymbolName("".into()),
1523            SymbolName("plus".into()),
1524        ]));
1525
1526        assert!(CallableFallbackPolicy::RuntimeNameResolution
1527            .allows_semantic_name_resolution_for(&dynamic));
1528        assert!(CallableFallbackPolicy::RuntimeNameResolution
1529            .allows_semantic_name_resolution_for(&imported));
1530        assert!(!CallableFallbackPolicy::RuntimeNameResolution
1531            .allows_semantic_name_resolution_for(&imported_missing_item));
1532        assert!(!CallableFallbackPolicy::RuntimeNameResolution
1533            .allows_semantic_name_resolution_for(&imported_empty_item_name));
1534        assert!(!CallableFallbackPolicy::RuntimeNameResolution
1535            .allows_semantic_name_resolution_for(&imported_mismatched_item));
1536        assert!(CallableFallbackPolicy::RuntimeNameResolution
1537            .allows_semantic_name_resolution_for(&method));
1538        assert!(!CallableFallbackPolicy::RuntimeNameResolution
1539            .allows_semantic_name_resolution_for(&whitespace_dynamic));
1540        assert!(!CallableFallbackPolicy::RuntimeNameResolution
1541            .allows_semantic_name_resolution_for(&whitespace_method));
1542        assert!(
1543            !CallableFallbackPolicy::ExternalBoundary.allows_semantic_name_resolution_for(&dynamic)
1544        );
1545        assert!(!CallableFallbackPolicy::ExternalBoundary
1546            .allows_semantic_name_resolution_for(&imported));
1547        assert!(
1548            !CallableFallbackPolicy::ExternalBoundary.allows_semantic_name_resolution_for(&method)
1549        );
1550        assert!(!CallableFallbackPolicy::ExternalBoundary
1551            .allows_semantic_name_resolution_for(&single_external));
1552        assert!(CallableFallbackPolicy::ExternalBoundary
1553            .allows_semantic_name_resolution_for(&qualified_external));
1554        assert!(!CallableFallbackPolicy::ExternalBoundary
1555            .allows_semantic_name_resolution_for(&whitespace_external));
1556        assert!(!CallableFallbackPolicy::ExternalBoundary
1557            .allows_semantic_name_resolution_for(&malformed_external));
1558
1559        assert!(CallableFallbackPolicy::RuntimeNameResolution.allows_vm_name_fallback_for(&dynamic));
1560        assert!(!CallableFallbackPolicy::RuntimeNameResolution
1561            .allows_vm_name_fallback_for(&whitespace_dynamic));
1562        assert!(
1563            CallableFallbackPolicy::RuntimeNameResolution.allows_vm_name_fallback_for(&imported)
1564        );
1565        assert!(!CallableFallbackPolicy::RuntimeNameResolution
1566            .allows_vm_name_fallback_for(&imported_missing_item));
1567        assert!(!CallableFallbackPolicy::RuntimeNameResolution
1568            .allows_vm_name_fallback_for(&imported_empty_item_name));
1569        assert!(!CallableFallbackPolicy::RuntimeNameResolution
1570            .allows_vm_name_fallback_for(&imported_mismatched_item));
1571        assert!(!CallableFallbackPolicy::RuntimeNameResolution.allows_vm_name_fallback_for(&method));
1572        assert!(!CallableFallbackPolicy::ExternalBoundary.allows_vm_name_fallback_for(&dynamic));
1573        assert!(
1574            !CallableFallbackPolicy::ExternalBoundary.allows_vm_name_fallback_for(&single_external)
1575        );
1576        assert!(CallableFallbackPolicy::ExternalBoundary
1577            .allows_vm_name_fallback_for(&qualified_external));
1578        assert!(!CallableFallbackPolicy::ExternalBoundary
1579            .allows_vm_name_fallback_for(&whitespace_external));
1580        assert!(!CallableFallbackPolicy::ExternalBoundary
1581            .allows_vm_name_fallback_for(&malformed_external));
1582
1583        assert_eq!(
1584            CallableFallbackPolicy::RuntimeNameResolution.vm_fallback_name_for(&dynamic),
1585            Some("sqrt".into())
1586        );
1587        assert_eq!(
1588            CallableFallbackPolicy::ExternalBoundary.vm_fallback_name_for(&dynamic),
1589            None
1590        );
1591        assert_eq!(
1592            CallableFallbackPolicy::RuntimeNameResolution.vm_fallback_name_for(&whitespace_dynamic),
1593            None
1594        );
1595        assert_eq!(
1596            CallableFallbackPolicy::RuntimeNameResolution.vm_fallback_name_for(&imported),
1597            Some("Point.origin".into())
1598        );
1599        assert_eq!(
1600            CallableFallbackPolicy::RuntimeNameResolution
1601                .vm_fallback_name_for(&imported_missing_item),
1602            None
1603        );
1604        assert_eq!(
1605            CallableFallbackPolicy::RuntimeNameResolution
1606                .vm_fallback_name_for(&imported_empty_item_name),
1607            None
1608        );
1609        assert_eq!(
1610            CallableFallbackPolicy::RuntimeNameResolution
1611                .vm_fallback_name_for(&imported_mismatched_item),
1612            None
1613        );
1614        assert_eq!(
1615            CallableFallbackPolicy::RuntimeNameResolution
1616                .resolution_name_for(&imported_missing_item),
1617            None
1618        );
1619        assert_eq!(
1620            CallableFallbackPolicy::RuntimeNameResolution
1621                .resolution_name_for(&imported_empty_item_name),
1622            None
1623        );
1624        assert_eq!(
1625            CallableFallbackPolicy::RuntimeNameResolution
1626                .resolution_name_for(&imported_mismatched_item),
1627            None
1628        );
1629        assert_eq!(
1630            CallableFallbackPolicy::RuntimeNameResolution.vm_fallback_name_for(&method),
1631            None
1632        );
1633        assert_eq!(
1634            CallableFallbackPolicy::RuntimeNameResolution.resolution_name_for(&method),
1635            Some("deal".into())
1636        );
1637        assert_eq!(
1638            CallableFallbackPolicy::RuntimeNameResolution.resolution_name_for(&whitespace_method),
1639            None
1640        );
1641        assert_eq!(
1642            CallableFallbackPolicy::ExternalBoundary.vm_fallback_name_for(&single_external),
1643            None
1644        );
1645        assert_eq!(
1646            CallableFallbackPolicy::ExternalBoundary.resolution_name_for(&single_external),
1647            None
1648        );
1649        assert_eq!(
1650            CallableFallbackPolicy::ExternalBoundary.vm_fallback_name_for(&qualified_external),
1651            Some("OverIdx.plus".into())
1652        );
1653        assert_eq!(
1654            CallableFallbackPolicy::ExternalBoundary.resolution_name_for(&qualified_external),
1655            Some("OverIdx.plus".into())
1656        );
1657        assert_eq!(
1658            CallableFallbackPolicy::ExternalBoundary.vm_fallback_name_for(&whitespace_external),
1659            None
1660        );
1661        assert_eq!(
1662            CallableFallbackPolicy::ExternalBoundary.resolution_name_for(&whitespace_external),
1663            None
1664        );
1665        assert_eq!(
1666            CallableFallbackPolicy::ExternalBoundary.vm_fallback_name_for(&malformed_external),
1667            None
1668        );
1669    }
1670}