codemaker/source/netmaker/netproduce.cxx          |   13 
 pyuno/Library_pyuno.mk                            |    1 
 pyuno/source/module/pyuno_except.cxx              |  100 ++++++-
 pyuno/source/module/pyuno_impl.hxx                |    6 
 pyuno/source/module/pyuno_service_constructor.cxx |  290 ++++++++++++++++++++++
 pyuno/source/module/uno.py                        |    2 
 6 files changed, 390 insertions(+), 22 deletions(-)

New commits:
commit 346e0f8891f632e6410dc369564bc56d271e09f3
Author:     Neil Roberts <[email protected]>
AuthorDate: Tue Feb 10 14:07:30 2026 +0100
Commit:     Stephan Bergmann <[email protected]>
CommitDate: Wed Mar 4 13:52:55 2026 +0100

    tdf#171122 pyuno: Handle service constructors
    
    This makes it so that services can be imported in Python with code like
    this:
    
    from com.sun.star.resource import StringResourceWithLocation
    
    And then an instance of the service can be constructed using any of its
    constructors as a class method like this:
    
    res = StringResourceWithLocation.create(ctx, root_url, True, locale,
                                            dlg, '', None)
    
    This has the advantage that the number of arguments can be checked
    properly and a more useful error message can be reported if they are not
    correct. Because pyuno knows what types the arguments are supposed to
    be, it can also do a better job of coercing them to the right ones. For
    example in the example above it is possible to pass None for an
    interface argument even though that normally represents the void type.
    
    This idea evolved out of the discussion for tdf#169229.
    
    Change-Id: I9b542178f1c101d58dcbbd241d4ecbc9fe38bc2c
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/199104
    Tested-by: Jenkins
    Reviewed-by: Stephan Bergmann <[email protected]>

diff --git a/pyuno/Library_pyuno.mk b/pyuno/Library_pyuno.mk
index 65b3855e49b7..4767632e62d1 100644
--- a/pyuno/Library_pyuno.mk
+++ b/pyuno/Library_pyuno.mk
@@ -46,6 +46,7 @@ $(eval $(call gb_Library_add_exception_objects,pyuno,\
     pyuno/source/module/pyuno_adapter \
     pyuno/source/module/pyuno_gc \
     pyuno/source/module/pyuno_iterator \
+    pyuno/source/module/pyuno_service_constructor \
 ))
 
 # vim:set noet sw=4 ts=4:
diff --git a/pyuno/source/module/pyuno_except.cxx 
b/pyuno/source/module/pyuno_except.cxx
index fe55c4bf36cb..c3d66f84b4d5 100644
--- a/pyuno/source/module/pyuno_except.cxx
+++ b/pyuno/source/module/pyuno_except.cxx
@@ -19,6 +19,8 @@
 #include "pyuno_impl.hxx"
 
 #include <typelib/typedescription.hxx>
+#include <com/sun/star/container/XHierarchicalNameAccess.hpp>
+#include <com/sun/star/reflection/XServiceTypeDescription2.hpp>
 #include <com/sun/star/script/CannotConvertException.hpp>
 
 
@@ -70,23 +72,16 @@ void raisePyExceptionWithAny( const css::uno::Any &anyExc )
 }
 
 /// @throws RuntimeException
-static PyRef createClass( const OUString & name, const Runtime &runtime )
+static PyRef createClassFromTypeDescription(
+    std::u16string_view name, typelib_TypeDescription* pType, const Runtime& 
runtime )
 {
-    // assuming that this is never deleted !
-    // note I don't have the knowledge how to initialize these type objects 
correctly !
-    TypeDescription desc( name );
-    if( ! desc.is() )
-    {
-        throw RuntimeException( "pyuno.getClass: uno exception " + name + " is 
unknown" );
-    }
-
-    bool isStruct = desc.get()->eTypeClass == typelib_TypeClass_STRUCT;
-    bool isExc = desc.get()->eTypeClass == typelib_TypeClass_EXCEPTION;
-    bool isInterface = desc.get()->eTypeClass == typelib_TypeClass_INTERFACE;
+    bool isStruct = pType->eTypeClass == typelib_TypeClass_STRUCT;
+    bool isExc = pType->eTypeClass == typelib_TypeClass_EXCEPTION;
+    bool isInterface = pType->eTypeClass == typelib_TypeClass_INTERFACE;
     if( !isStruct  && !isExc && ! isInterface )
     {
-        throw RuntimeException( "pyuno.getClass: " + name + "is a " +
-                    OUString::createFromAscii( typeClassToString( 
static_cast<css::uno::TypeClass>(desc.get()->eTypeClass)) ) +
+        throw RuntimeException( "pyuno.getClass: " + OUString::Concat(name) + 
"is a " +
+                    OUString::createFromAscii( typeClassToString( 
static_cast<css::uno::TypeClass>(pType->eTypeClass)) ) +
                     ", expected EXCEPTION, STRUCT or INTERFACE" );
     }
 
@@ -94,7 +89,7 @@ static PyRef createClass( const OUString & name, const 
Runtime &runtime )
     PyRef base;
     if( isInterface )
     {
-        typelib_InterfaceTypeDescription *pDesc = 
reinterpret_cast<typelib_InterfaceTypeDescription *>(desc.get());
+        typelib_InterfaceTypeDescription *pDesc = 
reinterpret_cast<typelib_InterfaceTypeDescription *>(pType);
         if( pDesc->pBaseTypeDescription )
         {
             base = getClass( pDesc->pBaseTypeDescription->aBase.pTypeName, 
runtime );
@@ -106,7 +101,7 @@ static PyRef createClass( const OUString & name, const 
Runtime &runtime )
     }
     else
     {
-        typelib_CompoundTypeDescription *pDesc = 
reinterpret_cast<typelib_CompoundTypeDescription*>(desc.get());
+        typelib_CompoundTypeDescription *pDesc = 
reinterpret_cast<typelib_CompoundTypeDescription*>(pType);
         if( pDesc->pBaseTypeDescription )
         {
             base = getClass( pDesc->pBaseTypeDescription->aBase.pTypeName, 
runtime );
@@ -183,6 +178,79 @@ static PyRef createClass( const OUString & name, const 
Runtime &runtime )
     return ret;
 }
 
+static PyRef createEmptyPyTypeForTypeDescription(
+    const css::uno::Reference<css::reflection::XTypeDescription>& xType)
+{
+    PyRef ret(
+        PyObject_CallFunctionObjArgs(
+            reinterpret_cast<PyObject*>(&PyType_Type),
+            ustring2PyString(xType->getName()).get(),
+            PyRef(PyTuple_New(0), SAL_NO_ACQUIRE).get(), // bases
+            PyRef(PyDict_New(), SAL_NO_ACQUIRE).get(),
+            nullptr),
+        SAL_NO_ACQUIRE);
+
+    return ret;
+}
+
+static PyRef createClassForService(
+    const css::uno::Reference<css::reflection::XServiceTypeDescription2>& 
xService)
+{
+    PyRef ret = createEmptyPyTypeForTypeDescription(xService);
+
+    // Set an attribute on the class for each of the constructors
+    for (const auto& xConstructor : xService->getConstructors())
+    {
+        OUString sName = xConstructor->getName();
+
+        // The name is empty for the default constructor
+        if (sName.isEmpty())
+        {
+            if (xConstructor->isDefaultConstructor())
+                sName = "create";
+            else
+                continue;
+        }
+
+        PyObject_SetAttr(
+            ret.get(),
+            ustring2PyString(sName).get(),
+            PyUNO_service_constructor_new(xService, xConstructor).get());
+    }
+
+    return ret;
+}
+
+/// @throws RuntimeException
+static PyRef createClass( const OUString & name, const Runtime &runtime )
+{
+    // assuming that this is never deleted !
+    // note I don't have the knowledge how to initialize these type objects 
correctly !
+    TypeDescription desc(name);
+    if (desc.is())
+        return createClassFromTypeDescription(name, desc.get(), runtime);
+
+    // If there’s no type description from the typelib then check if it’s a 
service using the type
+    // description manager.
+    css::uno::Any xType;
+
+    try
+    {
+        xType = runtime.getImpl()->cargo->xTdMgr->getByHierarchicalName(name);
+    }
+    catch (css::container::NoSuchElementException&)
+    {
+        // This will flow through to throw a runtime exception below
+    }
+
+    css::uno::Reference<css::reflection::XServiceTypeDescription2> xService;
+
+    if ((xType >>= xService) && xService.is())
+        return createClassForService(xService);
+
+    throw RuntimeException("pyuno.getClass: uno exception " + name + " is 
unknown");
+}
+
 bool isInstanceOfStructOrException( PyObject *obj)
 {
     PyRef attr(
diff --git a/pyuno/source/module/pyuno_impl.hxx 
b/pyuno/source/module/pyuno_impl.hxx
index 9dab16285ef3..245cecbc7832 100644
--- a/pyuno/source/module/pyuno_impl.hxx
+++ b/pyuno/source/module/pyuno_impl.hxx
@@ -56,6 +56,8 @@ namespace com::sun::star::container { class XEnumeration; }
 namespace com::sun::star::container { class XHierarchicalNameAccess; }
 namespace com::sun::star::lang { class XSingleServiceFactory; }
 namespace com::sun::star::reflection { class XIdlReflection; }
+namespace com::sun::star::reflection { class XServiceConstructorDescription; }
+namespace com::sun::star::reflection { class XServiceTypeDescription2; }
 namespace com::sun::star::script { class XInvocation2; }
 namespace com::sun::star::script { class XInvocationAdapterFactory2; }
 namespace com::sun::star::script { class XTypeConverter; }
@@ -179,6 +181,10 @@ PyRef PyUNO_callable_new (
     const css::uno::Reference<css::script::XInvocation2> &xInv,
     const OUString &methodName );
 
+PyRef PyUNO_service_constructor_new(
+    const css::uno::Reference<css::reflection::XServiceTypeDescription2>& 
xService,
+    const 
css::uno::Reference<css::reflection::XServiceConstructorDescription>& 
xConstructor);
+
 PyObject* PyUNO_Type_new (const char *typeName , css::uno::TypeClass t , const 
Runtime &r );
 PyObject* PyUNO_Enum_new( const char *enumBase, const char *enumValue, const 
Runtime &r );
 PyObject* PyUNO_char_new (sal_Unicode c , const Runtime &r);
diff --git a/pyuno/source/module/pyuno_service_constructor.cxx 
b/pyuno/source/module/pyuno_service_constructor.cxx
new file mode 100644
index 000000000000..6374b7092797
--- /dev/null
+++ b/pyuno/source/module/pyuno_service_constructor.cxx
@@ -0,0 +1,290 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ *   Licensed to the Apache Software Foundation (ASF) under one or more
+ *   contributor license agreements. See the NOTICE file distributed
+ *   with this work for additional information regarding copyright
+ *   ownership. The ASF licenses this file to you under the Apache
+ *   License, Version 2.0 (the "License"); you may not use this file
+ *   except in compliance with the License. You may obtain a copy of
+ *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#include "pyuno_impl.hxx"
+
+#include <vector>
+#include <osl/diagnose.h>
+#include <rtl/ustrbuf.hxx>
+#include <typelib/typedescription.hxx>
+#include <com/sun/star/reflection/XServiceConstructorDescription.hpp>
+#include <com/sun/star/reflection/XServiceTypeDescription2.hpp>
+#include <com/sun/star/script/CannotConvertException.hpp>
+#include <com/sun/star/script/XTypeConverter.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+
+namespace pyuno
+{
+namespace
+{
+struct PyUNO_service_constructor_Internals
+{
+    css::uno::Reference<css::reflection::XServiceTypeDescription2> xService;
+    css::uno::Reference<css::reflection::XServiceConstructorDescription> 
xConstructor;
+    // The parameter types are lazily converted to TypeDescriptions when the 
constructor is first
+    // called.
+    bool bTypesInitialized = false;
+    std::vector<css::uno::TypeDescription> xParamTypes;
+    // Whether the last parameter is a rest parameter
+    bool bHasRest = false;
+
+    void ensureParamTypes();
+};
+
+void PyUNO_service_constructor_Internals::ensureParamTypes()
+{
+    if (bTypesInitialized)
+        return;
+
+    css::uno::Sequence<css::uno::Reference<css::reflection::XParameter>> 
aParams
+        = xConstructor->getParameters();
+
+    for (const auto& xParameter : aParams)
+        xParamTypes.emplace_back(xParameter->getType()->getName());
+
+    bHasRest = aParams.getLength() > 0 && aParams[aParams.getLength() - 
1]->isRestParameter();
+
+    bTypesInitialized = true;
+}
+
+struct PyUNO_service_constructor
+{
+    PyObject_HEAD;
+
+    PyUNO_service_constructor_Internals members;
+};
+
+void PyUNO_service_constructor_dealloc(PyObject* self)
+{
+    PyUNO_service_constructor* me = 
reinterpret_cast<PyUNO_service_constructor*>(self);
+
+    // members was allocated with the placement new operator so we need to 
explicitly call the
+    // destructor
+    me->members.~PyUNO_service_constructor_Internals();
+
+    Py_TYPE(self)->tp_free(self);
+}
+
+PyObject* PyUNO_service_constructor_call(PyObject* self, PyObject* args,
+                                         SAL_UNUSED_PARAMETER PyObject*)
+{
+    PyUNO_service_constructor* me = 
reinterpret_cast<PyUNO_service_constructor*>(self);
+
+    me->members.ensureParamTypes();
+
+    Py_ssize_t nParams = PyTuple_Size(args);
+
+    // context, fixed params
+    sal_Int32 nMinParams = 1 + me->members.xParamTypes.size();
+    sal_Int32 nMaxParams;
+
+    if (me->members.bHasRest)
+    {
+        // The last parameter can be empty
+        nMinParams--;
+        nMaxParams = SAL_MAX_INT32;
+    }
+    else
+        nMaxParams = nMinParams;
+
+    if (nParams < nMinParams || nParams > nMaxParams)
+    {
+        OUString sConstructorName = me->members.xConstructor->getName();
+
+        if (sConstructorName.isEmpty() && 
me->members.xConstructor->isDefaultConstructor())
+            sConstructorName = "create";
+
+        OUStringBuffer buf(me->members.xService->getName() + "::" + 
sConstructorName
+                           + " requires ");
+
+        if (me->members.bHasRest)
+            buf.append("at least ");
+
+        buf.append(OUString::number(nMinParams) + " argument");
+
+        if (nMinParams > 1)
+            buf.append('s');
+
+        PyErr_SetString(
+            PyExc_AttributeError,
+            OUStringToOString(buf.makeStringAndClear(), 
RTL_TEXTENCODING_ASCII_US).getStr());
+
+        return nullptr;
+    }
+
+    try
+    {
+        Runtime runtime;
+        css::uno::Any contextAny = runtime.pyObject2Any(PyTuple_GetItem(args, 
0));
+        css::uno::Reference<css::uno::XComponentContext> xContext;
+
+        if (!(contextAny >>= xContext) || !xContext.is())
+        {
+            PyErr_SetString(PyExc_AttributeError,
+                            "First argument to a service constructor must be 
an XComponentContext");
+            return nullptr;
+        }
+
+        css::uno::Sequence<css::uno::Any> aParams(nParams - 1);
+        css::uno::Any* pParams = aParams.getArray();
+
+        for (sal_Int32 i = 0; i < nParams - 1; ++i)
+        {
+            css::uno::Any param = runtime.pyObject2Any(PyTuple_GetItem(args, i 
+ 1));
+
+            // Use the type of this parameter or the last one if we’re 
building the rest parameters
+            std::size_t nConstructorParam
+                = std::min(std::size_t(i), me->members.xParamTypes.size() - 1);
+            typelib_TypeDescription* pDestType = 
me->members.xParamTypes[nConstructorParam].get();
+
+            // Try to coax the any to the right type. This is needed for 
example to allow passing
+            // None to as an interface reference
+            pParams[i]
+                = runtime.getImpl()->cargo->xTypeConverter->convertTo(param, 
pDestType->pWeakRef);
+        }
+
+        css::uno::Reference<css::uno::XInterface> xInterface
+            = 
xContext->getServiceManager()->createInstanceWithArgumentsAndContext(
+                me->members.xService->getName(), aParams, xContext);
+        return 
runtime.any2PyObject(css::uno::Any(std::move(xInterface))).getAcquired();
+    }
+    catch (const css::reflection::InvocationTargetException& e)
+    {
+        raisePyExceptionWithAny(e.TargetException);
+    }
+    catch (const css::script::CannotConvertException& e)
+    {
+        raisePyExceptionWithAny(css::uno::Any(e));
+    }
+    catch (const css::lang::IllegalArgumentException& e)
+    {
+        raisePyExceptionWithAny(css::uno::Any(e));
+    }
+    catch (const css::uno::RuntimeException& e)
+    {
+        raisePyExceptionWithAny(css::uno::Any(e));
+    }
+
+    return nullptr;
+}
+
+PyTypeObject PyUNO_service_constructor_Type = {
+    PyVarObject_HEAD_INIT(nullptr, 0) "PyUNO_service_constructor",
+    sizeof(PyUNO_service_constructor),
+    0,
+    PyUNO_service_constructor_dealloc,
+#if PY_VERSION_HEX >= 0x03080000
+    0, // Py_ssize_t tp_vectorcall_offset
+#else
+    nullptr, // printfunc tp_print
+#endif
+    nullptr,
+    nullptr,
+    nullptr,
+    nullptr,
+    nullptr,
+    nullptr,
+    nullptr,
+    nullptr,
+    PyUNO_service_constructor_call,
+    nullptr,
+    nullptr,
+    nullptr,
+    nullptr,
+    0,
+    nullptr,
+    nullptr,
+    nullptr,
+    nullptr,
+    0,
+    nullptr,
+    nullptr,
+    nullptr,
+    nullptr,
+    nullptr,
+    nullptr,
+    nullptr,
+    nullptr,
+    nullptr,
+    0,
+    nullptr,
+    nullptr,
+    nullptr,
+    nullptr,
+    nullptr,
+    nullptr,
+    nullptr,
+    nullptr,
+    nullptr,
+    nullptr,
+    nullptr,
+    0
+#if PY_VERSION_HEX >= 0x03040000
+    ,
+    nullptr
+#if PY_VERSION_HEX >= 0x03080000
+    ,
+    nullptr // vectorcallfunc tp_vectorcall
+#if PY_VERSION_HEX < 0x03090000
+#if defined __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+#endif
+    ,
+    nullptr // tp_print
+#if defined __clang__
+#pragma clang diagnostic pop
+#endif
+#endif
+#if PY_VERSION_HEX >= 0x030C00A1
+    ,
+    0 // tp_watched
+#endif
+#if PY_VERSION_HEX >= 0x030D00A4
+    ,
+    0 // tp_versions_used
+#endif
+#endif
+#endif
+};
+}
+
+PyRef PyUNO_service_constructor_new(
+    const css::uno::Reference<css::reflection::XServiceTypeDescription2>& 
xService,
+    const 
css::uno::Reference<css::reflection::XServiceConstructorDescription>& 
xConstructor)
+{
+    OSL_ENSURE(xService.is(), "xService must be valid");
+    OSL_ENSURE(xConstructor.is(), "xConstructor must be valid");
+
+    PyUNO_service_constructor* self
+        = PyObject_New(PyUNO_service_constructor, 
&PyUNO_service_constructor_Type);
+    if (self == nullptr)
+        return nullptr; // NULL == Error!
+
+    // The memory for members was allocated above but we still need to call 
the constructor so we’ll
+    // use a placement new.
+    new (&self->members) PyUNO_service_constructor_Internals;
+
+    self->members.xService = xService;
+    self->members.xConstructor = xConstructor;
+
+    return PyRef(reinterpret_cast<PyObject*>(self), SAL_NO_ACQUIRE);
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/pyuno/source/module/uno.py b/pyuno/source/module/uno.py
index c5b5b25fa3ad..5cb6e6de9385 100644
--- a/pyuno/source/module/uno.py
+++ b/pyuno/source/module/uno.py
@@ -384,7 +384,7 @@ def _uno_import(name, *optargs, **kwargs):
             failed = False
 
             try:
-                # check for structs, exceptions or interfaces
+                # check for structs, exceptions, interfaces or services
                 d[class_name] = pyuno.getClass(name + "." + class_name)
             except RuntimeException:
                 # check for enums
commit 112e72dea2e4286bb289ff14e9d0aecb5aa31d2e
Author:     Neil Roberts <[email protected]>
AuthorDate: Wed Feb 11 10:56:37 2026 +0100
Commit:     Stephan Bergmann <[email protected]>
CommitDate: Wed Mar 4 13:52:42 2026 +0100

    netmaker: Fix generating a service constructor with a rest argument
    
    When a rest argument is used in an IDL, it was previously just
    generating a single Any argument for the constructor and then trying to
    pass that directly to createInstanceWithArgumentsAndContext. That
    doesn’t compile because that method requires an array of Anys for the
    arguments parameter. Instead this patch makes it generate “params Any[]
    rest” which seems to be the .NET way to do variadic arguments.
    
    I don’t think there are any LibreOffice services using constructors with
    rest parameters and presumably no one is using it outside of LO with
    .NET because it wouldn’t compile, so hopefully this patch has little
    risk. Fixing it enables us to use rest parameters in a future unit test
    for pyuno.
    
    Change-Id: I7dbf96f945011db546374ae1d6cf904a5bd262ce
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/199135
    Tested-by: Jenkins
    Reviewed-by: Stephan Bergmann <[email protected]>

diff --git a/codemaker/source/netmaker/netproduce.cxx 
b/codemaker/source/netmaker/netproduce.cxx
index 3eb68c22a7fe..0fe92b1abfa0 100644
--- a/codemaker/source/netmaker/netproduce.cxx
+++ b/codemaker/source/netmaker/netproduce.cxx
@@ -1020,11 +1020,14 @@ void NetProducer::produceService(
                 .append("(com.sun.star.uno.XComponentContext ctx");
             if (!ctor.parameters.empty())
                 file.append(", ");
-            separatedForeach(
-                ctor.parameters, [&file]() { file.append(", "); },
-                [this, &file](const auto& p) {
-                    file.append(getNetName(p.type)).append(" 
").append(getSafeIdentifier(p.name));
-                });
+            separatedForeach(ctor.parameters, [&file]() { file.append(", "); },
+                             [this, &file, restParam](const auto& p) {
+                                 if (&p == restParam)
+                                     file.append("params 
").append(getNetName(p.type)).append("[]");
+                                 else
+                                     file.append(getNetName(p.type));
+                                 file.append(" 
").append(getSafeIdentifier(p.name));
+                             });
             file.append(")").endLine().beginBlock();
 
             file.beginLine()

Reply via email to