lean4-htt/tests/lean/run/3943.lean
Kyle Miller 02c8c2f9e1
feat: use nondep flag in Expr.letE and LocalContext.ldecl (#8804)
This PR implements first-class support for nondependent let expressions
in the elaborator; recall that a let expression `let x : t := v; b` is
called *nondependent* if `fun x : t => b` typechecks, and the notation
for a nondependent let expression is `have x := v; b`. Previously we
encoded `have` using the `letFun` function, but now we make use of the
`nondep` flag in the `Expr.letE` constructor for the encoding. This has
been given full support throughout the metaprogramming interface and the
elaborator. Key changes to the metaprogramming interface:
- Local context `ldecl`s with `nondep := true` are generally treated as
`cdecl`s. This is because in the body of a `have` expression the
variable is opaque. Functions like `LocalDecl.isLet` by default return
`false` for nondependent `ldecl`s. In the rare case where it is needed,
they take an additional optional `allowNondep : Bool` flag (defaults to
`false`) if the variable is being processed in a context where the value
is relevant.
- Functions such as `mkLetFVars` by default generalize nondependent let
variables and create lambda expressions for them. The
`generalizeNondepLet` flag (default true) can be set to false if `have`
expressions should be produced instead. **Breaking change:** Uses of
`letLambdaTelescope`/`mkLetFVars` need to use `generalizeNondepLet :=
false`. See the next item.
- There are now some mapping functions to make telescoping operations
more convenient. See `mapLetTelescope` and `mapLambdaLetTelescope`.
There is also `mapLetDecl` as a counterpart to `withLetDecl` for
creating `let`/`have` expressions.
- Important note about the `generalizeNondepLet` flag: it should only be
used for variables in a local context that the metaprogram "owns". Since
nondependent let variables are treated as constants in most cases, the
`value` field might refer to variables that do not exist, if for example
those variables were cleared or reverted. Using `mapLetDecl` is always
fine.
- The simplifier will cache its let dependence calculations in the
nondep field of let expressions.
- The `intro` tactic still produces *dependent* local variables. Given
that the simplifier will transform lets into haves, it would be
surprising if that would prevent `intro` from creating a local variable
whose value cannot be used.

Note that nondependence of lets is not checked by the kernel. To
external checker authors: If the elaborator gets the nondep flag wrong,
we consider this to be an elaborator error. Feel free to typecheck `letE
n t v b true` as if it were `app (lam n t b default) v` and please
report issues.

This PR follows up from #8751, which made sure the nondep flag was
preserved in the C++ interface.
2025-06-22 21:54:57 +00:00

51 lines
2.1 KiB
Text

example (f : Nat → Nat) : (if f x = 0 then f x else f x + 1) + f x = y := by
simp (config := { contextual := true })
guard_target =ₛ (if f x = 0 then 0 else f x + 1) + f x = y
sorry
example (f : Nat → Nat) : f x = 0 → f x + 1 = y := by
simp (config := { contextual := true })
guard_target =ₛ f x = 0 → 1 = y
sorry
example (f : Nat → Nat) : let _ : f x = 0 := sorryAx _ false; f x + 1 = y := by
simp (config := { contextual := true, zeta := false, zetaUnused := false })
guard_target =ₛ have _ : f x = 0 := sorryAx _ false; 1 = y
sorry
def overlap : Nat → Nat
| 0 => 0
| 1 => 1
| n+1 => overlap n
example : (if (n = 0 → False) then overlap (n+1) else overlap (n+1)) = overlap n := by
simp (config := { contextual := true }) only [overlap]
guard_target =ₛ (if (n = 0 → False) then overlap n else overlap (n+1)) = overlap n
sorry
example : (if (n = 0 → False) then overlap (n+1) else overlap (n+1)) = overlap n := by
-- The following tactic should because the default discharger only uses assumptions available
-- when `simp` was invoked unless `contextual := true`
fail_if_success simp only [overlap]
guard_target =ₛ (if (n = 0 → False) then overlap (n+1) else overlap (n+1)) = overlap n
sorry
example : (if (n = 0 → False) then overlap (n+1) else overlap (n+1)) = overlap n := by
-- assumption is not a well-behaved discharger, and the following should still work as expected
simp (discharger := assumption) only [overlap]
guard_target =ₛ (if (n = 0 → False) then overlap n else overlap (n+1)) = overlap n
sorry
opaque p : Nat → Bool
opaque g : Nat → Nat
@[simp] theorem g_eq (h : p x) : g x = x := sorry
example : (if p x then g x else g x + 1) + g x = y := by
simp (discharger := assumption)
guard_target =ₛ (if p x then x else g x + 1) + g x = y
sorry
example : (let _ : p x := sorryAx _ false; g x + 1 = y) ↔ g x = y := by
simp (config := { zeta := false, zetaUnused := false }) (discharger := assumption)
guard_target =ₛ (have _ : p x := sorryAx _ false; x + 1 = y) ↔ g x = y
sorry