Skip to content

Commit be66a44

Browse files
nomeataclaude
andauthored
fix: fun_induction rejecting names for all hypotheses with let and generalizing (#14475)
This PR fixes a spurious "Too many variable names provided" error from `fun_induction` (and `induction`/`cases`) when an alternative had a `let`-bound field, so that all hypotheses of such an alternative can now be named. The error came from `getNumExplicitFields`, which counted nameable fields with a telescope that reduces `let` bindings away and then over-reads into later binders, disagreeing with the `introN` that actually names them. It now introduces the fields via the same `introN` path and counts the explicit (and `let`-bound) ones, so the count and the naming can no longer diverge. Fixes: #14472 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent aacd612 commit be66a44

2 files changed

Lines changed: 50 additions & 13 deletions

File tree

src/Lean/Elab/Tactic/Induction.lean

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -311,20 +311,19 @@ def checkAltNames (alts : Array Alt) (altsSyntax : Array Syntax) : TacticM Unit
311311
else
312312
throwOrLogErrorAt altStx m!"Invalid alternative name `{altName}`: {msg}"
313313

314-
/-- Given the goal `altMVarId` for a given alternative that introduces `numFields` new variables,
315-
return the number of explicit variables. Recall that when the `@` is not used, only the explicit variables can
316-
be named by the user. -/
317-
def getNumExplicitFields (altMVarId : MVarId) (numFields : Nat) : MetaM Nat := altMVarId.withContext do
318-
let target ← altMVarId.getType
314+
/-- Given the goal `altMVarId` for an alternative that introduces `numFields` new variables, return
315+
how many the user can name when the `@` modifier is absent: the explicit and the `let`-bound
316+
ones (`introN` names `let`-bound binders as explicit). -/
317+
def getNumExplicitFields (altMVarId : MVarId) (numFields : Nat) : MetaM Nat :=
319318
withoutModifyingState do
320-
-- The `numFields` count includes explicit, implicit and let-bound variables.
321-
-- `forallMetaBoundTelescope` will reduce let-bindings, so we don't just count how many
322-
-- explicit binders are in `bis`, but how many implicit ones.
323-
-- If this turns out to be insufficient, then the real (and complicated) logic for which
324-
-- arguments are explicit or implicit can be found in `introNImp`,
325-
let (_, bis, _) ← forallMetaBoundedTelescope target numFields
326-
let numImplicits := (bis.filter (!·.isExplicit)).size
327-
return numFields - numImplicits
319+
-- Introduce the fields as the subsequent `introN` will, so the two agree on which are nameable.
320+
let (fvarIds, altMVarId) ← altMVarId.introN numFields
321+
altMVarId.withContext do
322+
let mut numExplicit := 0
323+
for fvarId in fvarIds do
324+
if (← fvarId.getDecl).binderInfo.isExplicit then
325+
numExplicit := numExplicit + 1
326+
return numExplicit
328327

329328
def saveAltVarsInfo (altMVarId : MVarId) (altStx : Syntax) (fvarIds : Array FVarId) : TermElabM Unit :=
330329
withSaveInfoContext <| altMVarId.withContext do

tests/elab/issue14472.lean

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/-!
2+
Tests that `fun_induction` allows naming all hypotheses of an alternative when the
3+
combination of a `generalizing` clause, a `let` binding in the pattern, and structural
4+
recursion previously caused a spurious "Too many variable names provided" error (#14472).
5+
-/
6+
7+
def f (x : Nat) : Nat :=
8+
match x with
9+
| 0 => 0
10+
| n + 1 =>
11+
let i := 0
12+
f (n + i)
13+
14+
-- Naming all three fields (`n`, the `let`-bound `i`, and the induction hypothesis `ih`) is
15+
-- accepted; each name refers to the expected hypothesis in the reported state.
16+
/--
17+
trace: case case2
18+
n : Nat
19+
i : Nat := 0
20+
ih : ∀ {y : Nat}, f n = 0
21+
y : Nat
22+
⊢ f (n + i) = 0
23+
---
24+
warning: declaration uses `sorry`
25+
-/
26+
#guard_msgs in
27+
example {x y : Nat} : f x = 0 := by
28+
fun_induction f generalizing y with
29+
| case1 => simp
30+
| case2 n i ih => trace_state; sorry
31+
32+
-- Providing more names than there are fields is still rejected, now with the correct count.
33+
/-- error: Too many variable names provided at alternative `case2`: 4 provided, but 3 expected -/
34+
#guard_msgs in
35+
example {x y : Nat} : f x = 0 := by
36+
fun_induction f generalizing y with
37+
| case1 => simp
38+
| case2 n i ih extra => sorry

0 commit comments

Comments
 (0)