Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 22 additions & 3 deletions parser/typerecover.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,17 @@
"JsonPath": "JsonPath",
}

# libclang renders a fixed-width integer typedef's fully-resolved `canonical` as the
# platform builtin spelling (uint64_t -> "unsigned long" on LP64). When the c-field is the
# typedef but `canonical` is that platform alias, normalize `canonical` too, so the same
# underlying type is spelled identically catalog-wide -- e.g. the Tcell<T> cell-id accessors
# th3index_start_value (H3Index, from libh3) and tquadbin_start_value (Quadbin) BOTH read
# uint64_t, not one "unsigned long" and the other "uint64_t".
_CANON_ALIAS = {
"uint64_t": {"unsignedlong", "longunsignedint", "unsignedlonglong"},
"int64_t": {"long", "longint", "longlong"},
}

_NAMES = "|".join(sorted(_TYPE_MAP, key=len, reverse=True))
# optional const, a recoverable base, optional pointer stars, optional identifier
_DECL_RE = re.compile(
Expand Down Expand Up @@ -135,12 +146,20 @@ def _apply(slot, recovery):
# canonical collapses (slot spells `original`, e.g. a MobilityDB typedef
# such as Quadbin whose uint64 underlying type was the part that erased).
recoverable = (_nospace(collapsed), _nospace(original))
if _nospace(slot.get(key)) not in recoverable:
cur = _nospace(slot.get(key))
# `cur in recoverable`: the base name collapsed to int, or survived as the typedef.
# `cur == recovered`: libclang already rendered the c-field as the typedef's immediate
# underlying type (e.g. H3Index -> uint64_t) while leaving `canonical` at the fully
# resolved platform spelling ("unsigned long") -> fall through to normalize canonical.
if cur not in recoverable and cur != _nospace(recovered):
return 0
rewrote = slot.get(key) != recovered
slot[key] = recovered
if _nospace(slot.get("canonical")) in recoverable:
canon = _nospace(slot.get("canonical"))
if canon in recoverable or canon in _CANON_ALIAS.get(_nospace(recovered), ()):
rewrote = rewrote or slot.get("canonical") != recovered
slot["canonical"] = recovered
return 1
return 1 if rewrote else 0

def patch(fn):
rec = decls.get(fn.get("name"))
Expand Down
12 changes: 12 additions & 0 deletions tests/test_typerecover.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,18 @@ def test_uint64_recovered(self):
self.assertEqual(self._ret("span_hash_extended"), "uint64_t")
self.assertIn("uint64_t", self._param_ctypes("set_hash_extended"))

def test_cell_id_canonical_normalized_uniform(self):
# H3Index (libh3's typedef, whose fully-resolved canonical is the platform
# "unsigned long") and Quadbin (MobilityDB's typedef, recovered to "uint64_t")
# are BOTH uint64 cell ids; as Tcell<T> subtypes they must be spelled identically.
# The ``canonical`` field must normalize to "uint64_t" for both, not leave H3Index
# at "unsigned long" — a guard on the _CANON_ALIAS canonical-normalization pass.
for name in ("th3index_start_value", "th3index_end_value",
"tquadbin_start_value", "tquadbin_end_value", "h3index_in"):
rt = self.by_name[name]["returnType"]
self.assertEqual(rt["c"], "uint64_t", f"{name} c")
self.assertEqual(rt["canonical"], "uint64_t", f"{name} canonical")

# ---- genuine-int controls (must NOT be rewritten) ----------------------

def test_genuine_int_left_untouched(self):
Expand Down
Loading