Skip to content

[Type] ndarray typing 1: Add eval_str=True to inspect.signature() calls#411

Draft
hughperkins wants to merge 5 commits intomainfrom
hp/typing-t4-1-eval-str
Draft

[Type] ndarray typing 1: Add eval_str=True to inspect.signature() calls#411
hughperkins wants to merge 5 commits intomainfrom
hp/typing-t4-1-eval-str

Conversation

@hughperkins
Copy link
Collaborator

…annotations

Support from __future__ import annotations by resolving stringified type annotations at signature inspection time. Catch NameError from invalid string annotations and raise a clear QuadrantsSyntaxError.

Issue: #

Brief Summary

copilot:summary

Walkthrough

copilot:walkthrough

…annotations

Support `from __future__ import annotations` by resolving stringified type
annotations at signature inspection time. Catch NameError from invalid
string annotations and raise a clear QuadrantsSyntaxError.
@hughperkins
Copy link
Collaborator Author

Opus 4.6 review:

PR Review: hp/typing-t4-1-eval-str

Summary

This PR adds eval_str=True to all inspect.signature() calls in the kernel/func registration path, enabling proper evaluation of string annotations (e.g. from from __future__ import annotations). It also wraps the primary call in _func_base.check_parameter_annotations with a try/except that converts NameError (undefined type in annotation) into QuadrantsSyntaxError for clearer user feedback.

Issues Found

1. Inconsistent inspect.signature usage in _perf_dispatch.py

Location: python/quadrants/lang/_perf_dispatch.py, line 102

The PerformanceDispatcher.register decorator still uses inspect.signature(func) without eval_str=True:

def decorator(func: Callable | QuadrantsCallable) -> DispatchImpl:
    sig = inspect.signature(func)  # <-- missing eval_str=True

The constructor (line 61) uses eval_str=True for the prototype function, but registered implementations do not. For consistency and to handle from __future__ import annotations in registered implementations, this call should also use eval_str=True. If added, it should be wrapped in the same try/except NameError pattern for consistent error handling when annotations reference undefined types.

2. Missing error handling at other call sites

Locations:

  • _func_base.py line 195: _populate_global_vars_for_templates
  • _kernel_impl_dataclass.py line 76: extract_struct_locals_from_context

These calls use inspect.signature(..., eval_str=True) but do not catch NameError. If a kernel/func has an invalid type annotation (e.g. typo in a dataclass parameter), these will raise a raw NameError instead of QuadrantsSyntaxError. The error handling in check_parameter_annotations is good, but these downstream paths can still surface NameError before or in different code paths. Consider adding the same try/except at these sites for consistent user-facing errors.

3. Narrow exception handling

Location: _func_base.py lines 100–103

The PR only catches NameError. Per Python docs, when eval_str=True, the eval() used to resolve annotations "could potentially raise any kind of exception" (e.g. TypeError, AttributeError). Catching only NameError is reasonable for the common "undefined type" case, but other annotation evaluation failures would still propagate as raw exceptions. This is a minor point—NameError is likely the dominant case—but worth noting.

Suggestions for Improvement

  1. Add eval_str=True to _perf_dispatch.py line 102 for consistency with the rest of the codebase. If the registered function has invalid annotations, wrap in try/except and re-raise as QuadrantsSyntaxError for consistent UX.

  2. Consider a small helper to centralize the inspect.signature(..., eval_str=True) + NameError handling, e.g.:

    def get_signature_with_eval(func):
        try:
            return inspect.signature(func, eval_str=True)
        except NameError as e:
            raise QuadrantsSyntaxError(f"Invalid type annotation: {e}") from e

    This would reduce duplication and ensure consistent behavior across all call sites.

  3. Add a test for from __future__ import annotations with a kernel that uses type hints (e.g. NDArray, dataclass params) to lock in the intended behavior and guard against regressions.

Style & Correctness

  • Correctness: The change correctly enables string annotation evaluation. Existing tests (test_kernel_bad_argument_annotation, test_func_bad_argument_annotation) pass and continue to expect QuadrantsSyntaxError for invalid annotations.
  • Style: Matches existing patterns (e.g. QuadrantsSyntaxError usage, from e in re-raise).
  • Python version: eval_str is available in Python 3.10+; the project supports 3.10–3.13, so compatibility is fine.

Verdict

The PR achieves its goal and improves support for from __future__ import annotations. The main follow-ups are consistency (the _perf_dispatch call site) and optional hardening of error handling at the other eval_str=True call sites.

The register decorator's inspect.signature() call was missing
eval_str=True, inconsistent with all other call sites.
Wrap all inspect.signature(eval_str=True) calls with try/except that
catches both NameError and AttributeError, re-raising as
QuadrantsSyntaxError for clear user-facing errors. Previously only
check_parameter_annotations had error handling and it only caught
NameError.
Centralize the inspect.signature(eval_str=True) + error handling pattern
into a shared get_func_signature() helper in exception.py, replacing
5 inline try/except blocks across _func_base.py, _kernel_impl_dataclass.py,
and _perf_dispatch.py.
Verify that kernel parameter annotations are correctly resolved when
the module uses PEP 563 stringified annotations.
@hughperkins hughperkins marked this pull request as ready for review March 12, 2026 04:55
@hughperkins hughperkins marked this pull request as draft March 12, 2026 04:59
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.

1 participant