1use std::sync::Arc;
4
5use rustc_hash::FxHashSet;
6
7use crate::{Expr, Pattern};
8
9#[must_use]
11pub fn free_vars(expr: &Expr) -> FxHashSet<Arc<str>> {
12 let mut vars = FxHashSet::default();
13 collect_free(expr, &mut FxHashSet::default(), &mut vars);
14 vars
15}
16
17fn collect_free(expr: &Expr, bound: &mut FxHashSet<Arc<str>>, free: &mut FxHashSet<Arc<str>>) {
27 match expr {
28 Expr::Var(name) => {
29 if !bound.contains(name) {
30 free.insert(Arc::clone(name));
31 }
32 }
33 Expr::Lam(param, body) => {
34 let newly_bound = bound.insert(Arc::clone(param));
35 collect_free(body, bound, free);
36 if newly_bound {
37 bound.remove(param);
38 }
39 }
40 Expr::App(func, arg) => {
41 collect_free(func, bound, free);
42 collect_free(arg, bound, free);
43 }
44 Expr::Lit(_) => {}
45 Expr::Record(fields) => {
46 for (_, v) in fields {
47 collect_free(v, bound, free);
48 }
49 }
50 Expr::List(items) => {
51 for item in items {
52 collect_free(item, bound, free);
53 }
54 }
55 Expr::Field(expr, _) => collect_free(expr, bound, free),
56 Expr::Index(expr, idx) => {
57 collect_free(expr, bound, free);
58 collect_free(idx, bound, free);
59 }
60 Expr::Match { scrutinee, arms } => {
61 collect_free(scrutinee, bound, free);
62 for (pat, body) in arms {
63 let pat_vars = pattern_vars(pat);
64 let mut inserted = Vec::new();
65 for v in &pat_vars {
66 if bound.insert(Arc::clone(v)) {
67 inserted.push(Arc::clone(v));
68 }
69 }
70 collect_free(body, bound, free);
71 for v in &inserted {
72 bound.remove(v);
73 }
74 }
75 }
76 Expr::Let { name, value, body } => {
77 collect_free(value, bound, free);
78 let newly_bound = bound.insert(Arc::clone(name));
79 collect_free(body, bound, free);
80 if newly_bound {
81 bound.remove(name);
82 }
83 }
84 Expr::Builtin(_, args) => {
85 for arg in args {
86 collect_free(arg, bound, free);
87 }
88 }
89 }
90}
91
92#[must_use]
94pub fn pattern_vars(pat: &Pattern) -> Vec<Arc<str>> {
95 let mut vars = Vec::new();
96 collect_pattern_vars(pat, &mut vars);
97 vars
98}
99
100fn collect_pattern_vars(pat: &Pattern, vars: &mut Vec<Arc<str>>) {
101 match pat {
102 Pattern::Wildcard | Pattern::Lit(_) => {}
103 Pattern::Var(name) => vars.push(Arc::clone(name)),
104 Pattern::Record(fields) => {
105 for (_, p) in fields {
106 collect_pattern_vars(p, vars);
107 }
108 }
109 Pattern::List(items) => {
110 for p in items {
111 collect_pattern_vars(p, vars);
112 }
113 }
114 Pattern::Constructor(_, args) => {
115 for p in args {
116 collect_pattern_vars(p, vars);
117 }
118 }
119 }
120}
121
122fn rename_pattern_var(pat: &Pattern, from: &str, to: &Arc<str>) -> Pattern {
129 match pat {
130 Pattern::Wildcard | Pattern::Lit(_) => pat.clone(),
131 Pattern::Var(v) => {
132 if &**v == from {
133 Pattern::Var(Arc::clone(to))
134 } else {
135 pat.clone()
136 }
137 }
138 Pattern::Record(fields) => Pattern::Record(
139 fields
140 .iter()
141 .map(|(k, p)| (Arc::clone(k), rename_pattern_var(p, from, to)))
142 .collect(),
143 ),
144 Pattern::List(items) => Pattern::List(
145 items
146 .iter()
147 .map(|p| rename_pattern_var(p, from, to))
148 .collect(),
149 ),
150 Pattern::Constructor(ctor, args) => Pattern::Constructor(
151 Arc::clone(ctor),
152 args.iter()
153 .map(|p| rename_pattern_var(p, from, to))
154 .collect(),
155 ),
156 }
157}
158
159fn rename_avoid_set(body: &Expr, name: &str, replacement: &Expr) -> FxHashSet<Arc<str>> {
166 let mut avoid = free_vars(replacement);
167 avoid.extend(free_vars(body));
168 avoid.insert(Arc::from(name));
169 avoid
170}
171
172#[must_use]
180pub fn substitute(expr: &Expr, name: &str, replacement: &Expr) -> Expr {
181 match expr {
182 Expr::Var(v) => {
183 if &**v == name {
184 replacement.clone()
185 } else {
186 expr.clone()
187 }
188 }
189 Expr::Lam(param, body) => {
190 if &**param == name {
191 expr.clone()
193 } else if free_vars(replacement).contains(param) {
194 let fresh = fresh_name(param, &rename_avoid_set(body, name, replacement));
196 let renamed_body = substitute(body, param, &Expr::Var(Arc::clone(&fresh)));
197 Expr::Lam(
198 fresh,
199 Box::new(substitute(&renamed_body, name, replacement)),
200 )
201 } else {
202 Expr::Lam(
203 Arc::clone(param),
204 Box::new(substitute(body, name, replacement)),
205 )
206 }
207 }
208 Expr::App(func, arg) => Expr::App(
209 Box::new(substitute(func, name, replacement)),
210 Box::new(substitute(arg, name, replacement)),
211 ),
212 Expr::Lit(_) => expr.clone(),
213 Expr::Record(fields) => Expr::Record(
214 fields
215 .iter()
216 .map(|(k, v)| (Arc::clone(k), substitute(v, name, replacement)))
217 .collect(),
218 ),
219 Expr::List(items) => Expr::List(
220 items
221 .iter()
222 .map(|i| substitute(i, name, replacement))
223 .collect(),
224 ),
225 Expr::Field(e, f) => Expr::Field(Box::new(substitute(e, name, replacement)), Arc::clone(f)),
226 Expr::Index(e, idx) => Expr::Index(
227 Box::new(substitute(e, name, replacement)),
228 Box::new(substitute(idx, name, replacement)),
229 ),
230 Expr::Match { scrutinee, arms } => Expr::Match {
231 scrutinee: Box::new(substitute(scrutinee, name, replacement)),
232 arms: arms
233 .iter()
234 .map(|(pat, body)| substitute_match_arm(pat, body, name, replacement))
235 .collect(),
236 },
237 Expr::Let {
238 name: let_name,
239 value,
240 body,
241 } => {
242 let new_value = substitute(value, name, replacement);
243 if &**let_name == name {
244 Expr::Let {
246 name: Arc::clone(let_name),
247 value: Box::new(new_value),
248 body: body.clone(),
249 }
250 } else if free_vars(replacement).contains(let_name) {
251 let fresh = fresh_name(let_name, &rename_avoid_set(body, name, replacement));
253 let renamed_body = substitute(body, let_name, &Expr::Var(Arc::clone(&fresh)));
254 Expr::Let {
255 name: fresh,
256 value: Box::new(new_value),
257 body: Box::new(substitute(&renamed_body, name, replacement)),
258 }
259 } else {
260 Expr::Let {
261 name: Arc::clone(let_name),
262 value: Box::new(new_value),
263 body: Box::new(substitute(body, name, replacement)),
264 }
265 }
266 }
267 Expr::Builtin(op, args) => Expr::Builtin(
268 *op,
269 args.iter()
270 .map(|a| substitute(a, name, replacement))
271 .collect(),
272 ),
273 }
274}
275
276fn substitute_match_arm(
282 pat: &Pattern,
283 body: &Expr,
284 name: &str,
285 replacement: &Expr,
286) -> (Pattern, Expr) {
287 let pvars = pattern_vars(pat);
288 if pvars.iter().any(|v| &**v == name) {
289 return (pat.clone(), body.clone());
290 }
291 let replacement_free = free_vars(replacement);
292 let mut avoid = rename_avoid_set(body, name, replacement);
293 avoid.extend(pvars.iter().cloned());
294 let mut new_pat = pat.clone();
295 let mut new_body = body.clone();
296 for v in &pvars {
297 if !replacement_free.contains(v) {
298 continue;
299 }
300 let fresh = fresh_name(v, &avoid);
301 avoid.insert(Arc::clone(&fresh));
302 new_pat = rename_pattern_var(&new_pat, v, &fresh);
303 new_body = substitute(&new_body, v, &Expr::Var(Arc::clone(&fresh)));
304 }
305 (new_pat, substitute(&new_body, name, replacement))
306}
307
308fn fresh_name(base: &str, avoid: &FxHashSet<Arc<str>>) -> Arc<str> {
310 let mut candidate = format!("{base}'");
311 while avoid.contains(candidate.as_str()) {
312 candidate.push('\'');
313 }
314 Arc::from(candidate)
315}
316
317#[cfg(test)]
318mod tests {
319 use super::*;
320 use crate::eval::EvalConfig;
321 use crate::{Env, Literal};
322
323 #[test]
324 fn free_vars_simple() {
325 let expr = Expr::lam(
327 "x",
328 Expr::builtin(crate::BuiltinOp::Add, vec![Expr::var("x"), Expr::var("y")]),
329 );
330 let fv = free_vars(&expr);
331 assert!(fv.contains("y"));
332 assert!(!fv.contains("x"));
333 }
334
335 #[test]
336 fn substitute_simple() {
337 let expr = Expr::builtin(
339 crate::BuiltinOp::Add,
340 vec![Expr::var("x"), Expr::Lit(Literal::Int(1))],
341 );
342 let result = substitute(&expr, "x", &Expr::Lit(Literal::Int(42)));
343 assert_eq!(
344 result,
345 Expr::builtin(
346 crate::BuiltinOp::Add,
347 vec![Expr::Lit(Literal::Int(42)), Expr::Lit(Literal::Int(1))],
348 )
349 );
350 }
351
352 #[test]
353 fn substitute_avoids_capture() {
354 let expr = Expr::lam(
357 "y",
358 Expr::builtin(crate::BuiltinOp::Add, vec![Expr::var("x"), Expr::var("y")]),
359 );
360 let result = substitute(&expr, "x", &Expr::var("y"));
361 match &result {
363 Expr::Lam(param, _) => assert_ne!(&**param, "y"),
364 _ => panic!("expected Lam"),
365 }
366 }
367
368 #[test]
369 fn substitute_shadowed_by_let() {
370 let expr = Expr::let_in(
374 "x",
375 Expr::Lit(Literal::Int(1)),
376 Expr::builtin(crate::BuiltinOp::Add, vec![Expr::var("x"), Expr::var("y")]),
377 );
378 let result = substitute(&expr, "x", &Expr::Lit(Literal::Int(99)));
379 match &result {
380 Expr::Let { value, body, .. } => {
381 assert_eq!(**value, Expr::Lit(Literal::Int(1)));
383 assert!(
385 matches!(body.as_ref(), Expr::Builtin(_, args) if matches!(&args[0], Expr::Var(v) if &**v == "x"))
386 );
387 }
388 _ => panic!("expected Let"),
389 }
390 }
391
392 #[test]
393 fn free_vars_lambda_does_not_leak_into_siblings() {
394 let expr = Expr::Record(vec![
396 (Arc::from("f"), Expr::lam("x", Expr::var("x"))),
397 (Arc::from("g"), Expr::var("x")),
398 ]);
399 let fv = free_vars(&expr);
400 assert!(
401 fv.contains("x"),
402 "sibling occurrence of x is free, got {fv:?}"
403 );
404 }
405
406 #[test]
407 fn free_vars_respects_shadowed_lambda_binder() {
408 let expr = Expr::lam(
410 "x",
411 Expr::app(Expr::lam("x", Expr::var("x")), Expr::var("x")),
412 );
413 let fv = free_vars(&expr);
414 assert!(fv.is_empty(), "expression is closed, got {fv:?}");
415 }
416
417 #[test]
418 fn free_vars_let_does_not_leak_into_siblings() {
419 let expr = Expr::Record(vec![
421 (
422 Arc::from("f"),
423 Expr::let_in("x", Expr::Lit(Literal::Int(1)), Expr::var("x")),
424 ),
425 (Arc::from("g"), Expr::var("x")),
426 ]);
427 let fv = free_vars(&expr);
428 assert!(
429 fv.contains("x"),
430 "sibling occurrence of x is free, got {fv:?}"
431 );
432 }
433
434 #[test]
435 fn free_vars_respects_shadowed_let_binder() {
436 let expr = Expr::lam(
438 "x",
439 Expr::let_in("x", Expr::Lit(Literal::Int(1)), Expr::var("x")),
440 );
441 let fv = free_vars(&expr);
442 assert!(fv.is_empty(), "expression is closed, got {fv:?}");
443 }
444
445 #[test]
446 fn substitute_avoids_capture_under_let() {
447 let expr = Expr::let_in("x", Expr::Lit(Literal::Int(1)), Expr::var("z"));
449 let result = substitute(&expr, "z", &Expr::var("x"));
450 let env = Env::new().extend(Arc::from("x"), Literal::Int(42));
451 let Ok(value) = crate::eval::eval(&result, &env, &EvalConfig::default()) else {
452 panic!("substituted expression must evaluate, got {result:?}");
453 };
454 assert_eq!(value, Literal::Int(42), "got {result:?}");
455 }
456
457 #[test]
458 fn substitute_avoids_capture_under_match_arm() {
459 let expr = Expr::Match {
461 scrutinee: Box::new(Expr::Lit(Literal::Int(1))),
462 arms: vec![(Pattern::Var(Arc::from("x")), Expr::var("z"))],
463 };
464 let result = substitute(&expr, "z", &Expr::var("x"));
465 let env = Env::new().extend(Arc::from("x"), Literal::Int(42));
466 let Ok(value) = crate::eval::eval(&result, &env, &EvalConfig::default()) else {
467 panic!("substituted expression must evaluate, got {result:?}");
468 };
469 assert_eq!(value, Literal::Int(42), "got {result:?}");
470 }
471
472 #[test]
473 fn substitute_fresh_name_avoids_body_free_variables() {
474 let expr = Expr::lam(
477 "y",
478 Expr::builtin(
479 crate::BuiltinOp::Add,
480 vec![
481 Expr::var("y'"),
482 Expr::builtin(crate::BuiltinOp::Add, vec![Expr::var("x"), Expr::var("y")]),
483 ],
484 ),
485 );
486 let result = substitute(&expr, "x", &Expr::var("y"));
487 match &result {
488 Expr::Lam(param, _) => {
489 assert_ne!(&**param, "y", "binder must be renamed");
490 assert_ne!(&**param, "y'", "binder must not capture the body's y'");
491 }
492 other => panic!("expected Lam, got {other:?}"),
493 }
494 }
495}