diff --git a/src/fun/check/mod.rs b/src/fun/check/mod.rs index 7a210bd4..e54bdbd3 100644 --- a/src/fun/check/mod.rs +++ b/src/fun/check/mod.rs @@ -1,3 +1,4 @@ pub mod set_entrypoint; pub mod shared_names; +pub mod unbound_refs; pub mod unbound_vars; diff --git a/src/fun/check/unbound_refs.rs b/src/fun/check/unbound_refs.rs new file mode 100644 index 00000000..74f8d480 --- /dev/null +++ b/src/fun/check/unbound_refs.rs @@ -0,0 +1,37 @@ +use crate::{ + diagnostics::Diagnostics, + fun::{Ctx, Definitions, Name, Term}, + maybe_grow, +}; +use std::collections::HashSet; + +impl Ctx<'_> { + pub fn check_unbound_refs(&mut self) -> Result<(), Diagnostics> { + self.info.start_pass(); + for def in self.book.defs.values() { + let mut unbounds = HashSet::new(); + for rule in def.rules.iter() { + rule.body.check_unbound_refs(&self.book.defs, &mut unbounds); + } + for unbound in unbounds { + self.info.add_rule_error(format!("Reference to undefined function '{unbound}'"), def.name.clone()); + } + } + self.info.fatal(()) + } +} + +impl Term { + pub fn check_unbound_refs(&self, defs: &Definitions, unbounds: &mut HashSet) { + maybe_grow(|| { + if let Term::Ref { nam } = self { + if !defs.contains_key(nam) { + unbounds.insert(nam.clone()); + } + } + for child in self.children() { + child.check_unbound_refs(defs, unbounds); + } + }) + } +} diff --git a/src/fun/mod.rs b/src/fun/mod.rs index 60ccaf46..164295d0 100644 --- a/src/fun/mod.rs +++ b/src/fun/mod.rs @@ -36,7 +36,7 @@ impl Ctx<'_> { #[derive(Debug, Clone, Default)] pub struct Book { /// The function definitions. - pub defs: IndexMap, + pub defs: Definitions, /// The algebraic datatypes defined by the program pub adts: Adts, @@ -48,6 +48,7 @@ pub struct Book { pub entrypoint: Option, } +pub type Definitions = IndexMap; pub type Adts = IndexMap; pub type Constructors = IndexMap; diff --git a/src/lib.rs b/src/lib.rs index d2fbf23b..b6900e01 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -140,6 +140,8 @@ pub fn desugar_book( ctx.book.float_combinators(MAX_NET_SIZE); } + ctx.check_unbound_refs()?; + ctx.prune(opts.prune); if opts.merge { diff --git a/tests/golden_tests/hangs/bad_dup_interaction.bend b/tests/golden_tests/hangs/bad_dup_interaction.bend new file mode 100644 index 00000000..21a3d669 --- /dev/null +++ b/tests/golden_tests/hangs/bad_dup_interaction.bend @@ -0,0 +1,11 @@ +# A net that expands inifinitely like church exponentiation +# Compiles to this net: +# a +# & ({(b c) (d b)} (c d)) ~ {(e a) e} +main = + let {a1 a2} = + @b + let {b1 b2} = b + let (c1, c2) = b2 + ((b1 c2), c1) + (a1 a2) \ No newline at end of file diff --git a/tests/golden_tests/run_file/unbound_wrap.bend b/tests/golden_tests/run_file/unbound_wrap.bend new file mode 100644 index 00000000..b21d4a79 --- /dev/null +++ b/tests/golden_tests/run_file/unbound_wrap.bend @@ -0,0 +1,10 @@ +type Maybe = (Some x) | None + +Maybe/bind val nxt = match val { + Maybe/Some: (nxt val.x) + Maybe/None: Maybe/None +} + +main = with Maybe { + (wrap 1) +} \ No newline at end of file diff --git a/tests/snapshots/run_file__unbound_wrap.bend.snap b/tests/snapshots/run_file__unbound_wrap.bend.snap new file mode 100644 index 00000000..a154a25f --- /dev/null +++ b/tests/snapshots/run_file__unbound_wrap.bend.snap @@ -0,0 +1,7 @@ +--- +source: tests/golden_tests.rs +input_file: tests/golden_tests/run_file/unbound_wrap.bend +--- +Errors: +In definition 'main': + Reference to undefined function 'Maybe/wrap'