From b645b1bfb0938a640291970146c3cecabd9bb7cc Mon Sep 17 00:00:00 2001 From: Esteban Zimanyi Date: Thu, 23 Jul 2026 08:03:44 +0200 Subject: [PATCH] Exclude internal-API functions from a class's OO method list The object model classifies every function whose name matches a class prefix into classes[].methods, including the ones the catalog marks internal: the _p peeks, the bbox and skiplist plumbing, the raw _in/_out. For the Temporal class that is 59 of 162 entries. A binding that generates its object layer from this list would emit methods it cannot call, so each binding would re-derive the public/internal split the catalog already records in the api field. Treat a non-public function as internal machinery in _oo_excluded, alongside the aggregate transition helpers and the SQL-less comparators it already excludes. The function stays in the list with ooExclude set, so the reverse index remains complete and a binding keeps only the entries without the flag. The Temporal public method count goes from a list mixing 59 internal entries to 97 clean ones, with none leaking through. --- parser/object_model.py | 14 ++++++++++++-- tests/test_object_model.py | 13 +++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/parser/object_model.py b/parser/object_model.py index 56dccd8..fbe7611 100644 --- a/parser/object_model.py +++ b/parser/object_model.py @@ -212,9 +212,19 @@ def _ooname(fn_name: str, cls: str) -> str: def _oo_excluded(fn: dict, role: str) -> bool: """True for functions classified to a class that are internal machinery, - not user OO methods: aggregate transition helpers, and comparators with no - SQL function (qsort / bound / min / max sort helpers).""" + not user OO methods: functions the catalog marks internal (the ``_p`` peeks, + the bbox / skiplist plumbing, ``*_in`` / ``*_out``), aggregate transition + helpers, and comparators with no SQL function (qsort / bound / min / max sort + helpers). + + The internal-API check is the load-bearing one for a binding that generates + its object layer from ``classes[*].methods``: without it that list mixes the + public surface with functions a binding cannot call, so every binding would + re-derive the split. The catalog carries ``api`` per function, so the method + carries the exclusion and a binding keeps only ``not m['ooExclude']``.""" name = fn["name"] + if fn.get("api") != "public": + return True if any(name.endswith(s) for s in _OONAME_EXCLUDE_SUFFIXES): return True if role == "predicate" and not fn.get("sqlfn"): diff --git a/tests/test_object_model.py b/tests/test_object_model.py index a8c5683..60dd7aa 100644 --- a/tests/test_object_model.py +++ b/tests/test_object_model.py @@ -156,6 +156,19 @@ def test_classification(self): self.assertIsNone(ftc["add_int_int"]["class"]) self.assertIn("no-prefix-match", ftc["add_int_int"]["reason"]) + def test_internal_api_methods_are_excluded(self): + # A function the catalog marks internal is classified to its class (so + # the reverse index stays complete) but flagged ooExclude, so a binding + # generating from classes[*].methods keeps only the public surface. + idl = attach_object_model({"functions": [ + {"name": "temporal_num_instants", "api": "public"}, + {"name": "temporal_inst_n", "api": "internal"}, + ]}, MODEL, None) + meths = {m["backing"]: m + for m in idl["objectModel"]["classes"]["Temporal"]["methods"]} + self.assertNotIn("ooExclude", meths["temporal_num_instants"]) + self.assertTrue(meths["temporal_inst_n"].get("ooExclude")) + def test_tree_derived(self): om = self._attach(["temporal_merge"])["objectModel"] lat = om["lattice"]