From c39d98fa1086e87015706f5d93bb00d2c3287043 Mon Sep 17 00:00:00 2001 From: Esteban Zimanyi Date: Thu, 3 Sep 2026 19:24:40 +0200 Subject: [PATCH] Read a parameter's NULL note through the markup around it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Doxygen marks up the literal a description names, so one note is written four ways — `NULL`, ``NULL``, `@p NULL` and `\p NULL` — and the extractor reads only the bare and backticked pair. Eighteen parameters over sixteen functions carry a note an author marked up, and every one of them reaches the bindings as a parameter that must be supplied: the spheroid `interpolate_point4d_spheroid` takes the sphere path without, the measure of the nine `*_to_geomeas` walks, the cluster flag of `geo_union_dbscan`, and the identifier and distance a nearest-neighbour cursor step writes. `shape.nullable` carries 131 parameters over 102 functions, against 113 over 86, and no parameter loses the note it had. The suite states the markup forms beside the plain one, on a function whose third parameter carries no note at all. --- .github/workflows/pytest.yml | 2 +- parser/nullable.py | 9 +++++++-- tests/test_nullable.py | 21 +++++++++++++++++++++ 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index d06c02d..688605f 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -96,7 +96,7 @@ jobs: # carries, or a change to them is not exercised until after it merges. # Consumers use the action; this repository owns the rules. - name: Refuse a skip, and a suite that shrank - run: tools/check-test-outcome.py "$RUNNER_TEMP/pytest.log" --min-tests 317 + run: tools/check-test-outcome.py "$RUNNER_TEMP/pytest.log" --min-tests 318 # The rules earn their place by refusing a log that carries what they # name. Both fixtures are written here rather than tracked, and the diff --git a/parser/nullable.py b/parser/nullable.py index 41c411c..4a64dd5 100644 --- a/parser/nullable.py +++ b/parser/nullable.py @@ -27,8 +27,13 @@ _PARAM = re.compile( r'@param\[[^\]]*\]\s+(?P\w+(?:\s*,\s*\w+)*)\s+(?P.*?)' r'(?=\n\s*\*\s*@|\*/|\Z)', re.S) -_NULLISH = re.compile(r'may be\s+`?NULL`?|can be\s+`?NULL`?|`?NULL`?\s+is allowed' - r'|or\s+`?NULL`?', re.I) +# Doxygen marks up a parameter or literal it names — ``NULL``, `NULL`, @p NULL, +# \p NULL — and every one of those spellings is the same note. Reading only the +# bare and backticked forms drops the ones an author marked up, and a parameter +# whose note is unread reaches every binding as one that must be supplied. +_MARKED_NULL = r'(?:[@\\]p\s+)?`?NULL`?' +_NULLISH = re.compile(rf'may be\s+{_MARKED_NULL}|can be\s+{_MARKED_NULL}' + rf'|{_MARKED_NULL}\s+is allowed|or\s+{_MARKED_NULL}', re.I) def extract_nullable(meos_root: str | Path) -> dict[str, list[str]]: diff --git a/tests/test_nullable.py b/tests/test_nullable.py index 0d37bc4..ee158b0 100644 --- a/tests/test_nullable.py +++ b/tests/test_nullable.py @@ -36,6 +36,19 @@ { return NULL; } + +/** + * @brief Interpolate between two geography points + * @param[in] p1 First point + * @param[in] s Spheroid, may be @p NULL when the sphere is meant + * @param[in] tol Tolerance, or \\p NULL for the default + */ +int +interpolate_point4d_spheroid(const POINT4D *p1, const SPHEROID *s, + const double *tol) +{ + return 0; +} ''' @@ -57,6 +70,14 @@ def test_extracts_only_may_be_null_params(self): # `temp`, `inst`, `interp` carry no NULL note -> not nullable self.assertNotIn("temp", nul["temporal_as_mfjson"]) + def test_a_null_note_reads_through_the_markup_around_it(self): + # Doxygen marks up the literal it names, and `NULL`, @p NULL and \p NULL + # are one note. A parameter whose note goes unread reaches every binding + # as one that must be supplied. + nul = extract_nullable(self.tmp.name) + self.assertEqual(nul["interpolate_point4d_spheroid"], ["s", "tol"]) + self.assertNotIn("p1", nul["interpolate_point4d_spheroid"]) + def test_merge_only_existing_params(self): idl = {"functions": [ {"name": "temporal_as_mfjson",