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