Check if a field domain is a nested inductive occurrence and, if so, add an auxiliary entry to the flat block. A nested occurrence is: after peeling foralls, the result is `ExtInd Ds is` where `ExtInd` is a previously-declared inductive (not in our block) and some param arg `Ds[i]` mentions a block inductive. Important: do not WHNF the domain here.** Compile-side `replace_if_nested` (and Lean's
(
&mut self,
dom: &KExpr<M>,
block_addrs: &[Address],
flat: &mut Vec<FlatBlockMember<M>>,
aux_seen: &mut Vec<(Address, Vec<[u8; 32]>)>,
univ_offset: u64,
param_depth: usize
| 618 | Ok(flat) |
| 619 | } |
| 620 | |
| 621 | /// Check if a field domain is a nested inductive occurrence and, if so, |
| 622 | /// add an auxiliary entry to the flat block. |
| 623 | /// |
| 624 | /// A nested occurrence is: after peeling foralls, the result is `ExtInd Ds is` |
| 625 | /// where `ExtInd` is a previously-declared inductive (not in our block) and |
| 626 | /// some param arg `Ds[i]` mentions a block inductive. |
| 627 | /// |
| 628 | /// **Important: do not WHNF the domain here.** Compile-side |
| 629 | /// `replace_if_nested` (and Lean's C++ `is_nested_inductive_app`, |
| 630 | /// `inductive.cpp:920`) checks the head literally — if the head is a |
| 631 | /// definition like `IO.Ref`, it is *not* a nested-inductive occurrence. |
| 632 | /// WHNF would unfold `IO.Ref α` to `ST.Ref IO.RealWorld α`, which IS an |
| 633 | /// inductive — the kernel would then synthesize auxiliaries (e.g. |
| 634 | /// `_nested.ST_Ref_*`) that the compile side never generates, and |
| 635 | /// `populate_recursor_rules_from_block` would fail with `rec_ids/flat |
| 636 | /// count mismatch`. Peel `All` constructors structurally instead. |
| 637 | fn try_detect_nested( |
| 638 | &mut self, |
| 639 | dom: &KExpr<M>, |
| 640 | block_addrs: &[Address], |
| 641 | flat: &mut Vec<FlatBlockMember<M>>, |
| 642 | aux_seen: &mut Vec<(Address, Vec<KExpr<M>>)>, |
| 643 | univ_offset: u64, |
| 644 | param_depth: usize, // depth at the param context (before field locals) |
| 645 | n_rec_params: u64, // number of inductive parameters (valid Var refs in spec_params) |
| 646 | ) -> Result<(), TcError<M>> { |
| 647 | let saved_lctx = self.lctx.len(); |
| 648 | let result = (|| -> Result<(), TcError<M>> { |
| 649 | // Peel foralls structurally — no WHNF, see doc comment above. Open |
| 650 | // each peeled binder with a temporary fvar so domain-local dependencies |
| 651 | // in external inductive parameters are rejected by the same locality |
| 652 | // check as field-local dependencies. |
| 653 | let mut cur = dom.clone(); |
| 654 | while let ExprData::All(_, _, inner_dom, body, _) = cur.data() { |
| 655 | let inner_dom = inner_dom.clone(); |
| 656 | let body = body.clone(); |
| 657 | let (open, _) = self.open_binder_anon(inner_dom, &body); |
| 658 | cur = open; |
| 659 | } |
| 660 | |
| 661 | let (head, args) = collect_app_spine(&cur); |
| 662 | let head_id = match head.data() { |
| 663 | ExprData::Const(id, _, _) => id.clone(), |
| 664 | _ => return Ok(()), |
| 665 | }; |
| 666 | |
| 667 | // Skip if head is already a block member (direct recursive, not nested). |
| 668 | if block_addrs.contains(&head_id.addr) { |
| 669 | return Ok(()); |
| 670 | } |
| 671 | // Also skip if head is already a flat block member (already detected). |
| 672 | if flat.iter().any(|m| m.id.addr == head_id.addr && !m.is_aux) { |
| 673 | return Ok(()); |
| 674 | } |
| 675 | |
| 676 | // Check if head is an external inductive. |
| 677 | let (ext_params, ext_indices, ext_ctors, ext_lvls) = |
no test coverage detected