Skip to content

fix: Sym.DSimp.zetaDelta should unfold let-bound variables in head position - #15362

Merged
leodemoura merged 1 commit into
masterfrom
sym_dsimp_fun_issue
Sep 27, 2026
Merged

leodemoura merged 1 commit into
masterfrom
sym_dsimp_fun_issue

Conversation

@leodemoura

Copy link
Copy Markdown
Member

This PR fixes Sym.DSimp.zetaDelta and Sym.DSimp.zetaDeltaAll so that they unfold a let-bound variable that appears as the head of an application. Previously, dsimp [foo] and dsimp [*] in grind/sym, and the bv_decide normalization passes, left terms such as foo a b untouched when foo := fun x y => x + y was a local definition, while Meta.DSimp reduces them to a + b.

The root cause is that Sym.dsimp intentionally does not visit the head of an application (dsimpAppArgs only traverses the arguments), and both simprocs only matched a bare .fvar. As a result, a let-bound .fvar in argument position was unfolded, but the same .fvar in head position was never seen by the simproc. The fix makes zetaDelta and zetaDeltaAll match on e.getAppFn. When the head is a let-bound .fvar, the result is its value applied to the original arguments, built with mkAppNS to preserve maximal sharing. Beta-reduction of the exposed lambda is still left to the beta simproc, so zetaDeltaAll >> beta yields a + b = b + a for the example above. The doc-string of dsimpAppArgs now states the resulting contract: pre/post simprocs that rewrite heads must match on the whole application.

🤖 Generated with Claude Code

…position

This PR fixes `Sym.DSimp.zetaDelta` and `Sym.DSimp.zetaDeltaAll` so that they unfold a let-bound variable that appears as the head of an application. Previously, `dsimp [foo]` and `dsimp [*]` in `grind`/`sym`, and the `bv_decide` normalization passes, left terms such as `foo a b` untouched when `foo := fun x y => x + y` was a local definition, while `Meta.DSimp` reduces them to `a + b`.

The root cause is that `Sym.dsimp` intentionally does not visit the head of an application (`dsimpAppArgs` only traverses the arguments), and both simprocs only matched a bare `.fvar`. As a result, a let-bound `.fvar` in argument position was unfolded, but the same `.fvar` in head position was never seen by the simproc. The fix makes `zetaDelta` and `zetaDeltaAll` match on `e.getAppFn`. When the head is a let-bound `.fvar`, the result is its value applied to the original arguments, built with `mkAppNS` to preserve maximal sharing. Beta-reduction of the exposed lambda is still left to the `beta` simproc, so `zetaDeltaAll >> beta` yields `a + b = b + a` for the example above. The doc-string of `dsimpAppArgs` now states the resulting contract: `pre`/`post` simprocs that rewrite heads must match on the whole application.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@leodemoura leodemoura added the changelog-tactics User facing tactics label Sep 27, 2026
@leodemoura
leodemoura added this pull request to the merge queue Sep 27, 2026
@github-actions github-actions Bot added the toolchain-available A toolchain is available for this PR, at leanprover/lean4-pr-releases:pr-release-NNNN label Sep 27, 2026
Merged via the queue into master with commit e39e715 Sep 27, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog-tactics User facing tactics toolchain-available A toolchain is available for this PR, at leanprover/lean4-pr-releases:pr-release-NNNN

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant