1use std::collections::HashSet;
38
39use crate::analysis::unify::TypeError;
40use crate::analysis::{TypeDef, TypeRegistry};
41use crate::ast::stmt::{
42 ClosureBody, Expr, MatchArm, Pattern, ReadSource, SelectBranch, Stmt, StringPart,
43};
44use crate::intern::{Interner, Symbol};
45
46pub fn check_unknown_calls(
49 stmts: &[Stmt],
50 interner: &Interner,
51 registry: &TypeRegistry,
52) -> Result<(), TypeError> {
53 check_unknown_calls_with_exports(stmts, interner, registry, &HashSet::new())
54}
55
56pub fn check_unknown_calls_with_exports(
60 stmts: &[Stmt],
61 interner: &Interner,
62 registry: &TypeRegistry,
63 exports: &HashSet<String>,
64) -> Result<(), TypeError> {
65 let walk = harvest_calls(stmts);
66
67 let mut variants: HashSet<Symbol> = HashSet::new();
68 for (_, def) in registry.iter_types() {
69 if let TypeDef::Enum { variants: vs, .. } = def {
70 for v in vs {
71 variants.insert(v.name);
72 }
73 }
74 }
75
76 for site in &walk.calls {
77 let called = &site.function;
78 if walk.known.contains(called) || variants.contains(called) {
79 continue;
80 }
81 let name = interner.resolve(*called);
82 if matches!(name, "show" | "args" | "loaded") {
84 continue;
85 }
86 if crate::semantics::builtins::builtin_from_name(name).is_some() {
87 continue;
88 }
89 if exports.contains(name) {
90 continue;
91 }
92 return Err(TypeError::UnknownFunction { name: *called });
93 }
94 Ok(())
95}
96
97pub fn refuse_retired_calls(stmts: &[Stmt], interner: &Interner) -> Result<(), String> {
103 let walk = harvest_calls(stmts);
104 for site in &walk.calls {
105 if walk.known.contains(&site.function) {
106 continue;
107 }
108 if let Some(refusal) =
109 crate::semantics::builtins::retired_builtin(interner.resolve(site.function))
110 {
111 return Err(refusal.to_string());
112 }
113 }
114 Ok(())
115}
116
117pub fn proven_module_exports(proven: &str) -> HashSet<String> {
122 proven
123 .lines()
124 .filter_map(|line| {
125 let rest = line.trim_start().strip_prefix("pub fn ")?;
126 let end = rest
127 .find(|c: char| !c.is_alphanumeric() && c != '_')
128 .unwrap_or(rest.len());
129 (end > 0).then(|| rest[..end].to_string())
130 })
131 .collect()
132}
133
134pub(crate) struct CallSite<'a> {
138 pub(crate) function: Symbol,
139 pub(crate) args: &'a [&'a Expr<'a>],
140}
141
142#[derive(Default)]
148pub(crate) struct Walk<'a> {
149 pub(crate) calls: Vec<CallSite<'a>>,
150 pub(crate) known: HashSet<Symbol>,
151 pub(crate) value_refs: HashSet<Symbol>,
152}
153
154pub(crate) fn harvest_calls<'a>(stmts: &'a [Stmt<'a>]) -> Walk<'a> {
158 let mut walk = Walk::default();
159 walk.walk_stmts(stmts);
160 walk
161}
162
163impl<'a> Walk<'a> {
164 fn walk_stmts(&mut self, stmts: &'a [Stmt<'a>]) {
165 for stmt in stmts {
166 self.walk_stmt(stmt);
167 }
168 }
169
170 fn bind_pattern(&mut self, pattern: &Pattern) {
171 match pattern {
172 Pattern::Identifier(sym) => {
173 self.known.insert(*sym);
174 }
175 Pattern::Tuple(syms) => {
176 for sym in syms {
177 self.known.insert(*sym);
178 }
179 }
180 }
181 }
182
183 fn walk_stmt(&mut self, stmt: &'a Stmt<'a>) {
184 match stmt {
185 Stmt::ComponentDef(def) => {
187 for a in &def.actions {
188 self.known.insert(a.name);
189 }
190 for f in &def.state {
191 self.walk_expr(f.init);
192 }
193 for ev in &def.events {
194 self.walk_stmts(ev.body);
195 }
196 for a in &def.actions {
197 self.walk_stmts(a.body);
198 }
199 if let Some(view) = &def.view {
200 let (view_exprs, view_blocks) = view.exprs_and_blocks();
201 for e in view_exprs {
202 self.walk_expr(e);
203 }
204 for b in view_blocks {
205 self.walk_stmts(b);
206 }
207 }
208 }
209 Stmt::DirectiveDef {
210 name,
211 body,
212 removed,
213 ..
214 } => {
215 self.known.insert(*name);
216 self.walk_stmts(body);
217 if let Some(r) = removed {
218 self.walk_stmts(r);
219 }
220 }
221 Stmt::RouteDef { body, .. } => match &**body {
222 logicaffeine_language::ast::component::RouteBody::Statements(b) => {
223 self.walk_stmts(b)
224 }
225 logicaffeine_language::ast::component::RouteBody::View(view) => {
226 let (view_exprs, view_blocks) = view.exprs_and_blocks();
227 for e in view_exprs {
228 self.walk_expr(e);
229 }
230 for b in view_blocks {
231 self.walk_stmts(b);
232 }
233 }
234 },
235 Stmt::MiddlewareDef { body, .. } => self.walk_stmts(body),
236 Stmt::RedirectDef { .. } => {}
237 Stmt::ServeFetches => {}
239 Stmt::Replay { actions } => {
240 for a in actions {
241 self.known.insert(*a);
242 }
243 }
244 Stmt::GivenFetch {
245 url, status, body, ..
246 } => {
247 self.walk_expr(url);
248 self.walk_expr(status);
249 self.walk_expr(body);
250 }
251 Stmt::Fetch {
252 url, body, then, ..
253 } => {
254 self.known.insert(*then);
255 self.walk_expr(url);
256 if let Some(b) = body {
257 self.walk_expr(b);
258 }
259 }
260 Stmt::Ui(u) => {
261 for x in u.exprs() {
262 self.walk_expr(x);
263 }
264 }
265 Stmt::Let { var, value, .. } => {
266 self.known.insert(*var);
267 self.walk_expr(value);
268 }
269 Stmt::Set { target, value } => {
270 self.known.insert(*target);
271 self.walk_expr(value);
272 }
273 Stmt::Swap { a, b } => {
274 self.known.insert(*a);
275 self.known.insert(*b);
276 }
277 Stmt::Call { function, args } => {
278 self.calls.push(CallSite {
279 function: *function,
280 args,
281 });
282 for arg in args {
283 self.walk_expr(arg);
284 }
285 }
286 Stmt::If {
287 cond,
288 then_block,
289 else_block,
290 } => {
291 self.walk_expr(cond);
292 self.walk_stmts(then_block);
293 if let Some(else_b) = else_block {
294 self.walk_stmts(else_b);
295 }
296 }
297 Stmt::While {
298 cond,
299 body,
300 decreasing,
301 } => {
302 self.walk_expr(cond);
303 self.walk_stmts(body);
304 if let Some(d) = decreasing {
305 self.walk_expr(d);
306 }
307 }
308 Stmt::Repeat {
309 pattern,
310 iterable,
311 body,
312 } => {
313 self.bind_pattern(pattern);
314 self.walk_expr(iterable);
315 self.walk_stmts(body);
316 }
317 Stmt::Return { value } => {
318 if let Some(v) = value {
319 self.walk_expr(v);
320 }
321 }
322 Stmt::Break => {}
323 Stmt::Assert { .. } | Stmt::Trust { .. } => {}
325 Stmt::RuntimeAssert {
326 condition, reason, ..
327 } => {
328 self.walk_expr(condition);
329 if let Some(r) = reason {
330 self.walk_expr(r);
331 }
332 }
333 Stmt::Fail { reason } => self.walk_expr(reason),
334 Stmt::Give { object, recipient } | Stmt::Show { object, recipient } => {
335 self.walk_expr(object);
336 self.walk_expr(recipient);
337 }
338 Stmt::SetField { object, value, .. } => {
339 self.walk_expr(object);
340 self.walk_expr(value);
341 }
342 Stmt::StructDef { .. } => {}
343 Stmt::FunctionDef {
344 name, params, body, ..
345 } => {
346 self.known.insert(*name);
347 for (param, _) in params {
348 self.known.insert(*param);
349 }
350 self.walk_stmts(body);
351 }
352 Stmt::TestDef { cases, body, .. } => {
353 if let Some(table) = cases {
354 for column in &table.columns {
355 self.known.insert(*column);
356 }
357 for row in &table.rows {
358 for cell in row {
359 self.walk_expr(cell);
360 }
361 }
362 }
363 self.walk_stmts(body);
364 }
365 Stmt::Expect { condition } => self.walk_expr(condition),
366 Stmt::ExpectFail { body, .. } => self.walk_stmts(body),
367 Stmt::ExpectOutput { .. } => {}
368 Stmt::AdvanceTime { millis } => self.walk_expr(millis),
369 Stmt::GivenFile { .. } => {}
370 Stmt::Observe { payloads, .. } => {
371 for (_, value) in payloads {
372 self.walk_expr(value);
373 }
374 }
375 Stmt::During { body, .. } => self.walk_stmts(body),
376 Stmt::ForAll {
377 vars,
378 constraint,
379 body,
380 ..
381 } => {
382 for var in vars {
383 self.known.insert(*var);
384 }
385 if let Some(c) = constraint {
386 self.walk_expr(c);
387 }
388 self.walk_stmts(body);
389 }
390 Stmt::Inspect { target, arms, .. } => {
391 self.walk_expr(target);
392 for MatchArm { bindings, body, .. } in arms {
393 for (_, binding) in bindings {
394 self.known.insert(*binding);
395 }
396 self.walk_stmts(body);
397 }
398 }
399 Stmt::Push { value, collection }
400 | Stmt::Add { value, collection }
401 | Stmt::Remove { value, collection } => {
402 self.walk_expr(value);
403 self.walk_expr(collection);
404 }
405 Stmt::Pop { collection, into } => {
406 if let Some(var) = into {
407 self.known.insert(*var);
408 }
409 self.walk_expr(collection);
410 }
411 Stmt::SetIndex {
412 collection,
413 index,
414 value,
415 } => {
416 self.walk_expr(collection);
417 self.walk_expr(index);
418 self.walk_expr(value);
419 }
420 Stmt::Splice { body } | Stmt::Reversibly { body } => self.walk_stmts(body),
421 Stmt::Zone { name, body, .. } => {
422 self.known.insert(*name);
423 self.walk_stmts(body);
424 }
425 Stmt::Concurrent { tasks } | Stmt::Parallel { tasks } => self.walk_stmts(tasks),
426 Stmt::ReadFrom { var, source } => {
427 self.known.insert(*var);
428 if let ReadSource::File(path) = source {
429 self.walk_expr(path);
430 }
431 }
432 Stmt::WriteFile { content, path } => {
433 self.walk_expr(content);
434 self.walk_expr(path);
435 }
436 Stmt::Spawn { .. } => {}
437 Stmt::SendMessage {
438 message,
439 destination,
440 ..
441 } => {
442 self.walk_expr(message);
443 self.walk_expr(destination);
444 }
445 Stmt::AwaitMessage { source, into, .. } => {
446 self.known.insert(*into);
447 self.walk_expr(source);
448 }
449 Stmt::StreamMessage {
450 values,
451 destination,
452 } => {
453 self.walk_expr(values);
454 self.walk_expr(destination);
455 }
456 Stmt::MergeCrdt { source, target } | Stmt::JoinCrdt { source, target } => {
457 self.walk_expr(source);
458 self.walk_expr(target);
459 }
460 Stmt::IncreaseCrdt { object, amount, .. }
461 | Stmt::DecreaseCrdt { object, amount, .. } => {
462 self.walk_expr(object);
463 self.walk_expr(amount);
464 }
465 Stmt::AppendToSequence { sequence, value } => {
466 self.walk_expr(sequence);
467 self.walk_expr(value);
468 }
469 Stmt::ResolveConflict { object, value, .. } => {
470 self.walk_expr(object);
471 self.walk_expr(value);
472 }
473 Stmt::Check { .. } => {}
474 Stmt::Listen { address, secure } | Stmt::ConnectTo { address, secure } => {
475 self.walk_expr(address);
476 if let Some(pad) = secure {
477 self.walk_expr(pad.pad);
478 }
479 }
480 Stmt::Serve { port, handler } => {
481 self.walk_expr(port);
482 self.walk_expr(handler);
483 }
484 Stmt::LetPeerAgent { var, address } => {
485 self.known.insert(*var);
486 self.walk_expr(address);
487 }
488 Stmt::Sleep { milliseconds } => self.walk_expr(milliseconds),
489 Stmt::Sync { var, topic } => {
490 self.known.insert(*var);
491 self.walk_expr(topic);
492 }
493 Stmt::Mount { var, path } => {
494 self.known.insert(*var);
495 self.walk_expr(path);
496 }
497 Stmt::LaunchTask { args, .. } => {
500 for arg in args {
501 self.walk_expr(arg);
502 }
503 }
504 Stmt::LaunchTaskWithHandle { handle, args, .. } => {
505 self.known.insert(*handle);
506 for arg in args {
507 self.walk_expr(arg);
508 }
509 }
510 Stmt::CreatePipe { var, .. } => {
511 self.known.insert(*var);
512 }
513 Stmt::SendPipe { value, pipe } => {
514 self.walk_expr(value);
515 self.walk_expr(pipe);
516 }
517 Stmt::ReceivePipe { var, pipe } => {
518 self.known.insert(*var);
519 self.walk_expr(pipe);
520 }
521 Stmt::TrySendPipe {
522 value,
523 pipe,
524 result,
525 } => {
526 if let Some(var) = result {
527 self.known.insert(*var);
528 }
529 self.walk_expr(value);
530 self.walk_expr(pipe);
531 }
532 Stmt::TryReceivePipe { var, pipe } => {
533 self.known.insert(*var);
534 self.walk_expr(pipe);
535 }
536 Stmt::StopTask { handle } => self.walk_expr(handle),
537 Stmt::CloseChannel { channel } => self.walk_expr(channel),
538 Stmt::AwaitTask { handle, result } => {
539 if let Some(var) = result {
540 self.known.insert(*var);
541 }
542 self.walk_expr(handle);
543 }
544 Stmt::Select { branches } => {
545 for branch in branches {
546 match branch {
547 SelectBranch::Receive { var, pipe, body } => {
548 self.known.insert(*var);
549 self.walk_expr(pipe);
550 self.walk_stmts(body);
551 }
552 SelectBranch::Timeout { milliseconds, body } => {
553 self.walk_expr(milliseconds);
554 self.walk_stmts(body);
555 }
556 }
557 }
558 }
559 Stmt::Theorem(_) | Stmt::Definition(_) | Stmt::Axiom(_) | Stmt::Theory(_) => {}
560 Stmt::Escape { .. } => {}
561 Stmt::Require { .. } => {}
562 Stmt::Site(_) => {}
564 }
565 }
566
567 fn walk_expr(&mut self, expr: &'a Expr<'a>) {
568 match expr {
569 Expr::Dom(q) => {
570 if let Some(sel) = q.selector() {
571 self.walk_expr(sel);
572 }
573 }
574 Expr::View(v) => {
575 let (view_exprs, view_blocks) = v.exprs_and_blocks();
576 for e in view_exprs {
577 self.walk_expr(e);
578 }
579 for b in view_blocks {
580 self.walk_stmts(b);
581 }
582 }
583 Expr::Literal(_) | Expr::OptionNone | Expr::Escape { .. } => {}
584 Expr::Identifier(sym) => {
585 self.value_refs.insert(*sym);
586 }
587 Expr::BinaryOp { left, right, .. }
588 | Expr::Union { left, right }
589 | Expr::Intersection { left, right } => {
590 self.walk_expr(left);
591 self.walk_expr(right);
592 }
593 Expr::Not { operand } => self.walk_expr(operand),
594 Expr::Call { function, args } => {
595 self.calls.push(CallSite {
596 function: *function,
597 args,
598 });
599 for arg in args {
600 self.walk_expr(arg);
601 }
602 }
603 Expr::Index { collection, index } => {
604 self.walk_expr(collection);
605 self.walk_expr(index);
606 }
607 Expr::Slice {
608 collection,
609 start,
610 end,
611 } => {
612 self.walk_expr(collection);
613 self.walk_expr(start);
614 self.walk_expr(end);
615 }
616 Expr::Copy { expr } => self.walk_expr(expr),
617 Expr::Give { value } => self.walk_expr(value),
618 Expr::Length { collection } => self.walk_expr(collection),
619 Expr::Contains { collection, value } => {
620 self.walk_expr(collection);
621 self.walk_expr(value);
622 }
623 Expr::ManifestOf { zone } => self.walk_expr(zone),
624 Expr::ChunkAt { index, zone } => {
625 self.walk_expr(index);
626 self.walk_expr(zone);
627 }
628 Expr::List(items) | Expr::Tuple(items) => {
629 for item in items {
630 self.walk_expr(item);
631 }
632 }
633 Expr::Range { start, end } => {
634 self.walk_expr(start);
635 self.walk_expr(end);
636 }
637 Expr::FieldAccess { object, .. } => self.walk_expr(object),
638 Expr::New { init_fields, .. } => {
639 for (_, value) in init_fields {
640 self.walk_expr(value);
641 }
642 }
643 Expr::NewVariant { fields, .. } => {
644 for (_, value) in fields {
645 self.walk_expr(value);
646 }
647 }
648 Expr::OptionSome { value } => self.walk_expr(value),
649 Expr::WithCapacity { value, capacity } => {
650 self.walk_expr(value);
651 self.walk_expr(capacity);
652 }
653 Expr::Closure { params, body, .. } => {
654 for (param, _) in params {
655 self.known.insert(*param);
656 }
657 match body {
658 ClosureBody::Expression(e) => self.walk_expr(e),
659 ClosureBody::Block(stmts) => self.walk_stmts(stmts),
660 }
661 }
662 Expr::CallExpr { callee, args } => {
663 self.walk_expr(callee);
664 for arg in args {
665 self.walk_expr(arg);
666 }
667 }
668 Expr::InterpolatedString(parts) => {
669 for part in parts {
670 if let StringPart::Expr { value, .. } = part {
671 self.walk_expr(value);
672 }
673 }
674 }
675 }
676 }
677}
678
679#[cfg(test)]
680#[path = "../../../logicaffeine_tests/unit/logicaffeine_compile/analysis/known_calls/tests.rs"]
681mod tests;