Skip to content

Add name= override to bind_cxx_struct#323

Open
isVoid wants to merge 3 commits intoNVIDIA:mainfrom
isVoid:fea/bind-cxx-struct-name-override
Open

Add name= override to bind_cxx_struct#323
isVoid wants to merge 3 commits intoNVIDIA:mainfrom
isVoid:fea/bind-cxx-struct-name-override

Conversation

@isVoid
Copy link
Copy Markdown
Collaborator

@isVoid isVoid commented Apr 21, 2026

Summary

  • Adds an optional keyword name= to bind_cxx_struct that overrides struct_decl.name throughout: the Numba type name, the Python API class name, the CTYPE_MAPS registration key, and the shim template substitutions.
  • Motivation: when binding a class template specialization, struct_decl.name is just the unqualified template name (e.g. "Matrix"), but the shim code and type registry both need the fully qualified specialization name (e.g. "Namespace::Matrix<float, 3, 1>").
  • The override is plumbed through bind_cxx_struct_ctors, bind_cxx_struct_regular_methods, and bind_cxx_struct_conversion_opeartors so every downstream consumer sees the same name.

Test plan

  • New unit tests in numbast/tests/test_bind_cxx_struct_name_override.py against a fresh stub header with a demo::Vec<float,3> specialization:
    • Guard: parsed struct_decl.name on the specialization is "Vec" (motivates the override).
    • Without name=, the Numba type name and CTYPE_MAPS key use the unqualified specialization name.
    • With name=, the override drives Numba type name, Python API class name, and CTYPE_MAPS registration key.
  • pytest numbast/tests/test_bind_cxx_struct_name_override.py passes locally.

Summary by CodeRabbit

  • New Features

    • Optional name-override for struct binding so you can assign custom (including fully-qualified) names to C++ template specializations exposed to the Python API; override propagates to type registration and generated API class names.
  • Tests

    • New tests and sample data verifying default unqualified names, name-override propagation across APIs, and that fully-qualified template names are sanitized into valid C identifiers.

bind_cxx_struct gains an optional keyword `name=` that overrides
struct_decl.name throughout: the Numba type name, the Python API class
name, the CTYPE_MAPS registration key, and the shim template
substitutions.

When binding a class template specialization, struct_decl.name is just
the unqualified template name (e.g. "Matrix"), but the shim code and
type registry both need the fully qualified specialization name
(e.g. "Namespace::Matrix<float, 3, 1>"). The override is plumbed
through bind_cxx_struct_ctors, bind_cxx_struct_regular_methods, and
bind_cxx_struct_conversion_opeartors so every downstream consumer sees
the same name.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 21, 2026

📝 Walkthrough

Walkthrough

Adds an optional name: str | None parameter to bind_cxx_struct() to allow binding a parsed C++ struct under an alternate C++ name; the override is threaded through shim generation, type registration, and Python API naming while parsed declaration name remains for member lookup. Tests and header data added.

Changes

Cohort / File(s) Summary
Core Binding Logic
numbast/src/numbast/struct.py
Added `name: str
Test Data
numbast/tests/data/sample_name_override.cuh
New header defining demo::Vec<T,N> and ForceInstantiation to force demo::Vec<float,3> instantiation for name-override tests.
Tests
numbast/tests/test_bind_cxx_struct_name_override.py
New tests verifying default unqualified parsed name, binding without name= uses parsed struct_decl.name, binding with name= override propagates to Numba type name, Python API class name, and CTYPE_MAPS registration; also tests _sanitize_c_identifier behavior.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I nibble names and hop with glee,
Templates curled beneath a tree,
Vec can wear a brand-new coat,
Shims hum softly as I gloat,
Hooray — a rabbit-bound decree!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main change: adding a name= override parameter to bind_cxx_struct to handle template specialization naming. The title is concise, specific, and directly reflects the core feature being implemented.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
numbast/src/numbast/struct.py (1)

203-206: ⚠️ Potential issue | 🔴 Critical

Sanitize conversion-operator shim symbol names when name= contains C++ syntax.

At line 204, mangled_name embeds raw struct_name. The bind_cxx_structs() API explicitly documents that name can be a fully qualified template specialization (e.g., "Eigen::Matrix<float,3,1>"), which contains characters (:, <, >, ,, space) invalid in C/C++ function identifiers. Generated shim symbols fail to compile when conversion operators are present.

The deduplicate_overloads() function applies no sanitization—it only appends a counter suffix. Sanitize struct_name before embedding it in the symbol:

Proposed fix
-    mangled_name = deduplicate_overloads(
-        f"__{struct_name}__{conv.mangled_name}"
-    )
+    safe_struct_token = "".join(
+        ch if (ch.isalnum() or ch == "_") else "_"
+        for ch in struct_name
+    )
+    mangled_name = deduplicate_overloads(
+        f"__{safe_struct_token}__{conv.mangled_name}"
+    )

Also applies to: 231-241 (wrapper function bind_cxx_struct_conversion_opeartors passes unsanitized struct_name to bind_cxx_struct_conversion_opeartor).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@numbast/src/numbast/struct.py` around lines 203 - 206, The generated shim
symbol embeds raw struct_name (e.g., "Eigen::Matrix<float,3,1>") which contains
characters invalid in C/C++ identifiers; sanitize struct_name before composing
mangled_name/shim_func_name in bind_cxx_struct_conversion_opeartor (and wherever
bind_cxx_struct_conversion_opeartors forwards struct_name to it). Fix by
normalizing struct_name to a valid identifier (e.g., replace any character not
in [A-Za-z0-9_] with '_' or convert "::" to "__") via a small helper
(sanitize_identifier or sanitize_symbol) and use that sanitized name when
building f"__{struct_name}__{conv.mangled_name}" and shim_func_name; also pass
the sanitized struct_name into bind_cxx_struct_conversion_opeartor from
bind_cxx_struct_conversion_opeartors so all locations use the safe symbol name.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@numbast/src/numbast/struct.py`:
- Around line 479-482: The alias registration only looks up aliases by
struct_name and thus can miss aliases keyed by the parsed declaration name (e.g.
struct_decl.name) and overwrite existing mappings; update the block that sets
C2N to (1) gather alias lists from both aliases.get(struct_name, []) and
aliases.get(struct_decl.name, []) (or the original parsed name variable) and (2)
when iterating those alias names, set C2N[alias] = s_type only if the alias key
is not already present (or explicitly merge preserving existing entries) so
existing alias mappings aren’t dropped; reference variables: C2N, struct_name,
aliases, s_type, and struct_decl.name.

In `@numbast/tests/test_bind_cxx_struct_name_override.py`:
- Around line 44-70: Both tests mutate the global CTYPE_MAPS via bind_cxx_struct
(affecting keys like "Vec" and the override string) and never undo that change;
update each test (test_bind_without_name_uses_struct_decl_name and
test_bind_with_name_uses_override_everywhere) to isolate side effects by saving
any existing entries for the relevant keys (CTYPE_MAPS.get("Vec") and
CTYPE_MAPS.get(override)), run bind_cxx_struct and assertions, then restore the
original mapping (or pop the newly added keys) in a finally block so CTYPE_MAPS
is returned to its prior state; also ensure to_numba_type lookups remain valid
during the test before cleanup.

---

Outside diff comments:
In `@numbast/src/numbast/struct.py`:
- Around line 203-206: The generated shim symbol embeds raw struct_name (e.g.,
"Eigen::Matrix<float,3,1>") which contains characters invalid in C/C++
identifiers; sanitize struct_name before composing mangled_name/shim_func_name
in bind_cxx_struct_conversion_opeartor (and wherever
bind_cxx_struct_conversion_opeartors forwards struct_name to it). Fix by
normalizing struct_name to a valid identifier (e.g., replace any character not
in [A-Za-z0-9_] with '_' or convert "::" to "__") via a small helper
(sanitize_identifier or sanitize_symbol) and use that sanitized name when
building f"__{struct_name}__{conv.mangled_name}" and shim_func_name; also pass
the sanitized struct_name into bind_cxx_struct_conversion_opeartor from
bind_cxx_struct_conversion_opeartors so all locations use the safe symbol name.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b2089a07-ef2c-43c4-bc1d-3fdbc820c1ad

📥 Commits

Reviewing files that changed from the base of the PR and between f29a975 and 9111b1e.

📒 Files selected for processing (3)
  • numbast/src/numbast/struct.py
  • numbast/tests/data/sample_name_override.cuh
  • numbast/tests/test_bind_cxx_struct_name_override.py

Comment on lines +479 to 482
C2N[struct_name] = s_type
if struct_name in aliases:
for alias in aliases[struct_name]:
C2N[alias] = s_type
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Alias registration keyed only by override name can drop existing alias mappings.

When name is provided, aliases keyed by struct_decl.name are no longer applied because lookup now checks only struct_name. That is a behavior regression for callers that keep alias maps keyed by parsed names.

🔧 Proposed fix
     C2N[struct_name] = s_type
-    if struct_name in aliases:
-        for alias in aliases[struct_name]:
-            C2N[alias] = s_type
+    alias_keys = [struct_name]
+    if struct_decl.name != struct_name:
+        alias_keys.append(struct_decl.name)
+    for key in alias_keys:
+        for alias in aliases.get(key, []):
+            C2N[alias] = s_type
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@numbast/src/numbast/struct.py` around lines 479 - 482, The alias registration
only looks up aliases by struct_name and thus can miss aliases keyed by the
parsed declaration name (e.g. struct_decl.name) and overwrite existing mappings;
update the block that sets C2N to (1) gather alias lists from both
aliases.get(struct_name, []) and aliases.get(struct_decl.name, []) (or the
original parsed name variable) and (2) when iterating those alias names, set
C2N[alias] = s_type only if the alias key is not already present (or explicitly
merge preserving existing entries) so existing alias mappings aren’t dropped;
reference variables: C2N, struct_name, aliases, s_type, and struct_decl.name.

Comment thread numbast/tests/test_bind_cxx_struct_name_override.py
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
numbast/tests/test_bind_cxx_struct_name_override.py (1)

42-72: ⚠️ Potential issue | 🟡 Minor

Isolate CTYPE_MAPS mutations to prevent cross-test coupling.

Both binding tests mutate global CTYPE_MAPS and leave entries behind ("Vec" / override key). This can make suite order affect outcomes. Save prior values and restore them in finally.

Proposed isolation patch
 def test_bind_without_name_uses_struct_decl_name():
@@
-    S = bind_cxx_struct(shim_writer, cts, nbtypes.Type, StructModel)
-
-    assert S._nbtype.name == "Vec"
-    assert CTYPE_MAPS.get("Vec") is S._nbtype
-    assert to_numba_type("Vec") is S._nbtype
+    key = "Vec"
+    had_prev = key in CTYPE_MAPS
+    prev = CTYPE_MAPS.get(key)
+    try:
+        S = bind_cxx_struct(shim_writer, cts, nbtypes.Type, StructModel)
+        assert S._nbtype.name == key
+        assert CTYPE_MAPS.get(key) is S._nbtype
+        assert to_numba_type(key) is S._nbtype
+    finally:
+        if had_prev:
+            CTYPE_MAPS[key] = prev
+        else:
+            CTYPE_MAPS.pop(key, None)
@@
 def test_bind_with_name_uses_override_everywhere():
@@
-    S = bind_cxx_struct(
-        shim_writer,
-        cts,
-        nbtypes.Type,
-        StructModel,
-        name=override,
-    )
-
-    assert S._nbtype.name == override
-    assert S.__name__ == override
-    assert CTYPE_MAPS.get(override) is S._nbtype
-    assert to_numba_type(override) is S._nbtype
+    key = override
+    had_prev = key in CTYPE_MAPS
+    prev = CTYPE_MAPS.get(key)
+    try:
+        S = bind_cxx_struct(
+            shim_writer,
+            cts,
+            nbtypes.Type,
+            StructModel,
+            name=override,
+        )
+        assert S._nbtype.name == key
+        assert S.__name__ == key
+        assert CTYPE_MAPS.get(key) is S._nbtype
+        assert to_numba_type(key) is S._nbtype
+    finally:
+        if had_prev:
+            CTYPE_MAPS[key] = prev
+        else:
+            CTYPE_MAPS.pop(key, None)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@numbast/tests/test_bind_cxx_struct_name_override.py` around lines 42 - 72,
The tests mutate the global CTYPE_MAPS via bind_cxx_struct and leave keys ("Vec"
and the override) set, causing cross-test coupling; modify both
test_bind_without_name_uses_struct_decl_name and
test_bind_with_name_uses_override_everywhere to snapshot CTYPE_MAPS.get(key) for
the relevant key(s) before calling bind_cxx_struct and restore the original
value (or delete the key if it was absent) in a try/finally block after the
assertions; reference CTYPE_MAPS, bind_cxx_struct, S._nbtype.name, and the
override string to locate where to save/restore the prior mapping so the global
state is unchanged after each test runs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@numbast/tests/test_bind_cxx_struct_name_override.py`:
- Around line 42-72: The tests mutate the global CTYPE_MAPS via bind_cxx_struct
and leave keys ("Vec" and the override) set, causing cross-test coupling; modify
both test_bind_without_name_uses_struct_decl_name and
test_bind_with_name_uses_override_everywhere to snapshot CTYPE_MAPS.get(key) for
the relevant key(s) before calling bind_cxx_struct and restore the original
value (or delete the key if it was absent) in a try/finally block after the
assertions; reference CTYPE_MAPS, bind_cxx_struct, S._nbtype.name, and the
override string to locate where to save/restore the prior mapping so the global
state is unchanged after each test runs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 147a4fec-08fd-4f7f-9499-1f52cc05da9b

📥 Commits

Reviewing files that changed from the base of the PR and between 9111b1e and c9f4e39.

📒 Files selected for processing (1)
  • numbast/tests/test_bind_cxx_struct_name_override.py

@github-actions
Copy link
Copy Markdown

Doc Preview CI
🚀 View pre-built docs at
https://NVIDIA.github.io/numbast/pr-preview/pr-323/

Preview will be ready when GitHub Pages deployment finishes.

The conversion-operator bind path composes the shim's extern "C"
function name from struct_name. With the new name= override, callers
may pass a fully-qualified template specialization (e.g.
"Eigen::Matrix<float, 3, 1>"), which contains ':', '<', '>', ',',
and spaces — all invalid in a C identifier and rejected by nvrtc.

Sanitize struct_name (any char outside [A-Za-z0-9_] -> '_') before
interpolating into mangled_name/shim_func_name. The C++ type
reference inside the shim body still uses the original struct_name.

Also snapshot and restore CTYPE_MAPS around the name-override tests
so bind_cxx_struct side effects don't leak between tests.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
numbast/src/numbast/struct.py (1)

495-498: ⚠️ Potential issue | 🟠 Major

Alias registration should consult both override and parsed struct name.

Line 496 only checks aliases under struct_name. With name=..., aliases keyed by struct_decl.name are skipped, and Line 498 can also clobber an existing alias mapping.

🔧 Proposed fix
     C2N[struct_name] = s_type
-    if struct_name in aliases:
-        for alias in aliases[struct_name]:
-            C2N[alias] = s_type
+    alias_keys = [struct_name]
+    if struct_decl.name != struct_name:
+        alias_keys.append(struct_decl.name)
+    for key in alias_keys:
+        for alias in aliases.get(key, []):
+            C2N.setdefault(alias, s_type)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@numbast/src/numbast/struct.py` around lines 495 - 498, The alias registration
currently only uses struct_name and can skip aliases keyed by the parsed
declaration name and may overwrite existing mappings; update the logic around
C2N, struct_name, aliases, name and struct_decl.name so you collect aliases for
both the effective struct_name and the original struct_decl.name (if different),
iterate both alias lists and set C2N[alias] = s_type only when an alias is not
already present (i.e., avoid clobbering an existing mapping), ensuring you still
set C2N[struct_name] = s_type for the effective name.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@numbast/tests/test_bind_cxx_struct_name_override.py`:
- Around line 24-32: The autouse fixture _restore_ctype_maps should be removed
and its behavior inlined into each test that mutates CTYPE_MAPS: for every test
that calls bind_cxx_struct or otherwise mutates CTYPE_MAPS, take a local
snapshot (snapshot = dict(CTYPE_MAPS)) at the start and restore it in a finally
block (CTYPE_MAPS.clear(); CTYPE_MAPS.update(snapshot)) at the end of that test
so each test manages its own setup/teardown and can evolve independently.

---

Duplicate comments:
In `@numbast/src/numbast/struct.py`:
- Around line 495-498: The alias registration currently only uses struct_name
and can skip aliases keyed by the parsed declaration name and may overwrite
existing mappings; update the logic around C2N, struct_name, aliases, name and
struct_decl.name so you collect aliases for both the effective struct_name and
the original struct_decl.name (if different), iterate both alias lists and set
C2N[alias] = s_type only when an alias is not already present (i.e., avoid
clobbering an existing mapping), ensuring you still set C2N[struct_name] =
s_type for the effective name.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: cc77d48e-e10b-4d40-bf0e-635c581611ea

📥 Commits

Reviewing files that changed from the base of the PR and between c9f4e39 and 445a160.

📒 Files selected for processing (3)
  • numbast/src/numbast/struct.py
  • numbast/tests/data/sample_name_override.cuh
  • numbast/tests/test_bind_cxx_struct_name_override.py

Comment thread numbast/tests/test_bind_cxx_struct_name_override.py
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