lean4-htt/tests/elab/constructor_as_variable.lean
Kim Morrison 2daaa50afb
chore: constructorNameAsVariable linter respects linter.all (#4966)
This PR ensures `linter.all` disables `constructorNameAsVariable`.

The issue was discovered by @eric-wieser while investigating a quote4
issue.

This seems like an easy mistake to make when setting up a new linter,
and perhaps we need a better structure to make it easy to do the right
thing.

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 00:20:21 +00:00

190 lines
5.9 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import Lean.Elab.Command
/-!
Testing for linter.constructorNameAsVariable
This linter checks warns when a bound variable's name is the name of a constructor of the variable's
type, which probably indicates a namespace mistake, but can be otherwise hard to find.
The linter is designed to interact well with the suggestions provided when a non-constructor is used
where a constructor would be expected in a pattern, so that users who don't know Lean namespaces
will be guided to the right qualified names. Thus, both are tested together here.
-/
set_option linter.unusedVariables false
-- The test harness passes `-Dlinter.all=false`, which would disable this linter.
-- We unset `linter.all` so that the linter uses its default value (`true`).
-- This is a minimal inline version of Mathlib's `unset_option`.
open Lean Elab Command in
elab "unset_option " opt:ident : command => do
modifyScope fun scope => { scope with opts := scope.opts.erase opt.getId.eraseMacroScopes }
unset_option linter.all
inductive A where
| x | y
-- Test that the linter works even in the presence of errors (making it useful for confused new
-- users)
/--
warning: Local variable 'x' resembles constructor 'A.x' - write '.x' (with a dot) or 'A.x' to use the constructor.
Note: This linter can be disabled with `set_option linter.constructorNameAsVariable false`
-/
#guard_msgs(drop error, warning) in
def f : A → Unit
| x => _
-- Show that the linter also works when there are no errors
/--
warning: Local variable 'x' resembles constructor 'A.x' - write '.x' (with a dot) or 'A.x' to use the constructor.
Note: This linter can be disabled with `set_option linter.constructorNameAsVariable false`
-/
#guard_msgs(warning) in
def g : A → Unit
| x => ()
-- Check that turning it off works
#guard_msgs in
set_option linter.constructorNameAsVariable false in
def g' : A → Unit
| x => ()
-- Check that turning it off via `linter.all` works
#guard_msgs in
set_option linter.all false in
def g'' : A → Unit
| x => ()
-- Avoid false positives
#guard_msgs in
def h : A → Unit
| z => ()
-- Check that it works for let-bindings
/--
warning: Local variable 'x' resembles constructor 'A.x' - write '.x' (with a dot) or 'A.x' to use the constructor.
Note: This linter can be disabled with `set_option linter.constructorNameAsVariable false`
---
warning: Local variable 'y' resembles constructor 'A.y' - write '.y' (with a dot) or 'A.y' to use the constructor.
Note: This linter can be disabled with `set_option linter.constructorNameAsVariable false`
-/
#guard_msgs in
def i (a : A × A) : Unit :=
let (x, y) := a
()
/--
warning: Local variable 'x' resembles constructor 'A.x' - write '.x' (with a dot) or 'A.x' to use the constructor.
Note: This linter can be disabled with `set_option linter.constructorNameAsVariable false`
-/
#guard_msgs in
def i' : Unit :=
let x : A := .x
()
-- Check that it works in tactic proofs
/--
warning: Local variable 'x' resembles constructor 'A.x' - write '.x' (with a dot) or 'A.x' to use the constructor.
Note: This linter can be disabled with `set_option linter.constructorNameAsVariable false`
-/
#guard_msgs in
theorem j (a : A ⊕ A) : True := by
cases a with
| inl x => trivial
| inr z => trivial
-- Top-level names do not trigger the lint
#guard_msgs in
def x : A := A.x
/-! Test the interaction with the invalid match pattern error messages -/
inductive MyProd where
| construct : Nat → Nat → MyProd
/--
error: Invalid pattern: Expected a constructor or constant marked with `[match_pattern]`
Hint: Using `MyProd.construct` would be valid:
[apply] `MyProd.construct`
-/
#guard_msgs in
def ctorSuggestion1 (pair : MyProd) : Nat :=
match pair with
| construct x y => y
-- This test is a realistic situation if a user doesn't know how Lean namespaces work
/--
error: Invalid pattern: Expected a constructor or constant marked with `[match_pattern]`
Hint: Using one of these would be valid:
[apply] `Std.DHashMap.Internal.AssocList.cons`
[apply] `List.Pairwise.cons`
[apply] `Lean.Grind.AC.Seq.cons`
[apply] `List.Lex.cons`
[apply] `List.Sublist.below.cons`
[apply] `List.Perm.cons`
[apply] `List.Sublist.cons`
[apply] `Lean.AssocList.cons`
[apply] `List.Perm.below.cons`
[apply] `List.Lex.below.cons`
[apply] `List.Pairwise.below.cons`
[apply] `List.cons`
---
warning: Local variable 'nil' resembles constructor 'List.nil' - write '.nil' (with a dot) or 'List.nil' to use the constructor.
Note: This linter can be disabled with `set_option linter.constructorNameAsVariable false`
-/
#guard_msgs in
def ctorSuggestion2 (list : List α) : Nat :=
match list with
| nil => 0
| cons x xs => 1 + ctorSuggestion2 xs
-- Adding another `cons` also adds a suggestion
inductive StringList : Type where
| nil
| cons (s : String) (ss : StringList)
/--
error: Invalid pattern: Expected a constructor or constant marked with `[match_pattern]`
Hint: Using one of these would be valid:
[apply] `Std.DHashMap.Internal.AssocList.cons`
[apply] `List.Pairwise.cons`
[apply] `Lean.Grind.AC.Seq.cons`
[apply] `List.Lex.cons`
[apply] `List.Sublist.below.cons`
[apply] `List.Perm.cons`
[apply] `List.Sublist.cons`
[apply] `Lean.AssocList.cons`
[apply] `List.Perm.below.cons`
[apply] `List.Lex.below.cons`
[apply] `List.Pairwise.below.cons`
[apply] `List.cons`
[apply] `StringList.cons`
---
warning: Local variable 'nil' resembles constructor 'List.nil' - write '.nil' (with a dot) or 'List.nil' to use the constructor.
Note: This linter can be disabled with `set_option linter.constructorNameAsVariable false`
-/
#guard_msgs in
def ctorSuggestion3 (list : List α) : Nat :=
match list with
| nil => 0
| cons x xs => 1 + ctorSuggestion2 xs
-- There isn't always a suggestion to provide
/-- error: Invalid pattern: Expected a constructor or constant marked with `[match_pattern]` -/
#guard_msgs in
def ctorNoSuggestion (x : α) :=
match x with
| notAConstructor a b c => 42