• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mattwparas / steel / 17772690385

16 Sep 2025 04:36PM UTC coverage: 43.331% (+0.04%) from 43.289%
17772690385

Pull #519

github

web-flow
Merge 98d4fd22c into 3c10433b9
Pull Request #519: fix a bunch more clippy lints

56 of 123 new or added lines in 30 files covered. (45.53%)

8 existing lines in 3 files now uncovered.

12416 of 28654 relevant lines covered (43.33%)

2985398.75 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

62.6
/crates/steel-core/src/steel_vm/const_evaluation.rs
1
use crate::rvals::{IntoSteelVal, Result, SteelVal};
2
use crate::{
3
    compiler::compiler::OptLevel,
4
    parser::{
5
        ast::{Atom, Begin, Define, LambdaFunction, List, Quote},
6
        span_visitor::get_span,
7
        visitors::{ConsumingVisitor, VisitorMut},
8
    },
9
};
10
use crate::{
11
    parser::{
12
        ast::{ExprKind, If},
13
        interner::InternedString,
14
        kernel::Kernel,
15
        parser::SyntaxObject,
16
        tokens::TokenType,
17
        tryfrom_visitor::TryFromExprKindForSteelVal,
18
    },
19
    rerrs::ErrorKind,
20
    SteelErr,
21
};
22
use std::{
23
    cell::RefCell,
24
    collections::HashSet,
25
    convert::TryFrom,
26
    rc::{Rc, Weak},
27
};
28

29
use crate::values::HashMap;
30
use fxhash::{FxBuildHasher, FxHashSet};
31

32
use steel_parser::span::Span;
33
use steel_parser::tokens::{IntLiteral, RealLiteral};
34

35
use super::cache::MemoizationTable;
36

37
type SharedEnv = Rc<RefCell<ConstantEnv>>;
38

39
struct ConstantEnv {
40
    bindings: HashMap<InternedString, SteelVal, FxBuildHasher>,
41
    used_bindings: HashSet<InternedString, FxBuildHasher>,
42
    non_constant_bound: HashSet<InternedString, FxBuildHasher>,
43
    parent: Option<Weak<RefCell<ConstantEnv>>>,
44
}
45

46
impl ConstantEnv {
47
    fn root(bindings: HashMap<InternedString, SteelVal, FxBuildHasher>) -> Self {
11,472✔
48
        Self {
49
            bindings,
50
            used_bindings: HashSet::default(),
22,944✔
51
            non_constant_bound: HashSet::default(),
11,472✔
52
            parent: None,
53
        }
54
    }
55

56
    fn new_subexpression(parent: Weak<RefCell<ConstantEnv>>) -> Self {
214,610✔
57
        Self {
58
            bindings: HashMap::default(),
429,220✔
59
            used_bindings: HashSet::default(),
429,220✔
60
            non_constant_bound: HashSet::default(),
214,610✔
61
            parent: Some(parent),
214,610✔
62
        }
63
    }
64

65
    fn bind(&mut self, ident: &InternedString, value: SteelVal) {
31,667✔
66
        self.bindings.insert(*ident, value);
126,668✔
67
    }
68

69
    fn bind_non_constant(&mut self, ident: &InternedString) {
1,301,636✔
70
        self.non_constant_bound.insert(*ident);
3,904,908✔
71
    }
72

73
    fn get(&mut self, ident: &InternedString) -> Option<SteelVal> {
7,801,887✔
74
        if self.non_constant_bound.get(ident).is_some() {
23,405,661✔
75
            return None;
1,778,856✔
76
        }
77

78
        let value = self.bindings.get(ident);
79
        if value.is_none() {
80
            self.parent
6,018,731✔
81
                .as_ref()?
82
                .upgrade()
83
                .expect("Constant environment freed early")
84
                .borrow_mut()
85
                .get(ident)
86
        } else {
87
            self.used_bindings.insert(*ident);
4,300✔
88
            value.cloned()
89
        }
90
    }
91

92
    fn _set(&mut self, ident: &InternedString, value: SteelVal) -> Option<SteelVal> {
×
93
        let output = self.bindings.get(ident);
×
94
        if output.is_none() {
×
95
            self.parent
×
96
                .as_ref()?
97
                .upgrade()
98
                .expect("Constant environment freed early")
99
                .borrow_mut()
100
                ._set(ident, value)
×
101
        } else {
102
            self.bindings.insert(*ident, value)
×
103
        }
104
    }
105

106
    fn unbind(&mut self, ident: &InternedString) -> Option<()> {
193,918✔
107
        if self.bindings.get(ident).is_some() {
593,582✔
108
            self.bindings.remove(ident);
47,312✔
109
            self.used_bindings.insert(*ident);
23,656✔
110
        } else {
111
            self.parent
182,090✔
112
                .as_ref()?
113
                .upgrade()
114
                .expect("Constant environment freed early")
115
                .borrow_mut()
116
                .unbind(ident);
117
        }
118
        Some(())
136,904✔
119
    }
120
}
121

122
// Holds the global env that will eventually get passed down
123
// Holds the arena for all environments to eventually be dropped together
124
pub struct ConstantEvaluatorManager<'a> {
125
    global_env: SharedEnv,
126
    set_idents: FxHashSet<InternedString>,
127
    pub(crate) changed: bool,
128
    opt_level: OptLevel,
129
    _memoization_table: &'a mut MemoizationTable,
130
    kernel: &'a mut Option<Kernel>,
131
}
132

133
impl<'a> ConstantEvaluatorManager<'a> {
134
    pub fn new(
11,472✔
135
        memoization_table: &'a mut MemoizationTable,
136
        constant_bindings: HashMap<InternedString, SteelVal, FxBuildHasher>,
137
        opt_level: OptLevel,
138
        kernel: &'a mut Option<Kernel>,
139
    ) -> Self {
140
        Self {
141
            global_env: Rc::new(RefCell::new(ConstantEnv::root(constant_bindings))),
57,360✔
142
            set_idents: HashSet::default(),
22,944✔
143
            changed: false,
144
            opt_level,
145
            _memoization_table: memoization_table,
146
            kernel,
147
        }
148
    }
149

150
    pub fn run(&mut self, input: Vec<ExprKind>) -> Result<Vec<ExprKind>> {
11,472✔
151
        self.changed = false;
11,472✔
152

153
        let mut results = Vec::with_capacity(input.len());
45,888✔
154

155
        let mut collector = CollectSet::new(&mut self.set_idents);
34,416✔
156

157
        for expr in &input {
124,672✔
NEW
158
            collector.visit(expr);
×
159
        }
160

161
        // let mut collector = CollectSet::new(&mut self.set_idents);
162

163
        // Collect the set expressions, ignore them for the constant folding
164
        for expr in input {
124,672✔
165
            let mut collector = CollectSet::new(&mut self.set_idents);
169,800✔
166

167
            collector.visit(&expr);
169,800✔
168

169
            let expr_level_set_idents = std::mem::take(&mut collector.expr_level_set_idents);
169,800✔
170

171
            // println!("Length of expr level sets!: {:?}", expr_level_set_idents);
172

173
            drop(collector);
113,200✔
174

175
            let mut eval = ConstantEvaluator::new(
176
                Rc::clone(&self.global_env),
113,200✔
177
                &self.set_idents,
56,600✔
178
                &expr_level_set_idents,
56,600✔
179
                self.opt_level,
56,600✔
180
                self._memoization_table,
56,600✔
181
                self.kernel,
56,600✔
182
            );
183
            let mut output = eval.visit(expr)?;
226,400✔
184
            self.changed = self.changed || eval.changed;
56,595✔
185

186
            eval.changed = false;
×
187

188
            for _ in 0..10 {
56,624✔
189
                output = eval.visit(output)?;
226,496✔
190
                if !eval.changed {
×
191
                    break;
56,600✔
192
                }
193

194
                eval.changed = false;
×
195
            }
196

197
            results.push(output)
56,600✔
198
        }
199

200
        // Only run this on an expr by expr basis
201
        self.changed = false;
11,472✔
202

203
        Ok(results)
×
204

205
        // TODO: Only re-run with the manager on expressions that actually changed.
206
        // input
207
        //     .into_iter()
208
        //     .zip(expr_level_sets)
209
        //     .map(|(x, set)| {
210
        //         let mut eval = ConstantEvaluator::new(
211
        //             Rc::clone(&self.global_env),
212
        //             &self.set_idents,
213
        //             &set,
214
        //             self.opt_level,
215
        //             self.memoization_table,
216
        //             self.kernel,
217
        //         );
218
        //         let output = eval.visit(x);
219
        //         self.changed = self.changed || eval.changed;
220
        //         output
221
        //     })
222
        //     .collect()
223
    }
224
}
225

226
struct ConstantEvaluator<'a> {
227
    bindings: SharedEnv,
228
    set_idents: &'a FxHashSet<InternedString>,
229
    expr_level_set_idents: &'a [InternedString],
230
    changed: bool,
231
    opt_level: OptLevel,
232
    _memoization_table: &'a mut MemoizationTable,
233
    kernel: &'a mut Option<Kernel>,
234
    scope_contains_define: bool,
235
}
236

237
// Converts the atom value into a `TokenType`.
238
fn steelval_to_atom(value: &SteelVal) -> Option<TokenType<InternedString>> {
3,940✔
239
    match value {
3,940✔
240
        SteelVal::BoolV(b) => Some(TokenType::BooleanLiteral(*b)),
5✔
241
        SteelVal::NumV(n) => Some(RealLiteral::Float(*n).into()),
62✔
242
        SteelVal::CharV(c) => Some(TokenType::CharacterLiteral(*c)),
×
243
        SteelVal::IntV(i) => Some(IntLiteral::Small(*i).into()),
154✔
244
        SteelVal::StringV(s) => Some(TokenType::StringLiteral(s.to_arc_string())),
5,166✔
245
        _ => None,
1,244✔
246
    }
247
}
248

249
impl<'a> ConstantEvaluator<'a> {
250
    fn new(
56,600✔
251
        bindings: Rc<RefCell<ConstantEnv>>,
252
        set_idents: &'a FxHashSet<InternedString>,
253
        expr_level_set_idents: &'a [InternedString],
254
        opt_level: OptLevel,
255
        memoization_table: &'a mut MemoizationTable,
256
        kernel: &'a mut Option<Kernel>,
257
    ) -> Self {
258
        Self {
259
            bindings,
260
            set_idents,
261
            expr_level_set_idents,
262
            changed: false,
263
            opt_level,
264
            _memoization_table: memoization_table,
265
            kernel,
266
            scope_contains_define: false,
267
        }
268
    }
269

270
    fn to_constant(&self, expr: &ExprKind) -> Option<SteelVal> {
3,553,836✔
271
        match expr {
3,553,836✔
272
            ExprKind::Atom(Atom { syn, .. }) => self.eval_atom(syn),
1,637,932✔
273
            ExprKind::Quote(q) => {
205,394✔
274
                let inner = &q.expr;
410,788✔
275
                TryFromExprKindForSteelVal::try_from_expr_kind(inner.clone()).ok()
821,576✔
276
            }
277
            _ => None,
1,710,510✔
278
        }
279
    }
280

281
    fn eval_atom(&self, t: &SyntaxObject) -> Option<SteelVal> {
1,637,932✔
282
        match &t.ty {
1,637,932✔
283
            TokenType::BooleanLiteral(b) => Some((*b).into()),
17,320✔
284
            TokenType::Identifier(s) => {
1,542,456✔
285
                // If we found a set identifier, skip it
286
                if self.set_idents.get(s).is_some() || self.expr_level_set_idents.contains(s) {
6,163,082✔
287
                    self.bindings.borrow_mut().unbind(s);
7,139✔
288

289
                    return None;
×
290
                };
291
                self.bindings.borrow_mut().get(s)
×
292
            }
293
            // todo!() figure out if it is ok to expand scope of eval_atom.
294
            TokenType::Number(n) => (&**n).into_steelval().ok(),
79,292✔
295
            TokenType::StringLiteral(s) => Some(SteelVal::StringV((s.clone()).into())),
195,387✔
296
            TokenType::CharacterLiteral(c) => Some(SteelVal::CharV(*c)),
1,840✔
297
            _ => None,
24✔
298
        }
299
    }
300

301
    fn all_to_constant(&self, exprs: &[ExprKind]) -> Option<smallvec::SmallVec<[SteelVal; 8]>> {
1,882,883✔
302
        exprs.iter().map(|x| self.to_constant(x)).collect()
13,715,042✔
303
    }
304

305
    fn eval_kernel_function(
×
306
        &mut self,
307
        ident: InternedString,
308
        func: ExprKind,
309
        mut raw_args: Vec<ExprKind>,
310
        args: &[SteelVal],
311
    ) -> Result<ExprKind> {
312
        // TODO: We should just bail immediately if this results in an error
313
        let output = match self.kernel.as_mut().unwrap().call_function(&ident, args) {
×
314
            Ok(v) => v,
×
315
            Err(_) => {
×
316
                // log::error!("{:?}", e);
317
                raw_args.insert(0, func);
×
318
                return Ok(ExprKind::List(List::new(raw_args)));
×
319
            }
320
        };
321

322
        if let Some(new_token) = steelval_to_atom(&output) {
×
323
            let atom = Atom::new(SyntaxObject::new(new_token, get_span(&func)));
×
324
            // debug!(
325
            //     "Const evaluation of a function resulted in an atom: {}",
326
            //     atom
327
            // );
328
            self.changed = true;
×
329
            Ok(ExprKind::Atom(atom))
×
330
        } else if let Ok(lst) = ExprKind::try_from(&output) {
×
331
            self.changed = true;
×
332
            let output = ExprKind::Quote(Box::new(Quote::new(
×
333
                lst,
×
334
                SyntaxObject::new(TokenType::Quote, get_span(&func)),
×
335
            )));
336
            // debug!(
337
            //     "Const evaluation of a function resulted in a quoted value: {}",
338
            //     output
339
            // );
340
            Ok(output)
×
341
        } else {
342
            // debug!(
343
            //     "Unable to convert constant-evalutable function output to value: {}",
344
            //     func
345
            // );
346
            // Something went wrong
347
            raw_args.insert(0, func);
×
348
            Ok(ExprKind::List(List::new(raw_args)))
×
349
        }
350

351
        // todo!()
352
    }
353

354
    fn eval_function(
×
355
        &mut self,
356
        evaluated_func: SteelVal,
357
        func: ExprKind,
358
        mut raw_args: Vec<ExprKind>,
359
        args: &mut [SteelVal],
360
    ) -> Result<ExprKind> {
361
        if evaluated_func.is_function() {
×
362
            match evaluated_func {
×
363
                SteelVal::MutFunc(f) => {
×
364
                    let output = f(args)
×
365
                        .map_err(|e| e.set_span_if_none(func.atom_syntax_object().unwrap().span))?;
×
366

367
                    self.handle_output(output, func, raw_args)
×
368
                }
369
                // TODO: Eventually, re-enable the memoization table
370
                SteelVal::FuncV(f) => {
×
371
                    // TODO: Clean this up - we shouldn't even enter this section of the code w/o having
372
                    // the actual atom itself.
373
                    // let output = if let Some(output) = self
374
                    // .memoization_table
375
                    // .get(SteelVal::FuncV(f), args.to_vec())
376
                    // {
377
                    // output
378
                    // } else {
379

380
                    let output = f(args)
×
381
                        .map_err(|e| e.set_span_if_none(func.atom_syntax_object().unwrap().span))?;
×
382

383
                    // self.memoization_table.insert(
384
                    //     SteelVal::FuncV(f),
385
                    //     args.to_vec(),
386
                    //     output.clone(),
387
                    // );
388

389
                    // output
390
                    // };
391

392
                    self.handle_output(output, func, raw_args)
×
393
                }
394
                _ => {
×
395
                    // debug!(
396
                    //     "Found a non-constant evaluatable function: {}",
397
                    //     evaluated_func
398
                    // );
399
                    raw_args.insert(0, func);
×
400
                    // Not a constant evaluatable function, just return the original input
401
                    Ok(ExprKind::List(List::new(raw_args)))
×
402
                }
403
            }
404
        } else {
405
            raw_args.insert(0, func);
×
406
            Ok(ExprKind::List(List::new(raw_args)))
×
407
        }
408
    }
409

410
    fn handle_output(
×
411
        &mut self,
412
        output: SteelVal,
413
        func: ExprKind,
414
        // evaluated_func: &SteelVal,
415
        mut raw_args: Vec<ExprKind>,
416
    ) -> std::result::Result<ExprKind, crate::SteelErr> {
417
        if let Some(new_token) = steelval_to_atom(&output) {
×
418
            let atom = Atom::new(SyntaxObject::new(new_token, get_span(&func)));
×
419
            // debug!(
420
            //     "Const evaluation of a function resulted in an atom: {}",
421
            //     atom
422
            // );
423
            self.changed = true;
×
424
            Ok(ExprKind::Atom(atom))
×
425
        } else if let Ok(lst) = ExprKind::try_from(&output) {
×
426
            self.changed = true;
×
427
            let output = ExprKind::Quote(Box::new(Quote::new(
×
428
                lst,
×
429
                SyntaxObject::new(TokenType::Quote, get_span(&func)),
×
430
            )));
431
            // debug!(
432
            //     "Const evaluation of a function resulted in a quoted value: {}",
433
            //     output
434
            // );
435
            Ok(output)
×
436
        } else {
437
            // debug!(
438
            //     "Unable to convert constant-evalutable function output to value: {}",
439
            //     evaluated_func
440
            // );
441
            // Something went wrong
442
            raw_args.insert(0, func);
×
443
            Ok(ExprKind::List(List::new(raw_args)))
×
444
        }
445
    }
446
}
447

448
impl<'a> ConsumingVisitor for ConstantEvaluator<'a> {
449
    type Output = Result<ExprKind>;
450

451
    fn visit_if(&mut self, f: Box<crate::parser::ast::If>) -> Self::Output {
129,061✔
452
        // Visit the test expression
453
        let test_expr = self.visit(f.test_expr)?;
516,244✔
454

455
        if self.opt_level == OptLevel::Three {
×
456
            if let Some(test_expr) = self.to_constant(&test_expr) {
258,148✔
457
                self.changed = true;
×
458
                if test_expr.is_truthy() {
×
459
                    // debug!("Const evaluation resulted in taking the then branch");
460
                    return self.visit(f.then_expr);
60✔
461
                } else {
462
                    // debug!("Const evaluation resulted in taking the else branch");
463
                    return self.visit(f.else_expr);
6✔
464
                }
465
            }
466
        }
467

468
        // If we found a constant, we can elect to only take the truthy path
469
        // if let Some(test_expr) = self.to_constant(&test_expr) {
470
        //     self.changed = true;
471
        //     if test_expr.is_truthy() {
472
        //         self.visit(f.then_expr)
473
        //     } else {
474
        //         self.visit(f.else_expr)
475
        //     }
476
        // } else {
477
        Ok(ExprKind::If(
×
478
            If::new(
129,035✔
479
                test_expr,
129,035✔
480
                self.visit(f.then_expr)?,
×
481
                self.visit(f.else_expr)?,
129,035✔
482
                f.location,
×
483
            )
484
            .into(),
×
485
        ))
486
        // }
487
    }
488

489
    fn visit_define(&mut self, mut define: Box<crate::parser::ast::Define>) -> Self::Output {
1,094,587✔
490
        let identifier = &define.name.atom_identifier_or_else(
3,283,761✔
491
            throw!(BadSyntax => format!("Define expects an identifier, found: {}", define.name); define.location.span),
1,094,587✔
492
        )?;
493

494
        self.scope_contains_define = true;
×
495

496
        define.body = self.visit(define.body)?;
1,094,587✔
497

498
        if let Some(c) = self.to_constant(&define.body) {
31,327✔
499
            self.bindings.borrow_mut().bind(identifier, c);
×
500
        } else {
501
            self.bindings.borrow_mut().bind_non_constant(identifier);
2,126,520✔
502
        }
503

504
        Ok(ExprKind::Define(define))
×
505
    }
506

507
    fn visit_lambda_function(
173,555✔
508
        &mut self,
509
        mut lambda_function: Box<crate::parser::ast::LambdaFunction>,
510
    ) -> Self::Output {
511
        let parent = Rc::clone(&self.bindings);
520,665✔
512
        let mut new_env = ConstantEnv::new_subexpression(Rc::downgrade(&parent));
694,220✔
513

514
        let prev = self.scope_contains_define;
347,110✔
515
        self.scope_contains_define = false;
173,555✔
516

517
        for arg in &lambda_function.args {
564,343✔
518
            let identifier = arg.atom_identifier_or_else(
586,182✔
519
                throw!(BadSyntax => format!("lambda expects an identifier for the arguments, found: {arg}"); lambda_function.location.span),
195,394✔
520
            )?;
521
            new_env.bind_non_constant(identifier);
×
522
        }
523

524
        self.bindings = Rc::new(RefCell::new(new_env));
173,555✔
525

526
        lambda_function.body = self.visit(lambda_function.body)?;
173,555✔
527

528
        self.scope_contains_define = prev;
×
529
        self.bindings = parent;
×
530

531
        Ok(ExprKind::LambdaFunction(lambda_function))
×
532
    }
533

534
    // TODO remove constants from the begins
535
    fn visit_begin(&mut self, mut begin: Box<crate::parser::ast::Begin>) -> Self::Output {
98,362✔
536
        for expr in begin.exprs.iter_mut() {
1,380,137✔
537
            *expr = self.visit(std::mem::take(expr))?;
5,917,065✔
538
        }
539

540
        Ok(ExprKind::Begin(begin))
98,362✔
541
    }
542

543
    fn visit_return(&mut self, mut r: Box<crate::parser::ast::Return>) -> Self::Output {
450✔
544
        r.expr = self.visit(r.expr)?;
1,800✔
545
        Ok(ExprKind::Return(r))
×
546
    }
547

548
    fn visit_quote(&mut self, quote: Box<crate::parser::ast::Quote>) -> Self::Output {
1,249,517✔
549
        Ok(ExprKind::Quote(quote))
1,249,517✔
550
    }
551

552
    fn visit_macro(&mut self, _m: Box<crate::parser::ast::Macro>) -> Self::Output {
×
553
        stop!(Generic => "unexpected macro found in const evaluator");
×
554
    }
555

556
    fn visit_atom(&mut self, a: crate::parser::ast::Atom) -> Self::Output {
3,730,845✔
557
        // if let Some(inner) = self.eval_atom(&a.syn) {
558
        //     // TODO Check this part - be able to propagate quoted values
559
        //     if let Some(new_token) = steelval_to_atom(&inner) {
560
        //         let atom = Atom::new(SyntaxObject::new(new_token, a.syn.span));
561
        //         return Ok(ExprKind::Atom(atom));
562
        //     }
563
        // }
564
        // Ok(ExprKind::Atom(a))
565

566
        match &a.syn.ty {
3,730,845✔
567
            TokenType::Identifier(s) => {
3,531,379✔
568
                // If we found a set identifier, skip it
569
                if self.set_idents.get(s).is_some() || self.expr_level_set_idents.contains(s) {
3,474,389✔
570
                    self.bindings.borrow_mut().unbind(s);
57,532✔
571

572
                    return Ok(ExprKind::Atom(a));
×
573
                };
574
                if let Some(new_token) = self
2,696✔
575
                    .bindings
×
576
                    .borrow_mut()
NEW
577
                    .get(s)
×
578
                    .and_then(|x| steelval_to_atom(&x))
7,880✔
579
                {
NEW
580
                    Ok(ExprKind::Atom(Atom::new(SyntaxObject::new(
×
581
                        new_token, a.syn.span,
×
582
                    ))))
583
                } else {
584
                    Ok(ExprKind::Atom(a))
3,471,151✔
585
                }
586
            }
587

588
            _ => Ok(ExprKind::Atom(a)),
199,466✔
589
        }
590
    }
591

592
    // Certainly the most complicated case: function application
593
    // Check if its a function application, and go for it
594
    fn visit_list(&mut self, l: crate::parser::ast::List) -> Self::Output {
1,929,239✔
595
        if l.args.is_empty() {
3,858,478✔
596
            stop!(BadSyntax => "empty function application"; if l.location == Span::default() { get_span(&ExprKind::List(l)) } else { l.location });
×
597
        }
598

599
        if l.args.len() == 1 {
×
600
            let mut args_iter = l.args.into_iter();
139,068✔
601
            let func_expr = args_iter.next().unwrap();
185,424✔
602
            let func = self.visit(func_expr)?;
185,424✔
603

604
            if let Some(evaluated_func) = self.to_constant(&func) {
×
605
                // println!("Attempting to evaluate: {}", &func);
606
                return self.eval_function(evaluated_func, func, Vec::new(), &mut []);
×
607
            } else if let Some(ident) = func.atom_identifier().and_then(|x| {
179,450✔
608
                // TODO: @Matt 4/24/23 - this condition is super ugly and I would prefer if we cleaned it up
609
                if self.kernel.is_some() && self.kernel.as_ref().unwrap().is_constant(x) {
80,764✔
610
                    Some(x)
×
611
                } else {
612
                    None
40,382✔
613
                }
614
            }) {
615
                // log::debug!("Running kernel function!");
616

617
                return self.eval_kernel_function(ident.clone(), func, Vec::new(), &[]);
×
618
            } else {
619
                if let ExprKind::LambdaFunction(f) = &func {
50,796✔
620
                    if !f.rest {
×
621
                        if !f.args.is_empty() {
4,434✔
622
                            stop!(ArityMismatch => format!("function expected {} arguments, found 0", f.args.len()); f.location.span)
×
623
                        }
624

625
                        // If the body is constant we can safely remove the application
626
                        // Otherwise we can't eliminate the additional scope depth
627
                        if self.to_constant(&f.body).is_some() {
13,302✔
628
                            return Ok(f.body.clone());
1✔
629
                        }
630
                    }
631
                }
632

633
                let new_expr = vec![func].into_iter().chain(args_iter).collect();
46,355✔
634

635
                return Ok(ExprKind::List(List::new(new_expr)));
×
636
            }
637
        }
638

639
        let mut args = l.args.into_iter();
×
640

641
        let func_expr = args.next().expect("Function missing");
×
642
        let mut args: Vec<_> = args.map(|x| self.visit(x)).collect::<Result<_>>()?;
12,468,848✔
643

644
        // Resolve the arguments - if they're all constants, we have a chance to do constant evaluation
645
        if let Some(mut arguments) = self.all_to_constant(&args) {
91,093✔
646
            if let ExprKind::Atom(_) = &func_expr {
×
647
                // let span = get_span(&func_expr);
648

649
                if let Some(evaluated_func) = self.to_constant(&func_expr) {
90,800✔
650
                    // println!(
651
                    //     "Attempting to evaluate: {} with args: {:?}",
652
                    //     &func_expr, arguments
653
                    // );
654
                    // TODO: This shouldn't fail here under normal circumstances! If the end result is an error, we should
655
                    // just return the value that was originally passed in. Otherwise, this signals
656
                    // an error in the dataflow, and it means we're checking a condition that isn't constant
657
                    // before applying a check against a constant value (which probably means we're missing)
658
                    // something in the constant evaluation check. In which case, we should probably
659
                    // just not stop the execution just because we errored
660
                    return self.eval_function(evaluated_func, func_expr, args, &mut arguments);
×
661
                } else if let Some(ident) = func_expr.atom_identifier().and_then(|x| {
363,200✔
662
                    // TODO: @Matt 4/24/23 - this condition is super ugly and I would prefer if we cleaned it up
663
                    if self.kernel.is_some() && self.kernel.as_ref().unwrap().is_constant(x) {
181,600✔
664
                        Some(x)
×
665
                    } else {
666
                        None
90,800✔
667
                    }
668
                }) {
669
                    return self.eval_kernel_function(ident.clone(), func_expr, args, &arguments);
×
670
                }
671
                // return self.eval_function(func_expr, span, &arguments);
672
            }
673
        }
674

675
        match &func_expr {
1,882,883✔
676
            ExprKind::LambdaFunction(_) => {}
41,055✔
677
            _ => {
×
678
                let visited_func_expr = self.visit(func_expr)?;
7,367,312✔
679
                args.insert(0, visited_func_expr);
×
680
                return Ok(ExprKind::List(List::new(args)));
×
681
            } // ExprKind::
682
        }
683

684
        if let ExprKind::LambdaFunction(l) = func_expr {
41,055✔
685
            if l.args.len() != args.len() && !l.rest {
123,173✔
686
                // println!("{}", l);
687

688
                let m = format!(
×
689
                    "Anonymous function expected {} arguments, found {}",
690
                    l.args.len(),
×
691
                    args.len()
×
692
                );
693
                stop!(ArityMismatch => m; l.location.span);
×
694
            }
695

696
            let mut new_env = ConstantEnv::new_subexpression(Rc::downgrade(&self.bindings));
×
697

698
            if l.rest {
×
699
                if let Some((l_last, l_start)) = l.args.split_last() {
16✔
700
                    let non_list_bindings = &args[0..l_start.len()];
×
701
                    // let list_binding = &args[l_start.len()..];
702

703
                    // If this is a rest arg, bind differently
704
                    for (var, arg) in l_start.iter().zip(non_list_bindings) {
8✔
705
                        let identifier = var.atom_identifier_or_else(
12✔
706
                            throw!(BadSyntax => format!("lambda expects an identifier for the arguments: {var}"); l.location.span),
4✔
707
                        )?;
708
                        if let Some(c) = self.to_constant(arg) {
4✔
709
                            new_env.bind(identifier, c);
×
710
                        } else {
711
                            new_env.bind_non_constant(identifier);
×
712
                        }
713
                    }
714

715
                    let last_identifier = l_last.atom_identifier_or_else(
16✔
716
                            throw!(BadSyntax => format!("lambda expects an identifier for the arguments: {l_last}"); l.location.span),
×
717
                        )?;
718

719
                    let mut rest_args = Vec::new();
×
720

721
                    for arg in &args[l_start.len()..] {
28✔
722
                        if let Some(c) = self.to_constant(arg) {
84✔
723
                            rest_args.push(c);
×
724
                        } else {
725
                            new_env.bind_non_constant(last_identifier);
×
726
                            break;
×
727
                        }
728
                    }
729

730
                    // If the length is the same, we didn't need to break early, meaning
731
                    // the whole list is constant values
732
                    if rest_args.len() == args[l_start.len()..].len() {
8✔
733
                        let list = SteelVal::ListV(rest_args.into());
24✔
734

735
                        new_env.bind(last_identifier, list);
24✔
736
                    }
737
                }
738
            } else {
739
                // If this is a rest arg, bind differently
740
                for (var, arg) in l.args.iter().zip(args.iter()) {
127,667✔
741
                    let identifier = var.atom_identifier_or_else(
129,930✔
742
                    throw!(BadSyntax => format!("lambda expects an identifier for the arguments: {var}"); l.location.span),
43,310✔
743
                )?;
744
                    if let Some(c) = self.to_constant(arg) {
328✔
745
                        new_env.bind(identifier, c);
×
746
                    } else {
747
                        new_env.bind_non_constant(identifier);
85,964✔
748
                    }
749
                }
750
            }
751

752
            let parent = Rc::clone(&self.bindings);
41,055✔
753
            self.bindings = Rc::new(RefCell::new(new_env));
×
754

755
            // println!("Visiting body: {}", l.body);
756

757
            let output = self.visit(l.body)?;
41,055✔
758

759
            // Unwind the 'recursion'
760
            // self.bindings = parent;
761

762
            // Find which variables and arguments are actually used in the body of the function
763
            let mut actually_used_variables = smallvec::SmallVec::<[_; 8]>::new();
×
764
            let mut actually_used_arguments = smallvec::SmallVec::<[_; 8]>::new();
×
765

766
            let mut non_constant_arguments = Vec::new();
×
767

768
            let span = l.location.span;
×
769

770
            for (var, arg) in l.args.iter().zip(args.iter()) {
86,644✔
771
                let identifier = var.atom_identifier_or_else(
129,966✔
772
                    throw!(BadSyntax => format!("lambda expects an identifier for the arguments: {var}"); span),
43,322✔
773
                )?;
774

775
                // If the argument/variable is used internally, keep it
776
                // Also, if the argument is _not_ a constant
777
                if self.bindings.borrow().used_bindings.contains(identifier) {
257✔
778
                    // if self.to_constant(arg).is_none() {
779
                    // println!("FOUND ARGUMENT: {}", identifier);
780
                    actually_used_variables.push(var.clone());
1,285✔
781
                    actually_used_arguments.push(arg.clone());
771✔
782
                    // }
783
                } else if self.to_constant(arg).is_none() {
86,304✔
784
                    // actually_used_variables.push(var.clone());
785
                    // println!("Found a non constant argument: {}", arg);
786
                    non_constant_arguments.push(arg.clone());
128,946✔
787
                }
788
            }
789

790
            // Found no arguments are there are no non constant arguments
791
            // TODO: @Matt 12/30/23 - this is causing a miscompilation - actually used
792
            // arguments is found to be empty.
793
            if actually_used_arguments.is_empty()
41,055✔
794
                && non_constant_arguments.is_empty()
81,678✔
795
                && !self.scope_contains_define
60✔
796
            {
797
                // println!("Found no used arguments or non constant arguments, returning the body");
798
                // println!("Output: {}", output);
799

800
                // Unwind the recursion before we bail out
801
                self.bindings = parent;
110✔
802

803
                self.changed = true;
55✔
804
                return Ok(output);
55✔
805
            }
806

807
            // if actually_used_arguments.is_empty() {
808
            //     non_constant_arguments.push(output);
809
            //     return Ok(ExprKind::Begin(Begin::new(
810
            //         non_constant_arguments,
811
            //         l.location,
812
            //     )));
813
            // }
814

815
            // TODO only do this if all of the args are constant as well
816
            // Find a better way to do this
817
            if let Some(value_output) = self.to_constant(&output) {
15✔
818
                let mut non_constant_arguments: Vec<_> = args
×
819
                    .into_iter()
820
                    .filter(|x| self.to_constant(x).is_none())
63✔
821
                    .collect();
822

823
                // debug!("Found a constant output from the body");
824

825
                self.changed = true;
×
826
                self.bindings = parent;
×
827
                if non_constant_arguments.is_empty() {
×
828
                    // println!("Returning here!");
829
                    return ExprKind::try_from(&value_output)
16✔
830
                        .map_err(|x| SteelErr::new(ErrorKind::Generic, x.to_string()))
8✔
831
                        .map(|x| {
16✔
832
                            ExprKind::Quote(Box::new(Quote::new(
24✔
833
                                x,
16✔
834
                                SyntaxObject::default(TokenType::Quote),
8✔
835
                            )))
836
                        });
837
                } else {
838
                    non_constant_arguments.push(output);
7✔
839
                    // TODO come up witih a better location
840
                    return Ok(ExprKind::Begin(Box::new(Begin::new(
×
841
                        non_constant_arguments,
×
842
                        l.location,
×
843
                    ))));
844
                }
845
            }
846

847
            // Unwind the 'recursion'
848
            self.bindings = parent;
81,970✔
849

850
            // let constructed_func = ExprKind::LambdaFunction(
851
            //     LambdaFunction::new(actually_used_variables, output, l.location).into(),
852
            // );
853

854
            let func = if l.rest {
81,970✔
855
                LambdaFunction::new_with_rest_arg(l.args, output, l.location)
24✔
856
            } else {
857
                LambdaFunction::new(l.args, output, l.location)
40,979✔
858
            };
859

860
            let constructed_func = ExprKind::LambdaFunction(func.into());
81,970✔
861

862
            // Insert the visited function at the beginning of the args
863
            args.insert(0, constructed_func);
122,955✔
864
            // actually_used_arguments.insert(0, constructed_func);
865

866
            Ok(ExprKind::List(List::new(args)))
40,985✔
867
            // return Ok(ExprKind::List(List::new(actually_used_arguments)));
868

869
            // unimplemented!()
870
        } else {
871
            unreachable!();
872
        }
873
    }
874

875
    fn visit_syntax_rules(&mut self, _l: Box<crate::parser::ast::SyntaxRules>) -> Self::Output {
×
876
        stop!(Generic => "unexpected syntax rules in const evaluator");
×
877
    }
878

879
    fn visit_set(&mut self, mut s: Box<crate::parser::ast::Set>) -> Self::Output {
4,171✔
880
        let identifier = &s.variable.atom_identifier_or_else(
12,513✔
881
            throw!(BadSyntax => "set expects an identifier"; s.location.span),
4,171✔
882
        )?;
883

884
        self.bindings.borrow_mut().unbind(identifier);
×
885

886
        s.expr = self.visit(s.expr)?;
4,171✔
887

888
        Ok(ExprKind::Set(s))
×
889
    }
890

891
    fn visit_require(&mut self, s: Box<crate::parser::ast::Require>) -> Self::Output {
×
892
        stop!(Generic => "unexpected require - require is only allowed at the top level"; s.location.span);
×
893
    }
894

895
    // TODO come back to this
896
    fn visit_let(&mut self, l: Box<crate::parser::ast::Let>) -> Self::Output {
4,232✔
897
        // panic!("---------------------------Visiting let!--------------------");
898

899
        // let mut visited_bindings = Vec::new();
900

901
        // for (binding, expr) in l.bindings {
902
        //     visited_bindings.push((self.visit(binding)?, self.visit(expr)?));
903
        // }
904

905
        // l.bindings = visited_bindings;
906
        // l.body_expr = self.visit(l.body_expr)?;
907

908
        Ok(ExprKind::Let(l))
4,232✔
909
    }
910

911
    fn visit_vector(&mut self, v: crate::parser::ast::Vector) -> Self::Output {
432✔
912
        Ok(v.into())
432✔
913
    }
914
}
915

916
// TODO: If the value is local, we need to exclude it:
917
// entering and exiting a scope should push and pop it off.
918
struct CollectSet<'a> {
919
    set_idents: &'a mut FxHashSet<InternedString>,
920
    scopes: quickscope::ScopeSet<InternedString, FxBuildHasher>,
921
    pub expr_level_set_idents: smallvec::SmallVec<[InternedString; 32]>,
922
}
923

924
impl<'a> CollectSet<'a> {
925
    fn new(set_idents: &'a mut FxHashSet<InternedString>) -> Self {
68,072✔
926
        Self {
927
            set_idents,
928
            scopes: quickscope::ScopeSet::default(),
68,072✔
929
            expr_level_set_idents: smallvec::SmallVec::default(),
68,072✔
930
        }
931
    }
932
}
933

934
impl<'a> VisitorMut for CollectSet<'a> {
935
    type Output = ();
936

937
    fn visit_if(&mut self, f: &If) -> Self::Output {
131,556✔
938
        self.visit(&f.test_expr);
394,668✔
939
        self.visit(&f.then_expr);
394,668✔
940
        self.visit(&f.else_expr);
394,668✔
941
    }
942

943
    fn visit_define(&mut self, define: &Define) -> Self::Output {
1,079,432✔
944
        self.visit(&define.name);
3,238,296✔
945
        self.visit(&define.body);
3,238,296✔
946
    }
947

948
    fn visit_lambda_function(&mut self, lambda_function: &LambdaFunction) -> Self::Output {
214,630✔
949
        self.scopes.push_layer();
429,260✔
950

951
        for arg in &lambda_function.args {
698,594✔
952
            if let Some(ident) = arg.atom_identifier() {
241,982✔
953
                self.scopes.define(*ident);
×
954
            }
955
        }
956

957
        self.visit(&lambda_function.body);
643,890✔
958

959
        self.scopes.pop_layer();
429,260✔
960
    }
961

962
    fn visit_begin(&mut self, begin: &Begin) -> Self::Output {
101,212✔
963
        for expr in &begin.exprs {
2,502,820✔
964
            self.visit(expr);
×
965
        }
966
    }
967

968
    fn visit_return(&mut self, r: &crate::parser::ast::Return) -> Self::Output {
450✔
969
        self.visit(&r.expr);
1,350✔
970
    }
971

972
    fn visit_quote(&mut self, _quote: &Quote) -> Self::Output {}
2,477,856✔
973

974
    fn visit_macro(&mut self, _m: &crate::parser::ast::Macro) -> Self::Output {}
×
975

976
    fn visit_atom(&mut self, _a: &Atom) -> Self::Output {}
9,846,648✔
977

978
    fn visit_list(&mut self, l: &List) -> Self::Output {
1,955,886✔
979
        for expr in &l.args {
13,031,362✔
980
            self.visit(expr);
×
981
        }
982
    }
983

984
    fn visit_syntax_rules(&mut self, _l: &crate::parser::ast::SyntaxRules) -> Self::Output {}
×
985

986
    fn visit_set(&mut self, s: &crate::parser::ast::Set) -> Self::Output {
28,780✔
987
        if let Ok(identifier) = s.variable.atom_identifier_or_else(
86,340✔
988
            throw!(BadSyntax => "set expects an identifier"; s.location.span),
28,780✔
989
        ) {
990
            if !self.scopes.contains(identifier) {
28,652✔
991
                // println!("NOT IN SCOPE: {}", identifier.resolve());
992

993
                self.set_idents.insert(*identifier);
28,652✔
994
            } else {
995
                self.expr_level_set_idents.push(*identifier);
256✔
996

997
                // println!("IN SCOPE: {}", identifier.resolve());
998
            }
999

1000
            // self.set_idents.insert(*identifier);
1001
        }
1002

1003
        self.visit(&s.expr);
86,340✔
1004
    }
1005

1006
    fn visit_require(&mut self, _s: &crate::parser::ast::Require) -> Self::Output {}
×
1007

1008
    fn visit_let(&mut self, l: &crate::parser::ast::Let) -> Self::Output {
8,462✔
1009
        self.scopes.push_layer();
16,924✔
1010
        l.bindings.iter().for_each(|x| self.visit(&x.1));
93,070✔
1011

1012
        for (arg, _) in &l.bindings {
59,226✔
1013
            if let Some(ident) = arg.atom_identifier() {
25,382✔
1014
                self.scopes.define(*ident);
×
1015
            }
1016
        }
1017

1018
        self.visit(&l.body_expr);
25,386✔
1019

1020
        self.scopes.pop_layer();
16,924✔
1021
    }
1022

1023
    fn visit_vector(&mut self, _v: &crate::parser::ast::Vector) -> Self::Output {}
636✔
1024
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc