-
Notifications
You must be signed in to change notification settings - Fork 451
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add a linter for local vars that clash with their constructors #4301
feat: add a linter for local vars that clash with their constructors #4301
Conversation
CC @leodemoura - this is what we talked about yesterday |
Mathlib CI status (docs):
|
This is a draft until I fix the linter warnings during bootstrapping - but I think the linter itself is ready for review |
@@ -333,8 +333,8 @@ def SemanticTokenType.names : Array String := | |||
"event", "method", "macro", "modifier", "comment", "string", "number", | |||
"regexp", "operator", "decorator", "leanSorryLike"] | |||
|
|||
def SemanticTokenType.toNat (type : SemanticTokenType) : Nat := |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this new linter be limited to situations where denoting a constructor makes sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think so, but it's a good question to ask.
There's two reasons:
- For new Lean users, it may not be clear where a constructor can or can't be denoted by a name. Having the linter fire may help make it clear that this is a variable.
- For less-new Lean users, avoiding this lint can make it more difficult to introduce bugs during refactoring. While I've been going through trying to make the compiler avoid this lint, there's been a few cases where renaming the argument to the function but skipping its use sites results in a program that still type checks, because the function is defined in the datatype's namespace and the constructor in question is nullary. I'm being careful while I do the renaming, and using "rename symbol", but it seems useful to have code in general that's robust in the face of incomplete renamings.
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think both of those are valid concerns, but I'm also not sure whether they outweigh the potential annoyance of not being able to use a specific name in a position where you know it's safe and you can't even accidentally mess it up because constructors are not allowed in this position. I don't have a strong opinion either way!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you think of an example where using a constructor name is actually better? Maybe using fvar
to bind an Expr
known to be a .fvar n
. Hmm.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are plenty of examples in the diff of this PR where people felt like the constructor name was the most natural name to use (both because it is known to be the constructor and by accident) :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Though whether it's firing in places where constructor patterns are allowed is a bit of a red herring for this kind of situation, because we have a fair number of instances of let fvar := ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I'm going to aim for less disturbance right now, and then we can have the discussion later. I'll add an option to disable it for certain constructors, set the default to false, update-stage0, then set the default to true with all of the cases that we frequently use set to exceptions. Then we can take it case by case, and not all in one ginormous diff.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This has now been changed so that the linter only fires for nullary constructors that overlap bound variable names of the same type - only one constructor was hit by this in all of Lean.
At the same time, I also updated the error message for non-constructor things being applied in patterns so that it suggests valid names if any exist. This should also catch similar misunderstandings in those cases.
Now it doesn't need to be selectively disabled, which is a good sign.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
only one constructor was hit by this in all of Lean.
I am curious what the one example was? Was it just this one (SemanticTokenType
)? If so, I see how the linter here makes sense as I can imagine a very perplexing bug caused by some pattern match confusion between a variable match type
and constructor match .type
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Turns out I was wrong - there was this one, and then also https://github.com/leanprover/lean4/pull/4301/files#diff-ec4d9d868e0d4a5702d821884ef44382eb312b8de8fab76db01879ee97fead78L104-R108 .
So two places - not too terrible, I suppose, and I think both are easier to read with this change.
Simulating the next PR post-stage0-update is showing that there's something I don't understand about attributes and bootstrapping, so I'm marking draft until the followup PR also works locally. |
Stage0 update will no longer be needed after latest updates :) |
All right, I'm now pretty happy with this. Three questions:
|
Also update the error message for invalid names in application position to suggest valid alternatives.
ec17eb6
to
6099228
Compare
Rebase and squash to try to fix CI failure |
This came up when watching new Lean users in a class situation. A number of them were confused when they omitted a namespace on a constructor name, and Lean treated the variable as a pattern that matches anything.
For example, this program is accepted but may not do what the user thinks:
Adding a
branch
case todepth
results in a confusing message.With this linter, Lean marks
leaf
with:Additionally, the error message that occurs when invalid names are applied in patterns now suggests similar names. This means that:
now results in the following warning on
nil
:and error on
cons
:The list of suggested constructors is generated before the type of the pattern is known, so it's less accurate, but it truncates the list to ten elements to avoid being overwhelming. This mostly comes up with
mk
.