-
Notifications
You must be signed in to change notification settings - Fork 465
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add a linter for local vars that clash with their constructors
Also update the error message for invalid names in application position to suggest valid alternatives.
- Loading branch information
1 parent
c5120c1
commit 6099228
Showing
11 changed files
with
347 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
A new linter flags situations where a local variable's name is one of | ||
the argumentless constructors of its type. This can arise when a user either | ||
doesn't open a namespace or doesn't add a dot or leading qualifier, as | ||
in the following: | ||
|
||
```` | ||
inductive Tree (α : Type) where | ||
| leaf | ||
| branch (left : Tree α) (val : α) (right : Tree α) | ||
def depth : Tree α → Nat | ||
| leaf => 0 | ||
```` | ||
|
||
With this linter, the `leaf` pattern is highlighted as a local | ||
variable whose name overlaps with the constructor `Tree.leaf`. | ||
|
||
The linter can be disabled with `set_option linter.constructorNameAsVariable false`. | ||
|
||
Additionally, the error message that occurs when a name in a pattern that takes arguments isn't valid now suggests similar names that would be valid. This means that the following definition: | ||
|
||
``` | ||
def length (list : List α) : Nat := | ||
match list with | ||
| nil => 0 | ||
| cons x xs => length xs + 1 | ||
``` | ||
|
||
now results in the following warning: | ||
|
||
``` | ||
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` | ||
``` | ||
|
||
and error: | ||
|
||
``` | ||
invalid pattern, constructor or constant marked with '[match_pattern]' expected | ||
Suggestion: 'List.cons' is similar | ||
``` | ||
|
||
|
||
#4301 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
/- | ||
Copyright (c) 2024 Lean FRO, LLC. All rights reserved. | ||
Released under Apache 2.0 license as described in the file LICENSE. | ||
Authors: David Thrane Christiansen | ||
-/ | ||
prelude | ||
import Lean.Elab.Command | ||
import Lean.Linter.Util | ||
|
||
set_option linter.missingDocs true | ||
|
||
namespace Lean.Linter | ||
|
||
open Lean Elab Command | ||
open Lean.Linter (logLint) | ||
|
||
/-- | ||
A linter that warns when bound variable names are the same as constructor names for their types, | ||
modulo namespaces. | ||
-/ | ||
register_builtin_option linter.constructorNameAsVariable : Bool := { | ||
defValue := true, | ||
descr := "enable the linter that warns when bound variable names are nullary constructor names" | ||
} | ||
|
||
/-- | ||
Reports when bound variables' names overlap with constructor names for their type. This is to warn | ||
especially new users that they have built a pattern that matches anything, rather than one that | ||
matches a particular constructor. Use `linter.constructorNameAsVariable` to disable. | ||
-/ | ||
def constructorNameAsVariable : Linter where | ||
run cmdStx := do | ||
let some cmdStxRange := cmdStx.getRange? | ||
| return | ||
|
||
let infoTrees := (← get).infoState.trees.toArray | ||
let warnings : IO.Ref (Lean.HashMap String.Range (Syntax × Name × Name)) ← IO.mkRef {} | ||
|
||
for tree in infoTrees do | ||
tree.visitM' (preNode := fun ci info _ => do | ||
match info with | ||
| .ofTermInfo ti => | ||
match ti.expr with | ||
| .fvar id .. => | ||
let some range := info.range? | return | ||
if (← warnings.get).contains range then return | ||
let .original .. := info.stx.getHeadInfo | return | ||
if ti.isBinder then | ||
-- This is a local variable declaration. | ||
let some ldecl := ti.lctx.find? id | return | ||
-- Skip declarations which are outside the command syntax range, like `variable`s | ||
-- (it would be confusing to lint these), or those which are macro-generated | ||
if !cmdStxRange.contains range.start || ldecl.userName.hasMacroScopes then return | ||
let opts := ci.options | ||
-- we have to check for the option again here because it can be set locally | ||
if !linter.constructorNameAsVariable.get opts then return | ||
if let n@(.str .anonymous s) := info.stx.getId then | ||
-- Check whether the type is an inductive type, and get its constructors | ||
let ty ← | ||
if let some t := ti.expectedType? then pure t | ||
else ti.runMetaM ci (Meta.inferType ti.expr) | ||
let ty ← ti.runMetaM ci (instantiateMVars ty >>= Meta.whnf) | ||
if let .const tn _ := ty.getAppFn' then | ||
if let some (.inductInfo i) := (← getEnv).find? tn then | ||
for c in i.ctors do | ||
-- Only warn when the constructor has 0 fields. Pattern variables can't be | ||
-- confused with constructors that want arguments. | ||
if let some (.ctorInfo ctorInfo) := (← getEnv).find? c then | ||
if ctorInfo.numFields > 0 then continue | ||
if let .str _ cn := c then | ||
if cn == s then | ||
warnings.modify (·.insert range (info.stx, n, c)) | ||
else pure () | ||
| _ => pure () | ||
| _ => pure ()) | ||
|
||
-- Sort the outputs by position | ||
for (_range, declStx, userName, ctorName) in (← warnings.get).toArray.qsort (·.1.start < ·.1.start) do | ||
logLint linter.constructorNameAsVariable declStx <| | ||
m!"Local variable '{userName}' resembles constructor '{ctorName}' - " ++ | ||
m!"write '.{userName}' (with a dot) or '{ctorName}' to use the constructor." | ||
|
||
builtin_initialize addLinter constructorNameAsVariable |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
set_option linter.constructorNameAsVariable false | ||
|
||
inductive A where | ||
| a | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.