Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,35 @@ Notable changes. Format loosely follows [Keep a Changelog](https://keepachangelo
from the version it first shipped in), so a schema diff is evidence of a
possible behavior change, not proof of one.

### Fixed

- **`check` false-positived on every real layer rule with a `match` table**
— `HL.LayerRuleSpec.match` is typed `table<string, string|boolean>`
(LuaLS's generic map syntax), which `_has_table_alternative` didn't
recognize as table-shaped at all (it only checked for `{` or `HL.` in the
type expression), so a perfectly valid `match = { namespace = "..." }`
was flagged as a type mismatch. Found by running `check` against two
independent real-world Hyprland Lua configs — Garuda Linux's
distribution-shipped settings
([`garuda-hyprland-settings`](https://gitlab.com/garuda-linux/themes-and-settings/settings/garuda-hyprland-settings))
and the [sea-shell](https://github.com/MiyukiVigil/sea-shell) project —
both of which hit this identically, since it's the only way to write a
layer rule at all.

The same run surfaced a second, *upstream* gap, not a hyprvalidate bug:
both configs also use `hl.bind(..., { mouse = true })`, and `mouse` isn't
in `HL.BindOptions` at all. Traced directly into Hyprland's own source —
`mouse` is a real, implemented flag
([`LuaKeybind.cpp`](https://github.com/hyprwm/Hyprland/blob/main/src/config/lua/objects/LuaKeybind.cpp),
parsed identically to `click`/`drag`, which *are* in the schema) that's
simply missing, along with `auto_consuming`, `device_inclusive`, and
`devices`, from a hand-written field list in Hyprland's own
`meta/generateLuaStubs.py` (lines ~658–677) — the exact "hand-maintained
table drifts from what's real" bug class this project exists to catch,
found one level further upstream than usual. Not something hyprvalidate
can fix on its own without hand-maintaining the same kind of table it
refuses to elsewhere; a candidate for an upstream report.

## [0.3.0] — 2026-08-28

### Added
Expand Down
11 changes: 9 additions & 2 deletions hyprvalidate/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,9 +156,16 @@ class Finding:

# Type-expression tokens that indicate "this alternative is a table/class
# shape, not a scalar" - used to decide whether to skip a mismatch rather
# than to model the shape.
# than to model the shape. Found missing "table" (bare, e.g. `integer|table`,
# and LuaLS's generic form `table<string, string|boolean>`) by testing
# against two real, independent Hyprland Lua configs (Garuda Linux's
# distribution settings and the sea-shell project) - both use
# `hl.layer_rule({ match = { namespace = "..." } })`, which is exactly
# HL.LayerRuleSpec.match's real type, `table<string, string|boolean>`; this
# helper didn't recognize that as table-shaped at all, so every layer rule
# with a match table in the wild was a false positive here.
def _has_table_alternative(type_expr: str) -> bool:
return "{" in type_expr or "HL." in type_expr
return "{" in type_expr or "HL." in type_expr or "table" in type_expr


_SCALAR_COMPAT = {
Expand Down
2 changes: 1 addition & 1 deletion site/bundle.js

Large diffs are not rendered by default.

31 changes: 31 additions & 0 deletions tests/test_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,37 @@ def test_gradient_table_value_accepted_without_deep_structural_check():
assert findings == []


def test_generic_table_type_spec_field_accepts_a_table_value():
"""HL.LayerRuleSpec.match is typed `table<string, string|boolean>` -
LuaLS's generic map syntax, not `string|HL.Gradient` (a class
alternative) or a `{...}` literal shape. `_has_table_alternative`
didn't recognize "table<...>" at all, so this was a false positive on
every real layer rule with a match table - found by running
hyprvalidate against two independent real-world Hyprland Lua configs
(Garuda Linux's distribution settings and the sea-shell project), both
of which use exactly this pattern and both of which hit it."""
schema = _schema()
findings = checker.check_source(
schema,
'hl.layer_rule({ name = "blur-all-layers", match = { namespace = ".*" } })',
)
assert findings == []


def test_has_table_alternative_recognizes_every_real_table_shaped_form():
"""The schema uses three distinct textual shapes for "this alternative
is a table, not a scalar": a class reference (`HL.Gradient`), a
`{...}` literal shape, and LuaLS's generic `table<...>` / bare `table`
form - the last of which was missing before this fix. A genuinely
scalar-only type must still correctly return False, or every
type-mismatch check in this module goes blind."""
assert checker._has_table_alternative("string|HL.Gradient")
assert checker._has_table_alternative("integer|table")
assert checker._has_table_alternative("table<string, string|boolean>")
assert not checker._has_table_alternative("boolean")
assert not checker._has_table_alternative("string")


def test_non_literal_config_value_is_accepted_uncritically():
"""A variable reference can't be statically type-checked - accepted,
not flagged, per the documented scope limitation."""
Expand Down
Loading