Zulip Chat Archive
Stream: lean4 dev
Topic: Understanding `meta import`
π πππππππ πππ πππππ (Jun 08 2025 at 21:34):
Hello! I am trying to understand the new meta phase introduced in lean4#8660. For reference, two constraints are delineated there:
metadeclarations may use any local declaration, importedmetadeclarations, and anymeta imported declarations.- non-
metadeclarations may use a non-metadeclaration reachable only through non-metaimports.
meta is a marker that can be put on global constants in an environment (as opposed to e.g. a part of the type system for finer-grained specifications like T meta β T). It behaves like so:
- in their own module, constants are
metaiff defined viameta def - in downstream modules
meta imported constants are alwaysmetaimported constants aremetaiff they weremetain the module we're importing from (in particular if that module in turn usedmeta import, explaining why 'meta importis transitive')
(If a constant is both imported and meta imported, let's think of it as secretly two constants in the environment.)
Since meta will be used to avoid importing IR that is not needed at compile-time, let's define another marker has_ir that we put on those constants for which the IR is available (in a given environment). This behaves like so:
- in their own module, all constants are
has_ir - in downstream modules, constants are
has_iriff they aremeta
We have the restriction that meta defs may only use has_ir constants. This explains constraint 1.
However, constraint 2 is not explained: there is no obstacle to using a has_ir constant in a non-meta declaration; indeed, it must be possible to do that. From past discussions, I am guessing there is a further meaning to meta along the (somewhat circular) lines of 'exclude this code when compiling an object-level/non-meta executable'. Let's have another marker, non_obj, for that. This exactly coincides with meta:
- in its own module, a constant is
non_objiff it's ameta def(?) - in downstream modules, constants are
non_objiff they aremeta
Overall, we have meta == non_obj and meta => has_ir (for upstream constants meta == has_ir).
There is now a restriction that if a declaration uses a non_obj constant, that declaration must itself be non_obj. This explains constraint 2.
It also seems to imply constraint 3: non-meta declarations may not use meta declarations in their values (of course tactics and so on can be used to produce the values).
Now I have a few questions:
- Is this analysis accurate? In particular, will constraint 3 materialize?
- If so, and I did want to include an upstream
metaconstant in object code, can I "un-meta" it? (I can't just doimportandmeta importif the thing was originally ameta def.) - If I want to
#evalan upstream constant, or do anything else with its IR, do I have tometa importit? - How will precompilation of tactics fit into this picture? Will every module have two compilation targets, one for object-level code and one for
no_objcode?
Sebastian Ullrich (Jun 10 2025 at 07:30):
Hey Wojciech, thanks for thinking through this :) . I do appreciate feedback.
exclude this code when compiling an object-level/non-
metaexecutable
Right, this goal was not mentioned in the PR.
In particular, will constraint 3 materialize?
Ah, it already is there: https://github.com/leanprover/lean4/pull/8660/files#diff-d8e303a116f08503ab6b9ca0ce6a643d96c94e3dbf9f96cf239589385786c9c9R137. This follows from constraints 1 and 2 if you see them as exhaustive, but I see how that may have been unclear.
If so, and I did want to include an upstream
metaconstant in object code, can I "un-meta" it?
You can't. At that point we assume you are working against the intent of the code's author, so you should really work that out with them.
If I want to
#evalan upstream constant, or do anything else with its IR, do I have tometa importit?
#eval is special cased in the language server, but it will in fact break on the cmdline without meta (you're not using it there anyway, right?)
How will precompilation of tactics fit into this picture? Will every module have two compilation targets, one for object-level code and one for
no_objcode?
Likely, yeah. The current approach shouldn't break since it just includes everything but that would be a future refinement.
Alex Keizer (Jun 17 2025 at 13:05):
#evalΒ is special cased in the language server, but it will in fact break on the cmdline withoutΒmetaΒ (you're not using it there anyway, right?)
We sometimes use #guard_msgs in #eval ... for compile-time tests, would those break a lake build in this new system?
Sebastian Ullrich (Jun 18 2025 at 00:54):
Use the correct meta imports then, or don't make that test a module :)
Thomas Murrills (Jun 24 2025 at 22:32):
Hopefully this is a good place for more basic questions too! :) I'm wondering simply how meta import Foo behaves. Does it essentially mark all declarations coming from Foo with meta? (If so, transitively, or not?)
If so, is the intended design pattern for e.g. files defining tactics to just mark everything as meta def, or to use typical defs but only ever meta import the file? (Or neither?)
Thomas Murrills (Jun 24 2025 at 22:34):
Also, are there any prior discussions on this (or documentation, yet) for better familiarizing myself with the idea behind it?
Last updated: Dec 20 2025 at 21:32 UTC