1use std::collections::HashSet;
19
20use crate::ast::{Expr, MatchArm, Pattern, Spanned, StrPart, TailCallData};
21
22pub fn pattern_binding_names(pattern: &Pattern) -> Vec<String> {
26 let mut out = Vec::new();
27 collect_pattern_bindings(pattern, &mut out);
28 out
29}
30
31fn collect_pattern_bindings(pattern: &Pattern, out: &mut Vec<String>) {
32 match pattern {
33 Pattern::Ident(name) => out.push(name.clone()),
34 Pattern::Cons(head, tail) => {
35 out.push(head.clone());
36 out.push(tail.clone());
37 }
38 Pattern::Tuple(items) => {
39 for item in items {
40 collect_pattern_bindings(item, out);
41 }
42 }
43 Pattern::Constructor(_, binders) => {
44 for name in binders {
45 out.push(name.clone());
46 }
47 }
48 Pattern::Wildcard | Pattern::Literal(_) | Pattern::EmptyList => {}
49 }
50}
51
52pub fn rewrite_idents_scoped<F>(expr: &Spanned<Expr>, mut rewrite: F) -> Spanned<Expr>
65where
66 F: FnMut(&str) -> Option<Spanned<Expr>>,
67{
68 let scope = HashSet::new();
69 rewrite_inner(expr, &scope, &mut rewrite)
70}
71
72fn rewrite_inner<F>(expr: &Spanned<Expr>, scope: &HashSet<String>, rewrite: &mut F) -> Spanned<Expr>
73where
74 F: FnMut(&str) -> Option<Spanned<Expr>>,
75{
76 let line = expr.line;
77 match &expr.node {
78 Expr::Ident(name) => {
79 if scope.contains(name) {
80 Spanned::new(expr.node.clone(), line)
81 } else {
82 rewrite(name).unwrap_or_else(|| Spanned::new(expr.node.clone(), line))
83 }
84 }
85 Expr::Resolved { name, .. } => {
86 if scope.contains(name) {
87 Spanned::new(expr.node.clone(), line)
88 } else {
89 rewrite(name).unwrap_or_else(|| Spanned::new(expr.node.clone(), line))
90 }
91 }
92 Expr::Literal(_) => Spanned::new(expr.node.clone(), line),
93 Expr::Attr(inner, field) => Spanned::new(
94 Expr::Attr(
95 Box::new(rewrite_inner(inner, scope, rewrite)),
96 field.clone(),
97 ),
98 line,
99 ),
100 Expr::FnCall(callee, args) => Spanned::new(
101 Expr::FnCall(
102 Box::new(rewrite_inner(callee, scope, rewrite)),
103 args.iter()
104 .map(|a| rewrite_inner(a, scope, rewrite))
105 .collect(),
106 ),
107 line,
108 ),
109 Expr::BinOp(op, l, r) => Spanned::new(
110 Expr::BinOp(
111 *op,
112 Box::new(rewrite_inner(l, scope, rewrite)),
113 Box::new(rewrite_inner(r, scope, rewrite)),
114 ),
115 line,
116 ),
117 Expr::Neg(inner) => Spanned::new(
118 Expr::Neg(Box::new(rewrite_inner(inner, scope, rewrite))),
119 line,
120 ),
121 Expr::Match { subject, arms } => {
122 let new_subject = Box::new(rewrite_inner(subject, scope, rewrite));
123 let new_arms = arms
124 .iter()
125 .map(|arm| {
126 let shadowed = pattern_binding_names(&arm.pattern);
127 if shadowed.is_empty() {
128 MatchArm::new(
129 arm.pattern.clone(),
130 rewrite_inner(&arm.body, scope, rewrite),
131 )
132 } else {
133 let mut extended = scope.clone();
134 for name in shadowed {
135 extended.insert(name);
136 }
137 MatchArm::new(
138 arm.pattern.clone(),
139 rewrite_inner(&arm.body, &extended, rewrite),
140 )
141 }
142 })
143 .collect();
144 Spanned::new(
145 Expr::Match {
146 subject: new_subject,
147 arms: new_arms,
148 },
149 line,
150 )
151 }
152 Expr::Constructor(name, payload) => Spanned::new(
153 Expr::Constructor(
154 name.clone(),
155 payload
156 .as_ref()
157 .map(|inner| Box::new(rewrite_inner(inner, scope, rewrite))),
158 ),
159 line,
160 ),
161 Expr::ErrorProp(inner) => Spanned::new(
162 Expr::ErrorProp(Box::new(rewrite_inner(inner, scope, rewrite))),
163 line,
164 ),
165 Expr::InterpolatedStr(parts) => Spanned::new(
166 Expr::InterpolatedStr(
167 parts
168 .iter()
169 .map(|part| match part {
170 StrPart::Literal(s) => StrPart::Literal(s.clone()),
171 StrPart::Parsed(inner) => {
172 StrPart::Parsed(Box::new(rewrite_inner(inner, scope, rewrite)))
173 }
174 })
175 .collect(),
176 ),
177 line,
178 ),
179 Expr::List(items) => Spanned::new(
180 Expr::List(
181 items
182 .iter()
183 .map(|i| rewrite_inner(i, scope, rewrite))
184 .collect(),
185 ),
186 line,
187 ),
188 Expr::Tuple(items) => Spanned::new(
189 Expr::Tuple(
190 items
191 .iter()
192 .map(|i| rewrite_inner(i, scope, rewrite))
193 .collect(),
194 ),
195 line,
196 ),
197 Expr::IndependentProduct(items, flag) => Spanned::new(
198 Expr::IndependentProduct(
199 items
200 .iter()
201 .map(|i| rewrite_inner(i, scope, rewrite))
202 .collect(),
203 *flag,
204 ),
205 line,
206 ),
207 Expr::MapLiteral(entries) => Spanned::new(
208 Expr::MapLiteral(
209 entries
210 .iter()
211 .map(|(k, v)| {
212 (
213 rewrite_inner(k, scope, rewrite),
214 rewrite_inner(v, scope, rewrite),
215 )
216 })
217 .collect(),
218 ),
219 line,
220 ),
221 Expr::RecordCreate { type_name, fields } => Spanned::new(
222 Expr::RecordCreate {
223 type_name: type_name.clone(),
224 fields: fields
225 .iter()
226 .map(|(n, v)| (n.clone(), rewrite_inner(v, scope, rewrite)))
227 .collect(),
228 },
229 line,
230 ),
231 Expr::RecordUpdate {
232 type_name,
233 base,
234 updates,
235 } => Spanned::new(
236 Expr::RecordUpdate {
237 type_name: type_name.clone(),
238 base: Box::new(rewrite_inner(base, scope, rewrite)),
239 updates: updates
240 .iter()
241 .map(|(n, v)| (n.clone(), rewrite_inner(v, scope, rewrite)))
242 .collect(),
243 },
244 line,
245 ),
246 Expr::TailCall(data) => Spanned::new(
247 Expr::TailCall(Box::new(TailCallData::new(
248 data.target.clone(),
249 data.args
250 .iter()
251 .map(|a| rewrite_inner(a, scope, rewrite))
252 .collect(),
253 ))),
254 line,
255 ),
256 }
257}
258
259#[cfg(test)]
260mod tests {
261 use super::*;
262 use crate::ast::{BinOp, Literal};
263
264 fn bare(e: Expr) -> Spanned<Expr> {
265 Spanned::new(e, 1)
266 }
267
268 fn int(n: i64) -> Spanned<Expr> {
269 bare(Expr::Literal(Literal::Int(n)))
270 }
271
272 fn ident(s: &str) -> Spanned<Expr> {
273 bare(Expr::Ident(s.to_string()))
274 }
275
276 #[test]
277 fn pattern_shadowing_leaves_inner_bound_ident_alone() {
278 let e = bare(Expr::Match {
280 subject: Box::new(bare(Expr::Constructor(
281 "Option.Some".to_string(),
282 Some(Box::new(int(2))),
283 ))),
284 arms: vec![
285 MatchArm::new(
286 Pattern::Constructor("Option.Some".to_string(), vec!["x".to_string()]),
287 ident("x"),
288 ),
289 MatchArm::new(
290 Pattern::Constructor("Option.None".to_string(), vec![]),
291 int(0),
292 ),
293 ],
294 });
295 let out = rewrite_idents_scoped(&e, |n| if n == "x" { Some(int(1)) } else { None });
296 let Expr::Match { arms, .. } = &out.node else {
297 panic!("expected Match");
298 };
299 assert!(
300 matches!(&arms[0].body.node, Expr::Ident(s) if s == "x"),
301 "pattern-bound x should not be substituted: {:?}",
302 arms[0].body.node
303 );
304 }
305
306 #[test]
307 fn tuple_pattern_shadowing() {
308 let e = bare(Expr::Match {
309 subject: Box::new(bare(Expr::Tuple(vec![int(1), int(2)]))),
310 arms: vec![MatchArm::new(
311 Pattern::Tuple(vec![
312 Pattern::Ident("a".to_string()),
313 Pattern::Ident("b".to_string()),
314 ]),
315 bare(Expr::BinOp(
316 BinOp::Add,
317 Box::new(ident("a")),
318 Box::new(ident("b")),
319 )),
320 )],
321 });
322 let out = rewrite_idents_scoped(&e, |n| if n == "a" { Some(int(99)) } else { None });
323 let Expr::Match { arms, .. } = &out.node else {
324 panic!();
325 };
326 let Expr::BinOp(_, l, _) = &arms[0].body.node else {
327 panic!();
328 };
329 assert!(
330 matches!(&l.node, Expr::Ident(s) if s == "a"),
331 "tuple-pattern `a` should shadow outer substitution: {:?}",
332 l.node
333 );
334 }
335
336 #[test]
337 fn rewrites_in_non_shadowed_arm() {
338 let e = bare(Expr::Match {
339 subject: Box::new(int(42)),
340 arms: vec![MatchArm::new(Pattern::Wildcard, ident("x"))],
341 });
342 let out = rewrite_idents_scoped(&e, |n| if n == "x" { Some(int(7)) } else { None });
343 let Expr::Match { arms, .. } = &out.node else {
344 panic!();
345 };
346 assert!(matches!(&arms[0].body.node, Expr::Literal(Literal::Int(7))));
347 }
348
349 #[test]
350 fn cons_pattern_shadows_head_and_tail() {
351 let e = bare(Expr::Match {
352 subject: Box::new(bare(Expr::List(vec![int(1), int(2)]))),
353 arms: vec![MatchArm::new(
354 Pattern::Cons("h".to_string(), "t".to_string()),
355 bare(Expr::Tuple(vec![ident("h"), ident("t"), ident("z")])),
356 )],
357 });
358 let out = rewrite_idents_scoped(&e, |n| match n {
359 "h" => Some(int(100)),
360 "t" => Some(int(200)),
361 "z" => Some(int(300)),
362 _ => None,
363 });
364 let Expr::Match { arms, .. } = &out.node else {
365 panic!();
366 };
367 let Expr::Tuple(items) = &arms[0].body.node else {
368 panic!();
369 };
370 assert!(matches!(&items[0].node, Expr::Ident(s) if s == "h"));
372 assert!(matches!(&items[1].node, Expr::Ident(s) if s == "t"));
373 assert!(matches!(&items[2].node, Expr::Literal(Literal::Int(300))));
374 }
375}