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::VirtualCall { target, tool, .. } => {
1726                let tool_binding = ("tool".to_string(), format!("\"{tool}\""));
1727                let (capability, known_bindings) = match target {
1728                    crate::VirtualDispatch::Mcp { server } => (
1729                        format!("mcp.{server}"),
1730                        BTreeMap::from([
1731                            tool_binding,
1732                            ("transport".to_string(), "\"streamable_http\"".to_string()),
1733                        ]),
1734                    ),
1735                    crate::VirtualDispatch::Tools => (
1736                        crate::runtime::TOOLS_CAPABILITY.to_string(),
1737                        BTreeMap::from([tool_binding]),
1738                    ),
1739                };
1740                self.builder.push_effect(
1741                    self.caller,
1742                    AuthorityEffect {
1743                        capability: Some(capability),
1744                        sink: source_span(self.builder.sources, span)?,
1745                        known_bindings,
1746                        unresolved: false,
1747                        reason: None,
1748                    },
1749                    span,
1750                )?;
1751            }
1752            TypedExprKind::FieldAccess { receiver, name }
1753            | TypedExprKind::InterfacePropertyAccess { receiver, name, .. } => {
1754                self.accessor(*receiver, &name.name, false, span)?;
1755            }
1756            TypedExprKind::PostfixUnary {
1757                target: PostfixTarget::Field { receiver, name, .. },
1758                ..
1759            } => {
1760                self.accessor(*receiver, &name.name, false, span)?;
1761                self.accessor(*receiver, &name.name, true, span)?;
1762            }
1763            TypedExprKind::OptionalChain { base, parts } => {
1764                self.optional_chain(*base, parts)?;
1765            }
1766            _ => {}
1767        }
1768        Ok(())
1769    }
1770}
1771
1772impl CallVisitor<'_, '_> {
1773    fn optional_chain(
1774        &mut self,
1775        base: ExprId,
1776        parts: &[TypedChainPart],
1777    ) -> Result<(), CompilerFailure> {
1778        let mut receiver_type = self
1779            .builder
1780            .ta
1781            .try_expr(base)
1782            .map_err(crate::typechecker::arena_failure)?
1783            .ty
1784            .clone();
1785        for part in parts {
1786            match part {
1787                TypedChainPart::MethodCall {
1788                    iface,
1789                    name,
1790                    args,
1791                    result_ty,
1792                    span,
1793                    ..
1794                } => {
1795                    self.method_call(iface, &name.name, args, *span)?;
1796                    receiver_type = result_ty.clone();
1797                }
1798                TypedChainPart::Call {
1799                    result_ty, span, ..
1800                } => {
1801                    self.builder.add_unresolved(
1802                        self.caller,
1803                        *span,
1804                        "dynamic optional-call target",
1805                    )?;
1806                    receiver_type = result_ty.clone();
1807                }
1808                TypedChainPart::Field {
1809                    name,
1810                    result_ty,
1811                    span,
1812                    ..
1813                } => {
1814                    self.accessor_for_type(&receiver_type, &name.name, false, *span)?;
1815                    receiver_type = result_ty.clone();
1816                }
1817                TypedChainPart::InterfaceProperty {
1818                    result_ty, span, ..
1819                } => {
1820                    self.builder.add_unresolved(
1821                        self.caller,
1822                        *span,
1823                        "interface property may invoke an accessor",
1824                    )?;
1825                    receiver_type = result_ty.clone();
1826                }
1827                TypedChainPart::Index { result_ty, .. }
1828                | TypedChainPart::NonNull { result_ty, .. } => {
1829                    receiver_type = result_ty.clone();
1830                }
1831            }
1832        }
1833        Ok(())
1834    }
1835
1836    fn direct_call(
1837        &mut self,
1838        mangled: &MangledName,
1839        args: &[ExprId],
1840        span: Span,
1841    ) -> Result<(), CompilerFailure> {
1842        if crate::stdlib::security::is_check(mangled) {
1843            let capability = match args.first() {
1844                Some(arg) => match &self
1845                    .builder
1846                    .ta
1847                    .try_expr(*arg)
1848                    .map_err(crate::typechecker::arena_failure)?
1849                    .kind
1850                {
1851                    TypedExprKind::String(value) => Some(value.clone()),
1852                    _ => None,
1853                },
1854                None => None,
1855            };
1856            check_limit(
1857                self.builder.check_count,
1858                self.builder.limits.edges,
1859                span,
1860                "check",
1861            )?;
1862            self.builder.check_count += 1;
1863            self.builder.nodes[self.caller]
1864                .callable
1865                .checks
1866                .push(AuthorityCheck {
1867                    span: source_span(self.builder.sources, span)?,
1868                    capability,
1869                });
1870        }
1871        if let Some(&target) = self.builder.named.get(mangled) {
1872            return self.builder.add_edge(self.caller, target, span);
1873        }
1874        if mangled.as_str() == "submilli:http#request" {
1875            return self.dynamic_http(args, span);
1876        }
1877        if let Some(callee) = self.builder.capability_functions.get(mangled).copied() {
1878            self.builder
1879                .add_capability_effects(self.caller, callee, args, span)?;
1880        } else if !self.builder.known_functions.contains(mangled) {
1881            self.builder
1882                .add_unresolved(self.caller, span, "unresolved direct call target")?;
1883        }
1884        Ok(())
1885    }
1886
1887    fn dynamic_http(&mut self, args: &[ExprId], span: Span) -> Result<(), CompilerFailure> {
1888        let method = match args.first() {
1889            Some(id) => self
1890                .builder
1891                .ta
1892                .try_expr(*id)
1893                .map_err(crate::typechecker::arena_failure)?,
1894            None => return self.dynamic_http_effect(None, BTreeMap::new(), span),
1895        };
1896        let method = match &method.kind {
1897            TypedExprKind::String(method) => Some(method.to_ascii_lowercase()),
1898            _ => None,
1899        };
1900        let mut known_bindings = BTreeMap::new();
1901        if let Some(id) = args.get(1) {
1902            let url = self
1903                .builder
1904                .ta
1905                .try_expr(*id)
1906                .map_err(crate::typechecker::arena_failure)?;
1907            if let TypedExprKind::String(url) = &url.kind
1908                && let Ok(parsed) = url::Url::parse(url)
1909            {
1910                if let Some(host) = parsed.host_str() {
1911                    known_bindings.insert("host".to_string(), format!("\"{host}\""));
1912                }
1913                known_bindings.insert("path".to_string(), format!("\"{}\"", parsed.path()));
1914            }
1915        }
1916        self.dynamic_http_effect(method, known_bindings, span)
1917    }
1918
1919    fn dynamic_http_effect(
1920        &mut self,
1921        method: Option<String>,
1922        known_bindings: BTreeMap<String, String>,
1923        span: Span,
1924    ) -> Result<(), CompilerFailure> {
1925        self.builder.push_effect(
1926            self.caller,
1927            AuthorityEffect {
1928                capability: Some(method.as_ref().map_or_else(
1929                    || "http.<method>".to_string(),
1930                    |method| format!("http.{method}"),
1931                )),
1932                sink: source_span(self.builder.sources, span)?,
1933                known_bindings,
1934                unresolved: method.is_none(),
1935                reason: method
1936                    .is_none()
1937                    .then(|| "HTTP method is selected at runtime".to_string()),
1938            },
1939            span,
1940        )?;
1941        Ok(())
1942    }
1943
1944    fn method_call(
1945        &mut self,
1946        iface: &MangledName,
1947        name: &str,
1948        args: &[ExprId],
1949        span: Span,
1950    ) -> Result<(), CompilerFailure> {
1951        let resolved = if let Some(target) = self.builder.local_method(iface, name) {
1952            self.builder.add_edge(self.caller, target, span)?;
1953            true
1954        } else if let Some(callee) = self.builder.capability_method(iface, name) {
1955            self.builder
1956                .add_capability_effects(self.caller, callee, args, span)?;
1957            true
1958        } else {
1959            self.builder.known_method(iface, name)
1960        };
1961        if !resolved {
1962            self.builder
1963                .add_unresolved(self.caller, span, "unresolved method target")?;
1964        } else if self.builder.method_is_dynamic(iface, name) {
1965            self.builder.add_unresolved(
1966                self.caller,
1967                span,
1968                "virtual method target may be overridden",
1969            )?;
1970        }
1971        Ok(())
1972    }
1973
1974    fn super_method_call(
1975        &mut self,
1976        owner: &MangledName,
1977        name: &str,
1978        args: &[ExprId],
1979        span: Span,
1980    ) -> Result<(), CompilerFailure> {
1981        if let Some(&target) = self.builder.methods.get(&(owner.clone(), name.to_string())) {
1982            return self.builder.add_edge(self.caller, target, span);
1983        }
1984        if let Some(callee) = self
1985            .builder
1986            .capability_methods
1987            .get(&(owner.clone(), name.to_string()))
1988            .copied()
1989        {
1990            return self
1991                .builder
1992                .add_capability_effects(self.caller, callee, args, span);
1993        }
1994        if !self
1995            .builder
1996            .known_methods
1997            .contains(&(owner.clone(), name.to_string()))
1998        {
1999            self.builder
2000                .add_unresolved(self.caller, span, "unresolved super-method target")?;
2001        }
2002        Ok(())
2003    }
2004
2005    fn closure_target(&self, id: ExprId) -> Result<Option<usize>, CompilerFailure> {
2006        self.builder.held_callable(id)
2007    }
2008
2009    fn accessor(
2010        &mut self,
2011        receiver: ExprId,
2012        name: &str,
2013        setter: bool,
2014        span: Span,
2015    ) -> Result<(), CompilerFailure> {
2016        let receiver = self
2017            .builder
2018            .ta
2019            .try_expr(receiver)
2020            .map_err(crate::typechecker::arena_failure)?;
2021        self.accessor_for_type(&receiver.ty, name, setter, span)
2022    }
2023
2024    fn accessor_for_type(
2025        &mut self,
2026        receiver: &Type,
2027        name: &str,
2028        setter: bool,
2029        span: Span,
2030    ) -> Result<(), CompilerFailure> {
2031        let receiver = crate::typechecker::infer::narrowing::strip_null(receiver);
2032        let Type::ClassRef { mangled, .. } = receiver.peel() else {
2033            if receiver.is_structural_object() {
2034                self.builder.add_unresolved(
2035                    self.caller,
2036                    span,
2037                    "structural property may invoke an accessor",
2038                )?;
2039            }
2040            return Ok(());
2041        };
2042        self.accessor_for_class(mangled, name, setter, span)
2043    }
2044
2045    fn accessor_for_class(
2046        &mut self,
2047        class: &MangledName,
2048        name: &str,
2049        setter: bool,
2050        span: Span,
2051    ) -> Result<(), CompilerFailure> {
2052        let mut class = class.clone();
2053        for _ in 0..=self.builder.class_hierarchy.len() {
2054            if let Some(&target) =
2055                self.builder
2056                    .accessors
2057                    .get(&(class.clone(), name.to_string(), setter))
2058            {
2059                self.builder.add_edge(self.caller, target, span)?;
2060                self.builder.add_unresolved(
2061                    self.caller,
2062                    span,
2063                    "virtual accessor target may be overridden",
2064                )?;
2065                return Ok(());
2066            }
2067            if self.builder.external_accessor_exists(&class, name, setter) {
2068                self.builder.add_unresolved(
2069                    self.caller,
2070                    span,
2071                    "external accessor body is not locally analyzable",
2072                )?;
2073                return Ok(());
2074            }
2075            let Some(Some(parent)) = self.builder.class_hierarchy.get(&class) else {
2076                return Ok(());
2077            };
2078            class = parent.clone();
2079        }
2080        Ok(())
2081    }
2082}
2083
2084fn exports(ta: &TypedAst, kind: ExportKind) -> BTreeSet<MangledName> {
2085    ta.exports
2086        .iter()
2087        .filter(|entry| entry.kind == kind)
2088        .map(|entry| entry.target.clone())
2089        .collect()
2090}
2091
2092fn public_static_functions(
2093    declaration: &PackageDeclaration,
2094    exported_classes: &BTreeSet<MangledName>,
2095) -> BTreeSet<MangledName> {
2096    declaration
2097        .types
2098        .values()
2099        .filter(|symbol| exported_classes.contains(&symbol.mangled_name))
2100        .flat_map(|symbol| {
2101            let TypeKind::Class {
2102                statics,
2103                static_visibility,
2104                ..
2105            } = &symbol.kind
2106            else {
2107                return Vec::new();
2108            };
2109            statics
2110                .keys()
2111                .filter(|name| static_visibility.get(*name) != Some(&Visibility::Private))
2112                .map(|name| crate::mangle::static_member(&symbol.mangled_name, name))
2113                .collect()
2114        })
2115        .collect()
2116}
2117
2118fn own_static_names(ta: &TypedAst, class: &MangledName) -> BTreeSet<String> {
2119    let prefix = format!("{class}#static#");
2120    ta.functions
2121        .iter()
2122        .map(|function| &function.mangled_name)
2123        .chain(ta.globals.iter().map(|global| &global.mangled_name))
2124        .filter_map(|mangled| mangled.as_str().strip_prefix(&prefix).map(str::to_string))
2125        .collect()
2126}
2127
2128fn may_be_callable(ty: &Type) -> bool {
2129    match ty.peel() {
2130        Type::Function { .. } => true,
2131        Type::Union(members) => members.iter().any(may_be_callable),
2132        _ => false,
2133    }
2134}
2135
2136fn source_type_symbol<'a>(
2137    declaration: &'a PackageDeclaration,
2138    mangled: &MangledName,
2139) -> Option<&'a crate::TypeSymbol> {
2140    declaration
2141        .types
2142        .values()
2143        .find(|symbol| &symbol.mangled_name == mangled)
2144        .or_else(|| declaration.type_symbol(mangled))
2145}
2146
2147struct PackageClass<'a> {
2148    name: MangledName,
2149    methods: &'a BTreeMap<String, crate::MethodSig>,
2150    statics: &'a BTreeMap<String, crate::MethodSig>,
2151    parent: Option<MangledName>,
2152}
2153
2154struct ExternalClassSurface {
2155    methods: Vec<String>,
2156    accessors: Vec<(String, bool)>,
2157    function_fields: Vec<String>,
2158    statics: Vec<String>,
2159    static_function_fields: Vec<String>,
2160    parent: Option<MangledName>,
2161}
2162
2163impl ExternalClassSurface {
2164    fn member_count(&self) -> usize {
2165        self.methods
2166            .len()
2167            .saturating_add(self.accessors.len())
2168            .saturating_add(self.function_fields.len())
2169            .saturating_add(self.statics.len())
2170            .saturating_add(self.static_function_fields.len())
2171    }
2172}
2173
2174struct InheritedNames {
2175    methods: BTreeSet<String>,
2176    accessors: BTreeSet<(String, bool)>,
2177    statics: BTreeSet<String>,
2178    fields: BTreeSet<String>,
2179}
2180
2181fn external_class_surface(symbol: &crate::TypeSymbol) -> Option<ExternalClassSurface> {
2182    let TypeKind::Class {
2183        fields,
2184        methods,
2185        method_visibility,
2186        accessors,
2187        statics,
2188        static_visibility,
2189        static_fields,
2190        extends,
2191        ..
2192    } = &symbol.kind
2193    else {
2194        return None;
2195    };
2196    Some(ExternalClassSurface {
2197        methods: methods
2198            .keys()
2199            .filter(|name| method_visibility.get(*name) != Some(&Visibility::Private))
2200            .cloned()
2201            .collect(),
2202        accessors: accessors
2203            .iter()
2204            .filter(|accessor| {
2205                let name = match accessor {
2206                    crate::AccessorSig::Getter { name, .. }
2207                    | crate::AccessorSig::Setter { name, .. } => name,
2208                };
2209                fields
2210                    .get(name)
2211                    .is_none_or(|field| field.visibility == Visibility::Public)
2212            })
2213            .map(|accessor| match accessor {
2214                crate::AccessorSig::Getter { name, .. } => (name.clone(), false),
2215                crate::AccessorSig::Setter { name, .. } => (name.clone(), true),
2216            })
2217            .collect(),
2218        function_fields: fields
2219            .iter()
2220            .filter(|(_, field)| {
2221                field.visibility == Visibility::Public && may_be_callable(&field.ty)
2222            })
2223            .map(|(name, _)| name.clone())
2224            .collect(),
2225        statics: statics
2226            .keys()
2227            .filter(|name| static_visibility.get(*name) != Some(&Visibility::Private))
2228            .cloned()
2229            .collect(),
2230        static_function_fields: static_fields
2231            .iter()
2232            .filter(|(_, field)| {
2233                field.visibility == Visibility::Public && may_be_callable(&field.ty)
2234            })
2235            .map(|(name, _)| name.clone())
2236            .collect(),
2237        parent: extends.as_ref().map(|extends| extends.parent.clone()),
2238    })
2239}
2240
2241fn package_classes(declaration: &PackageDeclaration) -> Vec<PackageClass<'_>> {
2242    declaration
2243        .runtime_types
2244        .values()
2245        .chain(declaration.types.values())
2246        .filter_map(|symbol| {
2247            if let TypeKind::Class {
2248                methods,
2249                statics,
2250                extends,
2251                ..
2252            } = &symbol.kind
2253            {
2254                Some(PackageClass {
2255                    name: symbol.mangled_name.clone(),
2256                    methods,
2257                    statics,
2258                    parent: extends.as_ref().map(|parent| parent.parent.clone()),
2259                })
2260            } else {
2261                None
2262            }
2263        })
2264        .collect()
2265}
2266
2267fn source_span(sources: &Sources, span: Span) -> Result<AuthoritySpan, CompilerFailure> {
2268    let source = sources.get(span.file).ok_or_else(|| {
2269        crate::typechecker::invariant_failure("authority span names an unregistered source")
2270    })?;
2271    let (start_line, start_column) = source
2272        .line_index()
2273        .line_col(span.start)
2274        .map_err(|_| crate::typechecker::invariant_failure("authority span start is invalid"))?;
2275    let (end_line, end_column) = source
2276        .line_index()
2277        .line_col(span.end)
2278        .map_err(|_| crate::typechecker::invariant_failure("authority span end is invalid"))?;
2279    Ok(AuthoritySpan {
2280        path: source.path.as_str().to_string(),
2281        start: AuthorityPosition {
2282            byte: span.start,
2283            line: start_line,
2284            column: start_column,
2285        },
2286        end: AuthorityPosition {
2287            byte: span.end,
2288            line: end_line,
2289            column: end_column,
2290        },
2291    })
2292}
2293
2294fn limit(span: Span, message: String) -> CompilerFailure {
2295    CompilerFailure::Limit {
2296        stage: CompilerStage::Infer,
2297        span: Some(span),
2298        message,
2299        help: vec![
2300            "split the package into smaller modules or reduce generated call sites".to_string(),
2301        ],
2302    }
2303}
2304
2305fn check_limit(
2306    current: usize,
2307    maximum: usize,
2308    span: Span,
2309    subject: &str,
2310) -> Result<(), CompilerFailure> {
2311    if current < maximum {
2312        return Ok(());
2313    }
2314    Err(limit(
2315        span,
2316        format!("authority analysis exceeds the {subject} limit of {maximum}"),
2317    ))
2318}
2319
2320fn checked_authority_budget(
2321    current: usize,
2322    additional: usize,
2323    maximum: usize,
2324    span: Span,
2325    subject: &str,
2326) -> Result<usize, CompilerFailure> {
2327    let Some(next) = current.checked_add(additional) else {
2328        return Err(limit(
2329            span,
2330            format!("authority analysis {subject} count overflows the platform limit"),
2331        ));
2332    };
2333    if next > maximum {
2334        return Err(limit(
2335            span,
2336            format!("authority analysis exceeds the {subject} limit of {maximum}"),
2337        ));
2338    }
2339    Ok(next)
2340}
2341
2342fn edge_key(
2343    edge: &RawEdge,
2344    nodes: &[Node],
2345) -> (String, AuthoritySpan, Option<String>, bool, Option<String>) {
2346    (
2347        nodes[edge.caller].callable.id.clone(),
2348        edge.span.clone(),
2349        edge.target.map(|target| nodes[target].callable.id.clone()),
2350        edge.unresolved,
2351        edge.reason.clone(),
2352    )
2353}
2354
2355fn adjacency(count: usize, edges: &[RawEdge]) -> Vec<Vec<(usize, usize)>> {
2356    let mut adjacency = vec![Vec::new(); count];
2357    for (edge_index, edge) in edges.iter().enumerate() {
2358        if let Some(target) = edge.target {
2359            adjacency[edge.caller].push((target, edge_index));
2360        }
2361    }
2362    adjacency
2363}
2364
2365fn component_dag(
2366    component_of: &[usize],
2367    component_count: usize,
2368    edges: &[RawEdge],
2369) -> (Vec<BTreeSet<usize>>, Vec<usize>) {
2370    let mut component_edges = vec![BTreeSet::new(); component_count];
2371    let mut indegree = vec![0usize; component_count];
2372    for edge in edges {
2373        let Some(target) = edge.target else {
2374            continue;
2375        };
2376        let caller_component = component_of[edge.caller];
2377        let target_component = component_of[target];
2378        if caller_component != target_component
2379            && component_edges[caller_component].insert(target_component)
2380        {
2381            indegree[target_component] = indegree[target_component].saturating_add(1);
2382        }
2383    }
2384    let mut ready = indegree
2385        .iter()
2386        .enumerate()
2387        .filter_map(|(component, degree)| (*degree == 0).then_some(component))
2388        .collect::<BTreeSet<_>>();
2389    let mut topo = Vec::with_capacity(component_count);
2390    while let Some(component) = ready.pop_first() {
2391        topo.push(component);
2392        for target in component_edges[component].iter().copied() {
2393            indegree[target] = indegree[target].saturating_sub(1);
2394            if indegree[target] == 0 {
2395                ready.insert(target);
2396            }
2397        }
2398    }
2399    (component_edges, topo)
2400}
2401
2402fn strongly_connected_components(
2403    adjacency: &[Vec<(usize, usize)>],
2404) -> (Vec<usize>, Vec<Vec<usize>>) {
2405    let mut seen = vec![false; adjacency.len()];
2406    let mut finish = Vec::with_capacity(adjacency.len());
2407    for start in 0..adjacency.len() {
2408        if seen[start] {
2409            continue;
2410        }
2411        seen[start] = true;
2412        let mut stack = vec![(start, 0usize)];
2413        while let Some((node, next)) = stack.last_mut() {
2414            if let Some((target, _)) = adjacency[*node].get(*next) {
2415                *next = next.saturating_add(1);
2416                if !seen[*target] {
2417                    seen[*target] = true;
2418                    stack.push((*target, 0));
2419                }
2420            } else {
2421                finish.push(*node);
2422                stack.pop();
2423            }
2424        }
2425    }
2426    let mut reverse = vec![Vec::new(); adjacency.len()];
2427    for (caller, targets) in adjacency.iter().enumerate() {
2428        for (target, _) in targets {
2429            reverse[*target].push(caller);
2430        }
2431    }
2432    reverse
2433        .iter_mut()
2434        .for_each(|sources| sources.sort_unstable());
2435    let mut component_of = vec![usize::MAX; adjacency.len()];
2436    let mut components = Vec::new();
2437    for start in finish.into_iter().rev() {
2438        if component_of[start] != usize::MAX {
2439            continue;
2440        }
2441        let component = components.len();
2442        let mut members = Vec::new();
2443        let mut pending = vec![start];
2444        component_of[start] = component;
2445        while let Some(node) = pending.pop() {
2446            members.push(node);
2447            for source in reverse[node].iter().rev().copied() {
2448                if component_of[source] == usize::MAX {
2449                    component_of[source] = component;
2450                    pending.push(source);
2451                }
2452            }
2453        }
2454        members.sort_unstable();
2455        components.push(members);
2456    }
2457    (component_of, components)
2458}
2459
2460fn witness(
2461    root: usize,
2462    effect: &AuthorityEffect,
2463    adjacency: &[Vec<(usize, usize)>],
2464    edges: &[RawEdge],
2465    nodes: &[Node],
2466    work: &mut usize,
2467    maximum_work: usize,
2468) -> Result<Vec<AuthorityWitnessStep>, CompilerFailure> {
2469    if nodes[root]
2470        .callable
2471        .direct_effects
2472        .binary_search(effect)
2473        .is_ok()
2474    {
2475        return Ok(Vec::new());
2476    }
2477    let traversal_bound = nodes.len().checked_add(edges.len()).ok_or_else(|| {
2478        limit(
2479            nodes[root].raw_span,
2480            "authority witness-work count overflows the platform limit".to_string(),
2481        )
2482    })?;
2483    *work = checked_authority_budget(
2484        *work,
2485        traversal_bound,
2486        maximum_work,
2487        nodes[root].raw_span,
2488        "witness work",
2489    )?;
2490    let mut previous: Vec<Option<(usize, usize)>> = vec![None; nodes.len()];
2491    let mut seen = BTreeSet::from([root]);
2492    let mut pending = VecDeque::from([root]);
2493    let mut found = None;
2494    while let Some(node) = pending.pop_front() {
2495        for (target, edge_index) in &adjacency[node] {
2496            if !seen.insert(*target) {
2497                continue;
2498            }
2499            previous[*target] = Some((node, *edge_index));
2500            if nodes[*target]
2501                .callable
2502                .direct_effects
2503                .binary_search(effect)
2504                .is_ok()
2505            {
2506                found = Some(*target);
2507                break;
2508            }
2509            pending.push_back(*target);
2510        }
2511        if found.is_some() {
2512            break;
2513        }
2514    }
2515    let Some(mut current) = found else {
2516        return Ok(Vec::new());
2517    };
2518    let mut path = Vec::new();
2519    while current != root {
2520        let Some((caller, edge_index)) = previous[current] else {
2521            return Ok(Vec::new());
2522        };
2523        path.push(AuthorityWitnessStep {
2524            caller: nodes[caller].callable.id.clone(),
2525            target: nodes[current].callable.id.clone(),
2526            span: edges[edge_index].span.clone(),
2527        });
2528        current = caller;
2529    }
2530    path.reverse();
2531    Ok(path)
2532}
2533
2534#[cfg(test)]
2535mod tests {
2536    use super::*;
2537    use crate::{
2538        ModulePath, PackageSourceModule, Param, Type, ValueKind, ValueSymbol, compile_package,
2539    };
2540
2541    fn compile(modules: &[(&str, &str)]) -> AuthorityMap {
2542        compile_with_dependencies(modules, &[])
2543    }
2544
2545    fn compile_with_dependencies(
2546        modules: &[(&str, &str)],
2547        dependencies: &[&PackageDeclaration],
2548    ) -> AuthorityMap {
2549        let modules = modules
2550            .iter()
2551            .map(|(path, source)| PackageSourceModule {
2552                path: ModulePath::from(*path),
2553                source,
2554            })
2555            .collect::<Vec<_>>();
2556        compile_package(
2557            "@test/authority",
2558            ModulePath::from("lib"),
2559            &modules,
2560            dependencies,
2561        )
2562        .unwrap_or_else(|diagnostics| panic!("compile failed: {diagnostics:#?}"))
2563        .authority_map
2564    }
2565
2566    #[test]
2567    fn check_sites_are_facts_without_authorization_verdicts() {
2568        let source = r#"
2569import { get } from "submilli:http";
2570import { check as authorize } from "submilli:security";
2571/** Read. @capability acme.read {} */
2572export function checked(): void { authorize("acme.read", {}); get("https://example.com"); }
2573/** Read. @capability acme.read {} */
2574export function swallowed(): void { try { authorize("acme.read", {}); } catch {} get("https://example.com"); }
2575export function unchecked(): void { get("https://example.com"); }
2576"#;
2577        let compiled = compile_package(
2578            "@test/authority",
2579            ModulePath::from("lib"),
2580            &[PackageSourceModule {
2581                path: ModulePath::from("lib"),
2582                source,
2583            }],
2584            &[],
2585        )
2586        .unwrap();
2587        assert!(
2588            compiled
2589                .warnings
2590                .iter()
2591                .all(|warning| !warning.message.contains("public route")
2592                    && !warning.message.contains("successful direct")
2593                    && !warning.message.contains("cannot prove"))
2594        );
2595        for name in ["checked", "swallowed"] {
2596            let callable = compiled
2597                .authority_map
2598                .callables
2599                .iter()
2600                .find(|c| c.name == name)
2601                .unwrap();
2602            assert_eq!(callable.checks.len(), 1);
2603            assert_eq!(callable.checks[0].capability.as_deref(), Some("acme.read"));
2604            assert_eq!(callable.checks[0].span.path, "lib");
2605        }
2606        let json = serde_json::to_value(&compiled.authority_map).unwrap();
2607        assert!(json["routes"].as_array().unwrap().iter().all(|route| {
2608            route["effects"]
2609                .as_array()
2610                .unwrap()
2611                .iter()
2612                .all(|effect| effect.get("guard").is_none())
2613        }));
2614    }
2615
2616    fn route<'a>(map: &'a AuthorityMap, suffix: &str) -> &'a AuthorityRoute {
2617        map.routes
2618            .iter()
2619            .find(|route| route.callable.ends_with(suffix))
2620            .unwrap_or_else(|| panic!("missing route ending in {suffix}: {:#?}", map.routes))
2621    }
2622
2623    #[test]
2624    fn direct_and_recursive_helpers_propagate_one_http_effect_with_a_witness() {
2625        let map = compile(&[(
2626            "lib",
2627            r#"
2628                import { get } from "submilli:http";
2629                function first(url: string): void { second(url); }
2630                function second(url: string): void { if (url !== "") { first(""); } get(url); }
2631                export function fetch(): void { first("https://example.com/data"); }
2632            "#,
2633        )]);
2634        let route = route(&map, "#fetch");
2635        let effect = route
2636            .effects
2637            .iter()
2638            .find(|effect| effect.effect.capability.as_deref() == Some("http.get"))
2639            .expect("transitive HTTP effect");
2640        assert_eq!(effect.witness.len(), 2, "{effect:#?}");
2641        assert!(effect.witness[0].target.ends_with("#first"));
2642        assert!(effect.witness[1].target.ends_with("#second"));
2643    }
2644
2645    #[test]
2646    fn root_reexport_is_public_but_unreachable_private_helper_is_not_a_route() {
2647        let map = compile(&[
2648            ("lib", "export { send } from \"./internal\";"),
2649            (
2650                "internal",
2651                r#"
2652                    import { get } from "submilli:secrets";
2653                    export function send(): void { get("TOKEN"); }
2654                    function hidden(): void { get("HIDDEN"); }
2655                "#,
2656            ),
2657        ]);
2658        assert!(
2659            map.routes
2660                .iter()
2661                .any(|route| route.callable.ends_with("#send"))
2662        );
2663        assert!(
2664            !map.routes
2665                .iter()
2666                .any(|route| route.callable.ends_with("#hidden"))
2667        );
2668        assert!(route(&map, "#send").effects.iter().any(|effect| {
2669            effect.effect.capability.as_deref() == Some("secrets.get")
2670                && effect.effect.known_bindings.get("name") == Some(&"\"TOKEN\"".to_string())
2671        }));
2672    }
2673
2674    #[test]
2675    fn dynamic_closure_and_http_method_are_explicit_uncertainties() {
2676        let map = compile(&[(
2677            "lib",
2678            r#"
2679                import { request } from "submilli:http";
2680                export function run(method: string, callback: () => void): void {
2681                    callback();
2682                    request(method, "https://example.com/v1");
2683                }
2684            "#,
2685        )]);
2686        let route = route(&map, "#run");
2687        assert!(route.effects.iter().any(|effect| {
2688            effect.effect.capability.as_deref() == Some("http.<method>") && effect.effect.unresolved
2689        }));
2690        assert!(route.effects.iter().any(|effect| {
2691            effect.effect.capability.is_none()
2692                && effect.effect.reason.as_deref() == Some("dynamic closure target")
2693        }));
2694        assert!(
2695            map.edges
2696                .iter()
2697                .any(|edge| edge.unresolved && edge.target.is_none())
2698        );
2699    }
2700
2701    #[test]
2702    fn static_filesystem_mcp_and_tagged_dependency_sinks_are_reported() {
2703        let dependency_source = [PackageSourceModule {
2704            path: ModulePath::from("lib"),
2705            source: r#"
2706                /** @capability vendor.send { queue: $queue } */
2707                export function send(queue: string): void {}
2708            "#,
2709        }];
2710        let dependency = compile_package(
2711            "@vendor/queue",
2712            ModulePath::from("lib"),
2713            &dependency_source,
2714            &[],
2715        )
2716        .expect("dependency compiles")
2717        .declaration;
2718
2719        let mut mcp = PackageDeclaration::with_package("@mcp/issues");
2720        mcp.virtual_dispatch = Some(crate::VirtualDispatch::Mcp {
2721            server: "issues".to_string(),
2722        });
2723        mcp.values.insert(
2724            "create".to_string(),
2725            ValueSymbol {
2726                name: "create".to_string(),
2727                mangled_name: crate::mangle::package_symbol("@mcp/issues", "create"),
2728                declaration_span: Span::at(crate::FileId::MCP),
2729                kind: ValueKind::Function {
2730                    generics: Vec::new(),
2731                    params: vec![Param::new("title", Type::String)],
2732                    ret: Type::Unknown,
2733                    type_predicate: None,
2734                    doc: None,
2735                },
2736            },
2737        );
2738
2739        let map = compile_with_dependencies(
2740            &[(
2741                "lib",
2742                r#"
2743                    import { readText } from "submilli:fs";
2744                    import issues from "@mcp/issues";
2745                    import { send } from "@vendor/queue";
2746                    export class Client {
2747                        static run(): void {
2748                            readText("/workspace/input.txt");
2749                            issues.create("bug");
2750                            send("urgent");
2751                        }
2752                    }
2753                "#,
2754            )],
2755            &[&dependency, &mcp],
2756        );
2757        let route = map
2758            .routes
2759            .iter()
2760            .find(|route| route.callable.contains("#static#run"))
2761            .expect("public static route");
2762        let capabilities = route
2763            .effects
2764            .iter()
2765            .filter_map(|effect| effect.effect.capability.as_deref())
2766            .collect::<BTreeSet<_>>();
2767        assert!(capabilities.contains("fs.read"), "{capabilities:?}");
2768        assert!(capabilities.contains("mcp.issues"), "{capabilities:?}");
2769        assert!(capabilities.contains("vendor.send"), "{capabilities:?}");
2770    }
2771
2772    #[test]
2773    fn accessor_reads_and_writes_link_to_getter_and_setter_bodies() {
2774        let map = compile(&[(
2775            "lib",
2776            r#"
2777                import { get } from "submilli:secrets";
2778                class Vault {
2779                    get secret(): string { return get("READ_SECRET")!; }
2780                    set secret(value: string) { get("WRITE_SECRET"); }
2781                }
2782                export function run(): string {
2783                    const vault = new Vault();
2784                    vault.secret = "replacement";
2785                    return vault.secret;
2786                }
2787            "#,
2788        )]);
2789        let route = route(&map, "#run");
2790        let names = route
2791            .effects
2792            .iter()
2793            .filter_map(|effect| effect.effect.known_bindings.get("name"))
2794            .collect::<BTreeSet<_>>();
2795        assert!(names.contains(&"\"READ_SECRET\"".to_string()), "{names:?}");
2796        assert!(names.contains(&"\"WRITE_SECRET\"".to_string()), "{names:?}");
2797        assert_eq!(
2798            route
2799                .effects
2800                .iter()
2801                .filter(|effect| effect.effect.capability.is_some())
2802                .count(),
2803            2,
2804            "{route:#?}"
2805        );
2806    }
2807
2808    #[test]
2809    fn implicit_constructor_includes_parent_initialization_effects() {
2810        let map = compile(&[(
2811            "lib",
2812            r#"
2813                import { get } from "submilli:secrets";
2814                class Base { constructor() { get("PARENT"); } }
2815                class Child extends Base {}
2816                export function run(): void { new Child(); }
2817            "#,
2818        )]);
2819        assert!(route(&map, "#run").effects.iter().any(|effect| {
2820            effect.effect.capability.as_deref() == Some("secrets.get")
2821                && effect.effect.known_bindings.get("name") == Some(&"\"PARENT\"".to_string())
2822        }));
2823    }
2824
2825    #[test]
2826    fn virtual_dispatch_is_uncertain_and_inherited_public_methods_are_routes() {
2827        let map = compile(&[(
2828            "lib",
2829            r#"
2830                import { get } from "submilli:secrets";
2831                export class Base {
2832                    run(): void {}
2833                }
2834                class Child extends Base { run(): void { get("OVERRIDE"); } }
2835                class Hidden {
2836                    static readonly action: (() => void) | null = (): void => { get("STATIC"); };
2837                    inherited(): void { get("INHERITED"); }
2838                }
2839                export class Exposed extends Hidden {}
2840                export function dispatch(value: Base): void { value.run(); }
2841            "#,
2842        )]);
2843        assert!(route(&map, "#dispatch").effects.iter().any(|effect| {
2844            effect.effect.reason.as_deref() == Some("virtual method target may be overridden")
2845        }));
2846        assert!(route(&map, "#inherited").effects.iter().any(|effect| {
2847            effect.effect.known_bindings.get("name") == Some(&"\"INHERITED\"".to_string())
2848        }));
2849        assert!(
2850            map.routes.iter().any(|route| {
2851                route.effects.iter().any(|effect| {
2852                    effect.effect.known_bindings.get("name") == Some(&"\"STATIC\"".to_string())
2853                        || effect.effect.reason.as_deref()
2854                            == Some("static function field target is not directly recoverable")
2855                })
2856            }),
2857            "{map:#?}"
2858        );
2859    }
2860
2861    #[test]
2862    fn exported_and_returned_function_values_become_public_routes() {
2863        let map = compile(&[(
2864            "lib",
2865            r#"
2866                import { get } from "submilli:secrets";
2867                function helper(): void { get("ALIAS"); }
2868                export const alias = helper;
2869                function nested(): () => void {
2870                    return () => { get("RETURNED"); };
2871                }
2872                export function make(): () => () => void { return nested; }
2873                export const expressionMake = (): (() => void) =>
2874                    () => { get("EXPRESSION"); };
2875                function choose(): (() => void) | null {
2876                    return () => { get("INDIRECT"); };
2877                }
2878                export const indirect: (() => void) | null = choose();
2879            "#,
2880        )]);
2881        assert!(route(&map, "#helper").effects.iter().any(|effect| {
2882            effect.effect.known_bindings.get("name") == Some(&"\"ALIAS\"".to_string())
2883        }));
2884        assert!(map.routes.iter().any(|route| {
2885            route.callable.starts_with("closure:")
2886                && route.effects.iter().any(|effect| {
2887                    effect.effect.known_bindings.get("name") == Some(&"\"RETURNED\"".to_string())
2888                })
2889        }));
2890        assert!(map.routes.iter().any(|route| {
2891            route.callable.starts_with("closure:")
2892                && route.effects.iter().any(|effect| {
2893                    effect.effect.known_bindings.get("name") == Some(&"\"EXPRESSION\"".to_string())
2894                })
2895        }));
2896        assert!(map.routes.iter().any(|route| {
2897            route.callable.starts_with("global-function:")
2898                && route.effects.iter().any(|effect| {
2899                    effect.effect.reason.as_deref()
2900                        == Some("function-valued global target is not directly recoverable")
2901                })
2902        }));
2903    }
2904
2905    #[test]
2906    fn rebindable_function_globals_remain_uncertain() {
2907        let map = compile(&[(
2908            "lib",
2909            r#"
2910                import { get } from "submilli:secrets";
2911                let handler: () => void = (): void => {};
2912                export function install(): void {
2913                    handler = (): void => { get("REBOUND"); };
2914                }
2915                export function run(): void { handler(); }
2916            "#,
2917        )]);
2918        assert!(route(&map, "#run").effects.iter().any(|effect| {
2919            effect.effect.reason.as_deref() == Some("function-valued global may be rebound")
2920        }));
2921    }
2922
2923    #[test]
2924    fn public_instance_function_fields_are_routes() {
2925        let map = compile(&[(
2926            "lib",
2927            r#"
2928                import { get } from "submilli:secrets";
2929                class Base {
2930                    readonly inherited: (() => void) | null = (): void => { get("INHERITED_FIELD"); };
2931                }
2932                export class Child extends Base {
2933                    own: () => void = (): void => { get("OWN_FIELD"); };
2934                }
2935            "#,
2936        )]);
2937        for binding in ["\"INHERITED_FIELD\"", "\"OWN_FIELD\""] {
2938            assert!(map.routes.iter().any(|route| {
2939                route.effects.iter().any(|effect| {
2940                    effect.effect.known_bindings.get("name") == Some(&binding.to_string())
2941                })
2942            }));
2943        }
2944        assert!(map.routes.iter().any(|route| {
2945            route.effects.iter().any(|effect| {
2946                effect.effect.reason.as_deref() == Some("public function field may be reassigned")
2947            })
2948        }));
2949    }
2950
2951    #[test]
2952    fn optional_inherited_accessor_links_to_the_getter() {
2953        let map = compile(&[(
2954            "lib",
2955            r#"
2956                import { get } from "submilli:secrets";
2957                class Base { get secret(): string { return get("OPTIONAL")!; } }
2958                export class Child extends Base {}
2959                export function read(child: Child | null): string {
2960                    return child?.secret ?? "";
2961                }
2962            "#,
2963        )]);
2964        assert!(route(&map, "#read").effects.iter().any(|effect| {
2965            effect.effect.known_bindings.get("name") == Some(&"\"OPTIONAL\"".to_string())
2966        }));
2967    }
2968
2969    #[test]
2970    fn known_direct_interface_methods_are_not_uncertain() {
2971        let map = compile(&[(
2972            "lib",
2973            r#"
2974                export function append(): number {
2975                    const values = [1];
2976                    values.push(2);
2977                    return values.length;
2978                }
2979            "#,
2980        )]);
2981        assert!(route(&map, "#append").effects.is_empty(), "{map:#?}");
2982    }
2983
2984    #[test]
2985    fn optional_chain_advances_after_calls_and_structural_properties_are_uncertain() {
2986        let map = compile(&[(
2987            "lib",
2988            r#"
2989                import { get } from "submilli:secrets";
2990                export class Vault {
2991                    get secret(): string { return get("CHAINED")!; }
2992                }
2993                export class Factory { make(): Vault { return new Vault(); } }
2994                export interface Shaped { readonly property: string; }
2995                export function read(factory: Factory | null): string {
2996                    return factory?.make().secret ?? "";
2997                }
2998                export function readShaped(value: Shaped): string {
2999                    return value.property;
3000                }
3001            "#,
3002        )]);
3003        assert!(route(&map, "#read").effects.iter().any(|effect| {
3004            effect.effect.known_bindings.get("name") == Some(&"\"CHAINED\"".to_string())
3005        }));
3006        assert!(route(&map, "#readShaped").effects.iter().any(|effect| {
3007            effect.effect.reason.as_deref() == Some("structural property may invoke an accessor")
3008        }));
3009    }
3010
3011    #[test]
3012    fn external_inheritance_surfaces_and_super_calls_are_uncertain() {
3013        let dependency_source = [PackageSourceModule {
3014            path: ModulePath::from("lib"),
3015            source: r#"
3016                export class Parent {
3017                    constructor() {}
3018                    /** @capability vendor.send { queue: $queue, region: "us" } */
3019                    send(queue: string): void {}
3020                    inherited(): void {}
3021                    get visible(): string { return "visible"; }
3022                    private get hidden(): string { return "hidden"; }
3023                }
3024            "#,
3025        }];
3026        let dependency = compile_package(
3027            "@vendor/base",
3028            ModulePath::from("lib"),
3029            &dependency_source,
3030            &[],
3031        )
3032        .expect("dependency compiles")
3033        .declaration;
3034        let map = compile_with_dependencies(
3035            &[(
3036                "lib",
3037                r#"
3038                    import { Parent } from "@vendor/base";
3039                    export class Child extends Parent {
3040                        constructor() { super(); }
3041                    }
3042                    export function read(parent: Parent): string { return parent.visible; }
3043                "#,
3044            )],
3045            &[&dependency],
3046        );
3047        assert!(map.routes.iter().any(|route| {
3048            route.callable.starts_with("inherited:")
3049                && route.effects.iter().any(|effect| {
3050                    effect.effect.reason.as_deref()
3051                        == Some("external inherited member body is not locally analyzable")
3052                })
3053        }));
3054        assert!(map.routes.iter().any(|route| {
3055            route.callable.starts_with("inherited:")
3056                && route.effects.iter().any(|effect| {
3057                    effect.effect.capability.as_deref() == Some("vendor.send")
3058                        && effect.effect.unresolved
3059                        && effect.effect.known_bindings.get("region") == Some(&"\"us\"".to_string())
3060                })
3061        }));
3062        assert!(
3063            !map.routes
3064                .iter()
3065                .any(|route| route.callable.contains("hidden"))
3066        );
3067        assert!(route(&map, "#constructor").effects.iter().any(|effect| {
3068            effect.effect.reason.as_deref() == Some("external super-constructor target")
3069        }));
3070        assert!(route(&map, "#read").effects.iter().any(|effect| {
3071            effect.effect.reason.as_deref()
3072                == Some("external accessor body is not locally analyzable")
3073        }));
3074    }
3075
3076    #[test]
3077    fn analysis_limits_are_typed_compiler_failures_with_source_spans() {
3078        let span = Span::new(crate::FileId(7), 10, 20).expect("span");
3079        let error = check_limit(1, 1, span, "edge").expect_err("limit failure");
3080        assert!(matches!(
3081            error,
3082            CompilerFailure::Limit {
3083                stage: CompilerStage::Infer,
3084                span: Some(actual),
3085                ..
3086            } if actual == span
3087        ));
3088        assert!(matches!(
3089            checked_authority_budget(1, 1, 1, span, "propagation work"),
3090            Err(CompilerFailure::Limit {
3091                stage: CompilerStage::Infer,
3092                span: Some(actual),
3093                ..
3094            }) if actual == span
3095        ));
3096    }
3097}