1use 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#[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
177pub 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 #[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}