lean4-htt/tests/elab/constructor_as_variable.lean
Julia Markus Himmel da91aed2e2
feat: verification of String.dropWhile and String.takeWhile (#13155)
This PR verifies the `String.dropWhile` and `String.takeWhile`
functions.

It also includes a refactor of the `PatternModel` class so that the
`not_matches_empty` condition is moved into a separate typeclass
`StrictPatternModel`. This allows string patterns to implement
`LawfulForwardPatternModel` unconditionally, which means that more of
the general theory about patterns directly applies to string patterns
without having to do a case distinction for empty strings.

This PR also includes a study of the `PatternModel` machinery given to
slices `s` and `t` such that `s.copy = t.copy`. From these results, we
deduce statements like `s.copy.startsWith pat = s.startsWith pat` (which
is far from obvious!).
2026-04-03 14:02:21 +00:00

198 lines
6.4 KiB
Text
Raw Permalink 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] `List.cons`
[apply] `Lean.AssocList.cons`
[apply] `List.Lex.cons`
[apply] `List.Pairwise.cons`
[apply] `List.Perm.cons`
[apply] `List.Sublist.cons`
[apply] `List.Lex.below.cons`
[apply] `List.Pairwise.below.cons`
[apply] `List.Perm.below.cons`
[apply] `List.Sublist.below.cons`
[apply] `Lean.Grind.AC.Seq.cons`
[apply] `Std.DHashMap.Internal.AssocList.cons`
[apply] `String.Slice.Pattern.Model.IsLongestMatchAtChain.cons`
[apply] `String.Slice.Pattern.Model.IsLongestRevMatchAtChain.cons`
[apply] `String.Slice.Pattern.Model.IsLongestMatchAtChain.below.cons`
[apply] `String.Slice.Pattern.Model.IsLongestRevMatchAtChain.below.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] `List.cons`
[apply] `StringList.cons`
[apply] `Lean.AssocList.cons`
[apply] `List.Lex.cons`
[apply] `List.Pairwise.cons`
[apply] `List.Perm.cons`
[apply] `List.Sublist.cons`
[apply] `List.Lex.below.cons`
[apply] `List.Pairwise.below.cons`
[apply] `List.Perm.below.cons`
[apply] `List.Sublist.below.cons`
[apply] `Lean.Grind.AC.Seq.cons`
[apply] `Std.DHashMap.Internal.AssocList.cons`
[apply] `String.Slice.Pattern.Model.IsLongestMatchAtChain.cons`
[apply] `String.Slice.Pattern.Model.IsLongestRevMatchAtChain.cons`
[apply] `String.Slice.Pattern.Model.IsLongestMatchAtChain.below.cons`
[apply] `String.Slice.Pattern.Model.IsLongestRevMatchAtChain.below.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