fix: annotate the query views with the classes they return (#328) - #332
Open
livingstaccato wants to merge 2 commits into
Open
fix: annotate the query views with the classes they return (#328)#332livingstaccato wants to merge 2 commits into
livingstaccato wants to merge 2 commits into
Conversation
`BodyView.blocks()` only ever appends a `BlockView`, `attributes()` only an `AttributeView`, and `BlockView.body` is always a `BodyView`, but all of them were annotated `NodeView`. Callers under a strict type checker could not reach `block_type`, `labels`, `name_labels` or `AttributeView.name` without an `isinstance` narrowing or a cast for a runtime type that is never anything else. Narrow the annotations on `DocumentView`, `BodyView` and `BlockView`. The view classes stay imported inside the method bodies -- the cycle is real -- with `TYPE_CHECKING` imports added for the annotations alone, so there is no runtime change of any kind. The new tests assert the annotations rather than the runtime types: a runtime check passed before this change too, which is why nothing caught it.
The narrowed return annotations named `BlockView`, `BodyView` and `AttributeView` as forward references while the classes were imported inside each method. `typing.get_type_hints` reads a function's own globals, so every one of those annotations raised `NameError` for any caller that introspected it -- pydantic, a documentation builder, a runtime validator -- even though the classes were importable. Before the narrowing the annotations named `NodeView`, which is imported at module level, so this was a regression rather than a pre-existing gap. `AttributeView` has no cycle to break and moves to a plain top-level import. `BodyView` and `BlockView` do name each other, so each module imports the other at the bottom, after its own classes exist: the name lands in module globals, which is what resolution needs, and the cycle still cannot bite because neither import runs before the classes are defined. Verified under all three import orders. The tests asked for the hints with a hand-built `localns`, which supplied exactly the names that were missing and so could not see this. They now call `get_type_hints` bare, the way a consumer does.
Contributor
Author
|
Please hold off on merging this one for now — I want to do another review pass over it before it goes in. Opened as a draft for that reason; I will mark it ready and say so here once I am done. |
Contributor
Author
|
Review pass done, so the hold above no longer applies — this is ready for review now. Rebased on current 🤖 Drafted with Claude Code. |
Contributor
Author
|
hi there! i hope you're okay with this torrent of issues and PRs. if you'd rather me submit things/work through things differently please just let me know and i'll tweak my workflow. thanks for this project! :) |
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.
Fixes #328.
What
BodyView.blocks()and.attributes(), and theirDocumentViewandBlockViewdelegates, were annotatedList[NodeView]. Each only ever returns the concrete class, so under a strict type checkerblock_type,labels,name_labelsandAttributeView.nameneeded anisinstancenarrowing or a cast for a runtime type that is never anything else. They now sayList[BlockView]/List[AttributeView], andBlockView.bodysaysBodyView.Why the second commit exists
Naming those classes as forward references is not enough on its own.
typing.get_type_hintsresolves an annotation against the defining function's own globals, and the view classes were imported inside each method to break thebody<->blocksimport cycle. So every narrowed annotation raisedNameErrorfor anything that introspected it -- pydantic, a docs builder, a runtime validator -- while the previousNodeViewannotation resolved fine, since that one is imported at module level.AttributeViewhas no cycle and moves to a plain top-level import.BodyViewandBlockViewdo name each other, so each module imports the other at the bottom, after its own classes exist: the name lands in module globals, and neither import can run before the classes are defined. Verified under all three import orders.Tests
test/unit/query/test_view_annotations.pyasserts the annotations rather than the runtime types, because a runtime check passed before the change too. They callget_type_hintsbare -- an earlier draft passed a hand-builtlocalns, which supplied exactly the names that were missing and so could not see the problem. Against the tree without the second commit, those tests produce 18 errors.Compatibility
Return values are unchanged; this narrows declarations and moves two imports.
It is a static break for one shape of caller, and the earlier wording here got that wrong.
listis invariant, soviews: List[NodeView] = document.blocks()stops type-checking even thoughBlockViewis aNodeView-- mypy reports Incompatible types in assignment (expression has type "list[BlockView]", variable has type "list[NodeView]"). Runtime behaviour is identical, and the fix for such a caller is to narrow the annotation or drop it. Annotating the return asSequence[BlockView]would keep those callers compiling, but it would also removeappendand friends from the declared contract, which is a bigger decision than this PR should make on its own.Merging
It touches the same code as #333 (
hcl2/query/blocks.py). Whichever of those lands first, this one needs a rebase rather than a merge — the overlaps are real edits to the same methods, not adjacent lines, so resolving them by hand risks losing one of the two fixes. Say the word and I will rebase and re-run the suite.This pull request, and the investigation behind it, were produced by an AI assistant (Claude) working on behalf of the author. Please review with that provenance in mind.