From eeb15c67173b035e7184e9dc96bf13cc41ed9d2c Mon Sep 17 00:00:00 2001 From: Jonas Rembser Date: Mon, 5 Oct 2026 16:34:15 +0000 Subject: [PATCH] [core] Provide TParameter dictionaries for all arithmetic types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instantiating TParameter for a type without a dictionary (e.g. char) from the interpreter failed with unresolved symbols: IncrementalExecutor::executeFunction: symbol '_ZN10TParameterIcE5ClassEv' unresolved while linking symbol! IncrementalExecutor::executeFunction: symbol '_ZN10TParameterIcE8StreamerER7TBuffer' unresolved while linking symbol! because the interpreter-emitted vtable references the ClassDef-declared Class() and Streamer() members, which are only defined by dictionaries, and only Bool_t, Float_t, Double_t, Int_t, Long_t and Long64_t had one in LinkDef3.h. Add the remaining standard arithmetic types (Char_t, signed char, UChar_t, Short_t, UShort_t, UInt_t, ULong_t and ULong64_t). signed char is a type distinct from char, and is what std::int8_t aliases. Also restore the default `Option_t * = ""` argument on the TParameter overrides of TObject::Print() and TObject::ls(), so that they are callable without arguments through a TParameter*-typed reference (in particular from Python, as in the reproducer of the original report). Fixes https://github.com/root-project/root/issues/10724 🤖 Done with the help of AI --- .../pyroot/pythonizations/test/CMakeLists.txt | 3 +- .../pyroot/pythonizations/test/misc_pyz.py | 57 +++++++++++++++++++ core/base/inc/LinkDef3.h | 8 +++ core/base/inc/TParameter.h | 4 +- 4 files changed, 69 insertions(+), 3 deletions(-) diff --git a/bindings/pyroot/pythonizations/test/CMakeLists.txt b/bindings/pyroot/pythonizations/test/CMakeLists.txt index eec078f5546fd..fa8f90b08e5ea 100644 --- a/bindings/pyroot/pythonizations/test/CMakeLists.txt +++ b/bindings/pyroot/pythonizations/test/CMakeLists.txt @@ -85,7 +85,8 @@ ROOT_ADD_PYUNITTEST(pyroot_pyz_decorator pythonization_decorator.py) # Miscellaneous small pythonizations grouped in a single file to avoid paying the # Python-startup + import-ROOT overhead once per tiny test: TClass::DynamicCast, -# TContext context manager, TComplex operators, TIter iteration and TGraph getters. +# TContext context manager, TComplex operators, TIter iteration, TGraph getters +# and TParameter arithmetic instantiations. ROOT_ADD_PYUNITTEST(pyroot_pyz_misc misc_pyz.py) # RVec and subclasses pythonizations diff --git a/bindings/pyroot/pythonizations/test/misc_pyz.py b/bindings/pyroot/pythonizations/test/misc_pyz.py index d732af025ba59..04fada64fc94c 100644 --- a/bindings/pyroot/pythonizations/test/misc_pyz.py +++ b/bindings/pyroot/pythonizations/test/misc_pyz.py @@ -372,5 +372,62 @@ def test_graphmultierrors(self): self.assertEqual(list(eyhigh), list(aeyh)) +class TParameterTemplateInstantiations(unittest.TestCase): + """ + Instantiating TParameter for a type without a dictionary (e.g. char) + from the interpreter used to fail with unresolved + TParameter::Class() and TParameter::Streamer(TBuffer&) symbols, + see https://github.com/root-project/root/issues/10724. Check that all + standard arithmetic types can be constructed, printed and serialized + from the interpreter. + """ + + # (type name, value passed to the constructor, value expected from GetVal()) + # cppyy maps the character types to Python str, so 42 comes back as "*" + instantiations = [ + ("bool", True, True), + ("char", 42, "*"), + ("signed char", 42, "*"), + ("unsigned char", 42, "*"), + ("short", 42, 42), + ("unsigned short", 42, 42), + ("int", 42, 42), + ("unsigned int", 42, 42), + ("long", 42, 42), + ("unsigned long", 42, 42), + ("long long", 42, 42), + ("unsigned long long", 42, 42), + ("float", 0.5, 0.5), + ("double", 0.5, 0.5), + ] + + def test_construct_and_print(self): + for typename, val, expected in self.instantiations: + with self.subTest(typename=typename): + param = ROOT.TParameter[typename]("p", val) + self.assertEqual(param.GetVal(), expected) + # Print and ls write to std::cout; exercise the code path + # (they used to be uncallable without arguments) + param.Print() + param.ls() + + def test_has_dictionary(self): + for typename, _, _ in self.instantiations: + with self.subTest(typename=typename): + tclass = ROOT.TClass.GetClass(f"TParameter<{typename}>") + self.assertIsNotNone(tclass) + self.assertTrue(tclass.HasDictionary()) + + def test_io_roundtrip(self): + for typename, val, expected in self.instantiations: + with self.subTest(typename=typename): + param = ROOT.TParameter[typename]("p", val) + memfile = ROOT.TMemFile("TParameterTemplateInstantiations.root", "recreate") + memfile.WriteObject(param, "p") + param_read = memfile.Get("p") + self.assertEqual(param_read.GetVal(), expected) + memfile.Close() + + if __name__ == '__main__': unittest.main() diff --git a/core/base/inc/LinkDef3.h b/core/base/inc/LinkDef3.h index 0ff51977efc32..cc8dc78bd7923 100644 --- a/core/base/inc/LinkDef3.h +++ b/core/base/inc/LinkDef3.h @@ -262,10 +262,18 @@ #pragma extra_include "Rtypes.h"; #pragma link C++ class TParameter+; +#pragma link C++ class TParameter+; +#pragma link C++ class TParameter+; +#pragma link C++ class TParameter+; +#pragma link C++ class TParameter+; +#pragma link C++ class TParameter+; #pragma link C++ class TParameter+; #pragma link C++ class TParameter+; #pragma link C++ class TParameter+; +#pragma link C++ class TParameter+; #pragma link C++ class TParameter+; +#pragma link C++ class TParameter+; #pragma link C++ class TParameter+; +#pragma link C++ class TParameter+; #endif diff --git a/core/base/inc/TParameter.h b/core/base/inc/TParameter.h index a6a0edfd2dbf9..f52ae32b33d59 100644 --- a/core/base/inc/TParameter.h +++ b/core/base/inc/TParameter.h @@ -106,14 +106,14 @@ class TParameter : public TObject { return fName.CompareTo(obj->GetName()); } - void ls(Option_t *) const override + void ls(Option_t * = "") const override { // Print this parameter content TROOT::IndentLevel(); std::cout << "OBJ: " << IsA()->GetName() << "\t" << fName << " = " << fVal << std::endl; } - void Print(Option_t *) const override + void Print(Option_t * = "") const override { // Print this parameter content TROOT::IndentLevel();