feat(lazy-parity): add defaults.lazy and defaults.version#29
Merged
Conversation
Three lazy.nvim parity polish items so author-published specs and lazy.nvim configs port more cleanly: - defaults.lazy / defaults.version: setup() now honors a global lazy flag and a global version fallback, applied only when a spec doesn't set its own (version=false still opts out). Threaded through lazy.is_lazy + lazy.has_lazy_parent so dep-only children of a defaults.lazy parent are also lazy. - validate.lua: warns (does not error) on rocks/virtual/submodules — fields zpack silently ignored before. Behavior unchanged; users now see the gap. - docs/tips.md: short migration notes for the enabled/cond split (zpack separates "skip install" vs "skip load") and the unsupported-field list.
A user who ignored the validator's "expected string|table" warning would leak boolean false through normalize_version into vim.pack.add. Treat it as nil instead, mirroring the per-spec version=false escape hatch. Also document defaults.lazy/defaults.version in doc/zpack.txt (README and the LuaCATS class already had them) and add regression guards for the version priority chain (branch/sem_version vs defaults.version) and the defaults.lazy + explicit-trigger interaction. Trim verbose doc-comments on the same change for the no-comments-unless- non-obvious rule.
The runtime treats defaults.version=false as the no-default opt-out
(utils.normalize_version, pinned by version_test.lua "defaults.version=false
is treated as no default") but validate_config typed the field as
{string, table} only, so users writing the documented opt-out got a
spurious "expected string|table, got boolean" notify at every setup().
Widens the validator to {string, table, boolean}, mirroring
SPEC_FIELD_TYPES.version which already accepts the same shape for
per-spec values.
is_lazy and has_lazy_parent both evaluated event/cmd/ft/keys triggers and the defaults.lazy floor, with the floor woven in slightly differently at each site (terminal fallback vs. OR'd into the trigger check). The trigger-OR-floor decision now lives in one is_lazy_by_triggers_or_floor local; both callers route through it, so a future refinement to lazy resolution lands in one place. Behavior-preserving (497/0 busted, validated under defaults.lazy=true and false on triggerless specs, dep-of-lazy-parent, explicit spec.lazy opt-out, and triggered specs).
is_lazy_by_triggers_or_floor now checks defaults.lazy before resolving
trigger fields. When the floor implies lazy, the four try_resolve_field
calls are skipped, eliminating up to four duplicate "Failed to resolve"
notifies per setup() for throwing function-form resolvers in the
defaults.lazy=true case — the same redundant-notify concern the existing
"try_resolve_field re-invokes the same function-form spec field across
the setup pipeline (no caching)" decision flagged. Same answer either
order, since the helper is a disjunction.
defaults.version's LuaCATS annotation gains |false to match the validator
and utils.normalize_version (types.lua already does this for the per-spec
field); silences an LSP false-positive on the documented
`defaults = { version = false }` opt-out.
Also collapse default_lazy() to the bare-`and` idiom used elsewhere in
the PR (utils.normalize_version line 347), and drop one inline comment
in normalize_version that the doc-comment above it already covers.
497/0 busted, 0/0 luacheck.
797bbff added a per-spec warning loop in validate_spec that flagged rocks/submodules/virtual as "unsupported lazy.nvim field, ignored by zpack". Revert: the project's framing is "lazy.nvim spec drop-in compatibility" for the spec shape, not "policer of authoring style". Warning at setup() time on every unknown field shifts zpack from "thin layer over vim.pack" to runtime gatekeeper, which is scope creep. The README and docs/tips.md still document the gaps once, which is the right surface for migrating users. A truly load-bearing field (e.g. rocks enabling LuaRocks deps) surfaces as a runtime require-failure when the plugin's Lua side can't find the dep — clearer than a validator warning naming a field whose absence the user may not care about. Also removes the two validate_test.lua tests that pinned the warning, and the corresponding bullet from docs/tips.md. Decision pinned in .claude/review-decisions.md as "Unsupported lazy.nvim spec fields are silently accepted".
The validator accepts defaults.version as {string, table, boolean} so
that defaults.version=false (the documented no-default opt-out) passes.
But normalize_version's fallback guard was `default_version ~= nil and
default_version ~= false`, which let defaults.version=true through and
propagated the boolean into vim.pack.add's spec table — surfacing as a
less-obvious downstream error.
Tighten normalize_version to accept only string|table values for the
defaults.version fallback. Both true and false now silently no-op,
matching how spec-level `version = true` already falls through the
if-elseif chain as a silent no-op. The validator stays type-lenient
because vim.validate's flat type list can't express "string|table OR
exactly false"; the runtime guard does the precise filtering.
Adds two regression tests in version_test.lua: defaults.version=true
must not leak to vim.pack.add, and defaults.version=false must not
clobber an explicit per-spec version.
Decision pinned in .claude/review-decisions.md as
"defaults.version: both true and false are silent no-ops".
…triggers is_lazy_by_triggers_or_floor conflated two unrelated concerns: the spec-independent defaults.lazy short-circuit and the spec-dependent trigger resolution. Once default_lazy() is hoisted into M.is_lazy between the explicit-lazy guard and the trigger check, has_lazy_parent is only reached when default_lazy() is false — so the inner default_lazy() check at that site becomes dead. has_lazy_parent now answers a single, narrower question: "does any parent have an explicit lazy=true or a lazy trigger?" Renames the helper to has_lazy_triggers — accurate and matches the is_dependency_only naming pattern in the same file. Behavior-preserving (501/0 busted). The lazy_parent_cache invariant is unchanged; every yes-path still caches. Also adds two regression tests in conditional_test.lua: - pin has_lazy_parent inheritance via a triggered parent (the existing defaults.lazy=true test short-circuited via default_lazy() before reaching has_lazy_parent, so the inheritance path was effectively un-pinned); - pin that enabled=false wins over defaults.lazy=true (the prune layer runs upstream of is_lazy).
docs/tips.md gained an "enabled vs cond" bullet and a mention of defaults.lazy=true in the default-lazy bullet during the lazy-parity work, but the parallel §11 MIGRATING FROM LAZY.NVIM section in doc/zpack.txt was not updated. Mirrors both: the default-lazy entry now points at defaults.lazy=true as an alternative to per-spec lazy=true, and a new zpack-migration-enabled-cond entry explains the enabled/cond split that lazy.nvim collapses.
This file contains hidden or 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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
defaults.lazy = truemakes every spec lazy unless it setslazy = false(lazy.nvim parity).defaults.version = '<branch|tag|range>'applies a default version when a spec has noversion/sem_version/branch/tag/commit. Per-specversion = falseopts out;defaults.version = falseand= trueare silent no-ops.*zpack-migration-enabled-cond*), anddocs/tips.mdcover the new defaults and theenabledvsconddistinction.default_lazy()andhas_lazy_triggers()helpers inlazy.lua;is_lazyshort-circuits on the global floor before resolving function-form triggers.defaults.lazy(boolean) anddefaults.version(string/table/boolean).Test plan
nvim -u NONE -l tests/busted.lua— full suite greenluacheck lua/ tests/lua-language-server --checkversion = falseescape hatch, dep-inheritance underdefaults.lazy, andenabled = falsestill prunes underdefaults.lazy = true