Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge docstring and extra_arg changes #401

Merged
merged 9 commits into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "CxxWrap"
uuid = "1f15a43c-97ca-5a2a-ae31-89f07a497df4"
authors = ["Bart Janssens <[email protected]>"]
version = "0.14.2"
version = "0.15.0"

[deps]
Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
Expand All @@ -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.0"

[extras]
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,11 +179,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>("World")
.constructor<const std::string&>(false);
.constructor<const std::string&>(jlcxx::finalize_policy::no);
```

The `add_type` function actually builds two Julia types related to `World`.
Expand Down Expand Up @@ -648,10 +648,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.
Expand Down
43 changes: 35 additions & 8 deletions src/CxxWrap.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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.0", v"0.13")

using libcxxwrap_julia_jll # for libcxxwrap_julia and libcxxwrap_julia_stl

Expand Down Expand Up @@ -304,6 +304,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
Expand Down Expand Up @@ -469,9 +473,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
Expand Down Expand Up @@ -587,7 +591,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
Expand Down Expand Up @@ -639,13 +646,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

Expand Down
10 changes: 10 additions & 0 deletions test/functions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand Down
Loading