Repository navigation
Conversation
`explicit-any` is on for `src/math_spec`. The eleven files that still write `Any` name themselves in a sub-config list, and the rule is on everywhere else, so a file leaves that list and cannot come back. One line that must say `Any` says so with `# pyrefly: ignore[explicit-any]` and a reason; `unused-ignore` is already an error, so the pragma fails the gate the day the line stops needing it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015nY2qq2hQzyMFApWuD5zFW
…ot a mapping is refused `explicit-any` is an error for `src/math_spec` with nothing exempt, so the eleven-file list the gate landed with is gone and `src/` says `Any` nowhere. The 73 sites in the way are taken from #567, which did the work on top of the parser stack: the public doors take `Mapping[str, object]`, a raw value is `object` until pydantic has read it, the grammars hand back the node type their walk names, and the exclusivity proof compares a cell with a literal of its own kind. `_where_parser` keeps main's grammar and takes only the fold's return type. Two sites that PR leaves are narrowed rather than excused, so no pragma is needed: `ValidationInfo` is `Protocol[ContextT]` and takes its argument, and `JsonSchemaValue` is pydantic's `dict[str, Any]` where those signatures mean `dict[str, object]`. One refusal comes with the rewrite: a symbol table whose `dimensions:` or `names:` is a list or a string raised `AttributeError`, and now raises `SchemaError` naming the rewrite. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015nY2qq2hQzyMFApWuD5zFW
`WrittenExpression` and `WrittenCase` are what one `expressions:` entry takes as a mapping, published for a caller that builds it in code rather than reading it from YAML. A misspelled key is then a type error where the caller writes it, instead of a `SchemaError` at load. The TypedDict says which keys exist and what each takes. It does not say which combination is a model — one `expression:`, or `cases:` with the `dims:` and `otherwise:` they need — because that is a rule no TypedDict can carry. Loading still decides it, and still refuses a key that is not here. `test_the_written_form_takes_the_keys_its_block_takes` holds both against the committed schema, which `test_the_checked_in_json_schema_has_not_drifted` holds against the models. So a field added to the block and not here fails the suite rather than leaving a caller annotating a key that does not load. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015nY2qq2hQzyMFApWuD5zFW
…nto claude/elegant-pasteur-r0p6ui # Conflicts: # src/math_spec/model.py
Contributor
Author
|
Not a good change |
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.
Note
The following content was generated by AI.
WrittenExpressionandWrittenCaseare what oneexpressions:entry takes as a mapping. A caller that builds it in code gets a misspelled key as a type error where it writes it, instead of aSchemaErrorat load.What this changes
model.py, beside the blocks they mirror, exported and pinned intests/test_public_surface.py.WrittenExpressionistotal=False;WrittenCaserequireswhenandexpression, asExpressionCasedoes. The asymmetry is the models': a case has one shape, a block has two.expressionandotherwisetakestr | float, notstr. That is the input type the before-validator admits — a bare number is an expression — and it is what the published schema already says.test_the_written_form_takes_the_keys_its_block_takescompares the key set and the required keys against$defs.ExpressionBlockand$defs.ExpressionCasein the committed schema, whichtest_the_checked_in_json_schema_has_not_driftedalready holds against the models.Why
This is the answer to whether a copy of an upstream shape drifts: it drifts only if nothing holds it, and here something does. The consumer for it is lpspec, which types the same payload
str | Mapping[str, object]and forwards it without reading it — so the checking had nowhere to happen. Publishing the shape from the repository that owns the model puts it in one home rather than two.The union that was considered, measured, and not taken
An earlier version of this body said the block's one-form-or-the-other rule is one no TypedDict can carry. That was false. A union of
_OneExpression = {expression}and_Cases = {dims, cases, otherwise}carries it, on this repo's own pyrefly settings:{'expression': 'sum(p, over=generator)'}{'dims': [...], 'cases': {...}, 'otherwise': 0}{}It is not taken, for three reasons that only appeared once the cost was priced:
_one_form_or_the_othersays a named expression is oneexpression:or a set ofcases:, and this has both — it names the rewrite. A checker says the dict is not assignable to_Cases | _OneExpression, which names nothing. Moving that diagnosis earlier makes it worse.ExpressionBlockrequires nothing, because JSON Schema expresses both forms through the same all-optional properties. A union's requiredness therefore has no upstream counterpart, so the new claim would be the one hand-maintained thing here — the drift risk reappearing exactly where it is unheld.The trigger for revisiting is a consumer that builds these literals often enough to hit the both-or-neither mistake in practice.
The guard, deleted three ways
The test was written against the drift it exists to catch, and each mutation run with the tree restored after:
WrittenExpressionunit: str) addedWrittenCasemadetotal=FalseSo a field added to
ExpressionBlockand not here fails the suite, rather than leaving a caller annotating a key that does not load.What it is worth
It catches a key that does not exist. It catches nothing for a caller reading YAML from a file, which is most of them. I said as much before building it, and it is why I would not rank this high.
Gates
pixi.shis refused by this environment's egress proxy, so the gates ran from auvenvironment on Python 3.12.3 with pydantic 2.13.5 against the lock's 2.13.4, on the pinned pyrefly 1.2.0 and ruff 0.16.1.pyrefly checkpytest -n autoruff check,ruff format --checktyposmkdocs build --strictdocs.python.orginventory dropped for the run, which the proxy refuses with a 403compile-texThe committed schema did not move: no model field changed, only what the package publishes about them.
Branch, and what is deliberately not here
394599bbut not its ancestors. A force-push to restart the branch was refused by this environment, so the stale tip is merged in instead — one import-line conflict, resolved to keepTypedDict. The merge changes nothing: the tree is byte-identical to the commit before it, and the diff againstmainis this change alone.variables:,constraints:,piecewise:— has the same case and is not here.expressions:is the one a consumer builds in code today, and thirteen more TypedDicts on speculation is what YAGNI refuses.🤖 Generated with Claude Code
https://claude.ai/code/session_015nY2qq2hQzyMFApWuD5zFW