-
Notifications
You must be signed in to change notification settings - Fork 13
Fix scoped template bug #180
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
I am going ahead and merging this so I can add it to the Matlab fix PR borglab/gtsam#2310 in GTSAM. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR extends the template instantiation machinery and wrappers to better support scoped templates and a new SmartProjectionRigFactor specialization, while cleaning up type handling and some build configuration.
Changes:
- Refactors the template instantiator helpers and parser
Typename/TemplatedTypehandling to better represent names, namespaces, and template arguments, and adjustsInstantiationHelper’s API and tests accordingly (including the scoped template handling fix). - Adds support for
SmartProjectionRigFactor<gtsam::PinholeCamera<gtsam::Cal3_S2>>across interface fixtures, expected Python (pybind11) and MATLAB wrapper outputs, and the MATLAB wrapper tests. - Fixes minor issues and improves clarity in wrapper generation and build configuration (e.g., docstring extraction in
PybindWrapper,__repr__implementations, andCMakeLists.txtinstall path variable usage).
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test_template_instantiator.py | Updates imports and InstantiationHelper tests to match the refactored helper API and verifies scoped template and instantiation behavior. |
| tests/test_matlab_wrapper.py | Extends MATLAB wrapper tests to expect the new SmartProjectionRigFactorPinholeCameraCal3_S2.m file. |
| tests/fixtures/class.i | Adjusts interface fixture syntax/formatting and adds the SmartProjectionRigFactor templated class used for new instantiations. |
| tests/expected/python/class_pybind.cpp | Adds expected pybind11 bindings for SmartProjectionRigFactor<gtsam::PinholeCamera<gtsam::Cal3_S2>>. |
| tests/expected/matlab/class_wrapper.cpp | Adds expected MATLAB C++ bridge code for SmartProjectionRigFactorPinholeCameraCal3_S2 and reindexes subsequent MyFactor wrappers. |
| tests/expected/matlab/SmartProjectionRigFactorPinholeCameraCal3_S2.m | Introduces the expected MATLAB wrapper class for SmartProjectionRigFactorPinholeCameraCal3_S2, including add overloads. |
| tests/expected/matlab/MyFactorPosePoint2.m | Updates expected MATLAB wrapper indices to align with the new function ID assignments in class_wrapper.cpp. |
| gtwrap/template_instantiator/helpers.py | Refactors instantiation helpers, especially instantiate_type for scoped templates and InstantiationHelper’s API and typing. |
| gtwrap/template_instantiator/function.py | Fixes InstantiatedGlobalFunction.__repr__ to actually call the superclass __repr__() method. |
| gtwrap/template_instantiator/declaration.py | Simplifies fully-qualified name generation for instantiated forward declarations using the new Typename API. |
| gtwrap/template_instantiator/classes.py | Uses InstantiationHelper for constructors/methods and reworks parent-class instantiation and cpp typename construction with the new Typename shape. |
| gtwrap/pybind_wrapper.py | Tightens type hints (e.g., for ArgumentList) and restructures method-wrapping code for clarity, especially docstring injection and print-wrapper handling. |
| gtwrap/interface_parser/type.py | Redefines Typename construction/parse actions, adds helper methods (get_template_args, templated_name), and refactors Type/TemplatedType to use them. |
| gtwrap/interface_parser/function.py | Cleans up pyparsing imports for function parsing. |
| gtwrap/interface_parser/enum.py | Updates enum cpp_typename construction to use the new Typename(name, namespaces) API. |
| CMakeLists.txt | Fixes the GTWRAP_CMAKE_INSTALL_DIR variable to correctly expand ${INSTALL_CMAKE_DIR}. |
Comments suppressed due to low confidence (5)
gtwrap/interface_parser/type.py:196
- This 'lambda' is just a simple wrapper around a callable object. Use that object directly.
).setParseAction(lambda t: Type.from_parse_result(t))
gtwrap/interface_parser/type.py:286
- This 'lambda' is just a simple wrapper around a callable object. Use that object directly.
).setParseAction(lambda t: TemplatedType.from_parse_result(t))
gtwrap/pybind_wrapper.py:463
- Variable module_var is not used.
module_var = instance_name
gtwrap/interface_parser/type.py:344
- This assignment to 'typename' is unnecessary as it is redefined before this value is used.
typename = typename = "{typename}&".format(typename=typename)
gtwrap/interface_parser/type.py:15
- Import of 'Union' is not used.
from typing import List, Sequence, Union
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| new_typename = parser.type.Typename( | ||
| name=str_arg_typename.replace(scoped_template, | ||
| instantiation.templated_name()), | ||
| namespaces=instantiation.namespaces, | ||
| ) |
Copilot
AI
Jan 22, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
instantiate_type constructs a new Typename using parser.type.Typename, but gtwrap.interface_parser's public API does not expose a type submodule (it only re-exports symbols like Typename directly), so parser.type will raise an AttributeError at runtime. Use parser.Typename (or import Typename directly) instead of parser.type.Typename when creating new_typename.
| if isinstance(self.original.parent_class, parser.type.TemplatedType): | ||
| return instantiate_type( | ||
| self.original.parent_class, typenames, self.instantiations, | ||
| parser.Typename(self.namespaces())).typename | ||
| namespaces = self.namespaces() | ||
| typename = parser.Typename(name=namespaces[-1], | ||
| namespaces=namespaces[:-1]) | ||
| return instantiate_type(self.original.parent_class, typenames, |
Copilot
AI
Jan 22, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
instantiate_parent_class checks isinstance(self.original.parent_class, parser.type.TemplatedType), but gtwrap.interface_parser does not define a type attribute, so this branch will never be taken and will likely raise an AttributeError when evaluated. To correctly detect templated parent classes, reference TemplatedType via the public API (e.g. parser.TemplatedType) or import TemplatedType from gtwrap.interface_parser.type explicitly.
| """Get the TemplatedType from the parser results.""" | ||
| return TemplatedType(t.typename, t.template_params, t.is_const, | ||
| t.is_shared_ptr, t.is_ptr, t.is_ref) | ||
| return TemplatedType(t.typename, t.template_params.as_list(), |
Copilot
AI
Jan 22, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TemplatedType.from_parse_result calls t.template_params.as_list(), but pyparsing.ParseResults exposes asList() (capital "L") rather than as_list(), so this will raise an AttributeError during parsing of templated types. Convert the parse results using asList() or an equivalent (e.g. list(t.template_params)) so that the list of Type objects is built correctly.
| return TemplatedType(t.typename, t.template_params.as_list(), | |
| return TemplatedType(t.typename, list(t.template_params), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@varunagrawal I ran copilot review on your last PR here, and it did flag the issue. Gemini recommended to just use the deprecated call asList which is compatible across versions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not correct. Using a deprecated function call is putting a bandage on a fracture and will come back to bite us.
Updating the requirements in the pyproject.toml is the way to go.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. Is that already updated?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is.
Line 29 in 1740473
| "pyparsing>=3.2.5", |
I don't quite understand why Python/Pip is not respecting the requirement. Will look into it a bit more over the weekend after I finish the Hybrid State Estimation paper. :)
| obj = obj@gtsam.SmartProjectionFactorgtsam::PinholeCamera<gtsam::Cal3_S2>(uint64(5139824614673773682), base_ptr); | ||
| obj.ptr_SmartProjectionRigFactorPinholeCameraCal3_S2 = my_ptr; |
Copilot
AI
Jan 22, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The base-class constructor call obj = obj@gtsam.SmartProjectionFactorgtsam::PinholeCamera<gtsam::Cal3_S2>(...) appears malformed: it concatenates SmartProjectionFactor and gtsam::PinholeCamera<...> without a separator and mixes MATLAB dot notation with C++ :: syntax, which does not match the classdef base type on line 7 and is not a valid MATLAB class name. This should be updated to call the appropriate MATLAB base class (or handle superclass) using the same naming scheme as other inheritance wrappers (e.g. HessianFactor and ForwardKinematicsFactor).
| rule = ( | ||
| namespaces_name_rule("namespaces_and_name") # | ||
| ).setParseAction(lambda t: Typename(t)) | ||
| ).setParseAction(lambda t: Typename.from_parse_result(t)) |
Copilot
AI
Jan 22, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This 'lambda' is just a simple wrapper around a callable object. Use that object directly.
| ).setParseAction(lambda t: Typename.from_parse_result(t)) | |
| ).setParseAction(Typename.from_parse_result) |
| ``` | ||
| """ | ||
|
|
||
| rule = (Or(BASIC_TYPES)("typename")).setParseAction(lambda t: BasicType(t)) |
Copilot
AI
Jan 22, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This 'lambda' is just a simple wrapper around a callable object. Use that object directly.
| rule = (Or(BASIC_TYPES)("typename")).setParseAction(lambda t: BasicType(t)) | |
| rule = (Or(BASIC_TYPES)("typename")).setParseAction(BasicType) |
| Here `gtsam::Matrix` is a custom type. | ||
| """ | ||
|
|
||
| rule = (Typename.rule("typename")).setParseAction(lambda t: CustomType(t)) |
Copilot
AI
Jan 22, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This 'lambda' is just a simple wrapper around a callable object. Use that object directly.
| rule = (Typename.rule("typename")).setParseAction(lambda t: CustomType(t)) | |
| rule = (Typename.rule("typename")).setParseAction(CustomType) |
Fixes #175