From f21c92178ed94ea9fbf65f19f3dade33dd0205ce Mon Sep 17 00:00:00 2001 From: wpbonelli Date: Wed, 14 Oct 2026 16:22:33 -0700 Subject: [PATCH] docs(dfns): give the real reason for the inline-array position rule An inline array whose width varies by row (self-sizing, sibling- or lookup-sized) must be a record's last field. The old reason, that a reader couldn't tell where later fields start, is wrong for a sibling, which comes earlier in the row. The reason is that a row's layout should be fixed by the component's dimensions, except for its last field, so a row can be split without evaluating any other field, row or block. A union's arm, and so its width, is chosen by its own tokens, so a union may come anywhere (e.g. utl-obs's id, followed by id2). Co-Authored-By: Claude Opus 5.5 --- docs/md/dfnspec.md | 2 +- modflow_devtools/dfns/schema.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/md/dfnspec.md b/docs/md/dfnspec.md index bf232ff2..1e1ee49b 100644 --- a/docs/md/dfnspec.md +++ b/docs/md/dfnspec.md @@ -488,7 +488,7 @@ Arrays are not proper composites. An array does not have an item subfield as doe A 1D array may have absent or empty `shape`, indicating no constraint on its size, in which case it is called **self-sizing**. Self-sizing arrays are parsed by MF6 dynamically at runtime. The size of a self-sizing array may serve as a dimension for other arrays (see below). -An array appearing as a subfield of a record is called an **inline array**. Inline arrays with a declared shape are self-explanatory: their elements are written on the record's line, in [shape order](#shape). An inline array may only be self-sizing if it is the right-most subfield of the record; in this case the record is essentially a variadic tuple. The same goes for an inline array whose size varies by row, i.e. whose shape names a sibling field or a column of another list's row (see [Dimensions](#dimensions)): a reader couldn't tell where fields after it start. +An array appearing as a subfield of a record is called an **inline array**. Inline arrays with a declared shape are self-explanatory: their elements are written on the record's line, in [shape order](#shape). A row's layout should be fixed by the component's dimensions, except for its last field. So an inline array whose shape names only dimensions may be anywhere in the record, but one whose size varies by row must be the record's right-most subfield. That is a self-sizing array, which makes the record essentially a variadic tuple, or one whose shape names a sibling field or a column of another list's row (see [Dimensions](#dimensions)). The one exception is a [union](#union): a union's arm, and so its width, is chosen by the union's own tokens. For example, `utl-obs`'s `id` (a cellid, an index or a boundname) is followed by `id2`. A reader can then split a row into its fields without evaluating any other field, row or block, so a context-free grammar describes it, the last field taking the rest of the line. ##### Type-specific attributes diff --git a/modflow_devtools/dfns/schema.py b/modflow_devtools/dfns/schema.py index 384a0eee..77d07a69 100644 --- a/modflow_devtools/dfns/schema.py +++ b/modflow_devtools/dfns/schema.py @@ -1323,8 +1323,8 @@ def _check_shape_element( component), selected by a sibling whose ``fk`` references that block. A sibling or lookup varies by row, so the array must be the rightmost - field in its record, like a self-sizing array: a reader can't know where - fields after it start. + field in its record, like a self-sizing array: a row's layout is fixed + by the component's dimensions, except for its last field. Raises ValueError on any violation. """ @@ -1723,8 +1723,8 @@ def _check_list(lst: "List") -> None: def _check_array(arr: "Array", enclosing: "Record | None") -> None: if not arr.shape: # Self-sizing (shape=[]) is valid at the top level and as the rightmost - # subfield of a record. The only invalid case is non-rightmost in a record: - # subsequent fields on the same line would be unreadable. + # subfield of a record: a row's layout is fixed by the component's + # dimensions, except for its last field. if enclosing is not None: fields_list = list(enclosing.fields.keys()) if not fields_list or fields_list[-1] != arr.name: