Repository.mk | 1 RepositoryModule_host.mk | 1 desktop/Executable_soffice_bin.mk | 1 rust_uno/.gitignore | 3 rust_uno/Cargo.toml | 20 rust_uno/CustomTarget_cargo.mk | 18 rust_uno/CustomTarget_rustmaker.mk | 31 + rust_uno/Extension_rust_uno-example.mk | 26 rust_uno/Library_rust_uno-cpp.mk | 41 + rust_uno/Library_rust_uno-example.mk | 37 + rust_uno/Makefile | 14 rust_uno/Module_rust_uno.mk | 22 rust_uno/build.rs | 39 + rust_uno/example/Addons.xcu | 43 + rust_uno/example/META-INF/manifest.xml | 28 rust_uno/example/ProtocolHandler.xcu | 29 rust_uno/example/description.xml | 26 rust_uno/example/example.component | 28 rust_uno/example/example.cxx | 243 ++++++++ rust_uno/example/rust_uno_hook.hxx | 17 rust_uno/src/core/any.rs | 257 ++++++++ rust_uno/src/core/mod.rs | 37 + rust_uno/src/core/oustring.rs | 256 ++++++++ rust_uno/src/core/sequence.rs | 245 ++++++++ rust_uno/src/core/tests/string_tests.rs | 387 +++++++++++++ rust_uno/src/core/tests/type_tests.rs | 520 +++++++++++++++++ rust_uno/src/core/type.rs | 328 +++++++++++ rust_uno/src/core/uno_wrapper.rs | 59 + rust_uno/src/examples/any_example.rs | 158 +++++ rust_uno/src/examples/basic_example.rs | 120 ++++ rust_uno/src/examples/load_writer.rs | 124 ++++ rust_uno/src/examples/mod.rs | 41 + rust_uno/src/examples/string_example.rs | 131 ++++ rust_uno/src/examples/type_example.rs | 324 ++++++++++ rust_uno/src/ffi/mod.rs | 27 rust_uno/src/ffi/rtl_string.rs | 87 ++ rust_uno/src/ffi/sal_types.rs | 50 + rust_uno/src/ffi/tests/integration_tests.rs | 828 ++++++++++++++++++++++++++++ rust_uno/src/ffi/type_ffi.rs | 173 +++++ rust_uno/src/ffi/uno_any.rs | 108 +++ rust_uno/src/ffi/uno_bridge.rs | 37 + rust_uno/src/ffi/uno_sequence.rs | 54 + rust_uno/src/lib.rs | 24 rust_uno/uno_bootstrap.cxx | 86 ++ 44 files changed, 5129 insertions(+)
New commits: commit 3d75577640a345327122d8a89a2206831eba4019 Author: Mohamed Ali <[email protected]> AuthorDate: Thu Jun 12 18:52:45 2025 +0300 Commit: Stephan Bergmann <[email protected]> CommitDate: Thu Sep 11 21:39:08 2025 +0200 Rust Bindings: Add extension-based UNO integration with FFI architecture Implements LibreOffice extension providing Rust language binding for UNO API using opaque pointer FFI architecture with type-safe handles. Extension Features: - User-controlled execution via "Rust UNO → Example" menu - Optional installation/removal through LibreOffice extension system - Complete Writer document manipulation example Technical Implementation: - Generated Rust wrappers for all UNO types via rustmaker - C++ extern "C" bridges with proper UNO integration - Type-safe opaque handles (XTextDocumentHandle, etc.) - Extension architecture following LibreOffice standards - Menu integration via Addons.xcu and ProtocolHandler.xcu Architecture: - Rust RAII Wrappers ↔ C++ extern "C" Bridges ↔ LibreOffice UNO - Extension packaging with rust_uno-example.oxt - Integrated build system with CustomTarget and Library makefiles Build Commands: ./configure --enable-rust-uno Extension Structure: - rust_uno/example/ - Extension source files - workdir/Extension/rust_uno-example.oxt - Packaged extension - Automatic installation during LibreOffice build Runtime Validation: Successfully demonstrates complete UNO integration: - Desktop service creation and XComponentLoader interface - Writer document loading via loadComponentFromURL - Text manipulation through XTextDocument and XSimpleText - Proper interface querying and method invocation GSoC 2025: Rust UNO Language Binding Change-Id: I5cecb8ebd05aab396f444488c0ee2a9d483a9f62 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/186425 Tested-by: Jenkins Reviewed-by: Stephan Bergmann <[email protected]> diff --git a/Repository.mk b/Repository.mk index 25f8e9213085..3f5d69b0ddc4 100644 --- a/Repository.mk +++ b/Repository.mk @@ -779,6 +779,7 @@ $(eval $(call gb_Helper_register_libraries,EXTENSIONLIBS, \ active_native \ passive_native \ crashextension \ + rust_uno-example \ )) ifneq ($(ENABLE_JAVA),) diff --git a/RepositoryModule_host.mk b/RepositoryModule_host.mk index 8d23ee960f68..bb5add564c0c 100644 --- a/RepositoryModule_host.mk +++ b/RepositoryModule_host.mk @@ -126,6 +126,7 @@ $(eval $(call gb_Module_add_moduledirs,libreoffice,\ reportbuilder \ $(call gb_Helper_optional,DBCONNECTIVITY,reportdesign) \ ridljar \ + rust_uno \ sal \ salhelper \ sax \ diff --git a/desktop/Executable_soffice_bin.mk b/desktop/Executable_soffice_bin.mk index 5838572d16fa..5987a9a4d456 100644 --- a/desktop/Executable_soffice_bin.mk +++ b/desktop/Executable_soffice_bin.mk @@ -19,6 +19,7 @@ $(eval $(call gb_Executable_add_defs,soffice_bin,\ )) $(eval $(call gb_Executable_use_libraries,soffice_bin,\ + $(if $(ENABLE_RUST_UNO),rust_uno-cpp) \ sal \ sofficeapp \ )) diff --git a/rust_uno/.gitignore b/rust_uno/.gitignore new file mode 100644 index 000000000000..310213a05bc3 --- /dev/null +++ b/rust_uno/.gitignore @@ -0,0 +1,3 @@ +/target +Cargo.lock +generated/ diff --git a/rust_uno/Cargo.toml b/rust_uno/Cargo.toml new file mode 100644 index 000000000000..f54f9bc3c85e --- /dev/null +++ b/rust_uno/Cargo.toml @@ -0,0 +1,20 @@ +# -*- Mode: toml; tab-width: 4; indent-tabs-mode: nil; fill-column: 100 -*- +# +# 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/. +# + +[package] +name = "rust_uno" +version = "0.1.0" +edition = "2024" +description = "Rust FFI binding for LibreOffice UNO API" + +[lib] +name = "rust_uno" +crate-type = ["cdylib"] + +# vim: set noet sw=4 ts=4: diff --git a/rust_uno/CustomTarget_cargo.mk b/rust_uno/CustomTarget_cargo.mk new file mode 100644 index 000000000000..d27ec0b0797a --- /dev/null +++ b/rust_uno/CustomTarget_cargo.mk @@ -0,0 +1,18 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t; fill-column: 100 -*- +# +# 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/. +# + +$(eval $(call gb_CustomTarget_CustomTarget,rust_uno/cargo)) + +.PHONY: $(call gb_CustomTarget_get_target,rust_uno/cargo) +$(call gb_CustomTarget_get_target,rust_uno/cargo): \ + $(call gb_Library_get_target,rust_uno-cpp) \ + $(gb_CustomTarget_workdir)/rust_uno/rustmaker/cpp + cd $(SRCDIR)/rust_uno && cargo build $(if $(verbose),--verbose,) --release + +# vim: set noet sw=4 ts=4: diff --git a/rust_uno/CustomTarget_rustmaker.mk b/rust_uno/CustomTarget_rustmaker.mk new file mode 100644 index 000000000000..b519be3c627c --- /dev/null +++ b/rust_uno/CustomTarget_rustmaker.mk @@ -0,0 +1,31 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t; fill-column: 100 -*- +# +# 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/. +# + +$(eval $(call gb_CustomTarget_CustomTarget,rust_uno/rustmaker)) + +$(call gb_CustomTarget_get_target,rust_uno/rustmaker): \ + $(gb_CustomTarget_workdir)/rust_uno/rustmaker/cpp + +$(gb_CustomTarget_workdir)/rust_uno/rustmaker/cpp: \ + $(call gb_Executable_get_target,rustmaker) \ + $(call gb_Executable_get_runtime_dependencies,rustmaker) \ + $(call gb_UnoApi_get_target,offapi) \ + $(call gb_UnoApi_get_target,udkapi) \ + $(gb_CustomTarget_workdir)/rust_uno/rustmaker/.dir + rm -fr $(SRCDIR)/rust_uno/src/generated + mkdir $(SRCDIR)/rust_uno/src/generated + rm -fr $(gb_CustomTarget_workdir)/rust_uno/rustmaker/cpp + mkdir $(gb_CustomTarget_workdir)/rust_uno/rustmaker/cpp + $(call gb_Helper_abbreviate_dirs, \ + $(call gb_Helper_execute,rustmaker $(if $(verbose),--verbose,) -Ocpp $(gb_CustomTarget_workdir)/rust_uno/rustmaker/cpp \ + $(if $(verbose),--verbose,) -Orust $(SRCDIR)/rust_uno/src/generated \ + $(call gb_UnoApi_get_target,offapi) $(call gb_UnoApi_get_target,udkapi))) + touch $@ + +# vim: set noet sw=4 ts=4: diff --git a/rust_uno/Extension_rust_uno-example.mk b/rust_uno/Extension_rust_uno-example.mk new file mode 100644 index 000000000000..fea1677e1966 --- /dev/null +++ b/rust_uno/Extension_rust_uno-example.mk @@ -0,0 +1,26 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t; fill-column: 100 -*- +# +# 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/. +# + +$(eval $(call gb_Extension_Extension,rust_uno-example,rust_uno/example,nodeliver)) + +$(eval $(call gb_Extension_add_file,rust_uno-example,platform.components,$(call gb_Rdb_get_target,rust_uno-example))) + +$(eval $(call gb_Extension_add_files,rust_uno-example,, \ + $(SRCDIR)/rust_uno/example/Addons.xcu \ + $(SRCDIR)/rust_uno/example/ProtocolHandler.xcu \ + $(SRCDIR)/rust_uno/target/release/librust_uno.so \ +)) + +$(eval $(call gb_Extension_add_libraries,rust_uno-example, \ + rust_uno-example \ +)) + +$(SRCDIR)/rust_uno/target/release/librust_uno.so: $(call gb_CustomTarget_get_target,rust_uno/cargo) + +# vim: set noet sw=4 ts=4: diff --git a/rust_uno/Library_rust_uno-cpp.mk b/rust_uno/Library_rust_uno-cpp.mk new file mode 100644 index 000000000000..4a1875015b7f --- /dev/null +++ b/rust_uno/Library_rust_uno-cpp.mk @@ -0,0 +1,41 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t; fill-column: 100 -*- +# +# 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/. +# + +$(eval $(call gb_Library_Library,rust_uno-cpp)) + +$(eval $(call gb_Library_use_libraries,rust_uno-cpp, \ + cppu \ + cppuhelper \ + sal \ +)) + +$(eval $(call gb_Library_use_sdk_api,rust_uno-cpp)) + +# Add the uno_bootstrap.cxx file for bootstrap functionality +$(eval $(call gb_Library_add_exception_objects,rust_uno-cpp, \ + rust_uno/uno_bootstrap \ +)) + +# Combined generated files instead of thousands of individual files +rust_uno_generated_cxx = \ + rust_uno_bindings + +define rust_uno_add_generated_cxx +$(gb_CustomTarget_workdir)/rust_uno/rustmaker/cpp/$(1).cxx: \ + $(call gb_CustomTarget_get_target,rust_uno/rustmaker) + +$(eval $(call gb_Library_add_generated_exception_objects,rust_uno-cpp, \ + CustomTarget/rust_uno/rustmaker/cpp/$(1) \ +)) + +endef + +$(foreach gencxx,$(rust_uno_generated_cxx),$(eval $(call rust_uno_add_generated_cxx,$(gencxx)))) + +# vim: set noet sw=4 ts=4: diff --git a/rust_uno/Library_rust_uno-example.mk b/rust_uno/Library_rust_uno-example.mk new file mode 100644 index 000000000000..bee03c23c3b6 --- /dev/null +++ b/rust_uno/Library_rust_uno-example.mk @@ -0,0 +1,37 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t; fill-column: 100 -*- +# +# 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/. +# + +$(eval $(call gb_Library_Library,rust_uno-example)) + +$(eval $(call gb_Library_add_exception_objects,rust_uno-example, \ + rust_uno/example/example \ +)) + +$(eval $(call gb_Library_set_componentfile,rust_uno-example,rust_uno/example/example,rust_uno-example)) + +$(eval $(call gb_Library_set_external_code,rust_uno-example)) + +$(eval $(call gb_Library_use_externals,rust_uno-example, \ + boost_headers \ +)) + +$(eval $(call gb_Library_use_libraries,rust_uno-example, \ + cppu \ + cppuhelper \ + sal \ +)) + +$(eval $(call gb_Library_use_sdk_api,rust_uno-example)) + +$(call gb_Library_get_target,rust_uno-example): $(call gb_CustomTarget_get_target,rust_uno/cargo) +$(eval $(call gb_Library_add_libs,rust_uno-example,\ + $(SRCDIR)/rust_uno/target/release/librust_uno.so \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/rust_uno/Makefile b/rust_uno/Makefile new file mode 100644 index 000000000000..d5aa252aa8c3 --- /dev/null +++ b/rust_uno/Makefile @@ -0,0 +1,14 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t; fill-column: 100 -*- +# +# 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/. +# + +module_directory:=$(dir $(realpath $(firstword $(MAKEFILE_LIST)))) + +include $(module_directory)/../solenv/gbuild/partial_build.mk + +# vim: set noet sw=4 ts=4: diff --git a/rust_uno/Module_rust_uno.mk b/rust_uno/Module_rust_uno.mk new file mode 100644 index 000000000000..ab6fa695a0cc --- /dev/null +++ b/rust_uno/Module_rust_uno.mk @@ -0,0 +1,22 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t; fill-column: 100 -*- +# +# 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/. +# + +$(eval $(call gb_Module_Module,rust_uno)) + +ifeq ($(ENABLE_RUST_UNO),TRUE) +$(eval $(call gb_Module_add_targets,rust_uno, \ + CustomTarget_cargo \ + CustomTarget_rustmaker \ + Extension_rust_uno-example \ + Library_rust_uno-cpp \ + Library_rust_uno-example \ +)) +endif + +# vim: set noet sw=4 ts=4: diff --git a/rust_uno/build.rs b/rust_uno/build.rs new file mode 100644 index 000000000000..5d25a125b4fc --- /dev/null +++ b/rust_uno/build.rs @@ -0,0 +1,39 @@ +/* -*- Mode: rust; 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/. + */ + +//! Build script for rust_uno crate +//! +//! Links the generated C++ bridge library (librust_uno-cpp.so) to the Rust cdylib + +fn main() { + // Tell cargo to link against the rust_uno-cpp library + // This library contains the generated C++ bridge functions + println!("cargo:rustc-link-lib=rust_uno-cpplo"); + + // Add the LibreOffice instdir/program directory to the library search path + // This is where librust_uno-cpplo.so is located + if let Ok(instdir) = std::env::var("INSTDIR") { + println!("cargo:rustc-link-search=native={}/program", instdir); + } + + // Also try the workdir path where the library might be during build + if let Ok(workdir) = std::env::var("WORKDIR") { + println!( + "cargo:rustc-link-search=native={}/LinkTarget/Library", + workdir + ); + } + + // Fallback: try relative paths from the rust_uno directory + println!("cargo:rustc-link-search=native=../instdir/program"); + println!("cargo:rustc-link-search=native=../workdir/LinkTarget/Library"); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ + diff --git a/rust_uno/example/Addons.xcu b/rust_uno/example/Addons.xcu new file mode 100644 index 000000000000..9d906a2a4e9f --- /dev/null +++ b/rust_uno/example/Addons.xcu @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<o:items xmlns:o="http://openoffice.org/2001/registry"> + <item o:path="/org.openoffice.Office.Addons"> + <node o:name="AddonUI"> + <node o:name="OfficeMenuBar"> + <node o:name="org.libreoffice.rust_uno.example" + o:op="replace"> + <prop o:name="Title" xml:lang="en-US"> + <value>Rust UNO</value> + </prop> + <node o:name="Submenu"> + <node o:name="1" o:op="replace"> + <prop o:name="URL"> + <value>vnd.org.libreoffice.rust_uno.example:</value> + </prop> + <prop o:name="Title" xml:lang="en-US"> + <value>Example</value> + </prop> + </node> + </node> + </node> + </node> + </node> + </item> +</o:items> diff --git a/rust_uno/example/META-INF/manifest.xml b/rust_uno/example/META-INF/manifest.xml new file mode 100644 index 000000000000..51af7893f844 --- /dev/null +++ b/rust_uno/example/META-INF/manifest.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<m:manifest xmlns:m="http://openoffice.org/2001/manifest"> + <m:file-entry m:media-type="application/vnd.sun.star.configuration-data" + m:full-path="Addons.xcu"/> + <m:file-entry m:media-type="application/vnd.sun.star.configuration-data" + m:full-path="ProtocolHandler.xcu"/> + <m:file-entry + m:media-type="application/vnd.sun.star.uno-components;platform=@PLATFORM@" + m:full-path="platform.components"/> +</m:manifest> diff --git a/rust_uno/example/ProtocolHandler.xcu b/rust_uno/example/ProtocolHandler.xcu new file mode 100644 index 000000000000..2d205f7f878f --- /dev/null +++ b/rust_uno/example/ProtocolHandler.xcu @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<o:component-data xmlns:o="http://openoffice.org/2001/registry" + o:package="org.openoffice.Office" o:name="ProtocolHandler"> + <node o:name="HandlerSet"> + <node o:name="org.libreoffice.rust_uno.example" o:op="replace"> + <prop o:name="Protocols"> + <value>vnd.org.libreoffice.rust_uno.example:*</value> + </prop> + </node> + </node> +</o:component-data> diff --git a/rust_uno/example/description.xml b/rust_uno/example/description.xml new file mode 100644 index 000000000000..526680a84dbd --- /dev/null +++ b/rust_uno/example/description.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<d:description xmlns:d="http://openoffice.org/extensions/description/2006"> + <d:identifier value="org.libreoffice/rust_uno/example"/> + <d:version value="1"/> + <d:dependencies> + <d:OpenOffice.org-minimal-version d:name="OpenOffice.org 3.4" value="3.4"/> + </d:dependencies> +</d:description> diff --git a/rust_uno/example/example.component b/rust_uno/example/example.component new file mode 100644 index 000000000000..da779dac84c0 --- /dev/null +++ b/rust_uno/example/example.component @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="org.libreoffice.comp.rust_uno.example"> + <service name="org.libreoffice.rust_uno.example"/> + </implementation> + <implementation name="org.libreoffice.comp.rust_uno.example_singleton"> + <singleton name="org.libreoffice.rust_uno.example_singleton"/> + </implementation> +</component> diff --git a/rust_uno/example/example.cxx b/rust_uno/example/example.cxx new file mode 100644 index 000000000000..5fc0279281d5 --- /dev/null +++ b/rust_uno/example/example.cxx @@ -0,0 +1,243 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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 <sal/config.h> + +#include <cassert> + +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/frame/DispatchDescriptor.hpp> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/frame/XDispatch.hpp> +#include <com/sun/star/frame/XDispatchProvider.hpp> +#include <com/sun/star/frame/XStatusListener.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/uno/DeploymentException.hpp> +#include <com/sun/star/uno/Exception.hpp> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/RuntimeException.hpp> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/uno/XInterface.hpp> +#include <com/sun/star/util/URL.hpp> +#include <cppuhelper/factory.hxx> +#include <cppuhelper/implbase2.hxx> +#include <cppuhelper/implementationentry.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <cppuhelper/weak.hxx> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <sal/types.h> +#include <uno/lbnames.h> + +#include "rust_uno_hook.hxx" + +namespace +{ +class Provider + : public cppu::WeakImplHelper2<css::lang::XServiceInfo, css::frame::XDispatchProvider> +{ +public: + Provider(const Provider&) = delete; + const Provider& operator=(const Provider&) = delete; + + static css::uno::Reference<css::uno::XInterface> + SAL_CALL static_create(css::uno::Reference<css::uno::XComponentContext> const& xContext) + { + return static_cast<cppu::OWeakObject*>(new Provider(xContext)); + } + + static rtl::OUString SAL_CALL static_getImplementationName(); + + static css::uno::Sequence<rtl::OUString> SAL_CALL static_getSupportedServiceNames(); + +private: + explicit Provider(css::uno::Reference<css::uno::XComponentContext> const& context) + : context_(context) + { + assert(context.is()); + } + + virtual ~Provider() {} + + virtual rtl::OUString SAL_CALL getImplementationName() override + { + return static_getImplementationName(); + } + + virtual sal_Bool SAL_CALL supportsService(rtl::OUString const& ServiceName) override + { + return cppu::supportsService(this, ServiceName); + } + + virtual css::uno::Sequence<rtl::OUString> SAL_CALL getSupportedServiceNames() override + { + return static_getSupportedServiceNames(); + } + + virtual css::uno::Reference<css::frame::XDispatch> + SAL_CALL queryDispatch(css::util::URL const&, rtl::OUString const&, sal_Int32) override; + + virtual css::uno::Sequence<css::uno::Reference<css::frame::XDispatch>> SAL_CALL + queryDispatches(css::uno::Sequence<css::frame::DispatchDescriptor> const& Requests) override; + + css::uno::Reference<css::uno::XComponentContext> context_; +}; + +rtl::OUString Provider::static_getImplementationName() +{ + return rtl::OUString("org.libreoffice.comp.rust_uno.example"); +} + +css::uno::Sequence<rtl::OUString> Provider::static_getSupportedServiceNames() +{ + rtl::OUString name("org.libreoffice.rust_uno.example"); + return css::uno::Sequence<rtl::OUString>(&name, 1); +} + +css::uno::Reference<css::frame::XDispatch> Provider::queryDispatch(css::util::URL const&, + rtl::OUString const&, sal_Int32) +{ + css::uno::Reference<css::frame::XDispatch> dispatch; + if (!(context_->getValueByName("/singletons/org.libreoffice.rust_uno.example_singleton") + >>= dispatch) + || !dispatch.is()) + { + throw css::uno::DeploymentException("component context fails to supply singleton " + "org.libreoffice.rust_uno.example_singleton of type " + "com.sun.star.frame.XDispatch", + context_); + } + return dispatch; +} + +css::uno::Sequence<css::uno::Reference<css::frame::XDispatch>> +Provider::queryDispatches(css::uno::Sequence<css::frame::DispatchDescriptor> const& Requests) +{ + css::uno::Sequence<css::uno::Reference<css::frame::XDispatch>> s(Requests.getLength()); + for (sal_Int32 i = 0; i < s.getLength(); ++i) + { + s[i] + = queryDispatch(Requests[i].FeatureURL, Requests[i].FrameName, Requests[i].SearchFlags); + } + return s; +} + +class Dispatch : public cppu::WeakImplHelper2<css::lang::XServiceInfo, css::frame::XDispatch> +{ +public: + Dispatch(const Dispatch&) = delete; + const Dispatch& operator=(const Dispatch&) = delete; + + static css::uno::Reference<css::uno::XInterface> + SAL_CALL static_create(css::uno::Reference<css::uno::XComponentContext> const& xContext) + { + return static_cast<cppu::OWeakObject*>(new Dispatch(xContext)); + } + + static rtl::OUString SAL_CALL static_getImplementationName(); + + static css::uno::Sequence<rtl::OUString> SAL_CALL static_getSupportedServiceNames() + { + return css::uno::Sequence<rtl::OUString>(); + } + +private: + explicit Dispatch(css::uno::Reference<css::uno::XComponentContext> const& context) + : context_(context) + { + assert(context.is()); + } + + virtual ~Dispatch() {} + + virtual rtl::OUString SAL_CALL getImplementationName() override + { + return static_getImplementationName(); + } + + virtual sal_Bool SAL_CALL supportsService(rtl::OUString const& ServiceName) override + { + return cppu::supportsService(this, ServiceName); + } + + virtual css::uno::Sequence<rtl::OUString> SAL_CALL getSupportedServiceNames() override + { + return static_getSupportedServiceNames(); + } + + virtual void SAL_CALL dispatch(css::util::URL const&, + css::uno::Sequence<css::beans::PropertyValue> const&) override; + + virtual void SAL_CALL addStatusListener(css::uno::Reference<css::frame::XStatusListener> const&, + css::util::URL const&) override + { + } + + virtual void SAL_CALL removeStatusListener( + css::uno::Reference<css::frame::XStatusListener> const&, css::util::URL const&) override + { + } + + css::uno::Reference<css::uno::XComponentContext> context_; +}; + +rtl::OUString Dispatch::static_getImplementationName() +{ + return rtl::OUString("org.libreoffice.comp.rust_uno.example_singleton"); +} + +void Dispatch::dispatch(css::util::URL const&, css::uno::Sequence<css::beans::PropertyValue> const&) +{ + // === RUST UNO BINDING TEST === + // Call our Rust UNO binding to test it works within LibreOffice + SAL_INFO("desktop.app", "Testing Rust UNO binding..."); + try + { + run_rust_uno_test(); + SAL_INFO("desktop.app", "Rust UNO binding test completed successfully!"); + } + catch (...) + { + SAL_WARN("desktop.app", "Rust UNO binding test failed!"); + } +} + +cppu::ImplementationEntry const services[] = { + { &Provider::static_create, &Provider::static_getImplementationName, + &Provider::static_getSupportedServiceNames, &cppu::createSingleComponentFactory, nullptr, 0 }, + { &Dispatch::static_create, &Dispatch::static_getImplementationName, + &Dispatch::static_getSupportedServiceNames, &cppu::createSingleComponentFactory, nullptr, 0 }, + { nullptr, nullptr, nullptr, nullptr, nullptr, 0 } +}; +} + +extern "C" SAL_DLLPUBLIC_EXPORT void* +component_getFactory(char const* pImplName, void* pServiceManager, void* pRegistryKey) +{ + return cppu::component_getFactoryHelper(pImplName, pServiceManager, pRegistryKey, services); +} + +extern "C" SAL_DLLPUBLIC_EXPORT void +component_getImplementationEnvironment(char const** ppEnvTypeName, uno_Environment**) +{ + *ppEnvTypeName = CPPU_CURRENT_LANGUAGE_BINDING_NAME; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/rust_uno/example/rust_uno_hook.hxx b/rust_uno/example/rust_uno_hook.hxx new file mode 100644 index 000000000000..8987ffeb8b4f --- /dev/null +++ b/rust_uno/example/rust_uno_hook.hxx @@ -0,0 +1,17 @@ +/* -*- 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/. + */ + +#pragma once + +extern "C" { +/// Function from the Rust UNO binding library that we'll call from LibreOffice +void run_rust_uno_test(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/rust_uno/src/core/any.rs b/rust_uno/src/core/any.rs new file mode 100644 index 000000000000..7e307239720b --- /dev/null +++ b/rust_uno/src/core/any.rs @@ -0,0 +1,257 @@ +/* -*- Mode: rust; 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/. + */ + +//! UNO Any Types and Functions +//! +//! This module provides the high-level safe Rust wrapper around LibreOffice's uno_Any. +//! It includes automatic memory management and safe conversions to/from Rust values. +//! +//! ## Key Components +//! - `Any` - Safe Rust wrapper around LibreOffice's uno_Any +//! - Value conversion functions with automatic type handling +//! - Memory management with automatic cleanup +//! +//! ## Features +//! - Safe construction from various Rust types +//! - Automatic memory management via RAII +//! - Safe value extraction with type checking +//! - Clone, equality, and display trait implementations +//! - Direct FFI interop with raw pointer access methods + +use crate::ffi::uno_any::*; +// Interface operations now use generated XInterface wrapper methods +// TODO: Replace with generated XInterface::from_ptr(ptr).acquire() pattern + +// === Safe UNO Any Wrapper === + +/// UNO Any wrapper - a safe Rust wrapper around LibreOffice's uno_Any +/// +/// Any provides a safe, memory-managed interface to LibreOffice's native +/// any type. It automatically handles reference counting and memory cleanup. +pub struct Any { + inner: uno_Any, +} + +impl Any { + /// Create a new empty UNO Any (void type) + /// + /// This creates an empty Any that contains no value (void type). + pub fn new() -> Self { + unsafe { + let mut any = Any { + inner: uno_Any { + pType: std::ptr::null_mut(), + pData: std::ptr::null_mut(), + pReserved: std::ptr::null_mut(), + }, + }; + + // Initialize as void using uno_any_construct + uno_any_construct( + &mut any.inner, + std::ptr::null_mut(), // pSource (null for void) + std::ptr::null_mut(), // pTypeDescr (null for void) + None, + ); + any + } + } + + /// Create Any from a boolean value + /// + /// This creates an Any containing a boolean value. + pub fn from_bool(value: bool) -> Self { + unsafe { + let mut any = Any { + inner: uno_Any { + pType: std::ptr::null_mut(), + pData: std::ptr::null_mut(), + pReserved: std::ptr::null_mut(), + }, + }; + + let bool_val = if value { 1u8 } else { 0u8 }; + + // Initialize with boolean data using uno_type_any_construct + uno_type_any_construct( + &mut any.inner, + &bool_val as *const u8 as *mut std::ffi::c_void, + get_boolean_type(), + None, + ); + any + } + } + + /// Create Any from a 32-bit integer + /// + /// This creates an Any containing a 32-bit signed integer. + pub fn from_i32(value: i32) -> Self { + unsafe { + let mut any = Any { + inner: uno_Any { + pType: std::ptr::null_mut(), + pData: std::ptr::null_mut(), + pReserved: std::ptr::null_mut(), + }, + }; + + // Initialize with integer data using uno_type_any_construct + uno_type_any_construct( + &mut any.inner, + &value as *const i32 as *mut std::ffi::c_void, + get_long_type(), + None, + ); + + any + } + } + + /// Create Any from raw uno_Any (takes ownership) + /// + /// # Safety + /// The caller must ensure that: + /// - The uno_Any is valid and properly initialized + /// - The uno_Any is not used elsewhere after this call + /// - Proper reference counting is maintained + pub unsafe fn from_raw(any: uno_Any) -> Self { + Any { inner: any } + } + + /// Convert to raw uno_Any (releases ownership) + /// + /// The caller becomes responsible for calling uno_any_destruct + pub fn into_raw(self) -> uno_Any { + unsafe { + let inner = std::ptr::read(&self.inner); + std::mem::forget(self); // Don't run destructor + inner + } + } + + /// Get the raw pointer for FFI calls (retains ownership) + pub fn as_ptr(&self) -> *const uno_Any { + &self.inner + } + + /// Get the mutable raw pointer for FFI calls (retains ownership) + pub fn as_mut_ptr(&mut self) -> *mut uno_Any { + &mut self.inner + } + + /// Check if the Any contains a value (not void) + pub fn has_value(&self) -> bool { + !self.inner.pType.is_null() + } + + /// Clear the Any (set to void) + pub fn clear(&mut self) { + unsafe { + uno_any_clear(&mut self.inner, None); + } + } +} + +impl Default for Any { + fn default() -> Self { + Self::new() + } +} + +/// Clone implementation: Create a copy of another Any +/// +/// This creates a new Any that contains the same value but is independently +/// managed (proper deep copy with reference counting). +impl Clone for Any { + fn clone(&self) -> Self { + let mut new_any = Any::new(); + + if self.has_value() { + unsafe { + // Clone by assigning from the existing any's data using type reference + uno_type_any_assign( + &mut new_any.inner, + self.inner.pData, + self.inner.pType, + None, + None, + ); + } + } + + new_any + } +} + +/// Comparison with other Any +impl PartialEq for Any { + fn eq(&self, other: &Self) -> bool { + // Handle cases where one or both are void + match (self.has_value(), other.has_value()) { + (false, false) => true, // Both void = equal + (false, true) | (true, false) => false, // One void, one not = not equal + (true, true) => { + // Both have values - basic pointer comparison for now + self.inner.pType == other.inner.pType && self.inner.pData == other.inner.pData + } + } + } +} + +impl Eq for Any {} + +/// From trait implementation for bool +impl From<bool> for Any { + fn from(value: bool) -> Self { + Self::from_bool(value) + } +} + +/// From trait implementation for i32 +impl From<i32> for Any { + fn from(value: i32) -> Self { + Self::from_i32(value) + } +} + +/// Display trait for printing +impl std::fmt::Display for Any { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if !self.has_value() { + write!(f, "Any(void)") + } else { + write!(f, "Any(<value>)") + } + } +} + +/// Debug trait for debugging +impl std::fmt::Debug for Any { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Any") + .field("has_value", &self.has_value()) + .field("pType", &self.inner.pType) + .field("pData", &self.inner.pData) + .finish() + } +} + +/// Destructor: Automatic cleanup when Any goes out of scope +impl Drop for Any { + fn drop(&mut self) { + if self.has_value() { + unsafe { + uno_any_destruct(&mut self.inner, None); + } + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/rust_uno/src/core/mod.rs b/rust_uno/src/core/mod.rs new file mode 100644 index 000000000000..0546fdf51917 --- /dev/null +++ b/rust_uno/src/core/mod.rs @@ -0,0 +1,37 @@ +/* -*- Mode: rust; 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/. + */ + +//! Core Module +//! +//! This module contains the high-level safe Rust wrappers for LibreOffice UNO types. +//! These provide memory-safe, idiomatic Rust interfaces to the underlying C APIs. + +pub mod any; +pub mod oustring; +pub mod sequence; +pub mod r#type; +pub mod uno_wrapper; + +// Re-export the main types for convenience +pub use crate::ffi::type_ffi::typelib_TypeClass; +pub use any::Any; +pub use oustring::OUString; +pub use sequence::Sequence; +pub use r#type::Type; +// UnoInterface replaced with generated XInterface from rustmaker +pub use uno_wrapper::{UnoError, UnoResult, defaultBootstrap_InitialComponentContext}; + +// Include unit tests +#[cfg(test)] +mod tests { + mod string_tests; + mod type_tests; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/rust_uno/src/core/oustring.rs b/rust_uno/src/core/oustring.rs new file mode 100644 index 000000000000..8be5188168e1 --- /dev/null +++ b/rust_uno/src/core/oustring.rs @@ -0,0 +1,256 @@ +/* -*- Mode: rust; 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/. + */ + +//! UNO String Types and Functions +//! +//! This module provides the high-level safe Rust wrapper around LibreOffice's rtl_uString. +//! It includes automatic memory management and safe conversions to/from Rust strings. +//! +//! ## Key Components +//! - `OUString` - Safe Rust wrapper around LibreOffice's rtl_uString +//! - String conversion functions with automatic encoding handling +//! - Memory management with automatic reference counting +//! +//! ## Features +//! - UTF-8, UTF-16, and ASCII string creation methods +//! - Automatic memory management via RAII +//! - Safe conversion to/from Rust strings +//! - Clone, equality, and display trait implementations +//! - Direct FFI interop with raw pointer access methods + +use std::ptr::NonNull; + +use crate::ffi::rtl_string::*; +use crate::ffi::sal_types::*; + +// === Safe UNO String Wrapper === + +/// UNO String wrapper - a safe Rust wrapper around LibreOffice's rtl_uString +/// +/// OUString provides a safe, memory-managed interface to LibreOffice's native +/// string type. It automatically handles reference counting and memory cleanup, +/// and provides conversions to/from standard Rust string types. +pub struct OUString { + inner: NonNull<rtl_uString>, +} + +impl OUString { + /// Create a new empty UNO string + /// + /// This creates an empty OUString with zero length. + pub fn new() -> Self { + unsafe { + let mut ptr: *mut rtl_uString = std::ptr::null_mut(); + rtl_uString_new(&mut ptr); + + // Convert to NonNull, panic if allocation failed + let inner = + NonNull::new(ptr).expect("RTL string allocation failed - system out of memory"); + + OUString { inner } + } + } + + /// Create a UNO string from UTF-8 text + /// + /// This uses LibreOffice's direct UTF-8 to UString conversion function, + /// which is more efficient than converting through UTF-16. Handles all + /// valid UTF-8 including Unicode characters and emojis. + pub fn from_utf8(text: &str) -> Self { + unsafe { + let mut ptr: *mut rtl_uString = std::ptr::null_mut(); + rtl_string2UString( + &mut ptr, + text.as_ptr() as *const std::os::raw::c_char, + text.len() as sal_Int32, + RTL_TEXTENCODING_UTF8, + OSTRING_TO_OUSTRING_CVTFLAGS, + ); + + let inner = NonNull::new(ptr).expect("RTL UTF-8 string creation failed"); + + OUString { inner } + } + } + + /// Create a UNO string from ASCII text + /// + /// This method uses LibreOffice's native ASCII conversion function for + /// actual ASCII text (7-bit characters only). More efficient than UTF-8 + /// conversion when you know the text is pure ASCII. + pub fn from_ascii(text: &str) -> Self { + unsafe { + let mut ptr: *mut rtl_uString = std::ptr::null_mut(); + // Ensure the string is null-terminated for the C function + let c_string = std::ffi::CString::new(text).expect("CString creation failed"); + rtl_uString_newFromAscii(&mut ptr, c_string.as_ptr()); + + let inner = NonNull::new(ptr).expect("RTL ASCII string creation failed"); + + OUString { inner } + } + } + + /// Create OUString from UTF-16 data + /// + /// Creates a UNO string directly from UTF-16 data. This is the most efficient + /// method when you already have UTF-16 data, since LibreOffice uses UTF-16 + /// internally (sal_Unicode is UTF-16 code units). + pub fn from_utf16(data: &[sal_Unicode]) -> Self { + unsafe { + let mut ptr: *mut rtl_uString = std::ptr::null_mut(); + rtl_uString_newFromStr_WithLength(&mut ptr, data.as_ptr(), data.len() as sal_Int32); + + let inner = NonNull::new(ptr).expect("RTL UTF-16 string creation failed"); + + OUString { inner } + } + } + + /// Create OUString from raw rtl_uString pointer (takes ownership) + /// + /// # Safety + /// The caller must ensure that: + /// - The pointer is valid and points to a properly initialized rtl_uString + /// - The pointer is not used elsewhere after this call + /// - The rtl_uString has the correct reference count + pub unsafe fn from_raw(str: *mut rtl_uString) -> Self { + let inner = NonNull::new(str).expect("Cannot create OUString from null pointer"); + + unsafe { + rtl_uString_acquire(inner.as_ptr()); + } + OUString { inner } + } + + /// Convert to raw rtl_uString pointer (releases ownership) + /// + /// The caller becomes responsible for calling rtl_uString_release + pub fn into_raw(self) -> *mut rtl_uString { + let ptr = self.inner.as_ptr(); + std::mem::forget(self); // Don't run destructor + ptr + } + + /// Get the raw pointer for FFI calls (retains ownership) + pub fn as_ptr(&self) -> *const rtl_uString { + self.inner.as_ptr() as *const _ + } + + /// Get the mutable raw pointer for FFI calls (retains ownership) + pub fn as_mut_ptr(&mut self) -> *mut rtl_uString { + self.inner.as_ptr() + } + + /// Get the length of the string + pub fn len(&self) -> usize { + unsafe { rtl_uString_getLength(self.inner.as_ptr()) as usize } + } + + /// Check if the string is empty + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +impl Default for OUString { + fn default() -> Self { + Self::new() + } +} + +/// Clone implementation: Create a copy of another OUString +/// +/// This creates a new OUString that shares the same content but is independently +/// managed (proper deep copy with reference counting). +impl Clone for OUString { + fn clone(&self) -> Self { + unsafe { + rtl_uString_acquire(self.inner.as_ptr()); + OUString { inner: self.inner } + } + } +} + +/// From trait implementation for &str (UTF-8) +/// +/// Convenient conversion from string slices to OUString using UTF-8 encoding. +impl<T: AsRef<str>> From<T> for OUString { + fn from(text: T) -> Self { + Self::from_utf8(text.as_ref()) + } +} + +/// Comparison with other OUString +impl PartialEq for OUString { + fn eq(&self, other: &Self) -> bool { + unsafe { + // Compare lengths first (fast check) + let self_len = rtl_uString_getLength(self.inner.as_ptr()); + let other_len = rtl_uString_getLength(other.inner.as_ptr()); + + if self_len != other_len { + return false; + } + + // If lengths are equal, compare UTF-16 data directly + if self_len == 0 { + return true; // Both empty strings + } + + let self_ptr = rtl_uString_getStr(self.inner.as_ptr()); + let other_ptr = rtl_uString_getStr(other.inner.as_ptr()); + + // Compare UTF-16 data byte by byte + let self_slice = std::slice::from_raw_parts(self_ptr, self_len as usize); + let other_slice = std::slice::from_raw_parts(other_ptr, other_len as usize); + + self_slice == other_slice + } + } +} + +impl Eq for OUString {} + +/// Display trait for printing +impl std::fmt::Display for OUString { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if self.is_empty() { + return Ok(()); + } + + unsafe { + let ptr = rtl_uString_getStr(self.inner.as_ptr()); + let len = rtl_uString_getLength(self.inner.as_ptr()) as usize; + + // Convert from UTF-16 to UTF-8 + let utf16_slice = std::slice::from_raw_parts(ptr, len); + let string = String::from_utf16_lossy(utf16_slice); + write!(f, "{string}") + } + } +} + +/// Debug trait for debugging - shows type and content for UNO string identification +impl std::fmt::Debug for OUString { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("OUString").field(&self.to_string()).finish() + } +} + +/// Destructor: Automatic cleanup when OUString goes out of scope +impl Drop for OUString { + fn drop(&mut self) { + unsafe { + rtl_uString_release(self.inner.as_ptr()); + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/rust_uno/src/core/sequence.rs b/rust_uno/src/core/sequence.rs new file mode 100644 index 000000000000..f9d8a237b0a2 --- /dev/null +++ b/rust_uno/src/core/sequence.rs @@ -0,0 +1,245 @@ +/* -*- Mode: rust; 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/. + */ + +//! UNO Sequence Types and Functions +//! +//! This module provides the high-level safe Rust wrapper around LibreOffice's uno_Sequence. +//! It includes automatic memory management and safe conversions to/from Rust values. +//! +//! ## Key Components +//! - `Sequence` - Safe Rust wrapper around LibreOffice's uno_Sequence +//! - Value conversion functions with automatic type handling +//! - Memory management with automatic cleanup +//! +//! ## Features +//! - Safe construction from various Rust types +//! - Automatic memory management via RAII +//! - Safe value extraction with type checking +//! - Clone, equality, and display trait implementations +//! - Direct FFI interop with raw pointer access methods + +use crate::core::r#type::*; +use crate::ffi::type_ffi::*; +// Interface operations now use generated XInterface wrapper methods +// TODO: Replace with generated XInterface::from_ptr(ptr).acquire() pattern +use crate::ffi::uno_sequence::*; +use std::ptr::NonNull; + +// === Safe UNO Sequence Wrapper === + +/// UNO Sequence wrapper - a safe Rust wrapper around LibreOffice's uno_Sequence +/// +/// Sequence provides a safe, memory-managed interface to LibreOffice's native +/// Sequence type. It automatically handles reference counting and memory cleanup. +pub struct Sequence { + inner: NonNull<uno_Sequence>, +} + +impl Sequence { + /// Create a new empty Sequence + /// + /// Creates an empty sequence of Any elements, similar to the C++ default constructor. + /// The sequence will be empty (0 elements) and uses the Any type as the element type. + pub fn new() -> Self { + Self::with_capacity(0) + } + + pub fn with_capacity(capacity: i32) -> Self { + if capacity < 0 { + panic!("Sequence capacity cannot be negative: {}", capacity); + } + unsafe { + let mut ptr: *mut uno_Sequence = std::ptr::null_mut(); + + // Create an Any type for the sequence elements + let element_type = Type::new(); + + // Get the type description from the type reference + let mut type_desc = std::ptr::null_mut(); + typelib_typedescriptionreference_getDescription( + &mut type_desc, + element_type.get_typelib_type(), + ); + + // Construct empty sequence + let success = uno_type_sequence_construct( + &mut ptr, + type_desc, + std::ptr::null(), + capacity, + None, // TODO: Replace with generated XInterface acquire wrapper + ); + + // Check for construction success + if success == 0 { + panic!("Failed to construct UNO sequence"); + } + + Sequence { + inner: NonNull::new(ptr) + .expect("uno_type_sequence_construct returned null pointer"), + } + } + } + + /// Get the length of the sequence + /// + /// Returns the number of elements in this sequence. + /// Equivalent to C++ `getLength()` method. + pub fn get_length(&self) -> i32 { + unsafe { self.inner.as_ref().nElements } + } + + /// Check if the sequence has elements + /// + /// Returns true if the sequence contains at least one element. + /// Equivalent to C++ `hasElements()` method. + pub fn has_elements(&self) -> bool { + self.get_length() > 0 + } + + /// Get the length as usize for Rust iterator compatibility + /// + /// Returns the length as usize, which is commonly used in Rust. + pub fn len(&self) -> usize { + self.get_length() as usize + } + + /// Check if the sequence is empty + /// + /// Returns true if the sequence has no elements. + pub fn is_empty(&self) -> bool { + self.get_length() == 0 + } + + /// Get raw pointer to the underlying uno_Sequence + /// + /// This provides direct access to the underlying UNO sequence for FFI interop. + /// The returned pointer remains valid as long as this Sequence instance exists. + pub fn as_raw(&self) -> *mut uno_Sequence { + self.inner.as_ptr() + } + + /// Get the raw pointer for FFI calls (retains ownership) + pub fn as_ptr(&self) -> *const uno_Sequence { + self.inner.as_ptr() as *const _ + } + + /// Get the mutable raw pointer for FFI calls (retains ownership) + pub fn as_mut_ptr(&mut self) -> *mut uno_Sequence { + self.inner.as_ptr() + } +} + +impl Default for Sequence { + fn default() -> Self { + Self::new() + } +} + +/// Clone implementation: Create an independent copy of another Sequence +/// +/// This creates a new Sequence that references the same sequence data but is +/// independently managed. The clone operation increments the reference count +/// of the underlying sequence, ensuring proper memory management. +impl Clone for Sequence { + fn clone(&self) -> Self { + unsafe { + let ptr = self.inner.as_ptr(); + // Increment reference count + (*ptr).nRefCount += 1; + + Sequence { + inner: NonNull::new(ptr).expect("Cloning sequence with invalid pointer"), + } + } + } +} + +/// Equality comparison with other Sequence instances +/// +/// Two Sequence instances are considered equal if they have the same length +/// and contain the same elements in the same order. This uses pointer comparison +/// for efficiency when both sequences reference the same underlying data. +impl PartialEq for Sequence { + fn eq(&self, other: &Self) -> bool { + unsafe { + let self_ptr = self.inner.as_ptr(); + let other_ptr = other.inner.as_ptr(); + + // Fast path: same pointer + if self_ptr == other_ptr { + return true; + } + + let self_seq = self.inner.as_ref(); + let other_seq = other.inner.as_ref(); + + // Different lengths means not equal + if self_seq.nElements != other_seq.nElements { + return false; + } + + // For now, do basic comparison - in a full implementation, + // this would need to compare actual element values + self_seq.nElements == other_seq.nElements + } + } +} + +impl Eq for Sequence {} + +/// Debug trait for debugging - shows sequence length and type for UNO sequence identification +impl std::fmt::Debug for Sequence { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("Sequence") + .field(&format!("length={}", self.get_length())) + .finish() + } +} + +/// Display trait for printing sequences +/// +/// Provides a user-friendly string representation of the sequence. +impl std::fmt::Display for Sequence { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if self.is_empty() { + write!(f, "Sequence[]") + } else { + write!(f, "Sequence[{}]", self.get_length()) + } + } +} + +/// Destructor: Automatic cleanup when Sequence goes out of scope +impl Drop for Sequence { + fn drop(&mut self) { + unsafe { + let ptr = self.inner.as_ptr(); + let seq = &mut *ptr; + seq.nRefCount -= 1; + + // If reference count reaches zero, destroy the sequence + if seq.nRefCount == 0 { + // Get the element type for proper cleanup + let element_type = Type::new(); + let mut type_desc = std::ptr::null_mut(); + typelib_typedescriptionreference_getDescription( + &mut type_desc, + element_type.get_typelib_type(), + ); + + // Call UNO sequence destructor + uno_type_sequence_destroy(ptr, type_desc, None); // TODO: Replace with generated XInterface release wrapper + } + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/rust_uno/src/core/tests/string_tests.rs b/rust_uno/src/core/tests/string_tests.rs new file mode 100644 index 000000000000..b931dfa6a2ba --- /dev/null +++ b/rust_uno/src/core/tests/string_tests.rs @@ -0,0 +1,387 @@ +/* -*- Mode: rust; 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/. + */ + +//! Unit tests for OUString (Rust-side functionality) +//! +//! These tests verify OUString's Rust implementation including: +//! - Creation methods (new, from_utf8, from_ascii, from_utf16) +//! - Trait implementations (Clone, PartialEq, From, Display, Debug, Default) +//! - Instance methods (len, is_empty, as_ptr, etc.) +//! - Memory management and safety (RAII, reference counting) +//! - FFI interop (from_raw, into_raw) +//! +//! These tests do NOT require LibreOffice UNO components and test +//! only the Rust-side implementation and FFI safety. + +use crate::core::OUString; + +// === Creation Method Tests === + +#[test] +fn test_oustring_new() { + let s = OUString::new(); + assert_eq!(s.len(), 0); + assert!(s.is_empty()); + assert_eq!(s.to_string(), ""); +} + +#[test] +fn test_oustring_default() { + let s = OUString::default(); + assert_eq!(s.len(), 0); + assert!(s.is_empty()); + assert_eq!(s.to_string(), ""); +} + +#[test] +fn test_oustring_from_utf8() { + let s = OUString::from_utf8("Hello, 世界!"); + assert!(!s.is_empty()); + assert_eq!(s.to_string(), "Hello, 世界!"); + + // Test empty string + let empty = OUString::from_utf8(""); + assert!(empty.is_empty()); + assert_eq!(empty.len(), 0); + + // Test with Unicode characters + let unicode = OUString::from_utf8("Ñiño 🦀 café"); + assert_eq!(unicode.to_string(), "Ñiño 🦀 café"); +} + +#[test] +fn test_oustring_from_ascii() { + let s = OUString::from_ascii("Hello, World!"); + assert_eq!(s.to_string(), "Hello, World!"); + assert!(!s.is_empty()); + + // Test empty ASCII string + let empty = OUString::from_ascii(""); + assert!(empty.is_empty()); + assert_eq!(empty.len(), 0); +} + +// === Trait Implementation Tests === + +#[test] +fn test_oustring_clone() { + let original = OUString::from_utf8("Test string for cloning"); + let cloned = original.clone(); + + // They should be equal + assert_eq!(original, cloned); + assert_eq!(original.to_string(), cloned.to_string()); + assert_eq!(original.len(), cloned.len()); + + // Test cloning empty string + let empty = OUString::new(); + let empty_clone = empty.clone(); + assert_eq!(empty, empty_clone); + assert!(empty_clone.is_empty()); +} + +#[test] +fn test_oustring_partial_eq() { + let s1 = OUString::from_utf8("Hello"); + let s2 = OUString::from_utf8("Hello"); + let s3 = OUString::from_utf8("World"); + let empty1 = OUString::new(); + let empty2 = OUString::new(); + + // Equal strings + assert_eq!(s1, s2); + assert_eq!(empty1, empty2); + + // Different strings + assert_ne!(s1, s3); + assert_ne!(s1, empty1); + assert_ne!(empty1, s1); + + // Test with Unicode + let unicode1 = OUString::from_utf8("café"); + let unicode2 = OUString::from_utf8("café"); + let unicode3 = OUString::from_utf8("cafe"); // without accent + + assert_eq!(unicode1, unicode2); + assert_ne!(unicode1, unicode3); +} + +#[test] +fn test_oustring_from_str() { + let s = OUString::from("Hello from &str"); + assert_eq!(s.to_string(), "Hello from &str"); + + // Test with Unicode + let unicode: OUString = "Héllo wørld 🌍".into(); + assert_eq!(unicode.to_string(), "Héllo wørld 🌍"); +} + +#[test] +fn test_oustring_from_string() { + let rust_string = String::from("Hello from String"); + let s = OUString::from(rust_string); + assert_eq!(s.to_string(), "Hello from String"); + + // Test with owned Unicode String + let unicode_string = "Ñiño 🦀".to_string(); + let unicode_oustring = OUString::from(unicode_string); + assert_eq!(unicode_oustring.to_string(), "Ñiño 🦀"); +} + +#[test] +fn test_oustring_display() { + let s = OUString::from_utf8("Display test"); + assert_eq!(format!("{s}"), "Display test"); + + // Test empty string display + let empty = OUString::new(); + assert_eq!(format!("{empty}"), ""); + + // Test Unicode display + let unicode = OUString::from_utf8("Display 测试 🦀"); + assert_eq!(format!("{unicode}"), "Display 测试 🦀"); +} + +#[test] +fn test_oustring_debug() { + let s = OUString::from_utf8("Debug test"); + let debug_output = format!("{s:?}"); + assert!(debug_output.contains("OUString")); + assert!(debug_output.contains("Debug test")); + + // Test empty string debug + let empty = OUString::new(); + let empty_debug = format!("{empty:?}"); + assert!(empty_debug.contains("OUString")); +} + +// === Method Tests === + +#[test] +fn test_oustring_len() { + let empty = OUString::new(); + assert_eq!(empty.len(), 0); + + let ascii = OUString::from_utf8("Hello"); + assert_eq!(ascii.len(), 5); + + // Note: len() returns UTF-16 code units, not Unicode codepoints + let unicode = OUString::from_utf8("café"); // 'é' is one UTF-16 unit + assert_eq!(unicode.len(), 4); + + // Emoji takes 2 UTF-16 code units (surrogate pair) + let emoji = OUString::from_utf8("🦀"); + assert_eq!(emoji.len(), 2); +} + +#[test] +fn test_oustring_is_empty() { + let empty = OUString::new(); + assert!(empty.is_empty()); + + let non_empty = OUString::from_utf8("Not empty"); + assert!(!non_empty.is_empty()); + + let empty_from_str = OUString::from_utf8(""); + assert!(empty_from_str.is_empty()); +} + +// === Special Cases and Edge Cases === + +#[test] +fn test_oustring_special_characters() { + // Test various special Unicode characters + let special_chars = vec![ + " + " ", // Newline + " ", // Windows line ending + " ", // Tab + "\"", // Quote + "\", // Backslash + "©", // Copyright symbol + "€", // Euro symbol + "™", // Trademark + "🦀", // Rust crab emoji + "👨💻", // Man technologist (complex emoji) + ]; + + for special in special_chars { + let s = OUString::from_utf8(special); + assert_eq!(s.to_string(), special); + } +} + +#[test] +fn test_oustring_long_strings() { + // Test with long strings to ensure proper memory management + let long_text = "A".repeat(10000); + let s = OUString::from_utf8(&long_text); + assert_eq!(s.len(), 10000); + assert_eq!(s.to_string(), long_text); + + // Test cloning long strings + let cloned = s.clone(); + assert_eq!(cloned, s); + assert_eq!(cloned.len(), 10000); +} + +#[test] +fn test_oustring_multilingual() { + // Test various languages and scripts + let multilingual = "English, 中文, العربية, Русский, ελληνικά, עברית"; + let s = OUString::from_utf8(multilingual); + assert_eq!(s.to_string(), multilingual); + assert!(!s.is_empty()); +} + +#[test] +fn test_oustring_consistency_across_constructors() { + let text = "Test consistency"; + + let from_utf8 = OUString::from_utf8(text); + let from_str: OUString = text.into(); + let from_string = OUString::from(text.to_string()); + + // All should produce equivalent strings + assert_eq!(from_utf8, from_str); + assert_eq!(from_utf8, from_string); + assert_eq!(from_str, from_string); + + // All should have same content + assert_eq!(from_utf8.to_string(), text); + assert_eq!(from_str.to_string(), text); + assert_eq!(from_string.to_string(), text); +} + +#[test] +fn test_oustring_memory_safety() { + // Test that dropping strings doesn't cause issues + { + let s1 = OUString::from_utf8("Temporary string 1"); + let s2 = s1.clone(); + let s3 = OUString::from_utf8("Temporary string 2"); + + assert_eq!(s1, s2); + assert_ne!(s1, s3); + + // Strings will be dropped here - this should not cause issues + } + + // Create new strings after the previous ones were dropped + let s4 = OUString::from_utf8("After drop test"); + assert_eq!(s4.to_string(), "After drop test"); +} + +#[test] +fn test_oustring_memory_release() { + // Simple memory release test - create and drop many strings + // If memory isn't released properly, this would cause memory leaks + + // Create many strings in a loop and let them drop + for i in 0..1000 { + let s = OUString::from_utf8(&format!("Test string number {i}")); + assert_eq!(s.to_string(), format!("Test string number {i}")); + // String automatically drops here + } + + // Create and explicitly drop strings + for _ in 0..1000 { + let s1 = OUString::from_utf8("Temporary string"); + let s2 = s1.clone(); // This should increase ref count + let s3 = s2.clone(); // This should increase ref count more + + // All should be equal + assert_eq!(s1, s2); + assert_eq!(s2, s3); + + // All will be dropped here - ref counts should decrease properly + } + + // If we reach here without crashes or memory issues, memory management is working +} + +#[test] +fn test_oustring_reference_counting_behavior() { + // Test that cloning works properly with reference counting + let original = OUString::from_utf8("Reference counted string"); + + // Create multiple references + let clone1 = original.clone(); + let clone2 = original.clone(); + let clone3 = clone1.clone(); + + // All should be equal + assert_eq!(original, clone1); + assert_eq!(original, clone2); + assert_eq!(original, clone3); + assert_eq!(clone1, clone2); + assert_eq!(clone1, clone3); + assert_eq!(clone2, clone3); + + // All have same content + assert_eq!(original.to_string(), "Reference counted string"); + assert_eq!(clone1.to_string(), "Reference counted string"); + assert_eq!(clone2.to_string(), "Reference counted string"); + assert_eq!(clone3.to_string(), "Reference counted string"); + + // When this function ends, all clones should be properly released +} + +#[test] +fn test_oustring_memory_from_raw_into_raw() { + // Test raw pointer ownership transfer (unique functionality) + for i in 0..100 { + let original = OUString::from_utf8(&format!("Raw pointer test {i}")); + let original_content = original.to_string(); + + // Transfer ownership to raw pointer + let raw_ptr = original.into_raw(); + + // Create new OUString from raw pointer (takes ownership back) + let restored = unsafe { OUString::from_raw(raw_ptr) }; + + assert_eq!(restored.to_string(), original_content); + } +} + +#[test] +fn test_oustring_memory_mixed_operations() { + // Test complex combinations of operations (not covered by existing tests) + for i in 0..50 { + // Create initial string + let s1 = OUString::from_utf8(&format!("Mixed test {i}")); + + // Convert through different types + let s2 = OUString::from(s1.to_string()); + let s3: OUString = s1.to_string().as_str().into(); + + // Clone operations + let c1 = s1.clone(); + let c2 = s2.clone(); + let c3 = s3.clone(); + + // Verify equality + assert_eq!(s1, s2); + assert_eq!(s2, s3); + assert_eq!(c1, c2); + assert_eq!(c2, c3); + + // Raw pointer operations + let raw1 = c1.into_raw(); + let raw2 = c2.into_raw(); + + let restored1 = unsafe { OUString::from_raw(raw1) }; + let restored2 = unsafe { OUString::from_raw(raw2) }; + + assert_eq!(restored1, restored2); + assert_eq!(restored1.to_string(), format!("Mixed test {i}")); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/rust_uno/src/core/tests/type_tests.rs b/rust_uno/src/core/tests/type_tests.rs new file mode 100644 index 000000000000..3248ac372175 --- /dev/null +++ b/rust_uno/src/core/tests/type_tests.rs @@ -0,0 +1,520 @@ +/* -*- Mode: rust; 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/. + */ + +//! Unit tests for Type (Rust-side functionality) +//! +//! These tests verify Type's Rust implementation including: +//! - Creation methods (new, new_with_name, from_typelib_ref, from_typelib_ref_no_acquire) +//! - Trait implementations (Clone, PartialEq, From, Default) +//! - Instance methods (get_type_class, get_type_name, equals, is_assignable_from) +//! - Memory management and safety (RAII, reference counting) +//! - FFI interop (get_typelib_type) +//! - Ergonomic API (generic string parameters) +//! +//! These tests do NOT require LibreOffice UNO components and test +//! only the Rust-side implementation and FFI safety. + +use crate::core::{OUString, Type, typelib_TypeClass}; + +// === Creation Method Tests === + +#[test] +fn test_type_new() { + let void_type = Type::new(); + assert_eq!( + void_type.get_type_class(), + typelib_TypeClass::typelib_TypeClass_VOID + ); + assert_eq!(void_type.get_type_name().to_string(), "void"); +} + +#[test] +fn test_type_default() { + let default_type = Type::default(); + assert_eq!( + default_type.get_type_class(), + typelib_TypeClass::typelib_TypeClass_VOID + ); + assert_eq!(default_type.get_type_name().to_string(), "void"); + + // Test that default equals new() + let new_type = Type::new(); + assert!(default_type.equals(&new_type)); +} + +#[test] +fn test_type_new_with_name_str() { + let void_type = Type::new_with_name(typelib_TypeClass::typelib_TypeClass_VOID, "void"); + assert_eq!( + void_type.get_type_class(), + typelib_TypeClass::typelib_TypeClass_VOID + ); + assert_eq!(void_type.get_type_name().to_string(), "void"); + + let int_type = Type::new_with_name(typelib_TypeClass::typelib_TypeClass_LONG, "sal_Int32"); + assert_eq!( + int_type.get_type_class(), + typelib_TypeClass::typelib_TypeClass_LONG + ); + assert_eq!(int_type.get_type_name().to_string(), "sal_Int32"); + + let interface_type = Type::new_with_name( + typelib_TypeClass::typelib_TypeClass_INTERFACE, + "com.sun.star.lang.XComponent", + ); + assert_eq!( + interface_type.get_type_class(), + typelib_TypeClass::typelib_TypeClass_INTERFACE + ); + assert_eq!( + interface_type.get_type_name().to_string(), + "com.sun.star.lang.XComponent" + ); +} + +#[test] +fn test_type_new_with_name_string() { + let type_name = "com.sun.star.text.XText".to_string(); + let text_type = Type::new_with_name(typelib_TypeClass::typelib_TypeClass_INTERFACE, type_name); + assert_eq!( + text_type.get_type_class(), + typelib_TypeClass::typelib_TypeClass_INTERFACE + ); + assert_eq!( + text_type.get_type_name().to_string(), + "com.sun.star.text.XText" + ); +} + +#[test] +fn test_type_new_with_name_oustring() { + let oustring_name = OUString::from("com.sun.star.beans.XPropertySet"); + let prop_type = Type::new_with_name( + typelib_TypeClass::typelib_TypeClass_INTERFACE, + oustring_name, + ); + assert_eq!( + prop_type.get_type_class(), + typelib_TypeClass::typelib_TypeClass_INTERFACE + ); + assert_eq!( + prop_type.get_type_name().to_string(), + "com.sun.star.beans.XPropertySet" + ); +} + +// === From Trait Tests === + +#[test] +fn test_type_from_typelib_typeclass() { + let string_type = Type::from(typelib_TypeClass::typelib_TypeClass_STRING); + assert_eq!( + string_type.get_type_class(), + typelib_TypeClass::typelib_TypeClass_STRING + ); + + let boolean_type = Type::from(typelib_TypeClass::typelib_TypeClass_BOOLEAN); + assert_eq!( + boolean_type.get_type_class(), + typelib_TypeClass::typelib_TypeClass_BOOLEAN + ); + + let double_type = Type::from(typelib_TypeClass::typelib_TypeClass_DOUBLE); + assert_eq!( + double_type.get_type_class(), + typelib_TypeClass::typelib_TypeClass_DOUBLE + ); +} + +#[test] +fn test_type_from_tuple_with_str() { + let primitive_types = vec![ + ("void", typelib_TypeClass::typelib_TypeClass_VOID), + ("boolean", typelib_TypeClass::typelib_TypeClass_BOOLEAN), + ("byte", typelib_TypeClass::typelib_TypeClass_BYTE), + ("short", typelib_TypeClass::typelib_TypeClass_SHORT), + ( + "unsigned short", + typelib_TypeClass::typelib_TypeClass_UNSIGNED_SHORT, + ), + ("long", typelib_TypeClass::typelib_TypeClass_LONG), + ( + "unsigned long", + typelib_TypeClass::typelib_TypeClass_UNSIGNED_LONG, + ), + ("hyper", typelib_TypeClass::typelib_TypeClass_HYPER), + ( + "unsigned hyper", + typelib_TypeClass::typelib_TypeClass_UNSIGNED_HYPER, + ), + ("float", typelib_TypeClass::typelib_TypeClass_FLOAT), + ("double", typelib_TypeClass::typelib_TypeClass_DOUBLE), + ("char", typelib_TypeClass::typelib_TypeClass_CHAR), + ("string", typelib_TypeClass::typelib_TypeClass_STRING), + ("type", typelib_TypeClass::typelib_TypeClass_TYPE), + ("any", typelib_TypeClass::typelib_TypeClass_ANY), + ]; + + for (type_name, expected_class) in primitive_types { + let type_obj = Type::from((expected_class, type_name)); + assert_eq!( + type_obj.get_type_class(), + expected_class, + "Failed for type: {}", + type_name + ); + assert_eq!(type_obj.get_type_name().to_string(), type_name); + } +} + +#[test] +fn test_type_from_tuple_with_string() { + let type_name = "com.sun.star.awt.XControl".to_string(); + let control_type = Type::from((typelib_TypeClass::typelib_TypeClass_INTERFACE, type_name)); + assert_eq!( + control_type.get_type_class(), + typelib_TypeClass::typelib_TypeClass_INTERFACE + ); + assert_eq!( + control_type.get_type_name().to_string(), + "com.sun.star.awt.XControl" + ); +} + +#[test] +fn test_type_from_tuple_with_oustring() { + let type_name = OUString::from("com.sun.star.beans.XPropertySet"); + let prop_type = Type::from((typelib_TypeClass::typelib_TypeClass_INTERFACE, type_name)); + assert_eq!( + prop_type.get_type_class(), + typelib_TypeClass::typelib_TypeClass_INTERFACE + ); + assert_eq!( + prop_type.get_type_name().to_string(), + "com.sun.star.beans.XPropertySet" + ); +} + +// === Trait Implementation Tests === + +#[test] +fn test_type_clone() { + let original = Type::new_with_name( + typelib_TypeClass::typelib_TypeClass_INTERFACE, + "com.sun.star.lang.XComponent", + ); + let cloned = original.clone(); + + // They should be equal + assert!(original.equals(&cloned)); + assert_eq!(original.get_type_class(), cloned.get_type_class()); + assert_eq!( + original.get_type_name().to_string(), + cloned.get_type_name().to_string() + ); + + // Test cloning void type + let void_original = Type::new(); + let void_clone = void_original.clone(); + assert!(void_original.equals(&void_clone)); +} + +#[test] +fn test_type_partial_eq() { + let type1 = Type::new_with_name(typelib_TypeClass::typelib_TypeClass_LONG, "sal_Int32"); + let type2 = Type::new_with_name(typelib_TypeClass::typelib_TypeClass_LONG, "sal_Int32"); + let type3 = Type::new_with_name(typelib_TypeClass::typelib_TypeClass_SHORT, "sal_Int16"); + let void1 = Type::new(); + let void2 = Type::new(); + + // Equal types + assert_eq!(type1, type2); + assert_eq!(void1, void2); + + // Different types + assert_ne!(type1, type3); + assert_ne!(type1, void1); + + // Test with interface types + let interface1 = Type::from(( + typelib_TypeClass::typelib_TypeClass_INTERFACE, + "com.sun.star.lang.XComponent", + )); + let interface2 = Type::from(( + typelib_TypeClass::typelib_TypeClass_INTERFACE, + "com.sun.star.lang.XComponent", + )); + let interface3 = Type::from(( + typelib_TypeClass::typelib_TypeClass_INTERFACE, + "com.sun.star.uno.XInterface", + )); + + assert_eq!(interface1, interface2); + assert_ne!(interface1, interface3); +} + +// === Method Tests === + +#[test] +fn test_type_get_type_class() { + let void_type = Type::new(); + assert_eq!( + void_type.get_type_class(), + typelib_TypeClass::typelib_TypeClass_VOID + ); + + let string_type = Type::from(typelib_TypeClass::typelib_TypeClass_STRING); + assert_eq!( + string_type.get_type_class(), + typelib_TypeClass::typelib_TypeClass_STRING + ); + + let interface_type = Type::from(( + typelib_TypeClass::typelib_TypeClass_INTERFACE, + "com.sun.star.lang.XComponent", + )); + assert_eq!( + interface_type.get_type_class(), + typelib_TypeClass::typelib_TypeClass_INTERFACE + ); +} + +#[test] +fn test_type_get_type_name() { + let void_type = Type::new(); + assert_eq!(void_type.get_type_name().to_string(), "void"); + + let int_type = Type::new_with_name(typelib_TypeClass::typelib_TypeClass_LONG, "sal_Int32"); + assert_eq!(int_type.get_type_name().to_string(), "sal_Int32"); + + let interface_type = Type::from(( + typelib_TypeClass::typelib_TypeClass_INTERFACE, + "com.sun.star.text.XTextDocument", + )); + assert_eq!( + interface_type.get_type_name().to_string(), + "com.sun.star.text.XTextDocument" + ); +} + +#[test] +fn test_type_equals() { + let type1 = Type::new_with_name(typelib_TypeClass::typelib_TypeClass_LONG, "sal_Int32"); + let type2 = Type::new_with_name(typelib_TypeClass::typelib_TypeClass_LONG, "sal_Int32"); + let type3 = Type::new_with_name(typelib_TypeClass::typelib_TypeClass_SHORT, "sal_Int16"); + + // Test equality + assert!(type1.equals(&type2)); + assert!(type2.equals(&type1)); // Symmetry + + // Test inequality + assert!(!type1.equals(&type3)); + assert!(!type3.equals(&type1)); // Symmetry + + // Test self-equality + assert!(type1.equals(&type1)); +} + +#[test] +fn test_type_is_assignable_from() { + // Create different types for testing assignment compatibility + let long_type = Type::new_with_name(typelib_TypeClass::typelib_TypeClass_LONG, "sal_Int32"); + let short_type = Type::new_with_name(typelib_TypeClass::typelib_TypeClass_SHORT, "sal_Int16"); + let byte_type = Type::new_with_name(typelib_TypeClass::typelib_TypeClass_BYTE, "sal_Int8"); + + // Test self-assignment (should always be true) + assert!(long_type.is_assignable_from(&long_type)); + assert!(short_type.is_assignable_from(&short_type)); + assert!(byte_type.is_assignable_from(&byte_type)); + + // These tests depend on UNO's specific assignment rules + // Note: The actual behavior depends on LibreOffice's typelib implementation + // We're just testing that the method calls work correctly + let _long_from_short = long_type.is_assignable_from(&short_type); + let _long_from_byte = long_type.is_assignable_from(&byte_type); + let _short_from_byte = short_type.is_assignable_from(&byte_type); +} + +#[test] +fn test_type_get_typelib_type() { + let type_obj = Type::new_with_name(typelib_TypeClass::typelib_TypeClass_STRING, "string"); + let raw_ptr = type_obj.get_typelib_type(); + + // Should return a non-null pointer + assert!(!raw_ptr.is_null()); + + // Test with void type + let void_type = Type::new(); + let void_ptr = void_type.get_typelib_type(); + assert!(!void_ptr.is_null()); +} + +// === Special Cases and Edge Cases === + +#[test] +fn test_type_primitive_type_classes() { + // Test creating types for primitive type classes that have static type references + let primitive_type_classes = vec![ + typelib_TypeClass::typelib_TypeClass_VOID, + typelib_TypeClass::typelib_TypeClass_CHAR, + typelib_TypeClass::typelib_TypeClass_BOOLEAN, + typelib_TypeClass::typelib_TypeClass_BYTE, + typelib_TypeClass::typelib_TypeClass_SHORT, + typelib_TypeClass::typelib_TypeClass_UNSIGNED_SHORT, + typelib_TypeClass::typelib_TypeClass_LONG, + typelib_TypeClass::typelib_TypeClass_UNSIGNED_LONG, + typelib_TypeClass::typelib_TypeClass_HYPER, + typelib_TypeClass::typelib_TypeClass_UNSIGNED_HYPER, + typelib_TypeClass::typelib_TypeClass_FLOAT, + typelib_TypeClass::typelib_TypeClass_DOUBLE, + typelib_TypeClass::typelib_TypeClass_STRING, + typelib_TypeClass::typelib_TypeClass_TYPE, + typelib_TypeClass::typelib_TypeClass_ANY, + ]; + + for type_class in primitive_type_classes { + let type_obj = Type::from(type_class); + assert_eq!(type_obj.get_type_class(), type_class); + } +} + +#[test] +fn test_type_complex_interface_names() { + let complex_names = vec![ + "com.sun.star.lang.XComponent", + "com.sun.star.uno.XInterface", + "com.sun.star.text.XTextDocument", + "com.sun.star.beans.XPropertySet", + "com.sun.star.awt.XControl", + "com.sun.star.frame.XController", + ]; + + for name in complex_names { + let type_obj = Type::from((typelib_TypeClass::typelib_TypeClass_INTERFACE, name)); + assert_eq!( + type_obj.get_type_class(), + typelib_TypeClass::typelib_TypeClass_INTERFACE + ); + assert_eq!(type_obj.get_type_name().to_string(), name); + } +} + +#[test] +fn test_type_consistency_across_creation_methods() { + // Test that different creation methods produce equivalent results for same type + + // String type via different methods + let string_from_class = Type::from(typelib_TypeClass::typelib_TypeClass_STRING); + let string_from_tuple = Type::from((typelib_TypeClass::typelib_TypeClass_STRING, "string")); + let string_from_new_with_name = + Type::new_with_name(typelib_TypeClass::typelib_TypeClass_STRING, "string"); + + assert!(string_from_class.equals(&string_from_tuple)); + assert!(string_from_tuple.equals(&string_from_new_with_name)); + assert!(string_from_class.equals(&string_from_new_with_name)); + + // Interface type via different methods + let interface_name = "com.sun.star.lang.XComponent"; + let interface_from_tuple = Type::from(( + typelib_TypeClass::typelib_TypeClass_INTERFACE, + interface_name, + )); + let interface_from_new_with_name = Type::new_with_name( + typelib_TypeClass::typelib_TypeClass_INTERFACE, + interface_name, + ); + + assert!(interface_from_tuple.equals(&interface_from_new_with_name)); +} + +// === Memory Management Tests === + +#[test] +fn test_type_memory_safety() { + // Test that Types can be created and dropped without issues + + { + let _type1 = Type::new(); + let _type2 = Type::from(typelib_TypeClass::typelib_TypeClass_STRING); + let _type3 = Type::new_with_name(typelib_TypeClass::typelib_TypeClass_LONG, "sal_Int32"); + } // Types should be properly cleaned up here + + // Create more types after the previous ones were dropped + let type4 = Type::from(( + typelib_TypeClass::typelib_TypeClass_INTERFACE, + "com.sun.star.lang.XComponent", + )); + let type5 = type4.clone(); + + assert!(type4.equals(&type5)); +} + +#[test] +fn test_type_multiple_references() { + // Test that multiple references to the same logical type work correctly + let name = "com.sun.star.text.XTextDocument"; + + let type1 = Type::from((typelib_TypeClass::typelib_TypeClass_INTERFACE, name)); + let type2 = Type::from((typelib_TypeClass::typelib_TypeClass_INTERFACE, name)); + let type3 = type1.clone(); + + // All should be equal + assert!(type1.equals(&type2)); + assert!(type2.equals(&type3)); + assert!(type1.equals(&type3)); + + // All should have same properties + assert_eq!(type1.get_type_class(), type2.get_type_class()); + assert_eq!(type2.get_type_class(), type3.get_type_class()); + + assert_eq!( + type1.get_type_name().to_string(), + type2.get_type_name().to_string() + ); + assert_eq!( + type2.get_type_name().to_string(), + type3.get_type_name().to_string() + ); +} + +#[test] +fn test_type_mixed_operations() { + // Test mixing different operations + let original = Type::new_with_name( + typelib_TypeClass::typelib_TypeClass_INTERFACE, + "com.sun.star.lang.XComponent", + ); + + // Clone and compare + let cloned = original.clone(); + assert!(original.equals(&cloned)); + + // Create equivalent via From trait + let from_tuple = Type::from(( + typelib_TypeClass::typelib_TypeClass_INTERFACE, + "com.sun.star.lang.XComponent", + )); + assert!(original.equals(&from_tuple)); + + // Test assignment compatibility with itself + assert!(original.is_assignable_from(&original)); + assert!(cloned.is_assignable_from(&from_tuple)); + + // Check type information + assert_eq!( + original.get_type_class(), + typelib_TypeClass::typelib_TypeClass_INTERFACE + ); + assert_eq!( + original.get_type_name().to_string(), + "com.sun.star.lang.XComponent" + ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/rust_uno/src/core/type.rs b/rust_uno/src/core/type.rs new file mode 100644 index 000000000000..21363952ddcf --- /dev/null +++ b/rust_uno/src/core/type.rs @@ -0,0 +1,328 @@ +/* -*- Mode: rust; 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/. + */ + +//! UNO Type System and Type References +//! +//! This module provides the high-level safe Rust wrapper around LibreOffice's type system. +//! It includes safe type creation, automatic memory management, and type compatibility testing. +//! +//! ## Key Components +//! - `Type` - Safe Rust wrapper around LibreOffice's typelib_TypeDescriptionReference +//! +//! ## Features +//! - Safe type creation with automatic memory management via RAII +//! - Type equality and assignment compatibility testing +//! - Type class and name retrieval +//! - Clone, equality, and default trait implementations +//! - Direct FFI interop with raw pointer access methods +//! - Support for all UNO type classes (void, primitive types, interfaces, etc.) +//! - Safe From trait implementations for creating types from type classes and explicit type/name tuples + +use crate::core::oustring::*; +use crate::ffi::type_ffi::*; +use std::ptr; + +// === Safe Type Wrapper === + +/// Safe Rust wrapper for UNO Type +/// +/// This struct provides a safe, ergonomic interface to UNO's type system. +/// It handles memory management automatically and provides idiomatic Rust +/// methods for type operations and comparisons. +/// +/// # Examples +/// +/// ```rust +/// use rust_uno::{Type, typelib_TypeClass}; +/// +/// // Create a void type +/// let void_type = Type::new(); +/// +/// // Create a primitive type +/// let int_type = Type::from(typelib_TypeClass::typelib_TypeClass_LONG); +/// +/// // Create an interface type +/// let interface_type = Type::from(( +/// typelib_TypeClass::typelib_TypeClass_INTERFACE, +/// "com.sun.star.lang.XComponent" +/// )); +/// ``` +#[allow(non_snake_case)] // _pType follows FFI naming conventions +pub struct Type { + /// C typelib reference pointer + _pType: *mut typelib_TypeDescriptionReference, +} + +impl Type { + /// Create a new Type set to void + /// + /// Creates the default UNO type (void). This is equivalent to the default + /// constructor in C++ com::sun::star::uno::Type. + pub fn new() -> Self { + unsafe { + let void_type_ref = + typelib_static_type_getByTypeClass(typelib_TypeClass::typelib_TypeClass_VOID); + typelib_typedescriptionreference_acquire(*void_type_ref); + Type { + _pType: *void_type_ref, + } + } + } + + /// Create a Type from type class and name + /// + /// Creates a new Type from the specified type class and a string that will be + /// converted to OUString. Accepts any type that can be converted to OUString + /// (such as &str, String, etc.) through the Into trait. This is the most common + /// way to create custom types with specific names. + /// + /// # Arguments + /// * `type_class` - The UNO type class (VOID, STRING, INTERFACE, etc.) + /// * `type_name` - The fully qualified type name (convertible to OUString) + pub fn new_with_name<T: Into<OUString>>(type_class: typelib_TypeClass, type_name: T) -> Self { + let mut oustring_name = type_name.into(); + let mut type_ref: *mut typelib_TypeDescriptionReference = ptr::null_mut(); + + unsafe { + typelib_typedescriptionreference_new( + &mut type_ref, + type_class, + oustring_name.as_mut_ptr(), + ); + } + + Type { _pType: type_ref } + } + + /// Create Type from existing typelib reference (acquires reference) + /// + /// Takes ownership of an existing type reference by incrementing its reference count. + /// This is useful when interfacing with C code that provides type references. + /// + /// # Safety + /// The caller must ensure that `type_ref` points to a valid typelib_TypeDescriptionReference. + pub unsafe fn from_typelib_ref(type_ref: *mut typelib_TypeDescriptionReference) -> Self { + unsafe { + typelib_typedescriptionreference_acquire(type_ref); + } + Type { _pType: type_ref } + } + + /// Create Type from existing typelib reference (no acquire) + /// + /// Takes ownership of an existing type reference without incrementing reference count. + /// Use this when transferring ownership from C code that already has a reference. + /// This avoids unnecessary reference count manipulation when you know the ownership + /// is being transferred. + /// + /// # Safety + /// The caller must ensure that: + /// - `type_ref` points to a valid typelib_TypeDescriptionReference + /// - The reference count is properly managed (caller transfers ownership) + pub unsafe fn from_typelib_ref_no_acquire( + type_ref: *mut typelib_TypeDescriptionReference, + ) -> Self { + Type { _pType: type_ref } + } + + /// Get the type class of this type + /// + /// Returns the UNO type class (VOID, STRING, INTERFACE, etc.) that categorizes + /// this type within the UNO type system. This is used for type checking and + /// dispatch in UNO method calls. + pub fn get_type_class(&self) -> typelib_TypeClass { + unsafe { (*self._pType).eTypeClass } + } + + /// Get the fully qualified name of this type + /// + /// Returns an OUString containing the type name (e.g., "com.sun.star.lang.XComponent"). + /// For primitive types, returns the type name (e.g., "long", "string"). The returned + /// string is a copy, so it can be safely used without worrying about the lifetime + /// of the original Type instance. + pub fn get_type_name(&self) -> OUString { + unsafe { + let name_ptr = (*self._pType).pTypeName; + if name_ptr.is_null() { + return OUString::new(); + } + // Use OUString::from_raw which handles the acquisition internally + OUString::from_raw(name_ptr) + } + } + + /// Get the raw typelib reference pointer for FFI calls + /// + /// Returns the underlying pointer for FFI interoperability. The returned + /// pointer remains owned by this Type instance and should not be freed + /// by the caller. Useful when calling LibreOffice C APIs directly. + pub fn get_typelib_type(&self) -> *mut typelib_TypeDescriptionReference { + self._pType + } + + /// Convert Type into raw pointer (releases Rust ownership) + /// + /// Transfers ownership of the underlying type reference to the caller. + /// The caller becomes responsible for calling typelib_typedescriptionreference_release + /// when done with the pointer. The Type instance is consumed and cannot be used + /// after this call. + pub fn into_raw(self) -> *mut typelib_TypeDescriptionReference { + let ptr = self._pType; + std::mem::forget(self); // Don't run destructor since we're transferring ownership + ptr + } + + /// Test if values of this type can be assigned from values of the given type + /// + /// This includes widening conversions (e.g., long assignable from short) + /// as long as there is no data loss. The test follows UNO assignment rules + /// and is used in method dispatch to determine if argument types are compatible. + /// + /// # Arguments + /// * `other` - The source type to test assignment compatibility from + /// + /// # Returns + /// `true` if values of `other` type can be assigned to this type, `false` otherwise + pub fn is_assignable_from(&self, other: &Type) -> bool { + unsafe { typelib_typedescriptionreference_isAssignableFrom(self._pType, other._pType) != 0 } + } + + /// Test if this type is equal to another type + /// + /// Two types are equal if they have the same type class and fully qualified name. + /// This is used for type matching in UNO method dispatch and interface queries. + /// The comparison is efficient and uses LibreOffice's native type comparison. + /// + /// # Arguments + /// * `other` - The type to compare with + /// + /// # Returns + /// `true` if the types are equal, `false` otherwise + pub fn equals(&self, other: &Type) -> bool { + unsafe { typelib_typedescriptionreference_equals(self._pType, other._pType) != 0 } + } +} + +/// Default implementation: Creates a void type +/// +/// When a Type is created without explicit initialization, it defaults to +/// the void type, which is the UNO equivalent of "no type" or "empty type". +impl Default for Type { + fn default() -> Self { + Self::new() + } +} + +/// Clone implementation: Create an independent copy of another Type +/// +/// This creates a new Type that references the same type description but is +/// independently managed. The clone operation increments the reference count +/// of the underlying type description, ensuring proper memory management. +impl Clone for Type { + fn clone(&self) -> Self { + unsafe { + typelib_typedescriptionreference_acquire(self._pType); + } + Type { + _pType: self._pType, + } + } +} + +/// Equality comparison with other Type instances +/// +/// Two Type instances are considered equal if they refer to the same type +/// (same type class and name). This uses LibreOffice's native type comparison +/// which is efficient and handles all type categories correctly. +impl PartialEq for Type { + fn eq(&self, other: &Self) -> bool { + self.equals(other) + } +} + +impl Eq for Type {} + +/// Debug trait for debugging - shows type class and name for UNO type identification +/// +/// Displays the Type in a debug-friendly format showing both the type class +/// and the type name, which is useful for debugging UNO type-related issues. +impl std::fmt::Debug for Type { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let type_class = self.get_type_class(); + let type_name = self.get_type_name().to_string(); + f.debug_struct("Type") + .field("type_class", &type_class) + .field("type_name", &type_name) + .finish() + } +} + +/// Destructor: Automatic cleanup when Type goes out of scope +/// +/// Automatically releases the reference to the underlying type description +/// when the Type instance is dropped. This ensures proper memory management +/// without manual intervention. +impl Drop for Type { + fn drop(&mut self) { + unsafe { + typelib_typedescriptionreference_release(self._pType); + } + } +} + +/// From trait implementation for typelib_TypeClass (primitive types) +/// +/// Creates a Type from a primitive type class. This uses LibreOffice's static +/// type system to get pre-defined type references for standard UNO types like +/// void, boolean, string, etc. This is the most efficient way to create -e ... etc. - the rest is truncated
