Skip to content

fix: annotate the query views with the classes they return (#328) - #332

Open
livingstaccato wants to merge 2 commits into
amplify-education:mainfrom
livingstaccato:fix/query-view-annotations
Open

fix: annotate the query views with the classes they return (#328)#332
livingstaccato wants to merge 2 commits into
amplify-education:mainfrom
livingstaccato:fix/query-view-annotations

Conversation

@livingstaccato

@livingstaccato livingstaccato commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes #328.

What

BodyView.blocks() and .attributes(), and their DocumentView and BlockView delegates, were annotated List[NodeView]. Each only ever returns the concrete class, so under a strict type checker block_type, labels, name_labels and AttributeView.name needed an isinstance narrowing or a cast for a runtime type that is never anything else. They now say List[BlockView] / List[AttributeView], and BlockView.body says BodyView.

Why the second commit exists

Naming those classes as forward references is not enough on its own. typing.get_type_hints resolves an annotation against the defining function's own globals, and the view classes were imported inside each method to break the body <-> blocks import cycle. So every narrowed annotation raised NameError for anything that introspected it -- pydantic, a docs builder, a runtime validator -- while the previous NodeView annotation resolved fine, since that one is imported at module level.

AttributeView has no cycle 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, and neither import can run before the classes are defined. Verified under all three import orders.

Tests

test/unit/query/test_view_annotations.py asserts the annotations rather than the runtime types, because a runtime check passed before the change too. They call get_type_hints bare -- an earlier draft passed a hand-built localns, 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. list is invariant, so views: List[NodeView] = document.blocks() stops type-checking even though BlockView is a NodeView -- 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 as Sequence[BlockView] would keep those callers compiling, but it would also remove append and 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.

`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.
@livingstaccato

livingstaccato commented Sep 1, 2026

Copy link
Copy Markdown
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.

@livingstaccato
livingstaccato marked this pull request as ready for review September 3, 2026 02:01
@livingstaccato
livingstaccato requested a review from a team as a code owner September 3, 2026 02:01
@livingstaccato

livingstaccato commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

Review pass done, so the hold above no longer applies — this is ready for review now.

Rebased on current main; GitHub reports it mergeable as it stands.

🤖 Drafted with Claude Code.

@livingstaccato

Copy link
Copy Markdown
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! :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BodyView.blocks() and .attributes() are annotated as List[NodeView]

1 participant