Skip to content
Open
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
12 changes: 12 additions & 0 deletions ORE-SWIG/QuantExt-SWIG/SWIG/qle_common.i
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@
#include <ql/optional.hpp>
%}

// SWIG registers its smart-pointer machinery under the bare namespace `ext`:
// QuantLib-SWIG's common.i sets SWIG_SHARED_PTR_NAMESPACE to `ext`, and the C++
// alias `namespace ext = QuantLib::ext` it relies on lives inside a %{ %} block
// that the SWIG parser never sees. A declaration spelled
// `QuantLib::ext::shared_ptr<T>` is therefore registered as a type distinct from
// `ext::shared_ptr<T>` and is wrapped as an opaque pointer instead of a proper
// proxy, so a value produced by one spelling cannot be passed to the other -- for
// example OREApp::getCube() to InputParameters::setCube(). Declaring the alias
// where SWIG can see it collapses both spellings onto the same wrapped type.
// See https://github.com/OpenSourceRisk/Engine/issues/351
namespace QuantLib { namespace ext = ::ext; }


#if defined(SWIGPYTHON)
%typemap(in) boost::optional<bool> %{
Expand Down
59 changes: 59 additions & 0 deletions ORE-SWIG/test/test_cube_reuse.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""Regression tests for https://github.com/OpenSourceRisk/Engine/issues/351.

SWIG registers its smart-pointer machinery under the bare namespace ``ext``, so
a declaration spelled ``QuantLib::ext::shared_ptr<T>`` used to be registered as
a type distinct from ``ext::shared_ptr<T>`` and wrapped as an opaque pointer.
Setters written with the qualified spelling then rejected every value the
bindings could produce -- ``InputParameters.setCube`` could not be handed the
cube returned by ``OREApp.getCube``, which made in-memory cube reuse
unreachable from Python.
"""

import unittest

import ORE


class QualifiedSharedPtrSetterTest(unittest.TestCase):
"""Setters declared with the qualified spelling must accept wrapped objects."""

#: Setters that take a qualified ``shared_ptr`` whose argument type can be
#: constructed directly from Python.
ENGINE_DATA_SETTERS = (
"setXvaSensiPricingEngine",
"setParConversionPricingEngine",
"setParStressPricingEngine",
"setZeroToParShiftPricingEngine",
)

def test_engine_data_setters_accept_a_wrapped_engine_data(self) -> None:
"""These rejected every EngineData the bindings could produce."""
params = ORE.InputParameters()
for name in self.ENGINE_DATA_SETTERS:
if not hasattr(params, name):
continue # setter predates this release
with self.subTest(setter=name):
getattr(params, name)(ORE.EngineData())

def test_set_cube_accepts_a_wrapped_cube(self) -> None:
"""The round-trip issue #351 is about: hand a live cube to setCube.

Building the cube needs the in-memory cube classes to be concrete
(issue #354); where they are still abstract the round-trip cannot be
exercised and the test is skipped rather than failed.

setMarketCube is fixed by the same change but stays untested here:
AggregationScenarioData has no constructible subclass in the bindings.
"""
asof = ORE.Date(2, ORE.March, 2026)
dates = [ORE.Date(2, ORE.March, 2027), ORE.Date(2, ORE.March, 2028)]
try:
cube = ORE.DoublePrecisionInMemoryCubeN(asof, {"trade1"}, dates, 4)
except AttributeError:
self.skipTest("in-memory cubes not constructible here (issue #354)")
params = ORE.InputParameters()
params.setCube(cube)


if __name__ == "__main__":
unittest.main()