lean4-htt/tests/lean/run/sorry.lean
Robert J. Simmons f81e64936a
feat: improve error when an identifier is unbound because autoImplicit is off (#11119)
This PR introduces a clarifying note to "undefined identifier" error
messages when the undefined identifier is in a syntactic position where
autobinding might generally apply, but where and autobinding is
disabled. A corresponding note is made in the `lean.unknownIdentifier`
error explanation.

The core intended audience for this error message change is "newcomer
who would otherwise be baffled why the thing that works in this Mathlib
project gets 'unknown identifier' errors in this non-Mathlib project."

## Modified behavior

### Example 1
```lean4
set_option autoImplicit true in
set_option relaxedAutoImplicit false in
def thisBreaks (x : α₂) (y : size₂) := ()
```

Before:
```
Unknown identifier `size₂`
```

After:
```
Unknown identifier `size₂`

Note: It is not possible to treat `size₂` as an implicitly bound variable here because it has multiple characters while the `relaxedAutoImplicit` option is set to `false`.
```

### Example 2
```lean4
set_option autoImplicit false in
def thisAlsoBreaks (x : α₃) (y : size₃) := ()
```

Before:
```
Unknown identifier `α₃`
Unknown identifier `size₃`
```

After:
```
Unknown identifier `α₃`

Note: It is not possible to treat `α₃` as an implicitly bound variable here because the `autoImplicit` option is set to `false`.
Unknown identifier `size₃`

Note: It is not possible to treat `size₃` as an implicitly bound variable here because the `autoImplicit` option is set to `false`.
```

## How this works

The elaboration process knows whether it is considering syntax where we
be able to auto-bind implicits thanks to information in the
`Lean.Elab.Term.Context`.

Before this PR, this contains:
* `autoBoundImplicit`, a boolean that is true when we are considering
syntax that might be able to auto-bind implicit AND when the
`autoImplicit` flag is set to true
* `autoBoundImplicits`, an array of `Expr` variables that we've
autobound

After this PR, this contains:
* `autoBoundImplicitCtx`, an option which is `some` **whenever** we are
considering syntax that might be able to auto-bind implicit, and carries
the array of exprs as well as a copy of the `autoImplicit` flag's value.
(The latter lets us re-implement the `autoBoundImplicit` flag for
backward compatibility.)

Therefore, rather than having access to "elaboration is in an
autobinding context && flag is enabled", it's possible to recover both
of those individual values, and give different information to the user
in cases where we didn't attempt autobinding but would have if different
options had been set.

## Rationale

The revised error message avoids offering much guidance — it doesn't
actively suggest setting the option to a different value or suggest
adding an implicit binding. Care needs to be taken here to make sure
advice is not misleading; as the accepted RFC in #6462 points out, a
substantial portion of autobinding failures are just going to be
misspellings.

I considered and then rejected a code action here to that would add a
local `set_option autoImplicit true`. This seems undesirable or
counterproductive — if a project like Mathlib has proactively disabled
`autoImplicit`, its odd to be pushing local exceptions.

A hint prompting the user to add an implicit binding would be more
proper, but only in certain circumstances — we want to be conservative
in suggesting specific code actions! In a situation like this one, we'd
want to _avoid_ giving the suggestion of adding a `{HasArr}` binding,
which I think either requires tricky heuristics or means we'd want the
elaboration to play through the consequences of auto-binding and make
sure it doesn't cause any follow-on errors before suggesting adding an
implicit binding.

```
set_option autoImplicit true
set_option relaxedAutoImplicit false
instance has_arr : HasArr Preorder := { Arr := Function }
```

Additionally, it seems like it would make the most sense to offer to
auto-bind _all_ the relevant unknown identifiers at once. To avoid being
misleading, this too would seem to require playing through the
consequences of autobinding before being able to safely suggest the
change. This is enough additional complexity that I'm leaving it for
future work.

---------

Co-authored-by: David Thrane Christiansen <david@davidchristiansen.dk>
2025-11-19 03:11:34 +00:00

131 lines
3.1 KiB
Text

/-!
# Tests of the `sorry` term elaborator
-/
set_option pp.mvars false
/-!
Basic usage.
-/
/-- warning: declaration uses 'sorry' -/
#guard_msgs in example : False := sorry
/-- warning: declaration uses 'sorry' -/
#guard_msgs in example : False := by sorry
/-!
Pretty printing
-/
/-- info: sorry : Nat -/
#guard_msgs in #check (sorry : Nat)
/-- info: fun x => sorry : Nat → Nat -/
#guard_msgs in #check fun x : Nat => (sorry : Nat)
/-- info: fun x => sorry (x + 1) : Nat → Nat -/
#guard_msgs in #check fun x : Nat => (sorry : Nat → Nat) (x + 1)
/-!
Uniqueness
-/
/-- warning: declaration uses 'sorry' -/
#guard_msgs in
example : (sorry : Nat) = sorry := by
fail_if_success rfl
sorry
/-- warning: declaration uses 'sorry' -/
#guard_msgs in
def f (n : Nat) : Nat → Nat := sorry
example : f 0 0 = f 0 0 := rfl -- succeeds
/-!
If `sorry` is used for a function type, then one gets a family of unique `sorry`s.
-/
/--
error: Type mismatch
rfl
has type
?_ = ?_
but is expected to have type
f 0 1 = f 0 0
-/
#guard_msgs in example : f 0 1 = f 0 0 := rfl
/-!
It is not completely unique though. The `sorry` did not pay attention to variables in the local context.
-/
#guard_msgs in example : f 1 0 = f 0 0 := rfl
/-!
Showing source position when surfacing differences.
-/
-- note: the module name is `sorry` and not `lean.run.sorry` in the testing environment,
-- so this test fails in VS Code.
/--
error: Type mismatch
sorry
has type
sorry `«lean.run.sorry:77:43»
but is expected to have type
sorry `«lean.run.sorry:77:25»
-/
#guard_msgs in example : sorry := (sorry : sorry)
/-!
Elaboration errors are just labeled, not unique, to limit cascading errors.
-/
/--
error: Unknown identifier `a`
Note: It is not possible to treat `a` as an implicitly bound variable here because the `autoImplicit` option is set to `false`.
---
error: Unknown identifier `b`
Note: It is not possible to treat `b` as an implicitly bound variable here because the `autoImplicit` option is set to `false`.
---
trace: ⊢ sorry = sorry
-/
#guard_msgs in
set_option autoImplicit false in
example : a = b := by trace_state; rfl
/-!
Showing that the sorries in the previous test are labeled.
-/
/--
error: Unknown identifier `a`
Note: It is not possible to treat `a` as an implicitly bound variable here because the `autoImplicit` option is set to `false`.
---
error: Unknown identifier `b`
Note: It is not possible to treat `b` as an implicitly bound variable here because the `autoImplicit` option is set to `false`.
---
trace: ⊢ sorry `«lean.run.sorry:114:10» = sorry `«lean.run.sorry:114:14»
-/
#guard_msgs in
set_option autoImplicit false in
set_option pp.sorrySource true in
example : a = b := by trace_state; rfl
/-!
The local context should show `sorry` without showing its source position.
This requires `Lean.Widget.ppExprTagged` to have a pretty printing mode that doesn't override any pretty printing options.
https://github.com/leanprover/lean4/issues/6715
-/
/--
trace: n : Nat := sorry
⊢ True
---
warning: declaration uses 'sorry'
-/
#guard_msgs in
example : True := by
let n : Nat := sorry
trace_state
trivial