Skip to main content

sva_engine/instantiate/
mod.rs

1// Concern: holds one instance per argument tuple and resolves a name inside one | Non-concern: building the instances (build.rs), writing a resolved walk out (resolved.rs) | IO: (path) -> a body, a Node
2
3mod build;
4mod edges;
5mod resolved;
6
7use std::collections::{BTreeMap, BTreeSet, HashMap};
8use std::sync::Arc;
9
10use sva_ast::{Address, Arg, BinOp, ByteSpan, Defined, Expr, Literal};
11
12use crate::error::EngineError;
13use crate::time::Grid;
14use sva_ast::is_builtin;
15
16use build::Journal;
17pub use build::{from_roots, instantiate};
18use edges::Edges;
19
20pub(crate) type ScopeId = u32;
21
22/// Scope 0 binds nothing, reserved for defaults: a default stands outside every invocation.
23pub(crate) const NO_PARAMS: ScopeId = 0;
24
25#[derive(Clone, Copy, Debug)]
26pub struct Thunk<'a> {
27    pub(crate) expr: &'a Expr,
28    pub(crate) scope: ScopeId,
29}
30
31/// An expression owned where it was written: a node's line and each child taken down to it.
32#[derive(Clone, Debug)]
33pub(crate) struct Written {
34    defined: Arc<Defined>,
35    path: Arc<[u32]>,
36}
37
38impl Written {
39    /// `line` 0 is the body, `k` the default line `k - 1`.
40    pub(crate) fn of(defined: &Arc<Defined>, line: u32) -> Written {
41        Written {
42            defined: Arc::clone(defined),
43            path: [line].into(),
44        }
45    }
46
47    pub(crate) fn expr(&self) -> &Expr {
48        let (line, path) = self.path.split_first().expect("a line");
49        let top = match *line {
50            0 => &self.defined.body,
51            k => &self.defined.defaults[k as usize - 1].1,
52        };
53        let mut at = top;
54        for k in path {
55            at = child(at, *k).expect("a path down this expression");
56        }
57        at
58    }
59
60    /// Whether this is an argument a call was given by position, not by name.
61    pub(crate) fn is_positional(&self) -> bool {
62        let Some((last, upper)) = self.path[1..].split_last() else {
63            return false;
64        };
65        let parent = Written {
66            defined: Arc::clone(&self.defined),
67            path: [&self.path[..1], upper].concat().into(),
68        };
69        match parent.expr() {
70            Expr::Call { args, .. } => matches!(args.get(*last as usize), Some(Arg::Pos(_))),
71            _ => false,
72        }
73    }
74}
75
76/// A child's number on a walk down, as [`child`] reads it.
77#[derive(Clone, Copy)]
78pub(crate) enum Child {
79    First,
80    Right,
81    Arg(u32),
82    Bind(u32),
83}
84
85impl Child {
86    pub(crate) fn index(self) -> u32 {
87        match self {
88            Child::First => 0,
89            Child::Right => 1,
90            Child::Arg(k) => k,
91            Child::Bind(k) => k + 1,
92        }
93    }
94}
95
96fn child(e: &Expr, k: u32) -> Option<&Expr> {
97    let k = k as usize;
98    match e {
99        Expr::Bin(_, l, r) => [l, r].get(k).map(|c| &***c),
100        Expr::Call { args, .. } => args.get(k).map(|(Arg::Pos(x) | Arg::Named(_, x))| x),
101        Expr::Ref { arg, binds, .. } => match k {
102            0 => Some(arg),
103            k => binds.get(k - 1).map(|(_, x)| x),
104        },
105        Expr::SelfRef { arg, .. } | Expr::Indexed { arg, .. } => (k == 0).then_some(&**arg),
106        Expr::Lit(_) | Expr::Var(_) => None,
107    }
108}
109
110#[derive(Clone, Debug)]
111pub(crate) struct Bound {
112    pub(crate) written: Written,
113    pub(crate) scope: ScopeId,
114}
115
116impl Bound {
117    pub(crate) fn thunk(&self) -> Thunk<'_> {
118        Thunk {
119            expr: self.written.expr(),
120            scope: self.scope,
121        }
122    }
123}
124
125/// Sorted by name: the tuple names the instance. Held while owned or read in.
126#[derive(Debug)]
127pub(crate) struct Scope {
128    pub(crate) vars: Vec<(String, Bound)>,
129    keys: Vec<u64>,
130    seen: u64,
131    chained: bool,
132    held: u32,
133    /// The child instance each invocation scanned in it names, by where it is written.
134    sites: HashMap<usize, String>,
135}
136
137/// One instance: what it reads, who reads it, where it was first invoked.
138#[derive(Debug)]
139pub(crate) struct Instance {
140    pub(crate) body: Bound,
141    pub(crate) file: String,
142    given: Vec<String>,
143    site: (String, Option<ByteSpan>),
144    held: u32,
145    scanned: bool,
146}
147
148/// A name's length and first seven bytes in one word, so a lookup costs integer compares.
149#[inline]
150pub(crate) fn packed(name: &str) -> u64 {
151    let bytes = name.as_bytes();
152    let mut key = (bytes.len() as u64) << 56;
153    for (at, byte) in bytes.iter().take(7).enumerate() {
154        key |= u64::from(*byte) << (at * 8);
155    }
156    key
157}
158
159#[inline]
160fn bit(key: u64) -> u64 {
161    1u64 << ((key ^ (key >> 32)) & 63)
162}
163
164impl Scope {
165    pub(crate) fn new(vars: Vec<(String, Bound)>, chained: bool) -> Scope {
166        let keys: Vec<u64> = vars.iter().map(|(k, _)| packed(k)).collect();
167        let seen = keys.iter().fold(0, |acc, k| acc | bit(*k));
168        Scope {
169            vars,
170            keys,
171            seen,
172            chained,
173            held: 0,
174            sites: HashMap::new(),
175        }
176    }
177
178    /// One bit says no before a key is read. Two names of a length share a key past the
179    /// seventh byte, so every match is offered the name.
180    #[inline]
181    pub(crate) fn get(&self, name: &str, key: u64) -> Option<&Bound> {
182        if self.seen & bit(key) == 0 {
183            return None;
184        }
185        self.keys
186            .iter()
187            .enumerate()
188            .find(|(at, k)| **k == key && (name.len() <= 7 || self.vars[*at].0 == name))
189            .map(|(at, _)| &self.vars[at].1)
190    }
191}
192
193/// `time` is DYNAMIC: `p(t - d)` moves every `t` under the parameter, however deep. `sp` is a
194/// step of `grid`.
195#[derive(Clone, Copy)]
196pub struct Cx<'a> {
197    pub(crate) scope: ScopeId,
198    pub(crate) time: Option<&'a Time<'a>>,
199    pub(crate) grid: Grid,
200}
201
202pub(crate) struct Time<'a> {
203    pub(crate) expr: &'a Expr,
204    pub(crate) cx: Cx<'a>,
205}
206
207enum Move<'a> {
208    Here(&'a Expr, Cx<'a>),
209    Shifted(&'a Expr, ScopeId, &'a Expr),
210}
211
212impl<'a> Cx<'a> {
213    pub(crate) fn under(self, scope: ScopeId) -> Cx<'a> {
214        Cx { scope, ..self }
215    }
216
217    pub(crate) fn on(self, grid: Grid) -> Cx<'a> {
218        Cx { grid, ..self }
219    }
220}
221
222/// Read from sva-ast so the two crates cannot resolve a path two ways.
223pub fn resolve_ref_path(referencing: &str, ref_path: &str) -> Result<String, EngineError> {
224    sva_ast::resolve_ref_path(referencing, ref_path)
225        .ok_or_else(|| EngineError::RefAboveRoot(referencing.to_string(), ref_path.to_string()))
226}
227
228/// `@kick.comb(delay=0.03)` is `@comb(t, x=@kick, delay=0.03)`: one name, so a file never
229/// states a parameter order.
230pub const SIGNAL_PARAM: &str = "x";
231
232/// Bounds BREADTH: a chain of functions each invoking the next several times multiplies.
233pub const MAX_INSTANCES: usize = 65_536;
234
235/// A `Read` names the child INSTANCE, so this is reachable only past [`Instances::follow`].
236#[derive(Clone, Copy)]
237pub enum Node<'a> {
238    Lit(&'a Literal),
239    Name(&'a str),
240    Bin(BinOp, &'a Expr, &'a Expr),
241    Call {
242        name: &'a str,
243        args: &'a [Arg],
244        span: ByteSpan,
245    },
246    Read {
247        path: &'a str,
248        arg: &'a Expr,
249        address: Address,
250        span: ByteSpan,
251    },
252    Own {
253        arg: &'a Expr,
254        address: Address,
255        span: ByteSpan,
256    },
257    /// `x[i]`: `of`, what `x` is bound to, read at index `arg`.
258    Signal {
259        name: &'a str,
260        of: Thunk<'a>,
261        arg: &'a Expr,
262        span: ByteSpan,
263    },
264}
265
266/// One file's body beside what its parameters stand for: the body is shared, the scope is not.
267/// An instance lasts while a root or a reader holds it.
268#[derive(Debug)]
269pub struct Instances {
270    pub(crate) scopes: Vec<Option<Scope>>,
271    free: Vec<ScopeId>,
272    nodes: BTreeMap<String, Instance>,
273    edges: Edges,
274    pub(crate) own_terms: BTreeMap<String, String>,
275    files: BTreeMap<String, BTreeSet<String>>,
276    journal: Journal,
277    pub(crate) time: Expr,
278    /// The rate in use, whose step `sp` is.
279    pub(crate) rate: u32,
280}
281
282impl Instances {
283    pub fn new(rate: u32) -> Instances {
284        Instances {
285            scopes: vec![Some(Scope::new(Vec::new(), false))],
286            free: Vec::new(),
287            nodes: BTreeMap::new(),
288            edges: Edges::default(),
289            own_terms: BTreeMap::new(),
290            files: BTreeMap::new(),
291            journal: Journal::default(),
292            time: Expr::Var("t".to_string()),
293            rate,
294        }
295    }
296
297    pub(crate) fn count(&self) -> usize {
298        self.nodes.len()
299    }
300
301    pub(crate) fn rate(&self) -> u32 {
302        self.rate
303    }
304
305    pub(crate) fn grid(&self) -> Grid {
306        Grid::of(self.rate)
307    }
308
309    pub(crate) fn cx(&self, scope: ScopeId) -> Cx<'_> {
310        Cx {
311            scope,
312            time: None,
313            grid: self.grid(),
314        }
315    }
316
317    pub fn origin(&self, instance: &str) -> Option<&str> {
318        self.nodes.get(instance).map(|held| held.file.as_str())
319    }
320
321    pub fn deps(&self, path: &str) -> &[String] {
322        self.edges.of(path)
323    }
324
325    pub(crate) fn readers(&self, path: &str) -> impl Iterator<Item = &str> {
326        self.edges.readers(path)
327    }
328
329    /// `name` held as `held`, filed under its file: the one way an instance is added.
330    fn insert(&mut self, name: String, held: Instance) {
331        let files = self.files.entry(held.file.clone()).or_default();
332        files.insert(name.clone());
333        self.nodes.insert(name, held);
334    }
335
336    /// `name` let go, reading nothing and filed nowhere: the one way an instance goes.
337    fn remove(&mut self, name: &str) -> (Instance, Vec<String>) {
338        let held = self.nodes.remove(name).expect("an instance held");
339        let files = self
340            .files
341            .get_mut(&held.file)
342            .expect("its file's instances");
343        files.remove(name);
344        if files.is_empty() {
345            self.files.remove(&held.file);
346        }
347        (held, self.edges.cleared(name))
348    }
349
350    pub(crate) fn scope(&self, id: ScopeId) -> &Scope {
351        self.scopes[id as usize]
352            .as_ref()
353            .expect("a scope something holds")
354    }
355
356    pub fn paths(&self) -> impl Iterator<Item = &str> {
357        self.nodes.keys().map(String::as_str)
358    }
359
360    pub fn holds(&self, path: &str) -> bool {
361        self.nodes.contains_key(path)
362    }
363
364    /// A bare file name is the file read as a root, on its own terms; a call site's
365    /// instance carries the bindings that made it.
366    pub fn instance_of(&self, target: &str) -> Result<String, EngineError> {
367        if self.holds(target) {
368            return Ok(target.to_string());
369        }
370        if let Some(own) = self.own_terms.get(target) {
371            return Ok(own.clone());
372        }
373        sole(
374            target,
375            self.instances_of(target).map(|p| (p.clone(), p)).collect(),
376        )
377    }
378
379    pub fn instances_of<'a>(&'a self, file: &'a str) -> impl Iterator<Item = String> + 'a {
380        let held = self.files.get(file).into_iter().flatten();
381        held.map(String::clone)
382    }
383
384    pub fn at<'a>(&'a self, path: &str) -> Option<(&'a Expr, Cx<'a>)> {
385        let thunk = self.nodes.get(path)?.body.thunk();
386        Some((thunk.expr, self.cx(thunk.scope)))
387    }
388
389    pub fn bindings<'a>(&'a self, path: &str) -> Option<Vec<(&'a str, &'a Expr, Cx<'a>)>> {
390        let scope = self.nodes.get(path)?.body.scope;
391        Some(
392            self.vars(scope)
393                .map(|(name, value)| (name, value.expr, self.cx(value.scope)))
394                .collect(),
395        )
396    }
397
398    /// Each name `scope` binds, and what it stands for.
399    pub(crate) fn vars(&self, scope: ScopeId) -> impl Iterator<Item = (&str, Thunk<'_>)> {
400        let vars = self.scope(scope).vars.iter();
401        vars.map(|(name, bound)| (name.as_str(), bound.thunk()))
402    }
403
404    /// Every name any scope binds.
405    pub(crate) fn bound_names(&self) -> impl Iterator<Item = &str> {
406        let vars = self
407            .scopes
408            .iter()
409            .flatten()
410            .flat_map(|scope| scope.vars.iter());
411        vars.map(|(name, _)| name.as_str())
412    }
413}
414
415/// The one node a written name stands for: a file one argument tuple expanded is that
416/// instance, and a file several tuples share is none of them.
417pub(crate) fn sole<T>(target: &str, mut held: Vec<(String, T)>) -> Result<T, EngineError> {
418    match held.len() {
419        0 => Err(EngineError::UnknownNode(target.to_string())),
420        1 => Ok(held.remove(0).1),
421        _ => Err(EngineError::AmbiguousNode(
422            target.to_string(),
423            held.into_iter().map(|(name, _)| name).collect(),
424        )),
425    }
426}
427
428fn implicit_time(name: &str) -> bool {
429    sva_ast::FINITE_DIFFERENCE.contains(&name)
430        || sva_ast::MODAL.contains(&name)
431        || matches!(name, "noise" | "stft" | "istft")
432}
433
434impl Instances {
435    #[inline]
436    pub(crate) fn binds(&self, scope: ScopeId, name: &str) -> Option<Thunk<'_>> {
437        let bound = self.scope(scope).get(name, packed(name))?;
438        Some(bound.thunk())
439    }
440
441    /// A bound name continues in what it stands for; anything else is read where it stands.
442    #[inline]
443    pub fn follow<'a, R>(
444        &'a self,
445        e: &'a Expr,
446        cx: Cx<'a>,
447        go: impl FnOnce(&'a Expr, Cx<'_>) -> R,
448    ) -> Option<R> {
449        match self.step(e, cx)? {
450            Move::Here(e2, cx2) => Some(go(e2, cx2)),
451            Move::Shifted(e2, scope, when) => {
452                let moved = Time { expr: when, cx };
453                Some(go(
454                    e2,
455                    Cx {
456                        scope,
457                        time: Some(&moved),
458                        grid: cx.grid,
459                    },
460                ))
461            }
462        }
463    }
464
465    /// A walk of two at once needs both shifts in one frame, so it cannot take the closure.
466    #[inline(always)]
467    fn step<'a>(&'a self, e: &'a Expr, cx: Cx<'a>) -> Option<Move<'a>> {
468        match e {
469            Expr::Var(name) if name == "t" => cx.time.map(|t| Move::Here(t.expr, t.cx)),
470            Expr::Var(name) => self
471                .binds(cx.scope, name)
472                .map(|b| Move::Here(b.expr, cx.under(b.scope))),
473            Expr::Call { name, args, .. } => {
474                let bound = self.binds(cx.scope, name)?;
475                let [Arg::Pos(when)] = args.as_slice() else {
476                    return None;
477                };
478                Some(Move::Shifted(bound.expr, bound.scope, when))
479            }
480            _ => None,
481        }
482    }
483
484    #[inline]
485    pub fn node<'a>(&'a self, e: &'a Expr, cx: Cx<'_>) -> Node<'a> {
486        match e {
487            Expr::Lit(l) => Node::Lit(l),
488            Expr::Var(name) => Node::Name(name),
489            Expr::Bin(op, l, r) => Node::Bin(*op, l, r),
490            Expr::SelfRef { arg, address, span } => Node::Own {
491                arg,
492                address: *address,
493                span: *span,
494            },
495            Expr::Ref {
496                path,
497                arg,
498                address,
499                span,
500                ..
501            } => Node::Read {
502                path: self.site(e, cx.scope, path),
503                arg,
504                address: *address,
505                span: *span,
506            },
507            Expr::Indexed { name, arg, span } => match self.binds(cx.scope, name) {
508                Some(of) => Node::Signal {
509                    name,
510                    of,
511                    arg,
512                    span: *span,
513                },
514                None => Node::Name(name),
515            },
516            Expr::Call { name, args, span } if is_builtin(name) => Node::Call {
517                name,
518                args,
519                span: *span,
520            },
521            Expr::Call { name, span, .. } => Node::Read {
522                path: self.site(e, cx.scope, name),
523                arg: &self.time,
524                address: Address::Time,
525                span: *span,
526            },
527        }
528    }
529
530    fn site<'a>(&'a self, e: &Expr, scope: ScopeId, written: &'a str) -> &'a str {
531        match self
532            .scope(scope)
533            .sites
534            .get(&(std::ptr::from_ref(e) as usize))
535        {
536            Some(child) => child,
537            None => written,
538        }
539    }
540
541    /// A parameter's signal at the reader's own `t`: no shift the reader is under moves it.
542    pub(crate) fn signal<'a>(&self, of: Thunk<'a>, cx: Cx<'a>) -> Cx<'a> {
543        Cx {
544            scope: of.scope,
545            time: None,
546            grid: cx.grid,
547        }
548    }
549
550    pub(crate) fn is_now(&self, e: &Expr, cx: Cx) -> bool {
551        if let Some(r) = self.follow(e, cx, |e2, cx2| self.is_now(e2, cx2)) {
552            return r;
553        }
554        matches!(self.node(e, cx), Node::Name(n) if n == "t")
555    }
556
557    /// One hop down; `self(...)` is no edge.
558    fn direct_deps(&self, path: &str) -> Vec<String> {
559        let (e, cx) = self.at(path).expect("an instance");
560        let mut out = Vec::new();
561        self.direct_refs(e, cx, &mut out);
562        out.sort();
563        out.dedup();
564        out
565    }
566
567    fn direct_refs(&self, e: &Expr, cx: Cx, out: &mut Vec<String>) {
568        if self
569            .follow(e, cx, |e2, cx2| self.direct_refs(e2, cx2, out))
570            .is_some()
571        {
572            return;
573        }
574        match self.node(e, cx) {
575            Node::Lit(_) | Node::Name(_) => {}
576            Node::Bin(_, l, r) => {
577                self.direct_refs(l, cx, out);
578                self.direct_refs(r, cx, out);
579            }
580            Node::Call { args, .. } => {
581                for a in args {
582                    let (Arg::Pos(x) | Arg::Named(_, x)) = a;
583                    self.direct_refs(x, cx, out);
584                }
585            }
586            Node::Read { path, arg, .. } => {
587                out.push(path.to_string());
588                self.direct_refs(arg, cx, out);
589            }
590            Node::Own { arg, .. } => self.direct_refs(arg, cx, out),
591            Node::Signal { of, arg, .. } => {
592                self.direct_refs(of.expr, self.signal(of, cx), out);
593                self.direct_refs(arg, cx, out);
594            }
595        }
596    }
597
598    pub fn reads_self(&self, path: &str) -> bool {
599        self.at(path).is_some_and(|(e, cx)| self.holds_self(e, cx))
600    }
601
602    pub(crate) fn holds_self(&self, e: &Expr, cx: Cx) -> bool {
603        if let Some(r) = self.follow(e, cx, |e2, cx2| self.holds_self(e2, cx2)) {
604            return r;
605        }
606        match self.node(e, cx) {
607            Node::Lit(_) | Node::Name(_) => false,
608            Node::Own { .. } => true,
609            Node::Bin(_, l, r) => self.holds_self(l, cx) || self.holds_self(r, cx),
610            Node::Read { arg, .. } | Node::Signal { arg, .. } => self.holds_self(arg, cx),
611            Node::Call { args, .. } => args.iter().any(|a| {
612                let (Arg::Pos(x) | Arg::Named(_, x)) = a;
613                self.holds_self(x, cx)
614            }),
615        }
616    }
617
618    /// A value depending on WHICH sample is written rather than on the time handed to it.
619    pub(crate) fn position_dependent(&self, e: &Expr, cx: Cx) -> Option<String> {
620        if let Some(r) = self.follow(e, cx, |e2, cx2| self.position_dependent(e2, cx2)) {
621            return r;
622        }
623        match self.node(e, cx) {
624            Node::Lit(_) | Node::Name(_) => None,
625            Node::Own { .. } => Some("self".to_string()),
626            Node::Bin(_, l, r) => self
627                .position_dependent(l, cx)
628                .or_else(|| self.position_dependent(r, cx)),
629            Node::Read { arg, .. } | Node::Signal { arg, .. } => self.position_dependent(arg, cx),
630            Node::Call { name, args, .. } => {
631                if crate::vocabulary::shape(name).is_some() || name == "crop" || implicit_time(name)
632                {
633                    return Some(name.to_string());
634                }
635                args.iter().find_map(|a| {
636                    let (Arg::Pos(x) | Arg::Named(_, x)) = a;
637                    self.position_dependent(x, cx)
638                })
639            }
640        }
641    }
642
643    /// Two arguments are one tuple when they MEAN the same, walked both at once.
644    pub(crate) fn same<'x, 'y>(&self, a: &Expr, ax: Cx<'x>, b: &Expr, by: Cx<'y>) -> bool {
645        if let Some(moved) = self.step(a, ax) {
646            return match moved {
647                Move::Here(a2, ax2) => self.same(a2, ax2, b, by),
648                Move::Shifted(a2, scope, when) => {
649                    let moved = Time { expr: when, cx: ax };
650                    self.same(
651                        a2,
652                        Cx {
653                            scope,
654                            time: Some(&moved),
655                            grid: ax.grid,
656                        },
657                        b,
658                        by,
659                    )
660                }
661            };
662        }
663        if let Some(moved) = self.step(b, by) {
664            return match moved {
665                Move::Here(b2, by2) => self.same(a, ax, b2, by2),
666                Move::Shifted(b2, scope, when) => {
667                    let moved = Time { expr: when, cx: by };
668                    self.same(
669                        a,
670                        ax,
671                        b2,
672                        Cx {
673                            scope,
674                            time: Some(&moved),
675                            grid: by.grid,
676                        },
677                    )
678                }
679            };
680        }
681        match (self.node(a, ax), self.node(b, by)) {
682            (Node::Lit(x), Node::Lit(y)) => x == y,
683            (Node::Name(x), Node::Name(y)) => x == y,
684            (Node::Bin(o1, l1, r1), Node::Bin(o2, l2, r2)) => {
685                o1 == o2 && self.same(l1, ax, l2, by) && self.same(r1, ax, r2, by)
686            }
687            (
688                Node::Own {
689                    arg: x, address: i, ..
690                },
691                Node::Own {
692                    arg: y, address: j, ..
693                },
694            ) => i == j && self.same(x, ax, y, by),
695            (
696                Node::Read {
697                    path: p,
698                    arg: x,
699                    address: i,
700                    ..
701                },
702                Node::Read {
703                    path: q,
704                    arg: y,
705                    address: j,
706                    ..
707                },
708            ) => p == q && i == j && self.same(x, ax, y, by),
709            (Node::Signal { of: f, arg: x, .. }, Node::Signal { of: g, arg: y, .. }) => {
710                self.same(f.expr, self.signal(f, ax), g.expr, self.signal(g, by))
711                    && self.same(x, ax, y, by)
712            }
713            (
714                Node::Call {
715                    name: n1, args: a1, ..
716                },
717                Node::Call {
718                    name: n2, args: a2, ..
719                },
720            ) => {
721                n1 == n2
722                    && a1.len() == a2.len()
723                    && a1.iter().zip(a2).all(|(x, y)| match (x, y) {
724                        (Arg::Pos(x), Arg::Pos(y)) => self.same(x, ax, y, by),
725                        (Arg::Named(k1, x), Arg::Named(k2, y)) => {
726                            k1 == k2 && self.same(x, ax, y, by)
727                        }
728                        _ => false,
729                    })
730            }
731            _ => false,
732        }
733    }
734
735    pub(crate) fn same_binds(&self, scope: ScopeId, binds: &[(String, Bound)]) -> bool {
736        let held = &self.scope(scope).vars;
737        held.len() == binds.len()
738            && held.iter().zip(binds).all(|((k1, v1), (k2, v2))| {
739                let (v1, v2) = (v1.thunk(), v2.thunk());
740                k1 == k2 && self.same(v1.expr, self.cx(v1.scope), v2.expr, self.cx(v2.scope))
741            })
742    }
743}