Skip to content

Bind math_Vector-family templates + dependent OCCT surface; fix embind cross-inheritance overload aliasing#2

Open
marwndev wants to merge 3 commits into
taucad:occt-v8-emscripten-5from
marwndev:occt-v8-emscripten-5
Open

Bind math_Vector-family templates + dependent OCCT surface; fix embind cross-inheritance overload aliasing#2
marwndev wants to merge 3 commits into
taucad:occt-v8-emscripten-5from
marwndev:occt-v8-emscripten-5

Conversation

@marwndev

@marwndev marwndev commented Jun 7, 2026

Copy link
Copy Markdown

Summary

Three related changes that make the math_Vector / TKMath template surface bindable, surface the OCCT classes that depend on it, and fix an embind overload-resolution bug exposed along the way.

1. bindgen: discover + emit non-NCollection template instantiations

Lets template typedefs outside the NCollection family — e.g. math_VectorBase<double> (= math_Vector) and math_MatrixBase<double> (= math_Matrix) — be discovered and compiled.

  • discover.py: add EXTRA_TEMPLATE_CONTAINERS (math_VectorBase, math_MatrixBase) and ADMIT_TEMPLATE_CONTAINERS = NCOLLECTION_CONTAINERS | EXTRA (existing behaviour stays a strict subset, so it cannot regress), plus pass‑1 canonical resolution so a method param spelled via a typedef (const math_Vector&) resolves to its canonical container before bailing. Implements the long‑standing generic‑template‑discovery follow‑up.
  • ast/template_args.py: substitute the canonical forward‑declaration template parameter name. OCCT forward‑declares template<typename T> class math_VectorBase; then defines it with TheItemType; libclang renders a method's self‑type param (const math_VectorBase&) using the forward‑decl name T, while processTemplate keys templateArgs from the definition, so T was never substituted and an uncompilable math_VectorBase<T> leaked into select_overload<> signatures and val‑dispatch arg.as<> lambdas. Mapping the canonical param name onto the same ordinal fixes every self‑type‑param site at once.

2. libembind: fork inherited overload tables (cross‑inheritance aliasing fix)

embind keys method overloads by arg‑count → arg‑signature only; the this type is not in the key. When a derived class registers an override of a method whose overload dispatcher / per‑signature map is inherited from a base, ensureOverloadTable / ensureOverloadSignatureTable did not fork an own copy, so the write landed in the base's shared structures. With a base method overridden by multiple subclasses, they shared one per‑signature slot and the last registrant won — so the base and non‑overriding siblings dispatched to the wrong invoker and threw Expected null or instance of <sibling>, got an instance of <base>.

Concrete case: BOPAlgo_Builder::Perform is overridden by sibling BOPAlgo_Splitter and inherited by BOPAlgo_CellsBuilder; before this fix, both new BOPAlgo_CellsBuilder().Perform(...) and new BOPAlgo_Builder().Perform(...) threw Expected ... BOPAlgo_Splitter, got ... BOPAlgo_Options.

Fix: both helpers now fork inherited overload structures so each registering class owns its own dispatcher / per‑signature maps (tagged ocjsSignatureOwner); writes never escape to a base. Non‑inherited paths are byte‑for‑byte unchanged, so single‑class methods cannot regress. This is distinct from the sub‑2b constructor sibling‑aliasing handled by predicates/sibling_aliasing.py (that is within one class; this is across inheritance). The change is folded into src/patches/libembind-overloading.patch (regenerated against the vendored stock‑emscripten pristine snapshot — round‑trips exactly) with an updated libembind-overloading.expected.sha256.

3. full builds: bind the math_Vector‑dependent OCCT surface

Now that math_VectorBase<double> is discoverable + compilable, the classes excluded only because that base was unbound can be re‑included:

  • bindgen-filters.yaml: un‑exclude the walking‑line approximators (BRepApprox/GeomInt TheComputeLine{,Bezier}), the 2D tangent‑constraint solvers (GccAna_* / Geom2dGcc_* / GccEnt), and the ProjLib projection package. (Geom2dGcc_FunctionTanCuCuCu stays excluded — an internal undefined‑symbol functor, not public API.)
  • build-configs/full*.yml: add the now‑bindable symbols (math_VectorBase_double + the re‑included approximators / Gcc solvers / ProjLib classes), regenerated via scripts/enumerate-symbols.py.

Validation

  • Changes 1 & 2 were validated in a downstream multi‑threaded consumer build + its test suite: BOPAlgo_CellsBuilder.Perform, the 2D Gcc tangent‑constraint solvers, ProjLib projection, and direct math_Vector numeric use all work. A runtime audit of 157 signature dispatchers across 607 classes shows every one resolves to an ancestor‑or‑self owner (zero sibling mis‑resolution).
  • The newly‑included surface in change 3 is generation/compile‑validated; the full build links a broader set than that downstream build, so this repo's CI full.yml build is the authoritative full‑link check.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Expanded WebAssembly bindings so more OpenCASCADE APIs are available in the browser and multi-threaded builds.
    • Added support for more template-based container types during automatic discovery.
  • Bug Fixes

    • Improved overload selection so matching now considers argument types, not just count.
    • Better handling of inherited overloads and optional parameters, reducing incorrect dispatch and missing-method issues.
    • Updated generated binding checksum to match the latest output.

marwndev and others added 2 commits June 7, 2026 20:27
Let template typedefs outside the NCollection family — e.g.
math_VectorBase<double> (= math_Vector) and math_MatrixBase<double>
(= math_Matrix) — be discovered and compiled. Two coupled changes:

- discover.py: add EXTRA_TEMPLATE_CONTAINERS (math_VectorBase, math_MatrixBase)
  and ADMIT_TEMPLATE_CONTAINERS = NCOLLECTION_CONTAINERS | EXTRA (existing
  behaviour stays a strict subset, so it cannot regress), plus pass-1 canonical
  resolution so a method param spelled via a typedef (const math_Vector&)
  resolves to its canonical container before bailing. Implements the
  long-standing generic-template-discovery follow-up.
- ast/template_args.py: substitute the canonical forward-declaration template
  parameter name. OCCT forward-declares `template<typename T> class
  math_VectorBase;` then defines it with TheItemType; libclang renders a
  method's self-type param (const math_VectorBase&) using the forward-decl name
  T, while processTemplate keys templateArgs from the definition (TheItemType),
  so the T was never substituted and an uncompilable math_VectorBase<T> leaked
  into select_overload<> signatures and val-dispatch arg.as<> lambdas. Mapping
  the canonical param name onto the same ordinal fixes every self-type-param
  site at once.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… fix)

embind keys method overloads by arg-count -> arg-signature only; the `this`
type is not in the key. When a derived class registers an override of a method
whose overload dispatcher / per-signature map is inherited from a base,
ensureOverloadTable / ensureOverloadSignatureTable did not fork an own copy, so
the write landed in the base's shared structures. With a base method overridden
by multiple subclasses, all of them shared one per-signature slot and the last
registrant won -- so the base and non-overriding siblings dispatched to the
wrong invoker and threw "Expected null or instance of <sibling>, got an
instance of <base>".

Both helpers now fork inherited overload structures so each registering class
owns its own dispatcher / per-signature maps (tagged ocjsSignatureOwner);
writes never escape to a base. Non-inherited paths are unchanged. This is
distinct from the sub-2b constructor sibling-aliasing handled by
predicates/sibling_aliasing.py (that is within one class; this is across
inheritance).

Regenerated src/patches/libembind-overloading.patch against the vendored
stock-emscripten pristine snapshot (round-trips exactly) and updated
libembind-overloading.expected.sha256 to the new post-patch result.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 7, 2026

Copy link
Copy Markdown

@marwndev is attempting to deploy a commit to the TauCAD Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8b0b7dd7-0365-4dab-a34c-b691fb42bea3

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Regenerate build-configs/full*.yml via scripts/enumerate-symbols.py now that the
bindgen template-discovery change + the bindgen-filters.yaml un-exclusions make
the math_Vector-dependent surface bindable. The three configs share one bindings
list (only emcc flags differ); each is the verbatim enumerator output.

Adds vs the previous full.yml:
  - walking-line approximators (BRepApprox/GeomInt TheComputeLine{,Bezier}),
    2D tangent-constraint solvers (GccAna_*/Geom2dGcc_*/GccEnt), and the ProjLib
    projection package — previously excluded only because math_VectorBase<double>
    was unbound;
  - 6 IMeshData_*Handle the previous (stale) full.yml had missed; the enumerator
    emits these independently of this change.

math_VectorBase<double> is not emitted as a standalone symbol by the enumerator,
so it is not listed here; the classes that consume it are.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@marwndev
marwndev force-pushed the occt-v8-emscripten-5 branch from 70d1fd7 to c068b04 Compare June 8, 2026 09:52
@rifont

rifont commented Jun 14, 2026

Copy link
Copy Markdown

Thanks for the contribution @marwndev , I'll review and seek to incorporate these changes over the next few days! The math APIs are something I hadn't got a chance to properly exercise, so it's great you have on your downstream. I'll look to add additional smoke tests for these APIs too.

Cheers Richard

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (5)
src/ocjs_bindgen/discover.py (1)

461-467: 🎯 Functional Correctness | 🟠 Major

Mandatory fix: Use _extract_template_args to validate template parameters and prevent zero-argument base bindings.

The current logic relies on string parsing of canonical.spelling to derive template arguments. For aliased typedefs, libclang's rendering can strip parameters (e.g., math_VectorBase instead of math_VectorBase<double>), resulting in empty argument lists and the generation of uncompilable zero-argument bindings for base classes.

Query the AST directly using _extract_template_args(canonical) to ensure the args list reflects the actual template parameters. If no arguments remain after extraction, skip the entity to avoid admitting raw base classes like math_VectorBase.

         container, args = parsed
         canonical_container = CONTAINER_ALIASES.get(container, container)
+        # Prefer AST extraction over string parsing to handle typedef aliases correctly.
+        extracted_args = _extract_template_args(canonical)
+        if extracted_args:
+            args = extracted_args
         if canonical_container not in ADMIT_TEMPLATE_CONTAINERS:
             continue
+        # Reject base classes with zero template arguments.
+        if not args:
             continue
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ocjs_bindgen/discover.py` around lines 461 - 467, The template-argument
detection in discover.py is relying on parsing canonical.spelling, which can
drop aliased typedef parameters and admit zero-argument base bindings. Update
the template validation path around _parse_template_spelling and the
canonical_container check to use _extract_template_args(canonical) as the source
of args, then skip the entity whenever the extracted args list is empty so raw
base classes like math_VectorBase are not admitted.
src/patches/libembind-overloading.patch (4)

20-23: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Prefer exact signature matches before optional wildcards.

Because optional slots return true before exact primitive/class checks, overloads like optional<T> and T with the same arity become registration-order dependent. Defer wildcard matches until no exact key matches.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/patches/libembind-overloading.patch` around lines 20 - 23, The overload
matching in the embind patch is treating optional fields as an early wildcard,
which can make same-arity overloads registration-order dependent. Update the
matching logic around the fieldType lookup in the overload resolution code so
exact primitive/class signature checks are evaluated before any optional
wildcard acceptance, and only fall back to the optional path when no exact
registered type matches.

528-532: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Seed the raw signature for first static methods.

The instance-method path sets unboundTypesHandler.signature; this first static-method path does not. If a same-arity overload promotes the unresolved function, ensureOverloadSignatureTable stores the first entry under undefined.

Proposed fix
       if (undefined === proto[methodName]) {
         // This is the first function to be registered with this name.
         unboundTypesHandler.argCount = argCount-1;
+        unboundTypesHandler.signature = rawSignatureString;
         proto[methodName] = unboundTypesHandler;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/patches/libembind-overloading.patch` around lines 528 - 532, The first
static-method registration path in the overload handling logic is missing the
raw signature initialization that the instance-method path already sets on
unboundTypesHandler. Update the proto[methodName] first-registration branch in
the overloading patch so it seeds the signature for static methods before
storing the handler, using the same mechanism as the instance-method path, to
prevent ensureOverloadSignatureTable from recording the first overload under
undefined when a same-arity overload is promoted.

433-442: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Guard final constructor signature collisions.

Line 406 checks raw type IDs, but Line 441 stores the resolved JS signature. Distinct C++ overloads such as int and double both resolve to number, so the later constructor silently overwrites the earlier one.

Proposed fix
         if (undefined !== classType.registeredClass.constructor_body[argCount - 1].func) {
           classType.registeredClass.constructor_body[argCount - 1].func = func;
         }
+        if (undefined !== classType.registeredClass.constructor_body[argCount - 1].signatures[signatureString]) {
+          throw new BindingError(`Cannot register multiple constructors with identical javascript types of parameters for class '${classType.name}'!`);
+        }
         classType.registeredClass.constructor_body[argCount - 1].signatures[signatureString] = func;
         classType.registeredClass.constructor_body[argCount - 1].signaturesArray.push(signatureArray);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/patches/libembind-overloading.patch` around lines 433 - 442, The
constructor overloading logic in libembind-overloading.patch is still allowing
resolved JavaScript signatures to collide, so distinct C++ overloads like int
and double both map to number and overwrite each other. Update the constructor
registration path around craftInvokerFunction and constructor_body so that final
signature storage detects duplicate cppTypeToJsType-derived signature strings
before writing to signatures or signaturesArray, and preserve both overloads by
keeping separate entries or preventing the later one from replacing the earlier
one.

506-508: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Don’t silently replace same-class overloads with the same JS signature.

These paths unconditionally assign signatures[signatureString]. That is valid for an inherited override, but same-class overloads whose C++ types collapse to the same JS type will dispatch to the last registered invoker. Preserve enough owner/class metadata to allow inherited overrides while throwing for same-class duplicate JS signatures.

Also applies to: 565-567

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/patches/libembind-overloading.patch` around lines 506 - 508, The overload
registration in the embind patch is overwriting same-class entries in the
overload table when two C++ overloads collapse to the same JS signature. Update
the registration logic around the overloadTable/signatures handling to
distinguish same-class duplicates from inherited overrides by using owner/class
metadata, so inherited methods can still replace as intended while duplicate JS
signatures declared on the same class are rejected instead of silently
dispatching to the last invoker. Apply the same fix in the other mirrored
overload registration block referenced by this patch.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/ocjs_bindgen/discover.py`:
- Around line 461-467: The template-argument detection in discover.py is relying
on parsing canonical.spelling, which can drop aliased typedef parameters and
admit zero-argument base bindings. Update the template validation path around
_parse_template_spelling and the canonical_container check to use
_extract_template_args(canonical) as the source of args, then skip the entity
whenever the extracted args list is empty so raw base classes like
math_VectorBase are not admitted.

In `@src/patches/libembind-overloading.patch`:
- Around line 20-23: The overload matching in the embind patch is treating
optional fields as an early wildcard, which can make same-arity overloads
registration-order dependent. Update the matching logic around the fieldType
lookup in the overload resolution code so exact primitive/class signature checks
are evaluated before any optional wildcard acceptance, and only fall back to the
optional path when no exact registered type matches.
- Around line 528-532: The first static-method registration path in the overload
handling logic is missing the raw signature initialization that the
instance-method path already sets on unboundTypesHandler. Update the
proto[methodName] first-registration branch in the overloading patch so it seeds
the signature for static methods before storing the handler, using the same
mechanism as the instance-method path, to prevent ensureOverloadSignatureTable
from recording the first overload under undefined when a same-arity overload is
promoted.
- Around line 433-442: The constructor overloading logic in
libembind-overloading.patch is still allowing resolved JavaScript signatures to
collide, so distinct C++ overloads like int and double both map to number and
overwrite each other. Update the constructor registration path around
craftInvokerFunction and constructor_body so that final signature storage
detects duplicate cppTypeToJsType-derived signature strings before writing to
signatures or signaturesArray, and preserve both overloads by keeping separate
entries or preventing the later one from replacing the earlier one.
- Around line 506-508: The overload registration in the embind patch is
overwriting same-class entries in the overload table when two C++ overloads
collapse to the same JS signature. Update the registration logic around the
overloadTable/signatures handling to distinguish same-class duplicates from
inherited overrides by using owner/class metadata, so inherited methods can
still replace as intended while duplicate JS signatures declared on the same
class are rejected instead of silently dispatching to the last invoker. Apply
the same fix in the other mirrored overload registration block referenced by
this patch.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fa18b161-b2a3-427f-8e68-d6fb466f8d00

📥 Commits

Reviewing files that changed from the base of the PR and between 9eea6f5 and c068b04.

📒 Files selected for processing (8)
  • bindgen-filters.yaml
  • build-configs/full.yml
  • build-configs/full_multi.yml
  • build-configs/full_multi_browser.yml
  • src/ocjs_bindgen/ast/template_args.py
  • src/ocjs_bindgen/discover.py
  • src/patches/libembind-overloading.expected.sha256
  • src/patches/libembind-overloading.patch
💤 Files with no reviewable changes (1)
  • bindgen-filters.yaml

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.

2 participants