Zulip Chat Archive
Stream: mathlib4
Topic: haveLet linter crashing under `byAsSorry`
Kim Morrison (Aug 13 2024 at 02:28):
Now that we've got the new variable
command in, I'm trying to get Mathlib running under byAsSorry
. Initial signs are good!
However, the haveLet
linter is crashing with a stack overflow. I can work around this for now by putting
⟨`weak.linter.haveLet, .ofNat 0⟩,
in the lakefile. @Damiano Testa, would you be able to take a look at this?
If you checkout branch#byAsSorry, remove that line from the lakefile, and build, you should encounter the stack overflow not too far in!
Damiano Testa (Aug 13 2024 at 04:54):
Ok, I will take a look!
Damiano Testa (Aug 13 2024 at 05:09):
I have not gotten to the overflow yet, but I remembered that I had observed an issue during development: https://github.com/leanprover-community/mathlib4/pull/12190#issuecomment-2061005403. I'll try to build these files directly to see if observe the overflow!
Damiano Testa (Aug 13 2024 at 05:22):
I believe that I found a stack overflow in Mathlib.Data.UInt
, which I also think was one of the ones that I noticed earlier.
Damiano Testa (Aug 13 2024 at 16:38):
Kim, this seems exactly an issue that I had already tried to fix and failed while developing the linter. However, when the byAsSorry
option is on, maybe the linter should not run anyway.
Damiano Testa (Aug 13 2024 at 16:38):
Would it make sense to silence the linter while in byAsSorry
mode?
Damiano Testa (Aug 13 2024 at 16:39):
After all, if you are byAsSorry
ing everything, you probably do not care about have
s that should be let
s!
Kim Morrison (Aug 14 2024 at 03:10):
Yes, I've turned it off on the byAsSorry branch (and in fact I will turn off all linters), just thought you might be interested in the crash. :-)
Damiano Testa (Aug 14 2024 at 03:16):
I am interested and I had already observed it in 3 commands throughout Mathlib.
Eric thought that it was a consequence of his "hack" and they were fixed by explicitly initializing a MetaM
context. However, the "hack" handles the context better, in all cases where it does not crash!
Sébastien Gouëzel (Aug 21 2024 at 07:43):
I've just hit the overflow in the PFR project, without byAsSorry
. You can see it in https://github.com/sgouezel/pfr, commit f428dbcacae0f13d0ac49cb8ec481c7b1bbd586f, in the file PFR.Matlhlib.Probability.Independence.Kernel. It starts with
libc++abi: terminating due to uncaught exception of type lean::throwable: deep recursion was detected at 'interpreter' (potential solution: increase stack space in your system)
interpreter stacktrace:
#1 Mathlib.Linter.haveLet.InfoTree.foldInfoM._at.Mathlib.Linter.haveLet.nonPropHaves._spec_15._lambda_1
#2 Mathlib.Linter.haveLet.InfoTree.foldInfoM._at.Mathlib.Linter.haveLet.nonPropHaves._spec_15._lambda_1
and goes on like that for a long time. I don't see any suspicious line in the proof.
Damiano Testa (Aug 21 2024 at 07:44):
I think that Kim disabled the HaveLet
linter with byAsSorry
.
Damiano Testa (Aug 21 2024 at 07:45):
Since the linter is chatty when there is a sorry, it would flag everything, so I think that the linter should have a mechanism for only enabling itself when byAsSorry
is set, but I have not looked into that yet.
Sébastien Gouëzel (Aug 21 2024 at 07:46):
Note that there is no sorry in this proof, it is a perfectly fine proof that works fine with lake build
.
Damiano Testa (Aug 21 2024 at 07:47):
I have not actually looked at anything with byAsSorry
, but could it be that some version of the lemmas with by
replaced by sorry
are actually elaborated?
Damiano Testa (Aug 21 2024 at 07:48):
This may mean that the linter is also checking those "invisible, sorried lemmas" and causing trouble,
Damiano Testa (Aug 21 2024 at 07:48):
If you can point me to a more specific place in the repo, I can try to take a look.
Mario Carneiro (Aug 21 2024 at 07:48):
why does nonPropHaves
have partial
? It doesn't seem to be recursive
Sébastien Gouëzel (Aug 21 2024 at 07:48):
I haven't been clear: there is absolutely no sorry around, it's a complete proof.
Damiano Testa (Aug 21 2024 at 07:50):
Mario Carneiro said:
why does
nonPropHaves
havepartial
? It doesn't seem to be recursive
It might be an indication that it was modified from a version that was really partial to the current version that no longer is.
Sébastien Gouëzel (Aug 21 2024 at 07:52):
I have fixed the problem in my case, by renaming two unused variables hi
and hj
to _hi
and _hj
(this was signalled to me by the unusedVariables
linter when compiling on the command line). Once this is done, the stack overflow disappears. Looks like black magic...
Damiano Testa (Aug 21 2024 at 07:52):
Ah, the warnings for the unused variables made the linter active!
Sébastien Gouëzel (Aug 21 2024 at 07:53):
If it gives you a clue for how to fix it, that's great!
Damiano Testa (Aug 21 2024 at 07:54):
The linter is active when the command emits a warning. Typically, this is the presence of a sorry
, but, in fact, any warning makes the linter chatty.
Mario Carneiro (Aug 21 2024 at 07:54):
I'm looking at the implementation of the linter now, and it seems to be doing unnecessary things
Damiano Testa (Aug 21 2024 at 07:54):
Maybe I should change the HaveLet linter to only be chatty when there is a sorry
, not when any warning is present.
Damiano Testa (Aug 21 2024 at 07:55):
Mario Carneiro said:
I'm looking at the implementation of the linter now, and it seems to be doing unnecessary things
I'm happy to learn about ways of simplifying it! :smile:
Mario Carneiro (Aug 21 2024 at 07:55):
the implementation strategy I would suggest is:
- Find all
have
syntaxes in the command - Get the
e
inhave ... := e
- Look for info tree data on
e
, see if the type ofe
is a proposition and lint otherwise
Sébastien Gouëzel (Aug 21 2024 at 07:55):
What I don't get is why it wanted to talk to me, because there is no have
that should be a let
in this proof, as far as I can tell!
Damiano Testa (Aug 21 2024 at 07:56):
I think that it was trying to figure out whether it should be telling you something and got hung up somewhere.
Mario Carneiro (Aug 21 2024 at 07:58):
also the linter seems to miss that there are two have
syntaxes, it only looks for the tactic but there is also an expr
Damiano Testa (Aug 21 2024 at 07:58):
Mario Carneiro said:
the implementation strategy I would suggest is:
- Find all
have
syntaxes in the command- Get the
e
inhave ... := e
- Look for info tree data on
e
, see if the type ofe
is a proposition and lint otherwise
This makes me think that you can find the have
s first (easy) and then look at the infotrees arising from the have
without going through the whole infotree: how do I do that?
Mario Carneiro (Aug 21 2024 at 07:58):
You have to go through the info tree, but you can limit it to one pass if you find all the syntaxes you want to spot first and look for all of them
Damiano Testa (Aug 21 2024 at 07:59):
Mario Carneiro said:
also the linter seems to miss that there are two
have
syntaxes, it only looks for the tactic but there is also an expr
That was deliberate, since I was not comfortable with infotrees and wanted to decrease what I had to keep track of.
Mario Carneiro (Aug 21 2024 at 07:59):
and of course you can early exit if the proof never uses have
syntax
Damiano Testa (Aug 21 2024 at 08:00):
Oh, you mean that I extract the have
syntax, then run through the whole infotree and, whenever the node is one of the syntax bits that I extracted, then dive into it?
Mario Carneiro (Aug 21 2024 at 08:01):
unfortunately you can't navigate the info tree really, because the tree structure doesn't have any useful invariants
Mario Carneiro (Aug 21 2024 at 08:01):
so you just have to look at everything
Mario Carneiro (Aug 21 2024 at 08:02):
but you can set up a hashmap of syntax you care about and skip nodes that aren't talking about one of them
Damiano Testa (Aug 21 2024 at 08:02):
Looking at the code, why is
let .ofTacticInfo i := info | return #[]
let stx := i.stx
let .original .. := stx.getHeadInfo | return #[]
unless isHave? stx do return #[]
not already just looking at the have
nodes?
Mario Carneiro (Aug 21 2024 at 08:02):
it is (well, it is looking at have
tactic nodes), but the rest of the code is doing things not in my list
Mario Carneiro (Aug 21 2024 at 08:03):
With my suggestion, you only have to look at expr info nodes, not tactic info nodes
Mario Carneiro (Aug 21 2024 at 08:03):
because you are looking at the e
in have ... := e
, not the tactic itself
Damiano Testa (Aug 21 2024 at 08:04):
Ok, I think that I understand now: I should catch the stuff after the :=
and just inspect those nodes further -- I had missed this from your initial message, don't know why.
Mario Carneiro (Aug 21 2024 at 08:05):
right, the algorithm is really "lint if any expression occurring on the RHS of a have
term/tactic has a non-prop type"
Damiano Testa (Aug 21 2024 at 08:06):
Ok, I think that I had given up on that at the time, since there were many syntactic possibilities for the "stuff after :=
", but I know more about syntax now and I can probably do this better then.
Damiano Testa (Aug 21 2024 at 08:06):
In particular, wasn't there some form of have
that did not have :=
?
Mario Carneiro (Aug 21 2024 at 08:07):
I suppose that's true, but you can just look at any of the match arms in that case
Mario Carneiro (Aug 21 2024 at 08:08):
although it's more complicated if you actually want to make a Try this
suggestion / autofix
Damiano Testa (Aug 21 2024 at 08:08):
Try this
suggestions are unavailable to linters, though, right?
Damiano Testa (Aug 21 2024 at 08:09):
Oh, linters could run refactor-like code, though, right?
Mario Carneiro (Aug 21 2024 at 08:09):
code actions can detect linter warnings and suggest changes
Damiano Testa (Aug 21 2024 at 08:09):
Ah, ok. I have no experience with code-actions.
Damiano Testa (Aug 21 2024 at 08:11):
Anyway, I'll try to write a faster implementation of the linter by looking at e
in have ... := e
and then I will worry about edge cases for have
. The code action will wait a bit longer still.
Damiano Testa (Aug 21 2024 at 08:12):
For me, what ends up being efficient code is not always lined up with what I expect to be efficient, so these suggestions are great, because they give me a better sense of what is cheap and what isn't.
Michael Rothgang (Aug 21 2024 at 08:16):
Damiano Testa said:
Ah, the warnings for the unused variables made the linter active!
FWIW, #15942 had the same issue - thanks for implicitly telling me how that came about!
Yaël Dillies (Aug 21 2024 at 08:17):
Oh, maybe that's also why I had to comment out huge chunks of my proof of Kneser's theorem! It timed out for no clear reason
Mario Carneiro (Aug 21 2024 at 08:17):
I think in this case the issue is actually the way InfoTree.foldInfoM
is defined causes large stacks of closures to be constructed, meaning that any sufficiently large proof will produce a deep stack recursion (number of recursive calls equal to the number of nodes in the info tree for a command)
Damiano Testa (Aug 21 2024 at 08:18):
I will also add a todo to the linter, saying that it should only be active with sorry
. Or maybe, inspecting the term e
directly, I can simply put a check there if there is a sorry and flag those.
Yaël Dillies (Aug 21 2024 at 08:18):
Yaël Dillies said:
I had to comment out huge chunks of my proof of Kneser's theorem
It is roughly 400 lines long, for reference, in a single theorem
Damiano Testa (Aug 21 2024 at 08:19):
Yaël Dillies said:
Yaël Dillies said:
I had to comment out huge chunks of my proof of Kneser's theorem
It is roughly 400 lines long, for reference, in a single
theorem
Together with the longLine
and longFile
linters, it looks like we also need a longProof
linter.
Violeta Hernández (Aug 21 2024 at 21:21):
What is this byAsSorry
thing?
Violeta Hernández (Aug 21 2024 at 21:22):
I noticed this change in the SetTheory/Ordinal/Topology file. Is this really necessary? Using calc
in place of a one line proof seems like overkill.
image.png
Scott Carnahan (Aug 22 2024 at 11:57):
start of byAsSorry
:
https://leanprover.zulipchat.com/#narrow/stream/287929-mathlib4/topic/Running.20Mathlib.20under.20.60set_option.20debug.2EbyAsSorry.60/near/453913575
Violeta Hernández (Aug 23 2024 at 02:15):
Violeta Hernández said:
Using
calc
in place of a one line proof seems like overkill.
So, was this change accidental, or deliberate? Does it have anything to do with getting the linter to work?
Violeta Hernández (Aug 23 2024 at 02:18):
Oh wait I get it, the issue is that by
block at the end
Kim Morrison (Aug 23 2024 at 04:02):
The issue is that the middle term in the calc is not knowable before entering the by
block.
Kim Morrison (Aug 23 2024 at 04:03):
I had thought we would just be doing this everywhere in Mathlib, but it turns out not so much! I'm skeptical of forbidding it, but there are some nice payoffs to avoiding relying on it. (In particular, the potential for fast benchmarking builds of Mathlib, so we can do a better job of optimizing Lean for Mathlib performance.)
Mario Carneiro (Aug 23 2024 at 04:08):
Kim Morrison said:
The issue is that the middle term in the calc is not knowable before entering the
by
block.
shouldn't it be the RHS of that TFAE expression?
Kim Morrison (Aug 23 2024 at 04:11):
One would hope. I dimly remember that there was an unspecified universe or something here, that the by
block resolved? We'd have to look at the byAsSorry
branch, and restore the old proof and see what goes wrong.
Last updated: May 02 2025 at 03:31 UTC