For K-like recursors, try to synthesize a nullary constructor from the major premise's type. Returns `Ok(Some(ctor_app))` if successful. Algorithm (following lean4lean/nanoda): 1. Infer major's type, WHNF it 2. Check head constant matches the recursor's target inductive 3. Build nullary ctor: `Ctor.{levels} params...` 4. Infer ctor's type, check def-eq with major's type
(
&mut self,
major: &KExpr<M>,
rec_id: &KId<M>,
recr: &IotaInfo<M>,
)
| 1317 | k, |
| 1318 | params, |
| 1319 | motives, |
| 1320 | minors, |
| 1321 | indices, |
| 1322 | rules, |
| 1323 | lvls, |
| 1324 | .. |
| 1325 | }) => { |
| 1326 | let major_idx = u64_to_usize::<M>(params + motives + minors + indices)?; |
| 1327 | if spine.len() <= major_idx { |
| 1328 | return Ok(None); |
| 1329 | } |
| 1330 | IotaInfo { |
| 1331 | k, |
| 1332 | params: u64_to_usize::<M>(params)?, |
| 1333 | motives: u64_to_usize::<M>(motives)?, |
| 1334 | minors: u64_to_usize::<M>(minors)?, |
| 1335 | indices: u64_to_usize::<M>(indices)?, |
| 1336 | major_idx, |
| 1337 | // `rules` is already owned here (moved out of the KConst clone |
| 1338 | // `try_get_const` returned) — do not clone it again. |
| 1339 | rules, |
| 1340 | lvls, |
| 1341 | } |
| 1342 | }, |
| 1343 | _ => return Ok(None), |
| 1344 | }; |
| 1345 | |
| 1346 | // K-like recursor: try to synthesize a nullary constructor before WHNF. |
| 1347 | // This handles cases like `Eq.rec motive minor major` where major isn't |
| 1348 | // a constructor but its type matches the inductive — we build `Eq.refl params...`. |
| 1349 | let major = &spine[recr.major_idx]; |
| 1350 | let major = if recr.k { |
| 1351 | self |
| 1352 | .synth_ctor_when_k(major, &rec_id, &recr, &rec_us)? |
| 1353 | .unwrap_or_else(|| major.clone()) |
| 1354 | } else { |
| 1355 | major.clone() |
| 1356 | }; |
| 1357 | let major = match self.cleanup_nat_offset_major(&major)? { |
| 1358 | Some(cleaned) => cleaned, |
| 1359 | None => major, |
| 1360 | }; |
| 1361 | |
| 1362 | // WHNF the major premise. Cheap mode skips delta on the major itself, |
| 1363 | // matching Lean4Lean's `cheapRec` (TypeChecker.lean:337–341); the rest of |
| 1364 | // the iota machinery still gets a structural normal form to inspect. |
| 1365 | let mut major_whnf = if flags.cheap_rec { |
| 1366 | self.whnf_core_with_flags(&major, flags)? |
| 1367 | } else { |
| 1368 | self.whnf(&major)? |
| 1369 | }; |
| 1370 | |
| 1371 | // Nat literal → constructor form (one level: n → Nat.succ(lit(n-1))). |
| 1372 | // |
| 1373 | // Mirrors lean4 (`refs/lean4/src/kernel/inductive.h:91-93`) and |
| 1374 | // lean4lean (`refs/lean4lean/Lean4Lean/Inductive/Reduce.lean:70`): |
| 1375 | // unconditional peel. Truly runaway recursors (step case forces the |
| 1376 | // IH on every iteration) are bounded by `MAX_WHNF_FUEL` / outer |
no test coverage detected