Skip to content

feat[next]: friendly errors for call-time, construction and builtin misuse#2664

Draft
havogt wants to merge 11 commits into
GridTools:mainfrom
havogt:next_friendly_errors_runtime
Draft

feat[next]: friendly errors for call-time, construction and builtin misuse#2664
havogt wants to merge 11 commits into
GridTools:mainfrom
havogt:next_friendly_errors_runtime

Conversation

@havogt

@havogt havogt commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Call-time / construction half of the round-2 DSL error hardening. Split out of #2661 (follow-up to #2655).

Stacked on #2663. Until that merges, the diff here also shows #2663's commits — review the three call-time/construction commits at the top of the branch, and merge this after #2663 (the diff collapses to just those once #2663 lands).

Clearer DSLErrors for misuse at the call / construction / builtin boundary:

  • invalid arguments at call time (raw arrays, unsupported types), with a hint to wrap arrays in a field
  • misuse of signatures, constants, and connectivities
  • invalid offset providers, invalid dtypes, and constructor misuse (e.g. string dimensions in a domain)

Companion PRs:

havogt added 11 commits June 16, 2026 19:59
'global x' inside a field operator crashed SingleAssignTargetPass with
AttributeError ('str' object has no attribute '_fields') because the
pass visited the plain-string 'names' field of the statement. Skip
non-AST field values in NodeYielder.generic_visit so the statement
reaches the dialect parser, and register friendly names + hints for
'global'/'nonlocal' there.
Attribute accesses like 'a.T', 'a.transpose()' or 'np.sin(a)' leaked
raw AttributeError/ValueError from FOAST type deduction. Guard
visit_Attribute: report nonexistent attributes (with a NumPy-specific
note for fields/scalars and 'did you mean' for named collections),
nonexistent namespace members, and namespace members whose value has no
DSL type (with a hint towards GT4Py built-ins). Also publish the
did_you_mean helper for reuse.
Three crashes in FOAST type deduction become diagnostics:
- 'field[3]' leaked AttributeError ('ScalarType' has no 'dim'); now
  explains that absolute indexing is unavailable and points to field
  offsets / local-dimension indices.
- 'tuple[5]' on a 2-tuple leaked IndexError; now reports the index and
  the tuple size.
- 'IDim(3)' on a non-local dimension crashed an assert; now explains
  that only local-dimension indices can be constructed.
Three annotation failure modes leaked raw Python exceptions:
- annotations that typing.get_type_hints cannot resolve (invalid
  forward-reference strings, undefined names) raised SyntaxError or
  NameError; now wrapped into a DSLError pointing at the function.
- parameter annotations that have no GT4Py type (e.g. 'a: list',
  'inp: gtx.Field' without arguments) raised ValueError from type
  translation; now InvalidParameterAnnotationError, with the underlying
  reason as a note and a hint showing valid annotations.
- annotated assignments inside an operator ('b: gtx.Field[...] = a')
  raised NameError when the annotation used names not visible inside
  the function; now a DSLError explaining what names are available.
Three program-level crashes become diagnostics:
- a bare expression statement ('a + a') leaked a TypeError from IR node
  validation; now explains that program statements must be operator
  calls.
- calling a '@program' from another program crashed with an
  AssertionError; now a DSLError suggesting to call the field operators
  directly or compose programs in Python.
- referencing a plain (undecorated) Python function leaked a ValueError
  from type translation; now a DSLTypeError hinting at
  '@field_operator'/'@scan_operator'. Other non-translatable closure
  variables in programs get the same wrapping as in field operators.
DSLTypeError now forwards the structured diagnostic payload
(label/notes/hints) to DSLError.
The message started with a newline and embedded a multi-line code block,
which rendered awkwardly; the replacement suggestion is now a hint in
the structured diagnostics format.
Direct calls to field operators and scan operators ('as program') now
validate their arguments the same way program calls do, instead of
crashing deep inside the embedded execution or backend:
- non-GT4Py argument types (e.g. raw NumPy arrays) report which
  argument is wrong and hint at 'gtx.as_field' (also when calling
  programs, where this previously crashed in type inference);
- wrong dimensions/dtypes/argument counts raise 'Invalid argument
  types in call to ...' listing the mismatches;
- a wrong 'out' argument reports the expected vs actual type;
- an invalid 'domain' argument explains the expected mapping form.
Also turned into diagnostics: missing/invalid offset-provider entries
when applying a field offset (was KeyError/NotImplementedError),
unsupported scan-operator attributes like 'init=np.zeros(...)' (was a
crash in fingerprinting), string keys in 'domain' dicts (was an IR
validation TypeError), nested tuple unpacking (was AttributeError) and
invalid literals in type constructors like 'int32("abc")' (was a
ValueError at execution time).
…vity misuse

- Unsupported parameter-list features (keyword-only, positional-only,
  '*args', '**kwargs', defaults) were silently dropped, leading to
  misleading 'Undeclared symbol' errors; they now raise
  UnsupportedPythonFeatureError pointing at the parameter.
- Out-of-range integer literals reported 'Constants of type <class
  'int'> are not permitted'; the underlying reason is now a note.
- Calling a '@program' inside a field operator dumped the whole
  'ProgramType(...)' spec; now a one-line explanation with a hint.
- Unstructured offsets: a missing offset-provider entry leaked KeyError
  and a raw neighbor-table (NumPy array) entry leaked
  NotImplementedError from 'as_connectivity_field'; both are now
  diagnostics, the latter hinting at 'gtx.as_connectivity'.
…tructor misuse

- 'offset_provider' arguments that are not a mapping (e.g. a list of
  pairs or a plain string) were silently accepted and only crashed (or
  worse, went unnoticed) when an offset was looked up; programs and
  direct operator calls now reject them upfront with a DSLTypeError.
- 'as_field' with an unsupported dtype (e.g. float16) crashed an
  assert; now a ValueError naming the dtype.
- 'as_field' with string dimensions (e.g. ['IDim']) failed with a
  baffling "''D'' cannot be interpreted as 'UnitRange'"; now a
  TypeError explaining dimensions must be 'Dimension' objects.
- 'as_connectivity' with a non-integral neighbor table crashed an
  assert; now a ValueError naming the dtype.
- 'with_backend' with a non-backend object failed later with
  AttributeError; now a TypeError at the call.
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