Skip to main content

submilli_engine/
authority.rs

1//! Deterministic package authority call graphs.
2
3use std::collections::{BTreeMap, BTreeSet, VecDeque};
4use std::rc::Rc;
5
6use serde::{Deserialize, Serialize};
7
8use crate::compiler_error::{CompilerFailure, CompilerStage};
9use crate::typechecker::rules::body_walk::{self, Visitor};
10use crate::{
11    ClosureBody, DocCapability, ExportKind, ExprId, MangledName, PackageDeclaration, Param,
12    PostfixTarget, Sources, Span, StmtId, Type, TypeKind, TypedAst, TypedChainPart,
13    TypedClassAccessor, TypedExprKind, TypedStmtKind, TypedTypeDecl, Visibility,
14};
15
16const MAX_CALLABLES: usize = 1 << 16;
17const MAX_EDGES: usize = 1 << 18;
18const MAX_ROUTE_EFFECTS: usize = 1 << 18;
19const MAX_WITNESS_WORK: usize = 1 << 24;
20
21#[derive(Clone, Copy)]
22struct AnalysisLimits {
23    callables: usize,
24    edges: usize,
25    route_effects: usize,
26    witness_work: usize,
27}
28
29impl Default for AnalysisLimits {
30    fn default() -> Self {
31        Self {
32            callables: MAX_CALLABLES,
33            edges: MAX_EDGES,
34            route_effects: MAX_ROUTE_EFFECTS,
35            witness_work: MAX_WITNESS_WORK,
36        }
37    }
38}
39
40#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
41pub struct AuthorityMap {
42    pub callables: Vec<AuthorityCallable>,
43    pub edges: Vec<AuthorityEdge>,
44    pub routes: Vec<AuthorityRoute>,
45}
46
47#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
48pub struct AuthorityCallable {
49    pub id: String,
50    pub kind: AuthorityCallableKind,
51    pub name: String,
52    pub span: AuthoritySpan,
53    pub exposure: AuthorityExposure,
54    #[serde(default)]
55    pub checks: Vec<AuthorityCheck>,
56    pub direct_effects: Vec<AuthorityEffect>,
57    pub transitive_effects: Vec<AuthorityEffect>,
58}
59
60/// A syntactic check invocation, not evidence of successful authorization.
61#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
62pub struct AuthorityCheck {
63    pub span: AuthoritySpan,
64    pub capability: Option<String>,
65}
66
67#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
68#[serde(rename_all = "snake_case")]
69pub enum AuthorityCallableKind {
70    Function,
71    StaticMethod,
72    Method,
73    Constructor,
74    Getter,
75    Setter,
76    Closure,
77    FunctionValue,
78    ModuleInitializer,
79}
80
81#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
82#[serde(rename_all = "snake_case")]
83pub enum AuthorityExposure {
84    Public,
85    Private,
86    Initialization,
87}
88
89#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
90pub struct AuthoritySpan {
91    pub path: String,
92    pub start: AuthorityPosition,
93    pub end: AuthorityPosition,
94}
95
96#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
97pub struct AuthorityPosition {
98    pub byte: u32,
99    pub line: u32,
100    pub column: u32,
101}
102
103#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
104pub struct AuthorityEffect {
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub capability: Option<String>,
107    pub sink: AuthoritySpan,
108    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
109    pub known_bindings: BTreeMap<String, String>,
110    pub unresolved: bool,
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub reason: Option<String>,
113}
114
115#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
116pub struct AuthorityEdge {
117    pub caller: String,
118    #[serde(skip_serializing_if = "Option::is_none")]
119    pub target: Option<String>,
120    pub span: AuthoritySpan,
121    pub unresolved: bool,
122    #[serde(skip_serializing_if = "Option::is_none")]
123    pub reason: Option<String>,
124}
125
126#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
127pub struct AuthorityRoute {
128    pub callable: String,
129    pub effects: Vec<AuthorityRouteEffect>,
130}
131
132#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
133pub struct AuthorityRouteEffect {
134    pub effect: AuthorityEffect,
135    pub witness: Vec<AuthorityWitnessStep>,
136}
137
138#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
139pub struct AuthorityWitnessStep {
140    pub caller: String,
141    pub target: String,
142    pub span: AuthoritySpan,
143}
144
145struct Root {
146    statements: Vec<StmtId>,
147    expressions: Vec<ExprId>,
148}
149
150struct Node {
151    callable: AuthorityCallable,
152    root: Root,
153    raw_span: Span,
154}
155
156#[derive(Clone)]
157struct RawEdge {
158    caller: usize,
159    target: Option<usize>,
160    span: AuthoritySpan,
161    unresolved: bool,
162    reason: Option<String>,
163}
164
165struct Propagation {
166    adjacency: Vec<Vec<(usize, usize)>>,
167    component_of: Vec<usize>,
168    component_effects: Vec<BTreeSet<AuthorityEffect>>,
169}
170
171#[derive(Clone, Copy)]
172struct CapabilityCallee<'a> {
173    params: &'a [Param],
174    capabilities: &'a [DocCapability],
175}
176
177/// Build the authority graph for one already-typechecked package.
178pub(crate) fn analyse<'a>(
179    declaration: &'a PackageDeclaration,
180    ta: &'a TypedAst,
181    sources: &'a Sources,
182    dependencies: impl Iterator<Item = &'a PackageDeclaration>,
183) -> Result<AuthorityMap, CompilerFailure> {
184    let mut builder = Builder::new(
185        declaration,
186        ta,
187        sources,
188        dependencies,
189        AnalysisLimits::default(),
190    )?;
191    builder.scan()?;
192    builder.finish()
193}
194
195struct Builder<'a> {
196    declaration: &'a PackageDeclaration,
197    ta: &'a TypedAst,
198    sources: &'a Sources,
199    local_class_declarations: BTreeMap<MangledName, &'a crate::TypedClassDecl>,
200    nodes: Vec<Node>,
201    named: BTreeMap<MangledName, usize>,
202    methods: BTreeMap<(MangledName, String), usize>,
203    accessors: BTreeMap<(MangledName, String, bool), usize>,
204    classes: BTreeMap<MangledName, Option<MangledName>>,
205    closure_nodes: BTreeMap<ExprId, usize>,
206    closure_globals: BTreeMap<MangledName, usize>,
207    known_functions: BTreeSet<MangledName>,
208    known_methods: BTreeSet<(MangledName, String)>,
209    dynamically_dispatched_methods: BTreeSet<(MangledName, String)>,
210    capability_functions: BTreeMap<MangledName, CapabilityCallee<'a>>,
211    capability_methods: BTreeMap<(MangledName, String), CapabilityCallee<'a>>,
212    class_hierarchy: BTreeMap<MangledName, Option<MangledName>>,
213    external_classes: BTreeMap<MangledName, &'a crate::TypeSymbol>,
214    external_surfaces: BTreeMap<MangledName, Rc<ExternalClassSurface>>,
215    edges: Vec<RawEdge>,
216    exposure_edges: Vec<(usize, usize)>,
217    direct_effect_count: usize,
218    check_count: usize,
219    limits: AnalysisLimits,
220}
221
222impl<'a> Builder<'a> {
223    fn new(
224        declaration: &'a PackageDeclaration,
225        ta: &'a TypedAst,
226        sources: &'a Sources,
227        dependencies: impl Iterator<Item = &'a PackageDeclaration>,
228        limits: AnalysisLimits,
229    ) -> Result<Self, CompilerFailure> {
230        let local_class_declarations = ta
231            .types
232            .iter()
233            .filter_map(|declaration| match declaration {
234                TypedTypeDecl::Class(class) => Some((class.mangled_name.clone(), class)),
235                _ => None,
236            })
237            .collect();
238        let mut builder = Self {
239            declaration,
240            ta,
241            sources,
242            local_class_declarations,
243            nodes: Vec::new(),
244            named: BTreeMap::new(),
245            methods: BTreeMap::new(),
246            accessors: BTreeMap::new(),
247            classes: BTreeMap::new(),
248            closure_nodes: BTreeMap::new(),
249            closure_globals: BTreeMap::new(),
250            known_functions: BTreeSet::new(),
251            known_methods: BTreeSet::new(),
252            dynamically_dispatched_methods: BTreeSet::new(),
253            capability_functions: BTreeMap::new(),
254            capability_methods: BTreeMap::new(),
255            class_hierarchy: BTreeMap::new(),
256            external_classes: BTreeMap::new(),
257            external_surfaces: BTreeMap::new(),
258            edges: Vec::new(),
259            exposure_edges: Vec::new(),
260            direct_effect_count: 0,
261            check_count: 0,
262            limits,
263        };
264        builder.index_local()?;
265        for dependency in dependencies {
266            builder.index_dependency_declaration(dependency);
267        }
268        builder.expose_inherited_members(&exports(ta, ExportKind::Type))?;
269        builder.link_implicit_constructors()?;
270        Ok(builder)
271    }
272
273    fn index_local(&mut self) -> Result<(), CompilerFailure> {
274        let exported_functions = exports(self.ta, ExportKind::Function);
275        let exported_globals = exports(self.ta, ExportKind::Global);
276        let exported_classes = exports(self.ta, ExportKind::Type);
277        let public_statics = public_static_functions(self.declaration, &exported_classes);
278
279        self.index_functions(&exported_functions, &public_statics)?;
280        self.index_classes(&exported_classes)?;
281        self.index_closures()?;
282        self.index_module_initializers(&exported_globals)?;
283        Ok(())
284    }
285
286    fn index_functions(
287        &mut self,
288        exported_functions: &BTreeSet<MangledName>,
289        public_statics: &BTreeSet<MangledName>,
290    ) -> Result<(), CompilerFailure> {
291        for function in &self.ta.functions {
292            let kind = if function.mangled_name.as_str().contains("#static#") {
293                AuthorityCallableKind::StaticMethod
294            } else {
295                AuthorityCallableKind::Function
296            };
297            let exposure = if exported_functions.contains(&function.mangled_name)
298                || public_statics.contains(&function.mangled_name)
299            {
300                AuthorityExposure::Public
301            } else {
302                AuthorityExposure::Private
303            };
304            let index = self.push_node(
305                function.mangled_name.to_string(),
306                kind,
307                function.name.name.clone(),
308                function.span,
309                exposure,
310                vec![function.body],
311                Vec::new(),
312            )?;
313            self.named.insert(function.mangled_name.clone(), index);
314        }
315        Ok(())
316    }
317
318    fn index_classes(
319        &mut self,
320        exported_classes: &BTreeSet<MangledName>,
321    ) -> Result<(), CompilerFailure> {
322        for declaration in &self.ta.types {
323            let TypedTypeDecl::Class(class) = declaration else {
324                continue;
325            };
326            self.classes
327                .insert(class.mangled_name.clone(), class.extends.clone());
328            self.class_hierarchy
329                .insert(class.mangled_name.clone(), class.extends.clone());
330            let class_public = exported_classes.contains(&class.mangled_name);
331            let ctor_public = class_public && self.constructor_is_public(&class.mangled_name);
332            let ctor_mangled = crate::mangle::extend(&class.mangled_name, "constructor");
333            let mut ctor_statements = Vec::new();
334            if let Some(constructor) = &class.constructor {
335                ctor_statements.push(constructor.body);
336            }
337            let ctor_expressions = class
338                .fields
339                .iter()
340                .filter_map(|field| field.initializer)
341                .collect();
342            let ctor_span = class
343                .constructor
344                .as_ref()
345                .map_or(class.name.span, |constructor| constructor.span);
346            let ctor = self.push_node(
347                ctor_mangled.to_string(),
348                AuthorityCallableKind::Constructor,
349                format!("new {}", class.name.name),
350                ctor_span,
351                if ctor_public {
352                    AuthorityExposure::Public
353                } else {
354                    AuthorityExposure::Private
355                },
356                ctor_statements,
357                ctor_expressions,
358            )?;
359            self.named.insert(ctor_mangled, ctor);
360
361            for method in &class.methods {
362                self.dynamically_dispatched_methods
363                    .insert((class.mangled_name.clone(), method.name.name.clone()));
364                let id = crate::mangle::extend(&class.mangled_name, &method.name.name);
365                let index = self.push_node(
366                    id.to_string(),
367                    AuthorityCallableKind::Method,
368                    format!("{}.{}", class.name.name, method.name.name),
369                    method.name.span,
370                    if class_public && method.visibility == Visibility::Public {
371                        AuthorityExposure::Public
372                    } else {
373                        AuthorityExposure::Private
374                    },
375                    vec![method.body],
376                    Vec::new(),
377                )?;
378                self.methods.insert(
379                    (class.mangled_name.clone(), method.name.name.clone()),
380                    index,
381                );
382            }
383            for accessor in &class.accessors {
384                let is_setter = matches!(accessor, TypedClassAccessor::Setter { .. });
385                let suffix = if is_setter { "set" } else { "get" };
386                let id = format!("{}#{suffix}#{}", class.mangled_name, accessor.name().name);
387                let index = self.push_node(
388                    id,
389                    if is_setter {
390                        AuthorityCallableKind::Setter
391                    } else {
392                        AuthorityCallableKind::Getter
393                    },
394                    format!("{}.{}", class.name.name, accessor.name().name),
395                    accessor.name().span,
396                    if class_public && accessor.visibility() == Visibility::Public {
397                        AuthorityExposure::Public
398                    } else {
399                        AuthorityExposure::Private
400                    },
401                    vec![accessor.body()],
402                    Vec::new(),
403                )?;
404                self.accessors.insert(
405                    (
406                        class.mangled_name.clone(),
407                        accessor.name().name.clone(),
408                        is_setter,
409                    ),
410                    index,
411                );
412            }
413        }
414        Ok(())
415    }
416
417    fn index_closures(&mut self) -> Result<(), CompilerFailure> {
418        let expr_ids: Vec<_> = self
419            .ta
420            .expr_ids()
421            .map_err(crate::typechecker::arena_failure)?
422            .collect();
423        for id in expr_ids {
424            let expression = self
425                .ta
426                .try_expr(id)
427                .map_err(crate::typechecker::arena_failure)?;
428            let TypedExprKind::Closure { body, .. } = &expression.kind else {
429                continue;
430            };
431            let name = self
432                .ta
433                .nested_function_names
434                .get(&id)
435                .or_else(|| self.ta.closure_names.get(&id))
436                .map_or_else(|| "anonymous closure".to_string(), |name| name.name.clone());
437            let path = self
438                .sources
439                .get(expression.span.file)
440                .map_or("<unknown>", |source| source.path.as_str());
441            let node_id = format!(
442                "closure:{path}:{}:{}",
443                expression.span.start, expression.span.end
444            );
445            let (statements, expressions) = match body {
446                ClosureBody::Block(body) => (vec![*body], Vec::new()),
447                ClosureBody::Expr(result) => (Vec::new(), vec![*result]),
448            };
449            let index = self.push_node(
450                node_id,
451                AuthorityCallableKind::Closure,
452                name,
453                expression.span,
454                AuthorityExposure::Private,
455                statements,
456                expressions,
457            )?;
458            self.closure_nodes.insert(id, index);
459        }
460        Ok(())
461    }
462
463    fn index_module_initializers(
464        &mut self,
465        exported_globals: &BTreeSet<MangledName>,
466    ) -> Result<(), CompilerFailure> {
467        let mut module_roots: BTreeMap<u32, Vec<_>> = BTreeMap::new();
468        for statement in &self.ta.top_level_statements {
469            let span = self
470                .ta
471                .try_stmt(*statement)
472                .map_err(crate::typechecker::arena_failure)?
473                .span;
474            module_roots
475                .entry(span.file.0)
476                .or_default()
477                .push(*statement);
478            if let TypedStmtKind::AssignGlobal {
479                ident,
480                mangled,
481                target_ty,
482                value,
483            } = &self
484                .ta
485                .try_stmt(*statement)
486                .map_err(crate::typechecker::arena_failure)?
487                .kind
488            {
489                self.index_function_global(
490                    ident,
491                    mangled,
492                    target_ty,
493                    *value,
494                    exported_globals.contains(mangled),
495                )?;
496            }
497        }
498        for (file, statements) in module_roots {
499            let file = crate::FileId(file);
500            let span = self
501                .ta
502                .try_stmt(statements[0])
503                .map_err(crate::typechecker::arena_failure)?
504                .span;
505            let path = self
506                .sources
507                .get(file)
508                .map_or("<unknown>", |source| source.path.as_str());
509            self.push_node(
510                format!("module:{path}"),
511                AuthorityCallableKind::ModuleInitializer,
512                format!("module {path}"),
513                span,
514                AuthorityExposure::Initialization,
515                statements,
516                Vec::new(),
517            )?;
518        }
519        Ok(())
520    }
521
522    fn index_function_global(
523        &mut self,
524        ident: &crate::Ident,
525        mangled: &MangledName,
526        target_ty: &Type,
527        value: ExprId,
528        exported: bool,
529    ) -> Result<(), CompilerFailure> {
530        let held = self.held_callable(value)?;
531        if held.is_none() && !may_be_callable(target_ty) {
532            return Ok(());
533        }
534        let exposure = if exported {
535            AuthorityExposure::Public
536        } else {
537            AuthorityExposure::Private
538        };
539        let target = if self.ta.rebindable_globals.contains_key(mangled) {
540            let target = self.push_node(
541                format!("global-function:{mangled}"),
542                AuthorityCallableKind::FunctionValue,
543                ident.name.clone(),
544                ident.span,
545                exposure,
546                Vec::new(),
547                Vec::new(),
548            )?;
549            if let Some(held) = held {
550                self.add_edge(target, held, ident.span)?;
551                self.add_exposure_edge(target, held, ident.span)?;
552            }
553            self.add_unresolved(target, ident.span, "function-valued global may be rebound")?;
554            target
555        } else if let Some(held) = held {
556            held
557        } else {
558            let target = self.push_node(
559                format!("global-function:{mangled}"),
560                AuthorityCallableKind::FunctionValue,
561                ident.name.clone(),
562                ident.span,
563                exposure,
564                Vec::new(),
565                Vec::new(),
566            )?;
567            self.add_unresolved(
568                target,
569                ident.span,
570                "function-valued global target is not directly recoverable",
571            )?;
572            target
573        };
574        self.closure_globals.insert(mangled.clone(), target);
575        if exported {
576            self.nodes[target].callable.exposure = AuthorityExposure::Public;
577        }
578        Ok(())
579    }
580
581    fn expose_inherited_members(
582        &mut self,
583        exported_classes: &BTreeSet<MangledName>,
584    ) -> Result<(), CompilerFailure> {
585        let mut inheritance_work = 0usize;
586        for exported in exported_classes {
587            let Some(class) = self.local_class_declarations.get(exported).copied() else {
588                continue;
589            };
590            self.expose_local_instance_function_fields(exported, class, None)?;
591            self.expose_static_function_fields(exported, class.name.span)?;
592            let mut seen = InheritedNames {
593                methods: class
594                    .methods
595                    .iter()
596                    .map(|method| method.name.name.clone())
597                    .collect(),
598                accessors: class
599                    .accessors
600                    .iter()
601                    .map(|accessor| {
602                        (
603                            accessor.name().name.clone(),
604                            matches!(accessor, TypedClassAccessor::Setter { .. }),
605                        )
606                    })
607                    .collect(),
608                statics: own_static_names(self.ta, exported),
609                fields: class
610                    .fields
611                    .iter()
612                    .map(|field| field.name.name.clone())
613                    .collect(),
614            };
615            let mut parent = class.extends.clone();
616            for _ in 0..=self.class_hierarchy.len() {
617                let Some(parent_name) = parent else {
618                    break;
619                };
620                if let Some(parent_class) = self.local_class_declarations.get(&parent_name).copied()
621                {
622                    let static_count = parent_class
623                        .static_methods
624                        .len()
625                        .saturating_add(parent_class.static_fields.len());
626                    inheritance_work = checked_authority_budget(
627                        inheritance_work,
628                        parent_class
629                            .methods
630                            .len()
631                            .saturating_add(parent_class.accessors.len())
632                            .saturating_add(parent_class.fields.len())
633                            .saturating_add(static_count),
634                        self.limits.route_effects,
635                        class.name.span,
636                        "inherited-member work",
637                    )?;
638                    self.expose_local_inherited_methods(
639                        parent_class,
640                        &parent_name,
641                        &mut seen.methods,
642                    );
643                    self.expose_local_inherited_accessors(
644                        parent_class,
645                        &parent_name,
646                        &mut seen.accessors,
647                    );
648                    self.expose_local_instance_function_fields(
649                        exported,
650                        parent_class,
651                        Some(&mut seen.fields),
652                    )?;
653                    self.expose_local_inherited_statics(
654                        exported,
655                        parent_class,
656                        &parent_name,
657                        class.name.span,
658                        &mut seen.statics,
659                    )?;
660                    parent = parent_class.extends.clone();
661                    continue;
662                }
663                let Some(surface) = self.external_surfaces.get(&parent_name).cloned() else {
664                    break;
665                };
666                inheritance_work = checked_authority_budget(
667                    inheritance_work,
668                    surface.member_count(),
669                    self.limits.route_effects,
670                    class.name.span,
671                    "inherited-member work",
672                )?;
673                self.expose_external_inherited_members(
674                    exported,
675                    &parent_name,
676                    class.name.span,
677                    surface.as_ref(),
678                    &mut seen,
679                )?;
680                parent = surface.parent.clone();
681            }
682        }
683        Ok(())
684    }
685
686    fn expose_local_instance_function_fields(
687        &mut self,
688        exported: &MangledName,
689        class: &crate::TypedClassDecl,
690        mut seen: Option<&mut BTreeSet<String>>,
691    ) -> Result<(), CompilerFailure> {
692        let fields = class
693            .fields
694            .iter()
695            .filter_map(|field| {
696                let newly_visible = seen
697                    .as_deref_mut()
698                    .is_none_or(|seen| seen.insert(field.name.name.clone()));
699                (newly_visible
700                    && field.visibility == Visibility::Public
701                    && may_be_callable(&field.ty))
702                .then_some((
703                    field.name.name.clone(),
704                    field.name.span,
705                    field.initializer,
706                    field.readonly,
707                ))
708            })
709            .collect::<Vec<_>>();
710        for (name, span, initializer, readonly) in fields {
711            let initial_target = match initializer {
712                Some(initializer) => self.held_callable(initializer)?,
713                None => None,
714            };
715            let target = self.push_node(
716                format!("instance-field:{exported}:{}:{name}", class.mangled_name),
717                AuthorityCallableKind::FunctionValue,
718                format!("{exported}.{name}"),
719                span,
720                AuthorityExposure::Public,
721                Vec::new(),
722                Vec::new(),
723            )?;
724            let reason = if let Some(initial_target) = initial_target {
725                self.add_edge(target, initial_target, span)?;
726                self.add_exposure_edge(target, initial_target, span)?;
727                if readonly {
728                    "readonly function field may be reassigned during construction"
729                } else {
730                    "public function field may be reassigned"
731                }
732            } else {
733                "function-valued instance field target is not directly recoverable"
734            };
735            self.add_unresolved(target, span, reason)?;
736        }
737        Ok(())
738    }
739
740    fn expose_local_inherited_methods(
741        &mut self,
742        class: &crate::TypedClassDecl,
743        owner: &MangledName,
744        seen: &mut BTreeSet<String>,
745    ) {
746        for method in &class.methods {
747            if method.visibility == Visibility::Public
748                && seen.insert(method.name.name.clone())
749                && let Some(target) = self.methods.get(&(owner.clone(), method.name.name.clone()))
750            {
751                self.nodes[*target].callable.exposure = AuthorityExposure::Public;
752            }
753        }
754    }
755
756    fn expose_local_inherited_accessors(
757        &mut self,
758        class: &crate::TypedClassDecl,
759        owner: &MangledName,
760        seen: &mut BTreeSet<(String, bool)>,
761    ) {
762        for accessor in &class.accessors {
763            let setter = matches!(accessor, TypedClassAccessor::Setter { .. });
764            let key = (accessor.name().name.clone(), setter);
765            if accessor.visibility() == Visibility::Public
766                && seen.insert(key.clone())
767                && let Some(target) = self.accessors.get(&(owner.clone(), key.0, setter))
768            {
769                self.nodes[*target].callable.exposure = AuthorityExposure::Public;
770            }
771        }
772    }
773
774    fn expose_local_inherited_statics(
775        &mut self,
776        exported: &MangledName,
777        class: &crate::TypedClassDecl,
778        owner: &MangledName,
779        span: Span,
780        seen: &mut BTreeSet<String>,
781    ) -> Result<(), CompilerFailure> {
782        for (name, visibility) in &class.static_methods {
783            if *visibility != Visibility::Private
784                && seen.insert(name.clone())
785                && let Some(target) = self.named.get(&crate::mangle::static_member(owner, name))
786            {
787                self.nodes[*target].callable.exposure = AuthorityExposure::Public;
788            }
789        }
790        let function_fields = class
791            .static_fields
792            .iter()
793            .filter(|(name, field)| {
794                !seen.contains(*name)
795                    && field.visibility == Visibility::Public
796                    && may_be_callable(&field.ty)
797            })
798            .map(|(name, _)| name.clone())
799            .collect::<Vec<_>>();
800        for name in function_fields {
801            seen.insert(name.clone());
802            if let Some(target) = self.static_function_target(owner, &name) {
803                self.nodes[target].callable.exposure = AuthorityExposure::Public;
804            } else {
805                let target = self.push_node(
806                    format!("inherited-static-field:{exported}:{owner}:{name}"),
807                    AuthorityCallableKind::FunctionValue,
808                    format!("{exported}.{name}"),
809                    span,
810                    AuthorityExposure::Public,
811                    Vec::new(),
812                    Vec::new(),
813                )?;
814                self.add_unresolved(
815                    target,
816                    span,
817                    "static function field target is not directly recoverable",
818                )?;
819            }
820        }
821        Ok(())
822    }
823
824    fn expose_static_function_fields(
825        &mut self,
826        owner: &MangledName,
827        span: Span,
828    ) -> Result<(), CompilerFailure> {
829        let Some(class) = self.local_class_declarations.get(owner).copied() else {
830            return Ok(());
831        };
832        let names = class
833            .static_fields
834            .iter()
835            .filter(|(_, field)| {
836                field.visibility == Visibility::Public && may_be_callable(&field.ty)
837            })
838            .map(|(name, _)| name.clone())
839            .collect::<Vec<_>>();
840        checked_authority_budget(
841            self.nodes.len(),
842            names.len(),
843            self.limits.callables,
844            span,
845            "callable",
846        )?;
847        checked_authority_budget(
848            self.direct_effect_count,
849            names.len(),
850            self.limits.route_effects,
851            span,
852            "direct effect",
853        )?;
854        for name in names {
855            if let Some(target) = self.static_function_target(owner, &name) {
856                self.nodes[target].callable.exposure = AuthorityExposure::Public;
857            } else {
858                let target = self.push_node(
859                    format!("static-field:{owner}:{name}"),
860                    AuthorityCallableKind::FunctionValue,
861                    format!("{owner}.{name}"),
862                    span,
863                    AuthorityExposure::Public,
864                    Vec::new(),
865                    Vec::new(),
866                )?;
867                self.add_unresolved(
868                    target,
869                    span,
870                    "static function field target is not directly recoverable",
871                )?;
872            }
873        }
874        Ok(())
875    }
876
877    fn static_function_target(&self, owner: &MangledName, name: &str) -> Option<usize> {
878        let exact = crate::mangle::static_member(owner, name);
879        self.closure_globals.get(&exact).copied()
880    }
881
882    fn expose_external_inherited_members(
883        &mut self,
884        exported: &MangledName,
885        owner: &MangledName,
886        span: Span,
887        surface: &ExternalClassSurface,
888        seen: &mut InheritedNames,
889    ) -> Result<(), CompilerFailure> {
890        for name in &surface.methods {
891            if seen.methods.insert(name.clone()) {
892                self.push_external_inherited_surface(
893                    exported,
894                    owner,
895                    name,
896                    AuthorityCallableKind::Method,
897                    span,
898                )?;
899            }
900        }
901        for (name, setter) in &surface.accessors {
902            if seen.accessors.insert((name.clone(), *setter)) {
903                self.push_external_inherited_surface(
904                    exported,
905                    owner,
906                    name,
907                    if *setter {
908                        AuthorityCallableKind::Setter
909                    } else {
910                        AuthorityCallableKind::Getter
911                    },
912                    span,
913                )?;
914            }
915        }
916        for name in &surface.function_fields {
917            if seen.fields.insert(name.clone()) {
918                self.push_external_inherited_surface(
919                    exported,
920                    owner,
921                    name,
922                    AuthorityCallableKind::FunctionValue,
923                    span,
924                )?;
925            }
926        }
927        for name in &surface.statics {
928            if seen.statics.insert(name.clone()) {
929                self.push_external_inherited_surface(
930                    exported,
931                    owner,
932                    name,
933                    AuthorityCallableKind::StaticMethod,
934                    span,
935                )?;
936            }
937        }
938        for name in &surface.static_function_fields {
939            if seen.statics.insert(name.clone()) {
940                self.push_external_inherited_surface(
941                    exported,
942                    owner,
943                    name,
944                    AuthorityCallableKind::FunctionValue,
945                    span,
946                )?;
947            }
948        }
949        Ok(())
950    }
951
952    fn push_external_inherited_surface(
953        &mut self,
954        exported: &MangledName,
955        owner: &MangledName,
956        name: &str,
957        kind: AuthorityCallableKind,
958        span: Span,
959    ) -> Result<(), CompilerFailure> {
960        let index = self.push_node(
961            format!("inherited:{exported}:{owner}:{kind:?}:{name}"),
962            kind,
963            format!("{exported}.{name}"),
964            span,
965            AuthorityExposure::Public,
966            Vec::new(),
967            Vec::new(),
968        )?;
969        self.add_external_inherited_capabilities(index, owner, name, kind, span)?;
970        self.add_unresolved(
971            index,
972            span,
973            "external inherited member body is not locally analyzable",
974        )
975    }
976
977    fn add_external_inherited_capabilities(
978        &mut self,
979        index: usize,
980        owner: &MangledName,
981        name: &str,
982        kind: AuthorityCallableKind,
983        span: Span,
984    ) -> Result<(), CompilerFailure> {
985        let callee = match kind {
986            AuthorityCallableKind::Method => self
987                .capability_methods
988                .get(&(owner.clone(), name.to_string()))
989                .copied(),
990            AuthorityCallableKind::StaticMethod => self
991                .capability_functions
992                .get(&crate::mangle::static_member(owner, name))
993                .copied(),
994            _ => None,
995        };
996        if let Some(callee) = callee {
997            checked_authority_budget(
998                self.direct_effect_count,
999                callee.capabilities.len().saturating_add(1),
1000                self.limits.route_effects,
1001                span,
1002                "direct effect",
1003            )?;
1004            let mut effects = Vec::with_capacity(callee.capabilities.len());
1005            for capability in callee.capabilities {
1006                let derived =
1007                    crate::derive_call_site_capability(capability, callee.params, self.ta, &[])
1008                        .map_err(|error| {
1009                            error.fatal.unwrap_or_else(|| {
1010                        crate::typechecker::invariant_failure(
1011                            "inherited capability derivation failed without a typed failure",
1012                        )
1013                    })
1014                        })?;
1015                let unresolved = capability.bindings.iter().any(|binding| {
1016                    matches!(
1017                        binding.kind,
1018                        crate::DocCapabilityBindingKind::Parameter { .. }
1019                    )
1020                });
1021                effects.push(AuthorityEffect {
1022                    capability: Some(derived.capability),
1023                    sink: source_span(self.sources, span)?,
1024                    known_bindings: derived.known_bindings,
1025                    unresolved,
1026                    reason: unresolved.then(|| {
1027                        "some inherited capability bindings depend on caller arguments".to_string()
1028                    }),
1029                });
1030            }
1031            for effect in effects {
1032                self.push_effect(index, effect, span)?;
1033            }
1034        }
1035        Ok(())
1036    }
1037
1038    #[allow(clippy::too_many_arguments)]
1039    fn push_node(
1040        &mut self,
1041        id: String,
1042        kind: AuthorityCallableKind,
1043        name: String,
1044        span: Span,
1045        exposure: AuthorityExposure,
1046        statements: Vec<StmtId>,
1047        expressions: Vec<ExprId>,
1048    ) -> Result<usize, CompilerFailure> {
1049        check_limit(self.nodes.len(), self.limits.callables, span, "callable")?;
1050        let index = self.nodes.len();
1051        self.nodes.push(Node {
1052            callable: AuthorityCallable {
1053                id,
1054                kind,
1055                name,
1056                span: source_span(self.sources, span)?,
1057                exposure,
1058                checks: Vec::new(),
1059                direct_effects: Vec::new(),
1060                transitive_effects: Vec::new(),
1061            },
1062            root: Root {
1063                statements,
1064                expressions,
1065            },
1066            raw_span: span,
1067        });
1068        Ok(index)
1069    }
1070
1071    fn link_implicit_constructors(&mut self) -> Result<(), CompilerFailure> {
1072        for declaration in &self.ta.types {
1073            let TypedTypeDecl::Class(class) = declaration else {
1074                continue;
1075            };
1076            let (None, Some(parent)) = (&class.constructor, &class.extends) else {
1077                continue;
1078            };
1079            let child = crate::mangle::extend(&class.mangled_name, "constructor");
1080            let parent = crate::mangle::extend(parent, "constructor");
1081            let Some(&caller) = self.named.get(&child) else {
1082                continue;
1083            };
1084            if let Some(&target) = self.named.get(&parent) {
1085                self.add_edge(caller, target, class.name.span)?;
1086            } else {
1087                self.add_unresolved(caller, class.name.span, "inherited constructor target")?;
1088            }
1089        }
1090        Ok(())
1091    }
1092
1093    fn scan(&mut self) -> Result<(), CompilerFailure> {
1094        for caller in 0..self.nodes.len() {
1095            let statements = self.nodes[caller].root.statements.clone();
1096            let expressions = self.nodes[caller].root.expressions.clone();
1097            if self.nodes[caller].callable.kind == AuthorityCallableKind::Closure {
1098                for &expression in &expressions {
1099                    if let Some(target) = self.held_callable(expression)? {
1100                        let span = self
1101                            .ta
1102                            .try_expr(expression)
1103                            .map_err(crate::typechecker::arena_failure)?
1104                            .span;
1105                        self.add_exposure_edge(caller, target, span)?;
1106                    }
1107                }
1108            }
1109            let ta = self.ta;
1110            let mut visitor = CallVisitor {
1111                builder: self,
1112                caller,
1113            };
1114            body_walk::walk_roots(ta, statements, expressions, &mut visitor)?;
1115        }
1116        self.propagate_callable_exposure();
1117        self.edges.sort_by_key(|edge| edge_key(edge, &self.nodes));
1118        self.edges
1119            .dedup_by(|a, b| edge_key(a, &self.nodes) == edge_key(b, &self.nodes));
1120        Ok(())
1121    }
1122
1123    fn add_exposure_edge(
1124        &mut self,
1125        caller: usize,
1126        target: usize,
1127        span: Span,
1128    ) -> Result<(), CompilerFailure> {
1129        check_limit(
1130            self.exposure_edges.len(),
1131            self.limits.edges,
1132            span,
1133            "callable-exposure edge",
1134        )?;
1135        self.exposure_edges.push((caller, target));
1136        Ok(())
1137    }
1138
1139    fn propagate_callable_exposure(&mut self) {
1140        let mut outgoing = vec![Vec::new(); self.nodes.len()];
1141        for &(caller, target) in &self.exposure_edges {
1142            outgoing[caller].push(target);
1143        }
1144        let mut pending = self
1145            .nodes
1146            .iter()
1147            .enumerate()
1148            .filter_map(|(index, node)| {
1149                (node.callable.exposure == AuthorityExposure::Public).then_some(index)
1150            })
1151            .collect::<VecDeque<_>>();
1152        while let Some(caller) = pending.pop_front() {
1153            for &target in &outgoing[caller] {
1154                if self.nodes[target].callable.exposure == AuthorityExposure::Private {
1155                    self.nodes[target].callable.exposure = AuthorityExposure::Public;
1156                    pending.push_back(target);
1157                }
1158            }
1159        }
1160    }
1161
1162    fn add_edge(
1163        &mut self,
1164        caller: usize,
1165        target: usize,
1166        span: Span,
1167    ) -> Result<(), CompilerFailure> {
1168        self.push_edge(
1169            RawEdge {
1170                caller,
1171                target: Some(target),
1172                span: source_span(self.sources, span)?,
1173                unresolved: false,
1174                reason: None,
1175            },
1176            span,
1177        )
1178    }
1179
1180    fn add_unresolved(
1181        &mut self,
1182        caller: usize,
1183        span: Span,
1184        reason: &str,
1185    ) -> Result<(), CompilerFailure> {
1186        let effect = AuthorityEffect {
1187            capability: None,
1188            sink: source_span(self.sources, span)?,
1189            known_bindings: BTreeMap::new(),
1190            unresolved: true,
1191            reason: Some(reason.to_string()),
1192        };
1193        self.push_effect(caller, effect, span)?;
1194        self.push_edge(
1195            RawEdge {
1196                caller,
1197                target: None,
1198                span: source_span(self.sources, span)?,
1199                unresolved: true,
1200                reason: Some(reason.to_string()),
1201            },
1202            span,
1203        )
1204    }
1205
1206    fn push_edge(&mut self, edge: RawEdge, span: Span) -> Result<(), CompilerFailure> {
1207        check_limit(self.edges.len(), self.limits.edges, span, "edge")?;
1208        self.edges.push(edge);
1209        Ok(())
1210    }
1211
1212    fn push_effect(
1213        &mut self,
1214        caller: usize,
1215        effect: AuthorityEffect,
1216        span: Span,
1217    ) -> Result<(), CompilerFailure> {
1218        check_limit(
1219            self.direct_effect_count,
1220            self.limits.route_effects,
1221            span,
1222            "direct effect",
1223        )?;
1224        self.direct_effect_count += 1;
1225        self.nodes[caller].callable.direct_effects.push(effect);
1226        Ok(())
1227    }
1228
1229    fn add_capability_effects(
1230        &mut self,
1231        caller: usize,
1232        callee: CapabilityCallee<'_>,
1233        args: &[ExprId],
1234        span: Span,
1235    ) -> Result<(), CompilerFailure> {
1236        for capability in callee.capabilities {
1237            let derived =
1238                crate::derive_call_site_capability(capability, callee.params, self.ta, args)
1239                    .map_err(|error| {
1240                        error.fatal.unwrap_or_else(|| {
1241                            crate::typechecker::invariant_failure(
1242                                "capability derivation failed without a typed failure",
1243                            )
1244                        })
1245                    })?;
1246            self.push_effect(
1247                caller,
1248                AuthorityEffect {
1249                    capability: Some(derived.capability),
1250                    sink: source_span(self.sources, span)?,
1251                    known_bindings: derived.known_bindings,
1252                    unresolved: false,
1253                    reason: None,
1254                },
1255                span,
1256            )?;
1257        }
1258        Ok(())
1259    }
1260
1261    fn finish(mut self) -> Result<AuthorityMap, CompilerFailure> {
1262        for node in &mut self.nodes {
1263            node.callable.direct_effects.sort();
1264            node.callable.direct_effects.dedup();
1265        }
1266        let propagation = self.propagate_effects()?;
1267        for (index, component) in propagation.component_of.iter().enumerate() {
1268            self.nodes[index].callable.transitive_effects = propagation.component_effects
1269                [*component]
1270                .iter()
1271                .cloned()
1272                .collect();
1273        }
1274        let mut routes = self.build_routes(&propagation.adjacency)?;
1275        let edges = self.export_edges();
1276        let mut callables: Vec<_> = self.nodes.into_iter().map(|node| node.callable).collect();
1277        callables.sort_by(|a, b| a.id.cmp(&b.id));
1278        routes.sort_by(|a, b| a.callable.cmp(&b.callable));
1279        Ok(AuthorityMap {
1280            callables,
1281            edges,
1282            routes,
1283        })
1284    }
1285
1286    fn propagate_effects(&self) -> Result<Propagation, CompilerFailure> {
1287        let adjacency = adjacency(self.nodes.len(), &self.edges);
1288        let (component_of, components) = strongly_connected_components(&adjacency);
1289        let mut component_effects = components
1290            .iter()
1291            .map(|members| {
1292                members
1293                    .iter()
1294                    .flat_map(|member| self.nodes[*member].callable.direct_effects.iter().cloned())
1295                    .collect::<BTreeSet<_>>()
1296            })
1297            .collect::<Vec<_>>();
1298        let (component_edges, topo) = component_dag(&component_of, components.len(), &self.edges);
1299        let first_span = self
1300            .nodes
1301            .first()
1302            .map_or(Span::at(crate::FileId(0)), |node| node.raw_span);
1303        let mut propagated_entries = component_effects
1304            .iter()
1305            .try_fold(0usize, |total, effects| total.checked_add(effects.len()))
1306            .ok_or_else(|| {
1307                limit(
1308                    first_span,
1309                    "authority analysis direct-effect count overflows the platform limit"
1310                        .to_string(),
1311                )
1312            })?;
1313        let mut propagation_work = 0usize;
1314        for component in topo.into_iter().rev() {
1315            let span = self.nodes[components[component][0]].raw_span;
1316            let mut downstream = BTreeSet::new();
1317            for target in &component_edges[component] {
1318                propagation_work = checked_authority_budget(
1319                    propagation_work,
1320                    component_effects[*target].len(),
1321                    self.limits.route_effects,
1322                    span,
1323                    "propagation work",
1324                )?;
1325                for effect in &component_effects[*target] {
1326                    if !component_effects[component].contains(effect) {
1327                        downstream.insert(effect.clone());
1328                    }
1329                }
1330            }
1331            propagated_entries = checked_authority_budget(
1332                propagated_entries,
1333                downstream.len(),
1334                self.limits.route_effects,
1335                span,
1336                "propagated effect",
1337            )?;
1338            component_effects[component].extend(downstream);
1339        }
1340        let mut route_effects = 0usize;
1341        for component in &component_of {
1342            let span = self.nodes[components[*component][0]].raw_span;
1343            route_effects = checked_authority_budget(
1344                route_effects,
1345                component_effects[*component].len(),
1346                self.limits.route_effects,
1347                span,
1348                "route effect",
1349            )?;
1350        }
1351        Ok(Propagation {
1352            adjacency,
1353            component_of,
1354            component_effects,
1355        })
1356    }
1357
1358    fn build_routes(
1359        &self,
1360        adjacency: &[Vec<(usize, usize)>],
1361    ) -> Result<Vec<AuthorityRoute>, CompilerFailure> {
1362        let mut routes = Vec::new();
1363        let mut witness_work = 0usize;
1364        for (route, node) in self
1365            .nodes
1366            .iter()
1367            .enumerate()
1368            .filter(|(_, node)| node.callable.exposure != AuthorityExposure::Private)
1369        {
1370            let mut effects = Vec::new();
1371            for effect in &node.callable.transitive_effects {
1372                effects.push(AuthorityRouteEffect {
1373                    effect: effect.clone(),
1374                    witness: witness(
1375                        route,
1376                        effect,
1377                        adjacency,
1378                        &self.edges,
1379                        &self.nodes,
1380                        &mut witness_work,
1381                        self.limits.witness_work,
1382                    )?,
1383                });
1384            }
1385            routes.push(AuthorityRoute {
1386                callable: node.callable.id.clone(),
1387                effects,
1388            });
1389        }
1390        Ok(routes)
1391    }
1392
1393    fn export_edges(&self) -> Vec<AuthorityEdge> {
1394        self.edges
1395            .iter()
1396            .map(|edge| AuthorityEdge {
1397                caller: self.nodes[edge.caller].callable.id.clone(),
1398                target: edge
1399                    .target
1400                    .map(|target| self.nodes[target].callable.id.clone()),
1401                span: edge.span.clone(),
1402                unresolved: edge.unresolved,
1403                reason: edge.reason.clone(),
1404            })
1405            .collect()
1406    }
1407
1408    fn index_dependency_declaration(&mut self, declaration: &'a PackageDeclaration) {
1409        self.index_dependency_functions(declaration);
1410        self.index_dependency_classes(declaration);
1411        self.index_dependency_interfaces(declaration);
1412    }
1413
1414    fn index_dependency_functions(&mut self, declaration: &'a PackageDeclaration) {
1415        for symbol in declaration.values.values() {
1416            if let crate::ValueKind::Function { params, doc, .. } = &symbol.kind {
1417                self.known_functions.insert(symbol.mangled_name.clone());
1418                if let Some(doc) = doc.as_ref().filter(|doc| !doc.capabilities.is_empty()) {
1419                    self.capability_functions.insert(
1420                        symbol.mangled_name.clone(),
1421                        CapabilityCallee {
1422                            params,
1423                            capabilities: &doc.capabilities,
1424                        },
1425                    );
1426                }
1427            }
1428        }
1429    }
1430
1431    fn index_dependency_classes(&mut self, declaration: &'a PackageDeclaration) {
1432        for symbol in declaration
1433            .runtime_types
1434            .values()
1435            .chain(declaration.types.values())
1436        {
1437            if matches!(symbol.kind, TypeKind::Class { .. }) {
1438                self.external_classes
1439                    .insert(symbol.mangled_name.clone(), symbol);
1440                if let Some(surface) = external_class_surface(symbol) {
1441                    self.external_surfaces
1442                        .insert(symbol.mangled_name.clone(), Rc::new(surface));
1443                }
1444            }
1445        }
1446        for class in package_classes(declaration) {
1447            self.class_hierarchy
1448                .insert(class.name.clone(), class.parent.clone());
1449            for (name, method) in class.methods {
1450                let key = (class.name.clone(), name.clone());
1451                self.known_methods.insert(key.clone());
1452                self.dynamically_dispatched_methods.insert(key);
1453                if let Some(doc) = method
1454                    .doc
1455                    .as_ref()
1456                    .filter(|doc| !doc.capabilities.is_empty())
1457                {
1458                    self.capability_methods.insert(
1459                        (class.name.clone(), name.clone()),
1460                        CapabilityCallee {
1461                            params: &method.params,
1462                            capabilities: &doc.capabilities,
1463                        },
1464                    );
1465                }
1466            }
1467            for (name, method) in class.statics {
1468                let mangled = crate::mangle::static_member(&class.name, name);
1469                self.known_functions.insert(mangled.clone());
1470                if let Some(doc) = method
1471                    .doc
1472                    .as_ref()
1473                    .filter(|doc| !doc.capabilities.is_empty())
1474                {
1475                    self.capability_functions.insert(
1476                        mangled,
1477                        CapabilityCallee {
1478                            params: &method.params,
1479                            capabilities: &doc.capabilities,
1480                        },
1481                    );
1482                }
1483            }
1484        }
1485    }
1486
1487    fn index_dependency_interfaces(&mut self, declaration: &'a PackageDeclaration) {
1488        for symbol in declaration
1489            .runtime_types
1490            .values()
1491            .chain(declaration.types.values())
1492        {
1493            let TypeKind::Interface {
1494                methods, dispatch, ..
1495            } = &symbol.kind
1496            else {
1497                continue;
1498            };
1499            for (name, method) in methods {
1500                let key = (symbol.mangled_name.clone(), name.clone());
1501                self.known_methods.insert(key.clone());
1502                if *dispatch == crate::Dispatch::VTable {
1503                    self.dynamically_dispatched_methods.insert(key.clone());
1504                }
1505                if let Some(doc) = method
1506                    .doc
1507                    .as_ref()
1508                    .filter(|doc| !doc.capabilities.is_empty())
1509                {
1510                    self.capability_methods.insert(
1511                        key,
1512                        CapabilityCallee {
1513                            params: &method.params,
1514                            capabilities: &doc.capabilities,
1515                        },
1516                    );
1517                }
1518            }
1519        }
1520    }
1521
1522    fn local_method(&self, class: &MangledName, name: &str) -> Option<usize> {
1523        let mut class = class.clone();
1524        for _ in 0..=self.classes.len() {
1525            if let Some(&target) = self.methods.get(&(class.clone(), name.to_string())) {
1526                return Some(target);
1527            }
1528            class = self.classes.get(&class)?.clone()?;
1529        }
1530        None
1531    }
1532
1533    fn capability_method(&self, class: &MangledName, name: &str) -> Option<CapabilityCallee<'a>> {
1534        let mut class = class.clone();
1535        for _ in 0..=self.class_hierarchy.len() {
1536            if self
1537                .known_methods
1538                .contains(&(class.clone(), name.to_string()))
1539            {
1540                return self
1541                    .capability_methods
1542                    .get(&(class.clone(), name.to_string()))
1543                    .copied();
1544            }
1545            class = self.class_hierarchy.get(&class)?.clone()?;
1546        }
1547        None
1548    }
1549
1550    fn known_method(&self, class: &MangledName, name: &str) -> bool {
1551        let mut class = class.clone();
1552        for _ in 0..=self.class_hierarchy.len() {
1553            if self
1554                .known_methods
1555                .contains(&(class.clone(), name.to_string()))
1556            {
1557                return true;
1558            }
1559            let Some(parent) = self.class_hierarchy.get(&class) else {
1560                return false;
1561            };
1562            let Some(parent) = parent else {
1563                return false;
1564            };
1565            class = parent.clone();
1566        }
1567        false
1568    }
1569
1570    fn method_is_dynamic(&self, class: &MangledName, name: &str) -> bool {
1571        let mut class = class.clone();
1572        for _ in 0..=self.class_hierarchy.len() {
1573            let key = (class.clone(), name.to_string());
1574            if self.known_methods.contains(&key) || self.methods.contains_key(&key) {
1575                return self.dynamically_dispatched_methods.contains(&key);
1576            }
1577            let Some(Some(parent)) = self.class_hierarchy.get(&class) else {
1578                return false;
1579            };
1580            class = parent.clone();
1581        }
1582        false
1583    }
1584
1585    fn external_accessor_exists(&self, class: &MangledName, name: &str, setter: bool) -> bool {
1586        let Some(symbol) = self.external_classes.get(class) else {
1587            return false;
1588        };
1589        let TypeKind::Class {
1590            fields, accessors, ..
1591        } = &symbol.kind
1592        else {
1593            return false;
1594        };
1595        fields
1596            .get(name)
1597            .is_none_or(|field| field.visibility == Visibility::Public)
1598            && accessors.iter().any(|accessor| match accessor {
1599                crate::AccessorSig::Getter {
1600                    name: candidate, ..
1601                } => !setter && candidate == name,
1602                crate::AccessorSig::Setter {
1603                    name: candidate, ..
1604                } => setter && candidate == name,
1605            })
1606    }
1607
1608    fn held_callable(&self, mut id: ExprId) -> Result<Option<usize>, CompilerFailure> {
1609        for _ in 0..self.ta.exprs_len() {
1610            let expression = self
1611                .ta
1612                .try_expr(id)
1613                .map_err(crate::typechecker::arena_failure)?;
1614            match &expression.kind {
1615                TypedExprKind::Closure { .. } => {
1616                    return Ok(self.closure_nodes.get(&id).copied());
1617                }
1618                TypedExprKind::FunctionRef { mangled, .. } => {
1619                    return Ok(self.named.get(mangled).copied());
1620                }
1621                TypedExprKind::GlobalRef { mangled, .. } => {
1622                    return Ok(self.closure_globals.get(mangled).copied());
1623                }
1624                TypedExprKind::Cast { value, .. }
1625                | TypedExprKind::NonNullAssert { value }
1626                | TypedExprKind::Narrowed { inner: value, .. }
1627                | TypedExprKind::EffectThen { result: value, .. }
1628                | TypedExprKind::Sequence { result: value, .. } => id = *value,
1629                _ => return Ok(None),
1630            }
1631        }
1632        Err(crate::typechecker::invariant_failure(
1633            "closure value wrappers form a cycle",
1634        ))
1635    }
1636
1637    fn constructor_is_public(&self, class: &MangledName) -> bool {
1638        source_type_symbol(self.declaration, class).is_none_or(|symbol| {
1639            !matches!(
1640                &symbol.kind,
1641                TypeKind::Class {
1642                    constructor_visibility: Visibility::Private,
1643                    ..
1644                }
1645            )
1646        })
1647    }
1648}
1649
1650struct CallVisitor<'a, 'b> {
1651    builder: &'a mut Builder<'b>,
1652    caller: usize,
1653}
1654
1655impl Visitor for CallVisitor<'_, '_> {
1656    fn visit_stmt(&mut self, kind: &TypedStmtKind) -> Result<(), CompilerFailure> {
1657        if let TypedStmtKind::AssignField { receiver, name, .. } = kind {
1658            self.accessor(*receiver, &name.name, true, name.span)?;
1659        }
1660        if let TypedStmtKind::Return(Some(value)) = kind
1661            && let Some(target) = self.builder.held_callable(*value)?
1662        {
1663            let span = self
1664                .builder
1665                .ta
1666                .try_expr(*value)
1667                .map_err(crate::typechecker::arena_failure)?
1668                .span;
1669            self.builder.add_exposure_edge(self.caller, target, span)?;
1670        }
1671        Ok(())
1672    }
1673
1674    fn descend_into_closures(&self) -> bool {
1675        false
1676    }
1677
1678    fn visit_expr(&mut self, id: ExprId, kind: &TypedExprKind) -> Result<(), CompilerFailure> {
1679        let span = self
1680            .builder
1681            .ta
1682            .try_expr(id)
1683            .map_err(crate::typechecker::arena_failure)?
1684            .span;
1685        match kind {
1686            TypedExprKind::Call { mangled, args, .. } => self.direct_call(mangled, args, span)?,
1687            TypedExprKind::GenericCall { mangled, args, .. } => {
1688                let args = args.iter().map(|arg| arg.expr).collect::<Vec<_>>();
1689                self.direct_call(mangled, &args, span)?;
1690            }
1691            TypedExprKind::MethodCall {
1692                iface, name, args, ..
1693            } => {
1694                self.method_call(iface, &name.name, args, span)?;
1695            }
1696            TypedExprKind::SuperMethodCall { owner, name, args } => {
1697                self.super_method_call(owner, &name.name, args, span)?;
1698            }
1699            TypedExprKind::GenericMethodCall {
1700                iface, name, args, ..
1701            } => {
1702                let args = args.iter().map(|arg| arg.expr).collect::<Vec<_>>();
1703                self.method_call(iface, &name.name, &args, span)?;
1704            }
1705            TypedExprKind::SuperCtorCall { parent, .. } => {
1706                let target = crate::mangle::extend(parent, "constructor");
1707                if let Some(&target) = self.builder.named.get(&target) {
1708                    self.builder.add_edge(self.caller, target, span)?;
1709                } else {
1710                    self.builder.add_unresolved(
1711                        self.caller,
1712                        span,
1713                        "external super-constructor target",
1714                    )?;
1715                }
1716            }
1717            TypedExprKind::CallClosure { callee, .. } => {
1718                if let Some(target) = self.closure_target(*callee)? {
1719                    self.builder.add_edge(self.caller, target, span)?;
1720                } else {
1721                    self.builder
1722                        .add_unresolved(self.caller, span, "dynamic closure target")?;
1723                }
1724            }
1725            TypedExprKind::McpCall { server, tool, .. } => {
1726                self.builder.push_effect(
1727                    self.caller,
1728                    AuthorityEffect {
1729                        capability: Some(format!("mcp.{server}")),
1730                        sink: source_span(self.builder.sources, span)?,
1731                        known_bindings: BTreeMap::from([
1732                            ("tool".to_string(), format!("\"{tool}\"")),
1733                            ("transport".to_string(), "\"streamable_http\"".to_string()),
1734                        ]),
1735                        unresolved: false,
1736                        reason: None,
1737                    },
1738                    span,
1739                )?;
1740            }
1741            TypedExprKind::FieldAccess { receiver, name }
1742            | TypedExprKind::InterfacePropertyAccess { receiver, name, .. } => {
1743                self.accessor(*receiver, &name.name, false, span)?;
1744            }
1745            TypedExprKind::PostfixUnary {
1746                target: PostfixTarget::Field { receiver, name, .. },
1747                ..
1748            } => {
1749                self.accessor(*receiver, &name.name, false, span)?;
1750                self.accessor(*receiver, &name.name, true, span)?;
1751            }
1752            TypedExprKind::OptionalChain { base, parts } => {
1753                self.optional_chain(*base, parts)?;
1754            }
1755            _ => {}
1756        }
1757        Ok(())
1758    }
1759}
1760
1761impl CallVisitor<'_, '_> {
1762    fn optional_chain(
1763        &mut self,
1764        base: ExprId,
1765        parts: &[TypedChainPart],
1766    ) -> Result<(), CompilerFailure> {
1767        let mut receiver_type = self
1768            .builder
1769            .ta
1770            .try_expr(base)
1771            .map_err(crate::typechecker::arena_failure)?
1772            .ty
1773            .clone();
1774        for part in parts {
1775            match part {
1776                TypedChainPart::MethodCall {
1777                    iface,
1778                    name,
1779                    args,
1780                    result_ty,
1781                    span,
1782                    ..
1783                } => {
1784                    self.method_call(iface, &name.name, args, *span)?;
1785                    receiver_type = result_ty.clone();
1786                }
1787                TypedChainPart::Call {
1788                    result_ty, span, ..
1789                } => {
1790                    self.builder.add_unresolved(
1791                        self.caller,
1792                        *span,
1793                        "dynamic optional-call target",
1794                    )?;
1795                    receiver_type = result_ty.clone();
1796                }
1797                TypedChainPart::Field {
1798                    name,
1799                    result_ty,
1800                    span,
1801                    ..
1802                } => {
1803                    self.accessor_for_type(&receiver_type, &name.name, false, *span)?;
1804                    receiver_type = result_ty.clone();
1805                }
1806                TypedChainPart::InterfaceProperty {
1807                    result_ty, span, ..
1808                } => {
1809                    self.builder.add_unresolved(
1810                        self.caller,
1811                        *span,
1812                        "interface property may invoke an accessor",
1813                    )?;
1814                    receiver_type = result_ty.clone();
1815                }
1816                TypedChainPart::Index { result_ty, .. }
1817                | TypedChainPart::NonNull { result_ty, .. } => {
1818                    receiver_type = result_ty.clone();
1819                }
1820            }
1821        }
1822        Ok(())
1823    }
1824
1825    fn direct_call(
1826        &mut self,
1827        mangled: &MangledName,
1828        args: &[ExprId],
1829        span: Span,
1830    ) -> Result<(), CompilerFailure> {
1831        if crate::stdlib::security::is_check(mangled) {
1832            let capability = match args.first() {
1833                Some(arg) => match &self
1834                    .builder
1835                    .ta
1836                    .try_expr(*arg)
1837                    .map_err(crate::typechecker::arena_failure)?
1838                    .kind
1839                {
1840                    TypedExprKind::String(value) => Some(value.clone()),
1841                    _ => None,
1842                },
1843                None => None,
1844            };
1845            check_limit(
1846                self.builder.check_count,
1847                self.builder.limits.edges,
1848                span,
1849                "check",
1850            )?;
1851            self.builder.check_count += 1;
1852            self.builder.nodes[self.caller]
1853                .callable
1854                .checks
1855                .push(AuthorityCheck {
1856                    span: source_span(self.builder.sources, span)?,
1857                    capability,
1858                });
1859        }
1860        if let Some(&target) = self.builder.named.get(mangled) {
1861            return self.builder.add_edge(self.caller, target, span);
1862        }
1863        if mangled.as_str() == "submilli:http#request" {
1864            return self.dynamic_http(args, span);
1865        }
1866        if let Some(callee) = self.builder.capability_functions.get(mangled).copied() {
1867            self.builder
1868                .add_capability_effects(self.caller, callee, args, span)?;
1869        } else if !self.builder.known_functions.contains(mangled) {
1870            self.builder
1871                .add_unresolved(self.caller, span, "unresolved direct call target")?;
1872        }
1873        Ok(())
1874    }
1875
1876    fn dynamic_http(&mut self, args: &[ExprId], span: Span) -> Result<(), CompilerFailure> {
1877        let method = match args.first() {
1878            Some(id) => self
1879                .builder
1880                .ta
1881                .try_expr(*id)
1882                .map_err(crate::typechecker::arena_failure)?,
1883            None => return self.dynamic_http_effect(None, BTreeMap::new(), span),
1884        };
1885        let method = match &method.kind {
1886            TypedExprKind::String(method) => Some(method.to_ascii_lowercase()),
1887            _ => None,
1888        };
1889        let mut known_bindings = BTreeMap::new();
1890        if let Some(id) = args.get(1) {
1891            let url = self
1892                .builder
1893                .ta
1894                .try_expr(*id)
1895                .map_err(crate::typechecker::arena_failure)?;
1896            if let TypedExprKind::String(url) = &url.kind
1897                && let Ok(parsed) = url::Url::parse(url)
1898            {
1899                if let Some(host) = parsed.host_str() {
1900                    known_bindings.insert("host".to_string(), format!("\"{host}\""));
1901                }
1902                known_bindings.insert("path".to_string(), format!("\"{}\"", parsed.path()));
1903            }
1904        }
1905        self.dynamic_http_effect(method, known_bindings, span)
1906    }
1907
1908    fn dynamic_http_effect(
1909        &mut self,
1910        method: Option<String>,
1911        known_bindings: BTreeMap<String, String>,
1912        span: Span,
1913    ) -> Result<(), CompilerFailure> {
1914        self.builder.push_effect(
1915            self.caller,
1916            AuthorityEffect {
1917                capability: Some(method.as_ref().map_or_else(
1918                    || "http.<method>".to_string(),
1919                    |method| format!("http.{method}"),
1920                )),
1921                sink: source_span(self.builder.sources, span)?,
1922                known_bindings,
1923                unresolved: method.is_none(),
1924                reason: method
1925                    .is_none()
1926                    .then(|| "HTTP method is selected at runtime".to_string()),
1927            },
1928            span,
1929        )?;
1930        Ok(())
1931    }
1932
1933    fn method_call(
1934        &mut self,
1935        iface: &MangledName,
1936        name: &str,
1937        args: &[ExprId],
1938        span: Span,
1939    ) -> Result<(), CompilerFailure> {
1940        let resolved = if let Some(target) = self.builder.local_method(iface, name) {
1941            self.builder.add_edge(self.caller, target, span)?;
1942            true
1943        } else if let Some(callee) = self.builder.capability_method(iface, name) {
1944            self.builder
1945                .add_capability_effects(self.caller, callee, args, span)?;
1946            true
1947        } else {
1948            self.builder.known_method(iface, name)
1949        };
1950        if !resolved {
1951            self.builder
1952                .add_unresolved(self.caller, span, "unresolved method target")?;
1953        } else if self.builder.method_is_dynamic(iface, name) {
1954            self.builder.add_unresolved(
1955                self.caller,
1956                span,
1957                "virtual method target may be overridden",
1958            )?;
1959        }
1960        Ok(())
1961    }
1962
1963    fn super_method_call(
1964        &mut self,
1965        owner: &MangledName,
1966        name: &str,
1967        args: &[ExprId],
1968        span: Span,
1969    ) -> Result<(), CompilerFailure> {
1970        if let Some(&target) = self.builder.methods.get(&(owner.clone(), name.to_string())) {
1971            return self.builder.add_edge(self.caller, target, span);
1972        }
1973        if let Some(callee) = self
1974            .builder
1975            .capability_methods
1976            .get(&(owner.clone(), name.to_string()))
1977            .copied()
1978        {
1979            return self
1980                .builder
1981                .add_capability_effects(self.caller, callee, args, span);
1982        }
1983        if !self
1984            .builder
1985            .known_methods
1986            .contains(&(owner.clone(), name.to_string()))
1987        {
1988            self.builder
1989                .add_unresolved(self.caller, span, "unresolved super-method target")?;
1990        }
1991        Ok(())
1992    }
1993
1994    fn closure_target(&self, id: ExprId) -> Result<Option<usize>, CompilerFailure> {
1995        self.builder.held_callable(id)
1996    }
1997
1998    fn accessor(
1999        &mut self,
2000        receiver: ExprId,
2001        name: &str,
2002        setter: bool,
2003        span: Span,
2004    ) -> Result<(), CompilerFailure> {
2005        let receiver = self
2006            .builder
2007            .ta
2008            .try_expr(receiver)
2009            .map_err(crate::typechecker::arena_failure)?;
2010        self.accessor_for_type(&receiver.ty, name, setter, span)
2011    }
2012
2013    fn accessor_for_type(
2014        &mut self,
2015        receiver: &Type,
2016        name: &str,
2017        setter: bool,
2018        span: Span,
2019    ) -> Result<(), CompilerFailure> {
2020        let receiver = crate::typechecker::infer::narrowing::strip_null(receiver);
2021        let Type::ClassRef { mangled, .. } = receiver.peel() else {
2022            if receiver.is_structural_object() {
2023                self.builder.add_unresolved(
2024                    self.caller,
2025                    span,
2026                    "structural property may invoke an accessor",
2027                )?;
2028            }
2029            return Ok(());
2030        };
2031        self.accessor_for_class(mangled, name, setter, span)
2032    }
2033
2034    fn accessor_for_class(
2035        &mut self,
2036        class: &MangledName,
2037        name: &str,
2038        setter: bool,
2039        span: Span,
2040    ) -> Result<(), CompilerFailure> {
2041        let mut class = class.clone();
2042        for _ in 0..=self.builder.class_hierarchy.len() {
2043            if let Some(&target) =
2044                self.builder
2045                    .accessors
2046                    .get(&(class.clone(), name.to_string(), setter))
2047            {
2048                self.builder.add_edge(self.caller, target, span)?;
2049                self.builder.add_unresolved(
2050                    self.caller,
2051                    span,
2052                    "virtual accessor target may be overridden",
2053                )?;
2054                return Ok(());
2055            }
2056            if self.builder.external_accessor_exists(&class, name, setter) {
2057                self.builder.add_unresolved(
2058                    self.caller,
2059                    span,
2060                    "external accessor body is not locally analyzable",
2061                )?;
2062                return Ok(());
2063            }
2064            let Some(Some(parent)) = self.builder.class_hierarchy.get(&class) else {
2065                return Ok(());
2066            };
2067            class = parent.clone();
2068        }
2069        Ok(())
2070    }
2071}
2072
2073fn exports(ta: &TypedAst, kind: ExportKind) -> BTreeSet<MangledName> {
2074    ta.exports
2075        .iter()
2076        .filter(|entry| entry.kind == kind)
2077        .map(|entry| entry.target.clone())
2078        .collect()
2079}
2080
2081fn public_static_functions(
2082    declaration: &PackageDeclaration,
2083    exported_classes: &BTreeSet<MangledName>,
2084) -> BTreeSet<MangledName> {
2085    declaration
2086        .types
2087        .values()
2088        .filter(|symbol| exported_classes.contains(&symbol.mangled_name))
2089        .flat_map(|symbol| {
2090            let TypeKind::Class {
2091                statics,
2092                static_visibility,
2093                ..
2094            } = &symbol.kind
2095            else {
2096                return Vec::new();
2097            };
2098            statics
2099                .keys()
2100                .filter(|name| static_visibility.get(*name) != Some(&Visibility::Private))
2101                .map(|name| crate::mangle::static_member(&symbol.mangled_name, name))
2102                .collect()
2103        })
2104        .collect()
2105}
2106
2107fn own_static_names(ta: &TypedAst, class: &MangledName) -> BTreeSet<String> {
2108    let prefix = format!("{class}#static#");
2109    ta.functions
2110        .iter()
2111        .map(|function| &function.mangled_name)
2112        .chain(ta.globals.iter().map(|global| &global.mangled_name))
2113        .filter_map(|mangled| mangled.as_str().strip_prefix(&prefix).map(str::to_string))
2114        .collect()
2115}
2116
2117fn may_be_callable(ty: &Type) -> bool {
2118    match ty.peel() {
2119        Type::Function { .. } => true,
2120        Type::Union(members) => members.iter().any(may_be_callable),
2121        _ => false,
2122    }
2123}
2124
2125fn source_type_symbol<'a>(
2126    declaration: &'a PackageDeclaration,
2127    mangled: &MangledName,
2128) -> Option<&'a crate::TypeSymbol> {
2129    declaration
2130        .types
2131        .values()
2132        .find(|symbol| &symbol.mangled_name == mangled)
2133        .or_else(|| declaration.type_symbol(mangled))
2134}
2135
2136struct PackageClass<'a> {
2137    name: MangledName,
2138    methods: &'a BTreeMap<String, crate::MethodSig>,
2139    statics: &'a BTreeMap<String, crate::MethodSig>,
2140    parent: Option<MangledName>,
2141}
2142
2143struct ExternalClassSurface {
2144    methods: Vec<String>,
2145    accessors: Vec<(String, bool)>,
2146    function_fields: Vec<String>,
2147    statics: Vec<String>,
2148    static_function_fields: Vec<String>,
2149    parent: Option<MangledName>,
2150}
2151
2152impl ExternalClassSurface {
2153    fn member_count(&self) -> usize {
2154        self.methods
2155            .len()
2156            .saturating_add(self.accessors.len())
2157            .saturating_add(self.function_fields.len())
2158            .saturating_add(self.statics.len())
2159            .saturating_add(self.static_function_fields.len())
2160    }
2161}
2162
2163struct InheritedNames {
2164    methods: BTreeSet<String>,
2165    accessors: BTreeSet<(String, bool)>,
2166    statics: BTreeSet<String>,
2167    fields: BTreeSet<String>,
2168}
2169
2170fn external_class_surface(symbol: &crate::TypeSymbol) -> Option<ExternalClassSurface> {
2171    let TypeKind::Class {
2172        fields,
2173        methods,
2174        method_visibility,
2175        accessors,
2176        statics,
2177        static_visibility,
2178        static_fields,
2179        extends,
2180        ..
2181    } = &symbol.kind
2182    else {
2183        return None;
2184    };
2185    Some(ExternalClassSurface {
2186        methods: methods
2187            .keys()
2188            .filter(|name| method_visibility.get(*name) != Some(&Visibility::Private))
2189            .cloned()
2190            .collect(),
2191        accessors: accessors
2192            .iter()
2193            .filter(|accessor| {
2194                let name = match accessor {
2195                    crate::AccessorSig::Getter { name, .. }
2196                    | crate::AccessorSig::Setter { name, .. } => name,
2197                };
2198                fields
2199                    .get(name)
2200                    .is_none_or(|field| field.visibility == Visibility::Public)
2201            })
2202            .map(|accessor| match accessor {
2203                crate::AccessorSig::Getter { name, .. } => (name.clone(), false),
2204                crate::AccessorSig::Setter { name, .. } => (name.clone(), true),
2205            })
2206            .collect(),
2207        function_fields: fields
2208            .iter()
2209            .filter(|(_, field)| {
2210                field.visibility == Visibility::Public && may_be_callable(&field.ty)
2211            })
2212            .map(|(name, _)| name.clone())
2213            .collect(),
2214        statics: statics
2215            .keys()
2216            .filter(|name| static_visibility.get(*name) != Some(&Visibility::Private))
2217            .cloned()
2218            .collect(),
2219        static_function_fields: static_fields
2220            .iter()
2221            .filter(|(_, field)| {
2222                field.visibility == Visibility::Public && may_be_callable(&field.ty)
2223            })
2224            .map(|(name, _)| name.clone())
2225            .collect(),
2226        parent: extends.as_ref().map(|extends| extends.parent.clone()),
2227    })
2228}
2229
2230fn package_classes(declaration: &PackageDeclaration) -> Vec<PackageClass<'_>> {
2231    declaration
2232        .runtime_types
2233        .values()
2234        .chain(declaration.types.values())
2235        .filter_map(|symbol| {
2236            if let TypeKind::Class {
2237                methods,
2238                statics,
2239                extends,
2240                ..
2241            } = &symbol.kind
2242            {
2243                Some(PackageClass {
2244                    name: symbol.mangled_name.clone(),
2245                    methods,
2246                    statics,
2247                    parent: extends.as_ref().map(|parent| parent.parent.clone()),
2248                })
2249            } else {
2250                None
2251            }
2252        })
2253        .collect()
2254}
2255
2256fn source_span(sources: &Sources, span: Span) -> Result<AuthoritySpan, CompilerFailure> {
2257    let source = sources.get(span.file).ok_or_else(|| {
2258        crate::typechecker::invariant_failure("authority span names an unregistered source")
2259    })?;
2260    let (start_line, start_column) = source
2261        .line_index()
2262        .line_col(span.start)
2263        .map_err(|_| crate::typechecker::invariant_failure("authority span start is invalid"))?;
2264    let (end_line, end_column) = source
2265        .line_index()
2266        .line_col(span.end)
2267        .map_err(|_| crate::typechecker::invariant_failure("authority span end is invalid"))?;
2268    Ok(AuthoritySpan {
2269        path: source.path.as_str().to_string(),
2270        start: AuthorityPosition {
2271            byte: span.start,
2272            line: start_line,
2273            column: start_column,
2274        },
2275        end: AuthorityPosition {
2276            byte: span.end,
2277            line: end_line,
2278            column: end_column,
2279        },
2280    })
2281}
2282
2283fn limit(span: Span, message: String) -> CompilerFailure {
2284    CompilerFailure::Limit {
2285        stage: CompilerStage::Infer,
2286        span: Some(span),
2287        message,
2288        help: vec![
2289            "split the package into smaller modules or reduce generated call sites".to_string(),
2290        ],
2291    }
2292}
2293
2294fn check_limit(
2295    current: usize,
2296    maximum: usize,
2297    span: Span,
2298    subject: &str,
2299) -> Result<(), CompilerFailure> {
2300    if current < maximum {
2301        return Ok(());
2302    }
2303    Err(limit(
2304        span,
2305        format!("authority analysis exceeds the {subject} limit of {maximum}"),
2306    ))
2307}
2308
2309fn checked_authority_budget(
2310    current: usize,
2311    additional: usize,
2312    maximum: usize,
2313    span: Span,
2314    subject: &str,
2315) -> Result<usize, CompilerFailure> {
2316    let Some(next) = current.checked_add(additional) else {
2317        return Err(limit(
2318            span,
2319            format!("authority analysis {subject} count overflows the platform limit"),
2320        ));
2321    };
2322    if next > maximum {
2323        return Err(limit(
2324            span,
2325            format!("authority analysis exceeds the {subject} limit of {maximum}"),
2326        ));
2327    }
2328    Ok(next)
2329}
2330
2331fn edge_key(
2332    edge: &RawEdge,
2333    nodes: &[Node],
2334) -> (String, AuthoritySpan, Option<String>, bool, Option<String>) {
2335    (
2336        nodes[edge.caller].callable.id.clone(),
2337        edge.span.clone(),
2338        edge.target.map(|target| nodes[target].callable.id.clone()),
2339        edge.unresolved,
2340        edge.reason.clone(),
2341    )
2342}
2343
2344fn adjacency(count: usize, edges: &[RawEdge]) -> Vec<Vec<(usize, usize)>> {
2345    let mut adjacency = vec![Vec::new(); count];
2346    for (edge_index, edge) in edges.iter().enumerate() {
2347        if let Some(target) = edge.target {
2348            adjacency[edge.caller].push((target, edge_index));
2349        }
2350    }
2351    adjacency
2352}
2353
2354fn component_dag(
2355    component_of: &[usize],
2356    component_count: usize,
2357    edges: &[RawEdge],
2358) -> (Vec<BTreeSet<usize>>, Vec<usize>) {
2359    let mut component_edges = vec![BTreeSet::new(); component_count];
2360    let mut indegree = vec![0usize; component_count];
2361    for edge in edges {
2362        let Some(target) = edge.target else {
2363            continue;
2364        };
2365        let caller_component = component_of[edge.caller];
2366        let target_component = component_of[target];
2367        if caller_component != target_component
2368            && component_edges[caller_component].insert(target_component)
2369        {
2370            indegree[target_component] = indegree[target_component].saturating_add(1);
2371        }
2372    }
2373    let mut ready = indegree
2374        .iter()
2375        .enumerate()
2376        .filter_map(|(component, degree)| (*degree == 0).then_some(component))
2377        .collect::<BTreeSet<_>>();
2378    let mut topo = Vec::with_capacity(component_count);
2379    while let Some(component) = ready.pop_first() {
2380        topo.push(component);
2381        for target in component_edges[component].iter().copied() {
2382            indegree[target] = indegree[target].saturating_sub(1);
2383            if indegree[target] == 0 {
2384                ready.insert(target);
2385            }
2386        }
2387    }
2388    (component_edges, topo)
2389}
2390
2391fn strongly_connected_components(
2392    adjacency: &[Vec<(usize, usize)>],
2393) -> (Vec<usize>, Vec<Vec<usize>>) {
2394    let mut seen = vec![false; adjacency.len()];
2395    let mut finish = Vec::with_capacity(adjacency.len());
2396    for start in 0..adjacency.len() {
2397        if seen[start] {
2398            continue;
2399        }
2400        seen[start] = true;
2401        let mut stack = vec![(start, 0usize)];
2402        while let Some((node, next)) = stack.last_mut() {
2403            if let Some((target, _)) = adjacency[*node].get(*next) {
2404                *next = next.saturating_add(1);
2405                if !seen[*target] {
2406                    seen[*target] = true;
2407                    stack.push((*target, 0));
2408                }
2409            } else {
2410                finish.push(*node);
2411                stack.pop();
2412            }
2413        }
2414    }
2415    let mut reverse = vec![Vec::new(); adjacency.len()];
2416    for (caller, targets) in adjacency.iter().enumerate() {
2417        for (target, _) in targets {
2418            reverse[*target].push(caller);
2419        }
2420    }
2421    reverse
2422        .iter_mut()
2423        .for_each(|sources| sources.sort_unstable());
2424    let mut component_of = vec![usize::MAX; adjacency.len()];
2425    let mut components = Vec::new();
2426    for start in finish.into_iter().rev() {
2427        if component_of[start] != usize::MAX {
2428            continue;
2429        }
2430        let component = components.len();
2431        let mut members = Vec::new();
2432        let mut pending = vec![start];
2433        component_of[start] = component;
2434        while let Some(node) = pending.pop() {
2435            members.push(node);
2436            for source in reverse[node].iter().rev().copied() {
2437                if component_of[source] == usize::MAX {
2438                    component_of[source] = component;
2439                    pending.push(source);
2440                }
2441            }
2442        }
2443        members.sort_unstable();
2444        components.push(members);
2445    }
2446    (component_of, components)
2447}
2448
2449fn witness(
2450    root: usize,
2451    effect: &AuthorityEffect,
2452    adjacency: &[Vec<(usize, usize)>],
2453    edges: &[RawEdge],
2454    nodes: &[Node],
2455    work: &mut usize,
2456    maximum_work: usize,
2457) -> Result<Vec<AuthorityWitnessStep>, CompilerFailure> {
2458    if nodes[root]
2459        .callable
2460        .direct_effects
2461        .binary_search(effect)
2462        .is_ok()
2463    {
2464        return Ok(Vec::new());
2465    }
2466    let traversal_bound = nodes.len().checked_add(edges.len()).ok_or_else(|| {
2467        limit(
2468            nodes[root].raw_span,
2469            "authority witness-work count overflows the platform limit".to_string(),
2470        )
2471    })?;
2472    *work = checked_authority_budget(
2473        *work,
2474        traversal_bound,
2475        maximum_work,
2476        nodes[root].raw_span,
2477        "witness work",
2478    )?;
2479    let mut previous: Vec<Option<(usize, usize)>> = vec![None; nodes.len()];
2480    let mut seen = BTreeSet::from([root]);
2481    let mut pending = VecDeque::from([root]);
2482    let mut found = None;
2483    while let Some(node) = pending.pop_front() {
2484        for (target, edge_index) in &adjacency[node] {
2485            if !seen.insert(*target) {
2486                continue;
2487            }
2488            previous[*target] = Some((node, *edge_index));
2489            if nodes[*target]
2490                .callable
2491                .direct_effects
2492                .binary_search(effect)
2493                .is_ok()
2494            {
2495                found = Some(*target);
2496                break;
2497            }
2498            pending.push_back(*target);
2499        }
2500        if found.is_some() {
2501            break;
2502        }
2503    }
2504    let Some(mut current) = found else {
2505        return Ok(Vec::new());
2506    };
2507    let mut path = Vec::new();
2508    while current != root {
2509        let Some((caller, edge_index)) = previous[current] else {
2510            return Ok(Vec::new());
2511        };
2512        path.push(AuthorityWitnessStep {
2513            caller: nodes[caller].callable.id.clone(),
2514            target: nodes[current].callable.id.clone(),
2515            span: edges[edge_index].span.clone(),
2516        });
2517        current = caller;
2518    }
2519    path.reverse();
2520    Ok(path)
2521}
2522
2523#[cfg(test)]
2524mod tests {
2525    use super::*;
2526    use crate::{
2527        ModulePath, PackageSourceModule, Param, Type, ValueKind, ValueSymbol, compile_package,
2528    };
2529
2530    fn compile(modules: &[(&str, &str)]) -> AuthorityMap {
2531        compile_with_dependencies(modules, &[])
2532    }
2533
2534    fn compile_with_dependencies(
2535        modules: &[(&str, &str)],
2536        dependencies: &[&PackageDeclaration],
2537    ) -> AuthorityMap {
2538        let modules = modules
2539            .iter()
2540            .map(|(path, source)| PackageSourceModule {
2541                path: ModulePath::from(*path),
2542                source,
2543            })
2544            .collect::<Vec<_>>();
2545        compile_package(
2546            "@test/authority",
2547            ModulePath::from("lib"),
2548            &modules,
2549            dependencies,
2550        )
2551        .unwrap_or_else(|diagnostics| panic!("compile failed: {diagnostics:#?}"))
2552        .authority_map
2553    }
2554
2555    #[test]
2556    fn check_sites_are_facts_without_authorization_verdicts() {
2557        let source = r#"
2558import { get } from "submilli:http";
2559import { check as authorize } from "submilli:security";
2560/** Read. @capability acme.read {} */
2561export function checked(): void { authorize("acme.read", {}); get("https://example.com"); }
2562/** Read. @capability acme.read {} */
2563export function swallowed(): void { try { authorize("acme.read", {}); } catch {} get("https://example.com"); }
2564export function unchecked(): void { get("https://example.com"); }
2565"#;
2566        let compiled = compile_package(
2567            "@test/authority",
2568            ModulePath::from("lib"),
2569            &[PackageSourceModule {
2570                path: ModulePath::from("lib"),
2571                source,
2572            }],
2573            &[],
2574        )
2575        .unwrap();
2576        assert!(
2577            compiled
2578                .warnings
2579                .iter()
2580                .all(|warning| !warning.message.contains("public route")
2581                    && !warning.message.contains("successful direct")
2582                    && !warning.message.contains("cannot prove"))
2583        );
2584        for name in ["checked", "swallowed"] {
2585            let callable = compiled
2586                .authority_map
2587                .callables
2588                .iter()
2589                .find(|c| c.name == name)
2590                .unwrap();
2591            assert_eq!(callable.checks.len(), 1);
2592            assert_eq!(callable.checks[0].capability.as_deref(), Some("acme.read"));
2593            assert_eq!(callable.checks[0].span.path, "lib");
2594        }
2595        let json = serde_json::to_value(&compiled.authority_map).unwrap();
2596        assert!(json["routes"].as_array().unwrap().iter().all(|route| {
2597            route["effects"]
2598                .as_array()
2599                .unwrap()
2600                .iter()
2601                .all(|effect| effect.get("guard").is_none())
2602        }));
2603    }
2604
2605    fn route<'a>(map: &'a AuthorityMap, suffix: &str) -> &'a AuthorityRoute {
2606        map.routes
2607            .iter()
2608            .find(|route| route.callable.ends_with(suffix))
2609            .unwrap_or_else(|| panic!("missing route ending in {suffix}: {:#?}", map.routes))
2610    }
2611
2612    #[test]
2613    fn direct_and_recursive_helpers_propagate_one_http_effect_with_a_witness() {
2614        let map = compile(&[(
2615            "lib",
2616            r#"
2617                import { get } from "submilli:http";
2618                function first(url: string): void { second(url); }
2619                function second(url: string): void { if (url !== "") { first(""); } get(url); }
2620                export function fetch(): void { first("https://example.com/data"); }
2621            "#,
2622        )]);
2623        let route = route(&map, "#fetch");
2624        let effect = route
2625            .effects
2626            .iter()
2627            .find(|effect| effect.effect.capability.as_deref() == Some("http.get"))
2628            .expect("transitive HTTP effect");
2629        assert_eq!(effect.witness.len(), 2, "{effect:#?}");
2630        assert!(effect.witness[0].target.ends_with("#first"));
2631        assert!(effect.witness[1].target.ends_with("#second"));
2632    }
2633
2634    #[test]
2635    fn root_reexport_is_public_but_unreachable_private_helper_is_not_a_route() {
2636        let map = compile(&[
2637            ("lib", "export { send } from \"./internal\";"),
2638            (
2639                "internal",
2640                r#"
2641                    import { get } from "submilli:secrets";
2642                    export function send(): void { get("TOKEN"); }
2643                    function hidden(): void { get("HIDDEN"); }
2644                "#,
2645            ),
2646        ]);
2647        assert!(
2648            map.routes
2649                .iter()
2650                .any(|route| route.callable.ends_with("#send"))
2651        );
2652        assert!(
2653            !map.routes
2654                .iter()
2655                .any(|route| route.callable.ends_with("#hidden"))
2656        );
2657        assert!(route(&map, "#send").effects.iter().any(|effect| {
2658            effect.effect.capability.as_deref() == Some("secrets.get")
2659                && effect.effect.known_bindings.get("name") == Some(&"\"TOKEN\"".to_string())
2660        }));
2661    }
2662
2663    #[test]
2664    fn dynamic_closure_and_http_method_are_explicit_uncertainties() {
2665        let map = compile(&[(
2666            "lib",
2667            r#"
2668                import { request } from "submilli:http";
2669                export function run(method: string, callback: () => void): void {
2670                    callback();
2671                    request(method, "https://example.com/v1");
2672                }
2673            "#,
2674        )]);
2675        let route = route(&map, "#run");
2676        assert!(route.effects.iter().any(|effect| {
2677            effect.effect.capability.as_deref() == Some("http.<method>") && effect.effect.unresolved
2678        }));
2679        assert!(route.effects.iter().any(|effect| {
2680            effect.effect.capability.is_none()
2681                && effect.effect.reason.as_deref() == Some("dynamic closure target")
2682        }));
2683        assert!(
2684            map.edges
2685                .iter()
2686                .any(|edge| edge.unresolved && edge.target.is_none())
2687        );
2688    }
2689
2690    #[test]
2691    fn static_filesystem_mcp_and_tagged_dependency_sinks_are_reported() {
2692        let dependency_source = [PackageSourceModule {
2693            path: ModulePath::from("lib"),
2694            source: r#"
2695                /** @capability vendor.send { queue: $queue } */
2696                export function send(queue: string): void {}
2697            "#,
2698        }];
2699        let dependency = compile_package(
2700            "@vendor/queue",
2701            ModulePath::from("lib"),
2702            &dependency_source,
2703            &[],
2704        )
2705        .expect("dependency compiles")
2706        .declaration;
2707
2708        let mut mcp = PackageDeclaration::with_package("@mcp/issues");
2709        mcp.mcp_server = Some("issues".to_string());
2710        mcp.values.insert(
2711            "create".to_string(),
2712            ValueSymbol {
2713                name: "create".to_string(),
2714                mangled_name: crate::mangle::package_symbol("@mcp/issues", "create"),
2715                declaration_span: Span::at(crate::FileId::MCP),
2716                kind: ValueKind::Function {
2717                    generics: Vec::new(),
2718                    params: vec![Param::new("title", Type::String)],
2719                    ret: Type::Unknown,
2720                    type_predicate: None,
2721                    doc: None,
2722                },
2723            },
2724        );
2725
2726        let map = compile_with_dependencies(
2727            &[(
2728                "lib",
2729                r#"
2730                    import { readText } from "submilli:fs";
2731                    import issues from "@mcp/issues";
2732                    import { send } from "@vendor/queue";
2733                    export class Client {
2734                        static run(): void {
2735                            readText("/workspace/input.txt");
2736                            issues.create("bug");
2737                            send("urgent");
2738                        }
2739                    }
2740                "#,
2741            )],
2742            &[&dependency, &mcp],
2743        );
2744        let route = map
2745            .routes
2746            .iter()
2747            .find(|route| route.callable.contains("#static#run"))
2748            .expect("public static route");
2749        let capabilities = route
2750            .effects
2751            .iter()
2752            .filter_map(|effect| effect.effect.capability.as_deref())
2753            .collect::<BTreeSet<_>>();
2754        assert!(capabilities.contains("fs.read"), "{capabilities:?}");
2755        assert!(capabilities.contains("mcp.issues"), "{capabilities:?}");
2756        assert!(capabilities.contains("vendor.send"), "{capabilities:?}");
2757    }
2758
2759    #[test]
2760    fn accessor_reads_and_writes_link_to_getter_and_setter_bodies() {
2761        let map = compile(&[(
2762            "lib",
2763            r#"
2764                import { get } from "submilli:secrets";
2765                class Vault {
2766                    get secret(): string { return get("READ_SECRET")!; }
2767                    set secret(value: string) { get("WRITE_SECRET"); }
2768                }
2769                export function run(): string {
2770                    const vault = new Vault();
2771                    vault.secret = "replacement";
2772                    return vault.secret;
2773                }
2774            "#,
2775        )]);
2776        let route = route(&map, "#run");
2777        let names = route
2778            .effects
2779            .iter()
2780            .filter_map(|effect| effect.effect.known_bindings.get("name"))
2781            .collect::<BTreeSet<_>>();
2782        assert!(names.contains(&"\"READ_SECRET\"".to_string()), "{names:?}");
2783        assert!(names.contains(&"\"WRITE_SECRET\"".to_string()), "{names:?}");
2784        assert_eq!(
2785            route
2786                .effects
2787                .iter()
2788                .filter(|effect| effect.effect.capability.is_some())
2789                .count(),
2790            2,
2791            "{route:#?}"
2792        );
2793    }
2794
2795    #[test]
2796    fn implicit_constructor_includes_parent_initialization_effects() {
2797        let map = compile(&[(
2798            "lib",
2799            r#"
2800                import { get } from "submilli:secrets";
2801                class Base { constructor() { get("PARENT"); } }
2802                class Child extends Base {}
2803                export function run(): void { new Child(); }
2804            "#,
2805        )]);
2806        assert!(route(&map, "#run").effects.iter().any(|effect| {
2807            effect.effect.capability.as_deref() == Some("secrets.get")
2808                && effect.effect.known_bindings.get("name") == Some(&"\"PARENT\"".to_string())
2809        }));
2810    }
2811
2812    #[test]
2813    fn virtual_dispatch_is_uncertain_and_inherited_public_methods_are_routes() {
2814        let map = compile(&[(
2815            "lib",
2816            r#"
2817                import { get } from "submilli:secrets";
2818                export class Base {
2819                    run(): void {}
2820                }
2821                class Child extends Base { run(): void { get("OVERRIDE"); } }
2822                class Hidden {
2823                    static readonly action: (() => void) | null = (): void => { get("STATIC"); };
2824                    inherited(): void { get("INHERITED"); }
2825                }
2826                export class Exposed extends Hidden {}
2827                export function dispatch(value: Base): void { value.run(); }
2828            "#,
2829        )]);
2830        assert!(route(&map, "#dispatch").effects.iter().any(|effect| {
2831            effect.effect.reason.as_deref() == Some("virtual method target may be overridden")
2832        }));
2833        assert!(route(&map, "#inherited").effects.iter().any(|effect| {
2834            effect.effect.known_bindings.get("name") == Some(&"\"INHERITED\"".to_string())
2835        }));
2836        assert!(
2837            map.routes.iter().any(|route| {
2838                route.effects.iter().any(|effect| {
2839                    effect.effect.known_bindings.get("name") == Some(&"\"STATIC\"".to_string())
2840                        || effect.effect.reason.as_deref()
2841                            == Some("static function field target is not directly recoverable")
2842                })
2843            }),
2844            "{map:#?}"
2845        );
2846    }
2847
2848    #[test]
2849    fn exported_and_returned_function_values_become_public_routes() {
2850        let map = compile(&[(
2851            "lib",
2852            r#"
2853                import { get } from "submilli:secrets";
2854                function helper(): void { get("ALIAS"); }
2855                export const alias = helper;
2856                function nested(): () => void {
2857                    return () => { get("RETURNED"); };
2858                }
2859                export function make(): () => () => void { return nested; }
2860                export const expressionMake = (): (() => void) =>
2861                    () => { get("EXPRESSION"); };
2862                function choose(): (() => void) | null {
2863                    return () => { get("INDIRECT"); };
2864                }
2865                export const indirect: (() => void) | null = choose();
2866            "#,
2867        )]);
2868        assert!(route(&map, "#helper").effects.iter().any(|effect| {
2869            effect.effect.known_bindings.get("name") == Some(&"\"ALIAS\"".to_string())
2870        }));
2871        assert!(map.routes.iter().any(|route| {
2872            route.callable.starts_with("closure:")
2873                && route.effects.iter().any(|effect| {
2874                    effect.effect.known_bindings.get("name") == Some(&"\"RETURNED\"".to_string())
2875                })
2876        }));
2877        assert!(map.routes.iter().any(|route| {
2878            route.callable.starts_with("closure:")
2879                && route.effects.iter().any(|effect| {
2880                    effect.effect.known_bindings.get("name") == Some(&"\"EXPRESSION\"".to_string())
2881                })
2882        }));
2883        assert!(map.routes.iter().any(|route| {
2884            route.callable.starts_with("global-function:")
2885                && route.effects.iter().any(|effect| {
2886                    effect.effect.reason.as_deref()
2887                        == Some("function-valued global target is not directly recoverable")
2888                })
2889        }));
2890    }
2891
2892    #[test]
2893    fn rebindable_function_globals_remain_uncertain() {
2894        let map = compile(&[(
2895            "lib",
2896            r#"
2897                import { get } from "submilli:secrets";
2898                let handler: () => void = (): void => {};
2899                export function install(): void {
2900                    handler = (): void => { get("REBOUND"); };
2901                }
2902                export function run(): void { handler(); }
2903            "#,
2904        )]);
2905        assert!(route(&map, "#run").effects.iter().any(|effect| {
2906            effect.effect.reason.as_deref() == Some("function-valued global may be rebound")
2907        }));
2908    }
2909
2910    #[test]
2911    fn public_instance_function_fields_are_routes() {
2912        let map = compile(&[(
2913            "lib",
2914            r#"
2915                import { get } from "submilli:secrets";
2916                class Base {
2917                    readonly inherited: (() => void) | null = (): void => { get("INHERITED_FIELD"); };
2918                }
2919                export class Child extends Base {
2920                    own: () => void = (): void => { get("OWN_FIELD"); };
2921                }
2922            "#,
2923        )]);
2924        for binding in ["\"INHERITED_FIELD\"", "\"OWN_FIELD\""] {
2925            assert!(map.routes.iter().any(|route| {
2926                route.effects.iter().any(|effect| {
2927                    effect.effect.known_bindings.get("name") == Some(&binding.to_string())
2928                })
2929            }));
2930        }
2931        assert!(map.routes.iter().any(|route| {
2932            route.effects.iter().any(|effect| {
2933                effect.effect.reason.as_deref() == Some("public function field may be reassigned")
2934            })
2935        }));
2936    }
2937
2938    #[test]
2939    fn optional_inherited_accessor_links_to_the_getter() {
2940        let map = compile(&[(
2941            "lib",
2942            r#"
2943                import { get } from "submilli:secrets";
2944                class Base { get secret(): string { return get("OPTIONAL")!; } }
2945                export class Child extends Base {}
2946                export function read(child: Child | null): string {
2947                    return child?.secret ?? "";
2948                }
2949            "#,
2950        )]);
2951        assert!(route(&map, "#read").effects.iter().any(|effect| {
2952            effect.effect.known_bindings.get("name") == Some(&"\"OPTIONAL\"".to_string())
2953        }));
2954    }
2955
2956    #[test]
2957    fn known_direct_interface_methods_are_not_uncertain() {
2958        let map = compile(&[(
2959            "lib",
2960            r#"
2961                export function append(): number {
2962                    const values = [1];
2963                    values.push(2);
2964                    return values.length;
2965                }
2966            "#,
2967        )]);
2968        assert!(route(&map, "#append").effects.is_empty(), "{map:#?}");
2969    }
2970
2971    #[test]
2972    fn optional_chain_advances_after_calls_and_structural_properties_are_uncertain() {
2973        let map = compile(&[(
2974            "lib",
2975            r#"
2976                import { get } from "submilli:secrets";
2977                export class Vault {
2978                    get secret(): string { return get("CHAINED")!; }
2979                }
2980                export class Factory { make(): Vault { return new Vault(); } }
2981                export interface Shaped { readonly property: string; }
2982                export function read(factory: Factory | null): string {
2983                    return factory?.make().secret ?? "";
2984                }
2985                export function readShaped(value: Shaped): string {
2986                    return value.property;
2987                }
2988            "#,
2989        )]);
2990        assert!(route(&map, "#read").effects.iter().any(|effect| {
2991            effect.effect.known_bindings.get("name") == Some(&"\"CHAINED\"".to_string())
2992        }));
2993        assert!(route(&map, "#readShaped").effects.iter().any(|effect| {
2994            effect.effect.reason.as_deref() == Some("structural property may invoke an accessor")
2995        }));
2996    }
2997
2998    #[test]
2999    fn external_inheritance_surfaces_and_super_calls_are_uncertain() {
3000        let dependency_source = [PackageSourceModule {
3001            path: ModulePath::from("lib"),
3002            source: r#"
3003                export class Parent {
3004                    constructor() {}
3005                    /** @capability vendor.send { queue: $queue, region: "us" } */
3006                    send(queue: string): void {}
3007                    inherited(): void {}
3008                    get visible(): string { return "visible"; }
3009                    private get hidden(): string { return "hidden"; }
3010                }
3011            "#,
3012        }];
3013        let dependency = compile_package(
3014            "@vendor/base",
3015            ModulePath::from("lib"),
3016            &dependency_source,
3017            &[],
3018        )
3019        .expect("dependency compiles")
3020        .declaration;
3021        let map = compile_with_dependencies(
3022            &[(
3023                "lib",
3024                r#"
3025                    import { Parent } from "@vendor/base";
3026                    export class Child extends Parent {
3027                        constructor() { super(); }
3028                    }
3029                    export function read(parent: Parent): string { return parent.visible; }
3030                "#,
3031            )],
3032            &[&dependency],
3033        );
3034        assert!(map.routes.iter().any(|route| {
3035            route.callable.starts_with("inherited:")
3036                && route.effects.iter().any(|effect| {
3037                    effect.effect.reason.as_deref()
3038                        == Some("external inherited member body is not locally analyzable")
3039                })
3040        }));
3041        assert!(map.routes.iter().any(|route| {
3042            route.callable.starts_with("inherited:")
3043                && route.effects.iter().any(|effect| {
3044                    effect.effect.capability.as_deref() == Some("vendor.send")
3045                        && effect.effect.unresolved
3046                        && effect.effect.known_bindings.get("region") == Some(&"\"us\"".to_string())
3047                })
3048        }));
3049        assert!(
3050            !map.routes
3051                .iter()
3052                .any(|route| route.callable.contains("hidden"))
3053        );
3054        assert!(route(&map, "#constructor").effects.iter().any(|effect| {
3055            effect.effect.reason.as_deref() == Some("external super-constructor target")
3056        }));
3057        assert!(route(&map, "#read").effects.iter().any(|effect| {
3058            effect.effect.reason.as_deref()
3059                == Some("external accessor body is not locally analyzable")
3060        }));
3061    }
3062
3063    #[test]
3064    fn analysis_limits_are_typed_compiler_failures_with_source_spans() {
3065        let span = Span::new(crate::FileId(7), 10, 20).expect("span");
3066        let error = check_limit(1, 1, span, "edge").expect_err("limit failure");
3067        assert!(matches!(
3068            error,
3069            CompilerFailure::Limit {
3070                stage: CompilerStage::Infer,
3071                span: Some(actual),
3072                ..
3073            } if actual == span
3074        ));
3075        assert!(matches!(
3076            checked_authority_budget(1, 1, 1, span, "propagation work"),
3077            Err(CompilerFailure::Limit {
3078                stage: CompilerStage::Infer,
3079                span: Some(actual),
3080                ..
3081            }) if actual == span
3082        ));
3083    }
3084}