diff --git a/.github/workflows/test-linux-mac.yml b/.github/workflows/test-linux-mac.yml new file mode 100644 index 0000000..2013b78 --- /dev/null +++ b/.github/workflows/test-linux-mac.yml @@ -0,0 +1,67 @@ +name: test-linux-mac +on: + push: + branches-ignore: + - prerelease + pull_request: + branches-ignore: + - prerelease + +defaults: + run: + shell: bash + +jobs: + test: + name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + version: + - "1.6" + - "1.10" + - "nightly" + os: + - macos-13 + - ubuntu-latest + arch: + - x64 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: julia-actions/setup-julia@latest + with: + version: ${{ matrix.version }} + arch: ${{ matrix.arch }} + - name: Build libcxxwrap + continue-on-error: true + run: | + mkdir libcxxwrap && cd libcxxwrap + + body="${{github.event.pull_request.body}}" + first_line=$(echo "$body" | sed -n '1p') + if [[ "$first_line" == *"#"* ]]; then + IFS='#' read -r left right <<< "$first_line" + + repo_url=$(echo "$left" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//') + branch=$(echo "$right" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//') + else + repo_url="$first_line" + branch="main" + fi + echo "repo_url: '$repo_url'" + echo "branch: '$branch'" + git clone --branch "$branch" --single-branch "$repo_url" . + echo "done cloning" + + if [[ "$OSTYPE" != "darwin"* ]]; then + rm -f /opt/hostedtoolcache/julia/1.6*/x64/lib/julia/libstdc++.so.6 + fi + mkdir build && cd build + cmake -DCMAKE_INSTALL_PREFIX=$HOME/install -DAPPEND_OVERRIDES_TOML=ON -DOVERRIDE_VERSION_TO_JLL=ON -DCMAKE_BUILD_TYPE=Debug .. + VERBOSE=ON cmake --build . --config Debug --target install + cd ../.. + - uses: julia-actions/julia-buildpkg@latest + - uses: julia-actions/julia-runtest@latest diff --git a/.github/workflows/test-win.yml b/.github/workflows/test-win.yml new file mode 100644 index 0000000..7d35a75 --- /dev/null +++ b/.github/workflows/test-win.yml @@ -0,0 +1,76 @@ +name: test-win +on: + push: + branches-ignore: + - prerelease + pull_request: + branches-ignore: + - prerelease + +defaults: + run: + shell: bash + +jobs: + test: + name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + version: + - "1.7" + - "1.10" + - "nightly" + os: + - windows-latest + arch: + - x64 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: julia-actions/setup-julia@latest + with: + version: ${{ matrix.version }} + arch: ${{ matrix.arch }} + - name: Clone libcxxwrap + continue-on-error: true + run: | + mkdir libcxxwrap && cd libcxxwrap + + body="${{github.event.pull_request.body}}" + first_line=$(echo "$body" | sed -n '1p') + if [[ "$first_line" == *"#"* ]]; then + IFS='#' read -r left right <<< "$first_line" + + repo_url=$(echo "$left" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//') + branch=$(echo "$right" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//') + else + repo_url="$first_line" + branch="main" + fi + echo "repo_url: '$repo_url'" + echo "branch: '$branch'" + git clone --branch "$branch" --single-branch "$repo_url" . + echo "done cloning" + - name: Config 32bit + continue-on-error: true + if: ${{ matrix.arch == 'x86'}} + run: | + cd libcxxwrap && mkdir build && cd build + cmake -G "Visual Studio 17 2022" -A Win32 -DOVERRIDES_PATH=$HOMEDRIVE/$HOMEPATH/.julia/artifacts/Overrides.toml -DOVERRIDE_ROOT=./ -DAPPEND_OVERRIDES_TOML=ON .. + - name: Config 64bit + continue-on-error: true + if: ${{ matrix.arch == 'x64'}} + run: | + cd libcxxwrap && mkdir build && cd build + cmake -G "Visual Studio 17 2022" -A x64 -DOVERRIDES_PATH=$HOMEDRIVE/$HOMEPATH/.julia/artifacts/Overrides.toml -DOVERRIDE_ROOT=./ -DAPPEND_OVERRIDES_TOML=ON .. + - name: Build libcxxwrap + continue-on-error: true + run: | + cd libcxxwrap/build + cmake --build . --config Release + + - uses: julia-actions/julia-buildpkg@latest + - uses: julia-actions/julia-runtest@latest diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 904058f..0000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: test -on: - push: - branches-ignore: - - prerelease - pull_request: - branches-ignore: - - prerelease - -defaults: - run: - shell: bash - -jobs: - test: - name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - version: - - '1.6' - - '1.9' - - 'nightly' - os: - - macOS-latest - - windows-latest - - ubuntu-latest - arch: - - x64 - steps: - - uses: actions/checkout@v2 - - uses: julia-actions/setup-julia@latest - with: - version: ${{ matrix.version }} - arch: ${{ matrix.arch }} - - run: julia -e "using Pkg; Pkg.status(); Pkg.Registry.add(RegistrySpec(url = \"https://github.com/barche/CxxWrapTestRegistry.git\"))" - - uses: julia-actions/julia-buildpkg@latest - - uses: julia-actions/julia-runtest@latest - diff --git a/Project.toml b/Project.toml index 3a11d30..fd39918 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "CxxWrap" uuid = "1f15a43c-97ca-5a2a-ae31-89f07a497df4" authors = ["Bart Janssens "] -version = "0.14.0" +version = "0.15.1" [deps] Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" @@ -11,7 +11,7 @@ libcxxwrap_julia_jll = "3eaa8342-bff7-56a5-9981-c04077f7cee7" [compat] MacroTools = "0.5.9" julia = "1.6" -libcxxwrap_julia_jll = "0.11.0" +libcxxwrap_julia_jll = "0.12.3" [extras] BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" diff --git a/README.md b/README.md index e4f6028..47b7b20 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ # CxxWrap -![test](https://github.com/JuliaInterop/CxxWrap.jl/workflows/test/badge.svg) - This package aims to provide a Boost.Python-like wrapping for C++ types and functions to Julia. The idea is to write the code for the Julia wrapper in C++, and then use a one-liner on the Julia side to make the wrapped C++ library available there. @@ -179,11 +177,11 @@ CppTypes.set(w, "hello") ``` The manually added constructor using the `constructor` function also creates a finalizer. -This can be disabled by adding the argument `false`: +This can be disabled by adding the argument `jlcxx::finalize_policy::no`: ```c++ types.add_type("World") - .constructor(false); + .constructor(jlcxx::finalize_policy::no); ``` The `add_type` function actually builds two Julia types related to `World`. @@ -296,6 +294,29 @@ auto multi_vector_base = mod.add_type>>("MultiVectorBase") auto vector_base = mod.add_type>>("VectorBase", multi_vector_base.dt()); ``` +### Conversion + +Conversion to the base type happens automatically, or can be forced by calling convert, e.g. + +```julia +convert(A,b) +``` + +Where we have `b::B` and `B <: A` + +For the equivalent of a C++ `dynamic_cast`, we need to use pointers because the conversion may fail, i.e: + +```julia +convert(CxxPtr{B},CxxPtr(a)) +``` + +This is equivalent to the C++ code: +```c++ +dynamic_cast(&a); +``` + +Use `isnull` on the result to check if the conversion was successful or not. + See the test at [`examples/inheritance.cpp`](https://github.com/JuliaInterop/libcxxwrap-julia/tree/master/examples/inheritance.cpp) and [`test/inheritance.jl`](test/inheritance.jl). ## Enum types @@ -385,7 +406,8 @@ There is also an `apply_combination` method to make applying all combinations of Full example and test including non-type parameters at: [`examples/parametric.cpp`](https://github.com/JuliaInterop/libcxxwrap-julia/tree/master/examples/parametric.cpp) and [`test/parametric.jl`](test/parametric.jl). -## Constructors and destructors +## Memory management +### Constructors and destructors The default constructor and any manually added constructor using the `constructor` function will automatically create a Julia object that has a finalizer attached that calls delete to free the memory. To write a C++ function that returns a new object that can be garbage-collected in Julia, use the `jlcxx::create` function: @@ -405,6 +427,13 @@ wvec = cpp_function_returning_vector() julia_array = copy.(wvec) ``` +### Return values +If a wrapped C++ function returns an object by value, the wrapped object gets a finalizer +and is owned by Julia. The same holds if a smart pointer such as `shared_ptr` (automatically +wrapped in a `SharedPtr`) is returned by value. In contrast to that, if a reference or raw +pointer is returned from C++, then the default assumption is that the pointed-to object +lifetime is managed by C++. + ## Call operator overload Since Julia supports overloading the function call operator `()`, this can be used to wrap `operator()` by just omitting the method name: @@ -648,10 +677,10 @@ The behavior of the macro can be customized by adding methods to `CxxWrap.refere ## Exceptions When directly adding a regular free C++ function as a method, it will be called directly using `ccall` and any exception will abort the Julia program. -To avoid this, you can force wrapping it in an `std::function` to intercept the exception automatically by setting the `force_convert` argument to `method` to true: +To avoid this, you can force wrapping it in an `std::function` to intercept the exception automatically by setting the `jlcxx::calling_policy` argument to `std_function`: ```c++ -mod.method("test_exception", test_exception, true); +mod.method("test_exception", test_exception, jlcxx::calling_policy::std_function); ``` Member functions and lambdas are automatically wrapped in an `std::function` and so any exceptions thrown there are always intercepted and converted to a Julia exception. @@ -902,6 +931,10 @@ mod.method("getSecondaryWorldVector", [](const World* p)->const std::vector(false)` becomes `.constructor(jlcxx::finalize_policy::no)` + ## References * [JuliaCon 2020 Talk: Julia and C++: a technical overview of CxxWrap.jl](https://www.youtube.com/watch?v=u7IaXwKSUU0) * [JuliaCon 2020 Workshop: Wrapping a C++ library using CxxWrap.jl](https://www.youtube.com/watch?v=VoXmXtqLhdo) diff --git a/src/CxxWrap.jl b/src/CxxWrap.jl index b8e10ea..421bffe 100644 --- a/src/CxxWrap.jl +++ b/src/CxxWrap.jl @@ -10,7 +10,7 @@ ConstCxxPtr, ConstCxxRef, CxxRef, CxxPtr, CppEnum, ConstArray, CxxBool, CxxLong, CxxULong, CxxChar, CxxChar16, CxxChar32, CxxWchar, CxxUChar, CxxSignedChar, CxxLongLong, CxxULongLong, ptrunion, gcprotect, gcunprotect, isnull, libcxxwrapversion -const libcxxwrap_version_range = (v"0.11.0", v"0.12") +const libcxxwrap_version_range = (v"0.12.1", v"0.13") using libcxxwrap_julia_jll # for libcxxwrap_julia and libcxxwrap_julia_stl @@ -129,6 +129,10 @@ Base.flipsign(x::T, y::T) where {T <: CxxSigned} = reinterpret(T, flipsign(to_ju struct IsCxxType end struct IsNormalType end +struct CxxConst{T} + cpp_object::Ptr{T} +end + @inline cpp_trait_type(::Type) = IsNormalType # Enum type interface @@ -152,29 +156,48 @@ Base.show(io::IO, x::SmartPointer) = print(io, "C++ smart pointer of type ", typ allocated_type(t::Type) = Any dereferenced_type(t::Type) = Any -__cxxwrap_smartptr_dereference(p::SmartPointer{T}) where {T} = __cxxwrap_smartptr_dereference(CxxRef(p)) -__cxxwrap_smartptr_construct_from_other(t::Type{<:SmartPointer{T}}, p::SmartPointer{T}) where {T} = __cxxwrap_smartptr_construct_from_other(t,CxxRef(p)) -__cxxwrap_smartptr_cast_to_base(p::SmartPointer{T}) where {T} = __cxxwrap_smartptr_cast_to_base(CxxRef(p)) +function __cxxwrap_smartptr_dereference end +function __cxxwrap_smartptr_construct_from_other end +function __cxxwrap_smartptr_cast_to_base end +function __cxxwrap_make_const_smartptr end function Base.getindex(p::SmartPointer{T}) where {T} - return __cxxwrap_smartptr_dereference(p) + return __cxxwrap_smartptr_dereference(CxxRef(p)) end # No conversion if source and target type are identical Base.convert(::Type{T}, p::T) where {PT,T <: SmartPointer{PT}} = p +# Conversion from non-const to const +Base.convert(::Type{CT}, p::T) where {PT,T <: SmartPointer{PT},CT <: SmartPointer{CxxConst{PT}}} = __cxxwrap_make_const_smartptr(ConstCxxRef(p)) + # Construct from a related pointer, e.g. a std::weak_ptr from std::shared_ptr function Base.convert(::Type{T1}, p::SmartPointer{T}) where {T, T1 <: SmartPointer{T}} - return __cxxwrap_smartptr_construct_from_other(T1, p) + return __cxxwrap_smartptr_construct_from_other(T1, CxxRef(p)) end -# upcast to base class -function Base.convert(::Type{T1}, p::SmartPointer{<:BaseT}) where {BaseT, T1 <: SmartPointer{BaseT}} +# Construct from a related pointer, e.g. a std::weak_ptr from std::shared_ptr. Const versions +function Base.convert(::Type{T1}, p::SmartPointer{CxxConst{T}}) where {T, T1 <: SmartPointer{CxxConst{T}}} + return __cxxwrap_smartptr_construct_from_other(T1, CxxRef(p)) +end +# Avoid improper method resolution on Julia < 1.10 +function Base.convert(::Type{T1}, p::T1) where {T, T1 <: SmartPointer{CxxConst{T}}} + return p +end + +function _base_convert_impl(::Type{T1}, p) where{T1} # First convert to base type - base_p = __cxxwrap_smartptr_cast_to_base(p) + base_p = __cxxwrap_smartptr_cast_to_base(ConstCxxRef(p)) return convert(T1, base_p) end +# upcast to base class, non-const version +Base.convert(::Type{T1}, p::SmartPointer{<:BaseT}) where {BaseT, T1 <: SmartPointer{BaseT}} = _base_convert_impl(T1, p) +# upcast to base class, non-const to const version +Base.convert(::Type{T1}, p::SmartPointer{<:BaseT}) where {BaseT, T1 <: SmartPointer{CxxConst{BaseT}}} = _base_convert_impl(T1, p) +# upcast to base, const version +Base.convert(::Type{T1}, p::SmartPointer{CxxConst{SuperT}}) where {BaseT, SuperT<:BaseT, T1<:SmartPointer{CxxConst{BaseT}}} = _base_convert_impl(T1, p) + struct StrictlyTypedNumber{NumberT} value::NumberT @static if Sys.iswindows() @@ -271,6 +294,7 @@ Base.setindex!(x::CxxBaseRef, val, i::Int) = Base.setindex!(x[], val, i) Base.unsafe_convert(to_type::Type{<:CxxBaseRef}, x) = to_type(x.cpp_object) # This is defined on the C++ side for each wrapped type +cxxdowncast(::Type{T}, x::CxxPtr{T}) where {T} = x cxxupcast(x) = cxxupcast(CxxRef(x)) cxxupcast(x::CxxRef) = error("No upcast for type $(supertype(typeof(x))). Did you specialize SuperType to enable automatic upcasting?") function cxxupcast(::Type{T}, x) where {T} @@ -278,6 +302,9 @@ function cxxupcast(::Type{T}, x) where {T} end cxxupcast(::Type{T}, x::CxxBaseRef{T}) where {T} = x +# Dynamic cast equivalent +Base.convert(::Type{CxxPtr{DerivedT}}, x::CxxPtr{SuperT}) where {SuperT, DerivedT <: SuperT} = cxxdowncast(DerivedT, x) + struct ConstArray{T,N} <: AbstractArray{T,N} ptr::ConstCxxPtr{T} size::NTuple{N,Int} @@ -304,6 +331,10 @@ mutable struct CppFunctionInfo function_pointer::Ptr{Cvoid} thunk_pointer::Ptr{Cvoid} override_module::Module + doc::Any + argument_names::Array{Any,1} + argument_default_values::Array{Any,1} + n_keyword_arguments::Any end # Interpreted as a constructor for Julia > 0.5 @@ -358,7 +389,7 @@ function _register_function_pointers(func, precompiling) if haskey(__global_method_map, mkey) existing = __global_method_map[mkey] if existing[3] == precompiling - error("Double registration for method $mkey") + error("Double registration for method $mkey: $(func.name); $(func.argument_types); $(func.return_type)") end end __global_method_map[mkey] = fptrs @@ -469,9 +500,9 @@ function process_fname(fn::CallOpOverload) return :(arg1::$(fn._type)) end -make_func_declaration(fn, argmap, julia_mod) = :($(process_fname(fn, julia_mod))($(argmap...))) -function make_func_declaration(fn::Tuple{CallOpOverload,Module}, argmap, julia_mod) - return :($(process_fname(fn, julia_mod))($((argmap[2:end])...))) +make_func_declaration(fn, argmap, kw_argmap, julia_mod) = :($(process_fname(fn, julia_mod))($(argmap...);$(kw_argmap...))) +function make_func_declaration(fn::Tuple{CallOpOverload,Module}, argmap, kw_argmap, julia_mod) + return :($(process_fname(fn, julia_mod))($((argmap[2:end])...);$(kw_argmap...))) end # By default, no argument overloading happens @@ -498,6 +529,7 @@ function ptrunion(::Type{T}) where {T} return result end +# valuetype is the non-reference, non-pointer type that is used in the method argument list. Overloading this allows mapping these types to something more useful valuetype(t::Type) = valuetype(Base.invokelatest(cpp_trait_type,t), t) valuetype(::Type{IsNormalType}, ::Type{T}) where {T} = T function valuetype(::Type{IsCxxType}, ::Type{T}) where {T} @@ -507,10 +539,16 @@ function valuetype(::Type{IsCxxType}, ::Type{T}) where {T} end return T end +# Smart pointer arguments can also take subclass arguments function valuetype(::Type{<:SmartPointer{T}}) where {T} result{T2 <: T} = SmartPointer{T2} return result end +# Smart pointers with const arguments can also take both const and non-const subclass arguments +function valuetype(::Type{<:SmartPointer{CxxConst{T}}}) where {T} + result{T2 <: T} = Union{SmartPointer{T2},SmartPointer{CxxConst{T2}}} + return result +end map_julia_arg_type(t::Type) = Union{valuetype(t), argument_overloads(t)...} map_julia_arg_type(a::Type{StrictlyTypedNumber{T}}) where {T} = T @@ -548,10 +586,12 @@ map_julia_arg_type(t::Type{ConstCxxPtr{CxxChar}}) = Union{ConstPtrTypes{Cchar}, # names excluded from julia type mapping const __excluded_names = Set([ + :cxxdowncast, :cxxupcast, :__cxxwrap_smartptr_dereference, :__cxxwrap_smartptr_construct_from_other, - :__cxxwrap_smartptr_cast_to_base + :__cxxwrap_smartptr_cast_to_base, + :__cxxwrap_make_const_smartptr, ]) function Base.cconvert(to_type::Type{<:CxxBaseRef{T}}, x) where {T} @@ -568,6 +608,7 @@ Base.unsafe_convert(to_type::Type{<:CxxBaseRef}, v::Base.RefValue) = to_type(poi Base.cconvert(::Type{CxxPtr{CxxPtr{CxxChar}}}, v::Vector{String}) = Base.cconvert(Ptr{Ptr{Cchar}}, v) Base.unsafe_convert(to_type::Type{CxxPtr{CxxPtr{CxxChar}}}, a::Base.RefArray{Ptr{Int8}, Vector{Ptr{Int8}}, Any}) = to_type(Base.unsafe_convert(Ptr{Ptr{Cchar}}, a)) +Base.unsafe_convert(to_type::Type{CxxPtr{CxxPtr{CxxChar}}}, a::Base.RefArray{Ptr{Int8}, Vector{Ptr{Int8}}, Vector{Any}}) = to_type(Base.unsafe_convert(Ptr{Ptr{Cchar}}, a)) cxxconvert(to_type::Type{<:CxxBaseRef{T}}, x, ::Type{IsNormalType}) where {T} = Ref{T}(convert(T,x)) cxxconvert(to_type::Type{<:CxxBaseRef{T}}, x::Base.RefValue, ::Type{IsNormalType}) where {T} = x @@ -586,7 +627,10 @@ end function build_function_expression(func::CppFunctionInfo, funcidx, julia_mod) # Arguments and types argtypes = func.argument_types - argsymbols = map((i) -> Symbol(:arg,i[1]), enumerate(argtypes)) + argnames = func.argument_names + argsymbols = map((i) -> i[1] <= length(argnames) ? Symbol(argnames[i[1]]) : Symbol(:arg,i[1]), enumerate(argtypes)) + n_kw_args = func.n_keyword_arguments + arg_default_values = func.argument_default_values map_c_arg_type(t::Type) = map_c_arg_type(Base.invokelatest(cpp_trait_type, t), t) map_c_arg_type(::Type{IsNormalType}, t::Type) = t @@ -638,13 +682,33 @@ function build_function_expression(func::CppFunctionInfo, funcidx, julia_mod) # Build an array of arg1::Type1... expressions function argmap(signature) result = Expr[] - for (t, s) in zip(signature, argsymbols) - push!(result, :($s::$(map_julia_arg_type_named(func.name, t)))) + for (t, s, i) in zip(signature, argsymbols, 1:length(signature)) + argt = map_julia_arg_type_named(func.name, t) + if isassigned(arg_default_values, i) + # somewhat strange syntax to define default argument argument... + kw = Expr(:kw) + push!(kw.args, :($s::$argt)) + # convert to t to avoid problems on the calling site with mismatchin data types + # (e.g. f(x::Int64 = Int32(1)) = ... is not callable without argument because f(Int32) does not exist + push!(kw.args, t(arg_default_values[i])) + push!(result, kw) + else + push!(result, :($s::$argt)) + end end return result end - function_expression = :($(make_func_declaration((func.name,func.override_module), argmap(argtypes), julia_mod))::$(map_julia_return_type(func.julia_return_type)) = $call_exp) + complete_argmap = argmap(argtypes) + pos_argmap = complete_argmap[1:end-n_kw_args] + kw_argmap = complete_argmap[end-n_kw_args+1:end] + + function_expression = :($(make_func_declaration((func.name,func.override_module), pos_argmap, kw_argmap, julia_mod))::$(map_julia_return_type(func.julia_return_type)) = $call_exp) + + if func.doc != "" + function_expression = :(Core.@doc $(func.doc) $function_expression) + end + return function_expression end @@ -749,7 +813,7 @@ function readmodule(so_path_cb::Function, funcname, m::Module, flags) so_path = so_path_cb() fptr = Libdl.dlsym(Libdl.dlopen(so_path, flags), funcname) register_julia_module(m, fptr) - include_dependency(so_path) + include_dependency(Libdl.dlpath(so_path)) end function wrapmodule(so_path::String, funcname, m::Module, flags) @@ -843,7 +907,7 @@ macro cxxdereference(f) fdict[:kwargs] .= maparg.(fdict[:kwargs]) # Dereference the arguments - deref_expr = quote end + deref_expr = Expr(:block) for arg in vcat(fdict[:args], fdict[:kwargs]) (argname, _, slurp, _) = MacroTools.splitarg(arg) if argname === nothing @@ -876,8 +940,8 @@ ConstCxxPtr, ConstCxxRef, CxxRef, CxxPtr, CppEnum, ConstArray, CxxBool, CxxLong, CxxULong, CxxChar, CxxChar16, CxxChar32, CxxWchar, CxxUChar, CxxSignedChar, CxxLongLong, CxxULongLong, ptrunion, gcprotect, gcunprotect, isnull -using .StdLib: StdVector, StdString, StdWString, StdValArray, StdThread, StdDeque +using .StdLib: StdVector, StdString, StdWString, StdValArray, StdThread, StdDeque, StdQueue -export StdLib, StdVector, StdString, StdWString, StdValArray, StdThread, StdDeque +export StdLib, StdVector, StdString, StdWString, StdValArray, StdThread, StdDeque, StdQueue end # module diff --git a/src/StdLib.jl b/src/StdLib.jl index 5a11931..ea90e92 100644 --- a/src/StdLib.jl +++ b/src/StdLib.jl @@ -36,44 +36,69 @@ Base.ncodeunits(s::CppBasicString)::Int = cppsize(s) Base.codeunit(s::StdString) = UInt8 Base.codeunit(s::StdWString) = Cwchar_t == Int32 ? UInt32 : UInt16 Base.codeunit(s::CppBasicString, i::Integer) = reinterpret(codeunit(s), cxxgetindex(s,i)) -Base.isvalid(s::CppBasicString, i::Integer) = (0 < i <= ncodeunits(s)) -function Base.iterate(s::CppBasicString, i::Integer=1) - if !isvalid(s,i) - return nothing - end - return(convert(Char,codeunit(s,i)),i+1) +Base.isvalid(s::CppBasicString, i::Int) = checkbounds(Bool, s, i) && thisind(s, i) == i +@inline Base.between(b::T1, lo::T2, hi::T2) where {T1<:Integer,T2<:Integer} = (lo ≤ b) & (b ≤ hi) +Base.thisind(s::CppBasicString, i::Int) = Base._thisind_str(s, i) +Base.nextind(s::CppBasicString, i::Int) = Base._nextind_str(s, i) + +function Base.iterate(s::CppBasicString, i::Integer=firstindex(s)) + i > ncodeunits(s) && return nothing + return convert(Char, codeunit(s, i)), nextind(s, i) +end + +# Since the Julia base string iteration is `String` specific we need to implement our own. +# This implementation is based around a functioning `nextind` which allows us to convert the +# UTF-8 codeunits into their big-endian encoding. +function Base.iterate(s::StdString, i::Integer=firstindex(s)) + i > ncodeunits(s) && return nothing + j = isvalid(s, i) ? nextind(s, i) : i + 1 + u = UInt32(codeunit(s, i)) << 24 + (i += 1) < j || @goto ret + u |= UInt32(codeunit(s, i)) << 16 + (i += 1) < j || @goto ret + u |= UInt32(codeunit(s, i)) << 8 + (i += 1) < j || @goto ret + u |= UInt32(codeunit(s, i)) + @label ret + return reinterpret(Char, u), j +end + +function Base.getindex(s::CppBasicString, i::Int) + checkbounds(s, i) + isvalid(s, i) || Base.string_index_err(s, i) + c, i = iterate(s, i) + return c end -Base.getindex(s::CppBasicString, i::Int) = Char(cxxgetindex(s,i)) function StdWString(s::String) char_arr = transcode(Cwchar_t, s) StdWString(char_arr, length(char_arr)) end -function StdVector(v::Vector{T}) where {T} - if isempty(v) - return StdVector{T}() - end - if (CxxWrapCore.cpp_trait_type(T) == CxxWrapCore.IsCxxType) - return StdVector(CxxRef.(v)) - end +function StdVector{T}(v::Union{Vector{T},Vector{CxxRef{T}}}) where {T} result = StdVector{T}() - append(result, v) + isempty(v) || append(result, v) return result end +StdVector{T}(v::Vector) where {T} = StdVector{T}(convert(Vector{T}, v)) + function StdVector(v::Vector{CxxRef{T}}) where {T} - result = isconcretetype(T) ? StdVector{supertype(T)}() : StdVector{T}() - append(result, v) - return result + S = isconcretetype(T) ? supertype(T) : T + return StdVector{S}(v) end -function StdVector(v::Vector{Bool}) - result = StdVector{CxxBool}() - append(result, convert(Vector{CxxBool}, v)) - return result +function StdVector(v::Vector{T}) where {T} + S = if isconcretetype(T) && CxxWrapCore.cpp_trait_type(T) == CxxWrapCore.IsCxxType + supertype(T) + else + T + end + return StdVector{S}(v) end +StdVector(v::Vector{Bool}) = StdVector{CxxBool}(v) + Base.IndexStyle(::Type{<:StdVector}) = IndexLinear() Base.size(v::StdVector) = (Int(cppsize(v)),) Base.getindex(v::StdVector, i::Int) = cxxgetindex(v,i)[] @@ -101,7 +126,7 @@ end return v end -@cxxdereference Base.String(s::StdString) = unsafe_string(reinterpret(Ptr{Cchar},c_str(s).cpp_object)) +@cxxdereference Base.String(s::StdString) = unsafe_string(reinterpret(Ptr{Cchar},c_str(s).cpp_object), cppsize(s)) @cxxdereference function Base.String(s::StdWString) chars = unsafe_wrap(Vector{Cwchar_t}, reinterpret(Ptr{Cwchar_t},c_str(s).cpp_object), (cppsize(s),)) return transcode(String, chars) @@ -112,9 +137,52 @@ Base.cmp(a::String, b::CppBasicString) = cmp(a,String(b)) # Make sure functions taking a C++ string as argument can also take a Julia string CxxWrapCore.map_julia_arg_type(x::Type{<:StdString}) = AbstractString -StdLib.StdStringAllocated(x::String) = StdString(x) -Base.cconvert(::Type{CxxWrapCore.ConstCxxRef{StdString}}, x::String) = StdString(x) -Base.cconvert(::Type{StdLib.StdStringDereferenced}, x::String) = StdString(x) + +""" + StdString(str::String) + +Create a `StdString` from the contents of the string. Any null-characters ('\\0') will be +included in the string such that `ncodeunits(str) == ncodeunits(StdString(str))`. +""" +StdString(x::String) = StdString(x, ncodeunits(x)) + +""" + StdString(str::Union{Cstring, Base.CodeUnits, Vector{UInt8}, Ref{Int8}, Array{Int8}}) + +Create a `StdString` from the null-terminated character sequence. + +If you want to construct a `StdString` that includes the null-character ('\\0') either use +[`StdString(::String)`](@ref) or [`StdString(::Any, ::Int)`](@ref). + +## Examples + +```julia +julia> StdString(b"visible\\0hidden") +"visible" +``` +""" +StdString(::Union{Cstring, Base.CodeUnits, Vector{UInt8}, Ref{Int8}, Array{Int8}}) + +StdString(x::Cstring) = StdString(convert(Ptr{Int8}, x)) +StdString(x::Base.CodeUnits) = StdString(collect(x)) +StdString(x::Vector{UInt8}) = StdString(collect(reinterpret(Int8, x))) + +""" + StdString(str, n::Integer) + +Create a `StdString` from the first `n` code units of `str` (including null-characters). + +## Examples + +```julia +julia> StdString("visible\\0hidden", 10) +"visible\\0hi" +``` +""" +StdString(::Any, ::Integer) + +Base.cconvert(::Type{CxxWrapCore.ConstCxxRef{StdString}}, x::String) = StdString(x, ncodeunits(x)) +Base.cconvert(::Type{StdLib.StdStringDereferenced}, x::String) = StdString(x, ncodeunits(x)) Base.unsafe_convert(::Type{CxxWrapCore.ConstCxxRef{StdString}}, x::StdString) = ConstCxxRef(x) function StdValArray(v::Vector{T}) where {T} @@ -126,27 +194,37 @@ Base.size(v::StdValArray) = (Int(cppsize(v)),) Base.getindex(v::StdValArray, i::Int) = cxxgetindex(v,i)[] Base.setindex!(v::StdValArray{T}, val, i::Int) where {T} = cxxsetindex!(v, convert(T,val), i) -function StdDeque() where {T} - return StdDeque{T}() +# Deque +Base.IndexStyle(::Type{<:StdDeque}) = IndexLinear() +Base.size(v::StdDeque) = (Int(cppsize(v)),) +Base.getindex(v::StdDeque, i::Int) = cxxgetindex(v,i)[] +Base.setindex!(v::StdDeque{T}, val, i::Int) where {T} = cxxsetindex(v, convert(T,val), i) +Base.push!(v::StdDeque, x) = push_back(v, x) +Base.pushfirst!(v::StdDeque, x) = push_front(v, x) +Base.pop!(v::StdDeque) = pop_back(v) +Base.popfirst!(v::StdDeque) = pop_front(v) +Base.resize!(v::StdDeque, n::Integer) = resize(v, n) +Base.empty!(v::StdDeque) = clear(v) + +Base.:(==)(a::StdDequeIterator, b::StdDequeIterator) = iterator_is_equal(a,b) +function _iteration_tuple(d::StdDeque, state::StdDequeIterator) + if state == iteratorend(d) return nothing end + return (iterator_value(state), state) end +Base.iterate(d::StdDeque) = _iteration_tuple(d, iteratorbegin(d)) +Base.iterate(d::StdDeque, state::StdDequeIterator) = _iteration_tuple(d, iterator_next(state)) -Base.IndexStyle(::Type{<:StdDeque}) = IndexLinear() -Base.size(d::StdDeque) = (Int(cppsize(d)),) -Base.resize!(d::StdDeque, n::Integer) = resize(d, n) -Base.getindex(d::StdDeque, i::Int) = cxxgetindex(d, i)[] -Base.setindex!(d::StdDeque{T}, val, i::Int) where {T} = cxxsetindex(d, convert(T, val), i) -#TODO: edit the cxx part to enable push to get more than two arguments -Base.push!(d::StdDeque, x) = push_back(d, x) -Base.pushfirst!(d::StdDeque, x) = push_front(d, x) -Base.pop!(d::StdDeque) = pop_back(d) -Base.popfirst!(d::StdDeque) = pop_front(d) -Base.isempty(d::StdDeque) = isEmpty(d) -Base.empty!(d::StdDeque) = clear(d) - -# Iteration utilities -Base.:(==)(a::StdIterator, b::StdIterator) = iterator_is_equal(a, b) -_deque_iteration_tuple(d::StdDeque, state::StdIterator) = (state == iteratorend(d)) ? nothing : (iterator_value(state), state) -Base.iterate(d::StdDeque) = _deque_iteration_tuple(d, iteratorbegin(d)) -Base.iterate(d::StdDeque, state::StdIterator) = (state != iteratorend(d)) ? _deque_iteration_tuple(d, iterator_next(state)) : nothing -#TODO:remove the iterator_value method from the cxx part, since it is not needed -end # module + + +# Queue +Base.size(v::StdQueue) = (Int(cppsize(v)),) +Base.push!(v::StdQueue, x) = push_back(v, x) +Base.first(v::StdQueue) = front(v) +Base.pop!(v::StdQueue) = pop_front(v) + +function Base.fill!(v::T, x) where T <: Union{StdVector, StdValArray, StdDeque} + StdFill(v, x) + return v +end + +end \ No newline at end of file diff --git a/test/basic_types.jl b/test/basic_types.jl index 61f4d52..3c72ecb 100644 --- a/test/basic_types.jl +++ b/test/basic_types.jl @@ -34,9 +34,19 @@ end @testset "$(basename(@__FILE__)[1:end-3])" begin +function compare_collections(a, b) + equalities = a .== b + result = all(equalities) + if !result + neqs = (!).(equalities) + println("collections differ: $(a[neqs]) ≠ $(b[neqs])") + end + return result +end + let funcs = CxxWrap.CxxWrapCore.get_module_functions(CxxWrap.StdLib) @test CxxWrap.StdLib.__cxxwrap_methodkeys[1] == CxxWrap.CxxWrapCore.methodkey(funcs[1]) - @test all(CxxWrap.StdLib.__cxxwrap_methodkeys .== CxxWrap.CxxWrapCore.methodkey.(funcs)) + @test compare_collections(CxxWrap.StdLib.__cxxwrap_methodkeys, CxxWrap.CxxWrapCore.methodkey.(funcs)) end let a = BasicTypes.A(2,3) diff --git a/test/functions.jl b/test/functions.jl index 5c2b98c..fbdfe36 100644 --- a/test/functions.jl +++ b/test/functions.jl @@ -73,6 +73,16 @@ end # Test functions from the CppTestFunctions module @test CppTestFunctions.concatenate_numbers(4, 2.) == "42" +if VERSION < v"1.7" + @test startswith(methods(CppTestFunctions.concatenate_numbers_with_named_args).ms[1].slot_syms, "#self#\0i\0d\0") +else + @test startswith(methods(CppTestFunctions.concatenate_numbers_with_named_args)[1].slot_syms, "#self#\0i\0d\0") +end +@test CppTestFunctions.concatenate_numbers_with_kwargs(d=2., i=4) == "42" +@test CppTestFunctions.concatenate_numbers_with_default_values(3) == "35.2" +@test CppTestFunctions.concatenate_numbers_with_default_values_of_different_type(3) == "35" +@test CppTestFunctions.concatenate_numbers_with_default_kwarg(3) == "35.2" +@test CppTestFunctions.concatenate_numbers_with_default_kwarg(3, d=7) == "37" @test hasmethod(CppTestFunctions.concatenate_numbers, (Union{Cint,CxxWrap.CxxWrapCore.argument_overloads(Cint)...},Union{Cdouble,CxxWrap.CxxWrapCore.argument_overloads(Cdouble)...})) @test CppTestFunctions.concatenate_strings(2, "ho", "la") == "holahola" @test CppTestFunctions.test_int32_array(Int32[1,2]) diff --git a/test/inheritance.jl b/test/inheritance.jl index f2d2c1a..0eb960a 100644 --- a/test/inheritance.jl +++ b/test/inheritance.jl @@ -40,7 +40,15 @@ global d = D() @test take_ref(d) == "D" # factory function returning an abstract type A -@test message(create_abstract()) == "B" +let abstract_b = create_abstract() + @test message(abstract_b) == "B" + abstract_b_ptr = CxxPtr(abstract_b) + @test !isnull(convert(CxxPtr{B},abstract_b_ptr)) + @test message(convert(CxxPtr{B},abstract_b_ptr)) == "B" + @test isnull(convert(CxxPtr{C},abstract_b_ptr)) + @test isnull(convert(CxxPtr{D},abstract_b_ptr)) + @test convert(CxxPtr{A},abstract_b_ptr) === abstract_b_ptr +end @test dynamic_message_c(c) == "C" diff --git a/test/stdlib.jl b/test/stdlib.jl index 37d7b70..2584ede 100644 --- a/test/stdlib.jl +++ b/test/stdlib.jl @@ -1,6 +1,10 @@ using CxxWrap using Test +# Can use invalid character literals (e.g. '\xa8') as of Julia 1.9: +# https://github.com/JuliaLang/julia/pull/44989 +malformed_char(x) = reinterpret(Char, UInt32(x) << 24) + @testset "$(basename(@__FILE__)[1:end-3])" begin let s = StdString("test") @@ -38,34 +42,240 @@ let s = StdString("foo") @test unsafe_string(CxxWrap.StdLib.c_str(s),2) == "fo" end -stvec = StdVector(Int32[1,2,3]) -@test all(stvec .== [1,2,3]) -push!(stvec,1) -@test all(stvec .== [1,2,3,1]) -resize!(stvec,2) -@test all(stvec .== [1,2]) -append!(stvec,Int32[2,1]) -@test all(stvec .== [1,2,2,1]) -empty!(stvec) -@test isempty(stvec) - -@test all(StdVector([1,2,3]) .== [1,2,3]) -@test all(StdVector([1.0,2.0,3.0]) .== [1,2,3]) -@test all(StdVector([true, false, true]) .== [true, false, true]) -bvec = StdVector([true, false, true]) -append!(bvec, [true]) -@test all(bvec .== [true, false, true, true]) - -cxxstrings = StdString["one", "two", "three"] -svec = StdVector(CxxRef.(cxxstrings)) -@test all(svec .== ["one", "two", "three"]) -push!(svec, StdString("four")) -@test all(svec .== ["one", "two", "three", "four"]) -cxxappstrings = StdString["five", "six"] -append!(svec, CxxRef.(cxxappstrings)) -@test all(svec .== ["one", "two", "three", "four", "five", "six"]) -empty!(svec) -@test isempty(svec) +let str = "\x01\x00\x02" + std_str = StdString(codeunits(str)) + @test length(std_str) == 1 + @test collect(std_str) == ['\x01'] + @test ncodeunits(std_str) == 1 + @test codeunits(std_str) == b"\x01" + + std_str = StdString(str) + @test length(std_str) == 3 + @test collect(std_str) == ['\x01', '\x00', '\x02'] + @test ncodeunits(std_str) == 3 + @test codeunits(std_str) == b"\x01\x00\x02" + + std_str = StdString(str, 2) + @test length(std_str) == 2 + @test collect(std_str) == ['\x01', '\x00'] + @test ncodeunits(std_str) == 2 + @test codeunits(std_str) == b"\x01\x00" + + std_str = convert(StdString, str) + @test length(std_str) == 3 + @test collect(std_str) == ['\x01', '\x00', '\x02'] + @test ncodeunits(std_str) == 3 + @test codeunits(std_str) == b"\x01\x00\x02" + @test convert(String, std_str) == str +end + +let str = "α\0β" + std_str = StdString(codeunits(str)) + @test length(std_str) == 1 + @test collect(std_str) == ['α'] + @test ncodeunits(std_str) == 2 + @test codeunits(std_str) == b"α" + + std_str = StdString(str) + @test length(std_str) == 3 + @test collect(std_str) == ['α', '\0', 'β'] + @test ncodeunits(std_str) == 5 + @test codeunits(std_str) == b"α\0β" + + std_str = StdString(str, 4) + @test length(std_str) == 3 + @test collect(std_str) == ['α', '\0', malformed_char(0xce)] + @test ncodeunits(std_str) == 4 + @test codeunits(std_str) == b"α\0\xce" + + std_str = convert(StdString, str) + @test length(std_str) == 3 + @test collect(std_str) == ['α', '\0', 'β'] + @test ncodeunits(std_str) == 5 + @test codeunits(std_str) == b"α\0β" + @test convert(String, std_str) == str +end + +@testset "StdString" begin + @testset "null-terminated constructors" begin + c_str = Cstring(Base.unsafe_convert(Ptr{Cchar}, "visible\0hidden")) + @test StdString(c_str) == "visible" + @test StdString(b"visible\0hidden") == "visible" + @test StdString(UInt8[0xff, 0x00, 0xff]) == "\xff" + end + + @testset "iterate" begin + s = StdString("𨉟") + @test iterate(s) == ('𨉟', 5) + @test iterate(s, firstindex(s)) == ('𨉟', 5) + @test iterate(s, 2) == (malformed_char(0xa8), 3) + @test iterate(s, 3) == (malformed_char(0x89), 4) + @test iterate(s, 4) == (malformed_char(0x9f), 5) + @test iterate(s, 5) === nothing + @test iterate(s, typemax(Int)) === nothing + end + + @testset "getindex" begin + s = StdString("α") + @test getindex(s, firstindex(s)) == 'α' + @test_throws StringIndexError getindex(s, 2) + @test_throws BoundsError getindex(s, 3) + end +end + +@testset "StdWString" begin + @testset "iterate" begin + char = codeunit(StdWString()) == UInt32 ? '😄' : 'α' + s = StdWString(string(char)) + @test iterate(s) == (char, 2) + @test iterate(s, firstindex(s)) == (char, 2) + @test iterate(s, 2) === nothing + @test iterate(s, typemax(Int)) === nothing + end + + @testset "getindex" begin + char = codeunit(StdWString()) == UInt32 ? '😄' : 'α' + s = StdWString(string(char)) + @test getindex(s, firstindex(s)) == char + @test_throws BoundsError getindex(s, 2) + end +end + +@testset "StdVector" begin + @testset "parameterized constructors" begin + vec = StdVector{Int}() + @test vec isa StdVector{Int} + @test isempty(vec) + + vec = StdVector{Int}([]) + @test vec isa StdVector{Int} + @test isempty(vec) + + vec = StdVector{Any}([]) + @test vec isa StdVector{Any} + @test isempty(vec) + + vec = StdVector{Int}([1,2,3]) + @test vec isa StdVector{Int} + @test vec == [1,2,3] + + vec = StdVector{Any}([1,2,3]) + @test vec isa StdVector{Any} + @test vec == [1,2,3] + + vec = StdVector{Float64}([1,2,3]) + @test vec isa StdVector{Float64} + @test vec == [1.0,2.0,3.0] + + vec = StdVector{CxxBool}([true, false, true]) + @test vec isa StdVector{CxxBool} + @test vec == [true, false, true] + + vec = StdVector{StdString}(["a", "b", "c"]) + @test vec isa StdVector{StdString} + @test vec == ["a", "b", "c"] + + svec_alloc = StdString.(["a", "b", "c"])::Vector{CxxWrap.StdLib.StdStringAllocated} + vec = StdVector{StdString}(svec_alloc) + @test vec isa StdVector{StdString} + @test vec == ["a", "b", "c"] + + svec_ref = CxxRef.(StdString["a", "b", "c"]) + vec = StdVector{StdString}(svec_ref) + @test vec isa StdVector{StdString} + @test vec == ["a", "b", "c"] + + svec_deref = getindex.(svec_ref)::Vector{CxxWrap.StdLib.StdStringDereferenced} + vec = StdVector{StdString}(svec_deref) + @test vec isa StdVector{StdString} + @test vec == ["a", "b", "c"] + + @test_throws MethodError StdVector{Bool}([true]) + @test_throws MethodError StdVector{eltype(svec_alloc)}(svec_alloc) + @test_throws MethodError StdVector{eltype(svec_deref)}(svec_deref) + end + + @testset "constructors" begin + @test_throws MethodError StdVector() + + vec = StdVector(Int[]) + @test vec isa StdVector{Int} + @test isempty(vec) + + vec = StdVector(Any[]) + @test vec isa StdVector{Any} + @test isempty(vec) + + vec = StdVector([1,2,3]) + @test vec isa StdVector{Int} + @test vec == [1,2,3] + + vec = StdVector(Any[1,2,3]) + @test vec isa StdVector{Any} + @test vec == [1,2,3] + + vec = StdVector([1.0, 2.0, 3.0]) + @test vec isa StdVector{Float64} + @test vec == [1,2,3] + + vec = StdVector([true, false, true]) + @test vec isa StdVector{CxxBool} + @test vec == [true, false, true] + + vec = StdVector(StdString["a", "b", "c"]) + @test vec isa StdVector{StdString} + @test vec == ["a", "b", "c"] + + svec_alloc = StdString.(["a", "b", "c"])::Vector{CxxWrap.StdLib.StdStringAllocated} + vec = StdVector(svec_alloc) + @test vec isa StdVector{StdString} + @test vec == ["a", "b", "c"] + + svec_ref = CxxRef.(StdString["a", "b", "c"]) + vec = StdVector(svec_ref) + @test vec isa StdVector{StdString} + @test vec == ["a", "b", "c"] + + svec_deref = getindex.(svec_ref)::Vector{CxxWrap.StdLib.StdStringDereferenced} + vec = StdVector(svec_deref) + @test vec isa StdVector{StdString} + @test vec == ["a", "b", "c"] + + @test_throws MethodError StdVector(["a", "b", "c"]) + end + + @testset "mutating with integer" begin + stvec = StdVector(Int32[1,2,3]) + @test stvec == [1,2,3] + push!(stvec,1) + @test stvec == [1,2,3,1] + resize!(stvec, 2) + @test stvec == [1,2] + append!(stvec, Int32[2,1]) + @test stvec == [1,2,2,1] + empty!(stvec) + @test isempty(stvec) + end + + @testset "mutating with bool" begin + bvec = StdVector([true, false, true]) + append!(bvec, [true]) + @test bvec == [true, false, true, true] + end + + @testset "mutating with StdString" begin + cxxstrings = StdString["one", "two", "three"] + svec = StdVector(CxxRef.(cxxstrings)) + @test svec == ["one", "two", "three"] + push!(svec, StdString("four")) + @test svec == ["one", "two", "three", "four"] + cxxappstrings = StdString["five", "six"] + append!(svec, CxxRef.(cxxappstrings)) + @test svec == ["one", "two", "three", "four", "five", "six"] + empty!(svec) + @test isempty(svec) + end +end stvec = StdVector(Int32[1,2,3]) for vref in (CxxRef(stvec), CxxPtr(stvec)) @@ -122,4 +332,62 @@ let @test state == nothing end -end \ No newline at end of file +let + @show "test queue" + queue = StdQueue{Int64}() + @test length(queue) == 0 + push!(queue, 10) + push!(queue, 20) + @test length(queue) == 2 + @test first(queue) == 10 + pop!(queue) + @test first(queue) == 20 + @test length(queue) == 1 +end + +@static if isdefined(StdLib, :HAS_RANGES) + +@testset "StdFill" begin + @testset "fill StdVector" begin + v = StdVector{Int64}([1, 2, 3, 4, 5]) + fill!(v, 1) + for x in v + @test x == 1 + end + end + + @testset "fill StdValArray" begin + v = StdValArray([1.0, 2.0, 3.0]) + fill!(v, 2) + for x in v + @test x == 2 + end + end + + @testset "fill StdDeque" begin + deq = StdDeque{Int64}() + for i = 1:10 + push!(deq, i) + end + fill!(deq, 3) + for x in deq + @test x == 3 + end + end +end + +@testset "StdDequeIterator" begin + d = StdDeque{Int64}() + for i = 1:4 + push!(d, i) + end + iteration_tuple = iterate(d) + for i = 1:4 + @test iteration_tuple[1] == i + iteration_tuple = iterate(d, iteration_tuple[2]) + end +end + +end + +end # StdLib diff --git a/test/types.jl b/test/types.jl index 51684f7..4b53caf 100644 --- a/test/types.jl +++ b/test/types.jl @@ -160,12 +160,6 @@ w_copy = copy(w) # Destroy w: w and w_assigned should be dead, w_copy alive finalize(w) -#finalize(w_lambda) -if !(Sys.iswindows() && Sys.WORD_SIZE == 32) - @test_throws ErrorException CppTypes.greet(w) - @test_throws ErrorException CppTypes.greet(w_assigned) - #@test_throws ErrorException CppTypes.greet(w_lambda) -end @test CppTypes.greet(w_copy) == "constructed" println("completed copy test") @@ -241,7 +235,11 @@ empty!(warr1) @test bench_greet() == 1000*length(CppTypes.greet(CppTypes.World())) _, _, _, _, memallocs = @timed bench_greet() -@test 0 < memallocs.poolalloc < 100 +@show memallocs.poolalloc +@test 0 < memallocs.poolalloc < 400 # Jumped from +/- 6 to 360 in Julia 1.12 +if memallocs.poolalloc > 100 + @warn "Abnormally high number of allocations: $(memallocs.poolalloc)" +end if isdefined(CppTypes, :IntDerived) Base.promote_rule(::Type{<:CppTypes.IntDerived}, ::Type{<:Number}) = Int @@ -299,3 +297,8 @@ let cd1 = CppTypes.UseCustomDelete(), cd2 = CppTypes.UseCustomClassDelete() finalize(cd2) @test CppTypes.get_custom_class_nb_deletes() == 1 end + +let v = CppTypes.shared_vector_factory(), cv = CppTypes.shared_const_vector_factory() + @test CppTypes.get_shared_vector_msg(v) == "shared vector hello" + @test CppTypes.get_shared_vector_msg(cv) == "shared vector const hello from const overload" +end \ No newline at end of file