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