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