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