refactor(language): the front end is a package, and the fence is a test - #367
Merged
FBumann merged 1 commit intoJul 31, 2026
Merged
Conversation
The ten modules from the file reader to the dim checker never reach forward to a consumer — no `lowering`, no `piecewise`, no `sources`, no `api`, no `relational`. That was true by habit and unenforced: hard rule 0 claims "the layers are ordered, and imports prove it", but only the lazy-import half was ever checked, so nothing stopped `expression_parser.py` from importing `lowering.py` next month. Move nine of them under `src/lpspec/language/` and enforce membership off the path, the way `relational/`, `linopy/` and `typeset/` already are. It is the mirror of hard rule 2: the engine may not see the schema or the AST, and now the language may not see what is done with the AST it produces. That is what keeps `lps.check()` a pass with no data and no plan, and a second consumer cheap rather than a second opinion. `errors.py` stays at the top level on purpose — it is the dependency-free leaf *both* fences point at (`ENGINE_MAY_IMPORT`), and moving it in would put the language's import path on the engine's list. `lowering.py`, `piecewise.py`, `sources.py` and `api.py` stay too: each is legitimately on the line between the halves, and the flat namespace is where that belongs. No behaviour change — imports and paths only. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
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.
Stacked on #363 (docs base) — that PR is doc-only, so review this one's second commit.
Why
Ten modules — from the file reader to the dim checker — never reach forward to a consumer of the AST they produce. I checked every
from lpspec…edge includingTYPE_CHECKINGblocks: none of them importslowering,piecewise,sources,apiorrelational.That was true by habit and unenforced. Hard rule 0 claims "the layers are ordered, and imports prove it", but only the lazy-import half is checked (
DELIBERATE_LAZY_IMPORTS). Nothing stoppedexpression_parser.pyfrom importinglowering.pynext month, and the doc would have quietly become wrong.What
Nine modules move to
src/lpspec/language/, and membership is read off the path the wayrelational/,linopy/andtypeset/already are:test_language_never_reaches_a_consumeris the fence. It is the mirror of hard rule 2: the engine may not see the schema or the AST; the language may not see what is done with the AST it produces. What a model means cannot depend on what any consumer does with it — which is what keepslps.check()a pass with no data and no plan, and a second consumer cheap rather than a second opinion.What deliberately did not move
errors.py— the dependency-free leaf both fences point at.LANGUAGE_MAY_IMPORT = ENGINE_MAY_IMPORT, and moving it underlanguage/would put the language's import path on the engine's allowlist, which is exactly the near-zero import surface rule 2 protects.lowering.py,piecewise.py,sources.py,api.py— each is legitimately on the line.piecewise.pyis the interesting one: it emits declarations (language) but consultscheck_core_subsetto do it (plan), which is also why it is the one declared lazy-import edge. The flat top level is where a both-sides module belongs.No behaviour change: imports and paths only.
gitrecorded all nine as renames.Docs
docs/ARCHITECTURE.md: module map paths, the pipeline diagram's edge labels, hard rule 1 gains the closed-from-the-front clause, and the closing paragraph now says four subpackages — it said "Two subpackages" while listing three, which this makes worth fixing rather than worse.Test plan
uv run pytest— 701 passed, 4 skipped, 1 xfaileduv run ruff check ./ruff format --check .— clean (theN806per-file-ignore forwhere_parser.pymoved with it)uv run pyrefly check <src files>— 0 errorsuv run mkdocs build --strict— builds🤖 Generated with Claude Code