Skip to content

Commit

Permalink
docstring support for method(...)
Browse files Browse the repository at this point in the history
  • Loading branch information
melven authored and barche committed Jan 11, 2024
1 parent a0b65ad commit c244461
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 27 deletions.
2 changes: 1 addition & 1 deletion examples/functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ JLCXX_MODULE init_test_module(jlcxx::Module& mod)
mod.method("test_int64_array", test_int64_array);
mod.method("test_float_array", test_float_array);
mod.method("test_double_array", test_double_array);
mod.method("test_exception", test_exception, true);
mod.method("test_exception", test_exception, "", true);
mod.method("test_array_len", test_array_len);
mod.method("test_array_set", test_array_set);
mod.method("test_array_get", test_array_get);
Expand Down
2 changes: 1 addition & 1 deletion examples/hello.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ std::string greet()

JLCXX_MODULE define_julia_module(jlcxx::Module& mod)
{
mod.method("greet", &greet);
mod.method("greet", &greet, "documentation for greet");
}
63 changes: 39 additions & 24 deletions include/jlcxx/module.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,17 @@ class JLCXX_API FunctionWrapperBase
return m_name;
}

inline void set_doc(jl_value_t* doc)
{
protect_from_gc(doc);
m_doc = doc;
}

inline jl_value_t* doc() const
{
return m_doc;
}

inline void set_override_module(jl_module_t* mod) { m_override_module = (jl_value_t*)mod; }
inline jl_value_t* override_module() const { return m_override_module; }

Expand All @@ -179,6 +190,7 @@ class JLCXX_API FunctionWrapperBase

private:
jl_value_t* m_name = nullptr;
jl_value_t* m_doc = nullptr;
Module* m_module;
std::pair<jl_datatype_t*,jl_datatype_t*> m_return_type = std::make_pair(nullptr,nullptr);

Expand Down Expand Up @@ -521,50 +533,53 @@ class JLCXX_API Module

/// Define a new function
template<typename R, typename... Args>
FunctionWrapperBase& method(const std::string& name, std::function<R(Args...)> f)
FunctionWrapperBase& method(const std::string& name, std::function<R(Args...)> f, const std::string& doc = std::string())
{
auto* new_wrapper = new FunctionWrapper<R, Args...>(this, f);
new_wrapper->set_name((jl_value_t*)jl_symbol(name.c_str()));
new_wrapper->set_doc(jl_cstr_to_string(doc.c_str()));
append_function(new_wrapper);
return *new_wrapper;
}

/// Define a new function. Overload for pointers
template<typename R, typename... Args>
FunctionWrapperBase& method(const std::string& name, R(*f)(Args...), const bool force_convert = false)
FunctionWrapperBase& method(const std::string& name, R(*f)(Args...), const std::string& doc = std::string(), const bool force_convert = false)
{
const bool need_convert = force_convert || detail::NeedConvertHelper<R, Args...>()();

// Conversion is automatic when using the std::function calling method, so if we need conversion we use that
if(need_convert)
{
return method(name, std::function<R(Args...)>(f));
return method(name, std::function<R(Args...)>(f), doc);
}

// No conversion needed -> call can be through a naked function pointer
auto* new_wrapper = new FunctionPtrWrapper<R, Args...>(this, f);
new_wrapper->set_name((jl_value_t*)jl_symbol(name.c_str()));
new_wrapper->set_doc(jl_cstr_to_string(doc.c_str()));
append_function(new_wrapper);
return *new_wrapper;
}

/// Define a new function. Overload for lambda
template<typename LambdaT>
FunctionWrapperBase& method(const std::string& name, LambdaT&& lambda, typename std::enable_if<!std::is_member_function_pointer<LambdaT>::value, bool>::type = true)
FunctionWrapperBase& method(const std::string& name, LambdaT&& lambda, const std::string& doc = std::string(), typename std::enable_if<!std::is_member_function_pointer<LambdaT>::value, bool>::type = true)
{
return add_lambda(name, std::forward<LambdaT>(lambda), &LambdaT::operator());
return add_lambda(name, std::forward<LambdaT>(lambda), &LambdaT::operator(), doc);
}

/// Add a constructor with the given argument types for the given datatype (used to get the name)
template<typename T, typename... ArgsT>
void constructor(jl_datatype_t* dt, bool finalize=true)
void constructor(jl_datatype_t* dt, bool finalize=true, const std::string& doc = std::string())
{
FunctionWrapperBase &new_wrapper = finalize ? method("dummy", [](ArgsT... args) { return create<T, true>(args...); }) : method("dummy", [](ArgsT... args) { return create<T, false>(args...); });
new_wrapper.set_name(detail::make_fname("ConstructorFname", dt));
new_wrapper.set_doc(jl_cstr_to_string(doc.c_str()));
}

template<typename T, typename R, typename LambdaT, typename... ArgsT>
void constructor(jl_datatype_t* dt, LambdaT&& lambda, R(LambdaT::*)(ArgsT...) const, bool finalize)
void constructor(jl_datatype_t* dt, LambdaT&& lambda, R(LambdaT::*)(ArgsT...) const, bool finalize, const std::string& doc = std::string())
{
static_assert(std::is_same<T*,R>::value, "Constructor lambda function must return a pointer to the constructed object, of the correct type");
FunctionWrapperBase &new_wrapper = method("dummy", [=](ArgsT... args)
Expand All @@ -573,7 +588,7 @@ class JLCXX_API Module
assert(jl_is_mutable_datatype(concrete_dt));
T* cpp_obj = lambda(std::forward<ArgsT>(args)...);
return boxed_cpp_pointer(cpp_obj, concrete_dt, finalize);
});
}, doc);
new_wrapper.set_name(detail::make_fname("ConstructorFname", dt));
}

Expand Down Expand Up @@ -697,9 +712,9 @@ class JLCXX_API Module
TypeWrapper<T> add_type_internal(const std::string& name, JLSuperT* super);

template<typename R, typename LambdaT, typename... ArgsT>
FunctionWrapperBase& add_lambda(const std::string& name, LambdaT&& lambda, R(LambdaT::*)(ArgsT...) const)
FunctionWrapperBase& add_lambda(const std::string& name, LambdaT&& lambda, R(LambdaT::*)(ArgsT...) const, const std::string& doc = std::string())
{
return method(name, std::function<R(ArgsT...)>(std::forward<LambdaT>(lambda)));
return method(name, std::function<R(ArgsT...)>(std::forward<LambdaT>(lambda)), doc);
}

void set_constant(const std::string& name, jl_value_t* boxed_const);
Expand Down Expand Up @@ -1005,51 +1020,51 @@ class TypeWrapper

/// Define a member function
template<typename R, typename CT, typename... ArgsT>
TypeWrapper<T>& method(const std::string& name, R(CT::*f)(ArgsT...))
TypeWrapper<T>& method(const std::string& name, R(CT::*f)(ArgsT...), const std::string& doc = std::string())
{
m_module.method(name, [f](T& obj, ArgsT... args) -> R { return (obj.*f)(args...); } );
m_module.method(name, [f](T* obj, ArgsT... args) -> R { return ((*obj).*f)(args...); } );
m_module.method(name, [f](T& obj, ArgsT... args) -> R { return (obj.*f)(args...); }, doc );
m_module.method(name, [f](T* obj, ArgsT... args) -> R { return ((*obj).*f)(args...); }, doc );
return *this;
}

/// Define a member function, const version
template<typename R, typename CT, typename... ArgsT>
TypeWrapper<T>& method(const std::string& name, R(CT::*f)(ArgsT...) const)
TypeWrapper<T>& method(const std::string& name, R(CT::*f)(ArgsT...) const, const std::string& doc = std::string())
{
m_module.method(name, [f](const T& obj, ArgsT... args) -> R { return (obj.*f)(args...); } );
m_module.method(name, [f](const T* obj, ArgsT... args) -> R { return ((*obj).*f)(args...); } );
m_module.method(name, [f](const T& obj, ArgsT... args) -> R { return (obj.*f)(args...); }, doc );
m_module.method(name, [f](const T* obj, ArgsT... args) -> R { return ((*obj).*f)(args...); }, doc );
return *this;
}

/// Define a "member" function using a lambda
template<typename LambdaT>
TypeWrapper<T>& method(const std::string& name, LambdaT&& lambda, typename std::enable_if<!std::is_member_function_pointer<LambdaT>::value, bool>::type = true)
TypeWrapper<T>& method(const std::string& name, LambdaT&& lambda, const std::string& doc = std::string(), typename std::enable_if<!std::is_member_function_pointer<LambdaT>::value, bool>::type = true)
{
m_module.method(name, std::forward<LambdaT>(lambda));
m_module.method(name, std::forward<LambdaT>(lambda), doc);
return *this;
}

/// Call operator overload. For concrete type box to work around https://github.com/JuliaLang/julia/issues/14919
template<typename R, typename CT, typename... ArgsT>
TypeWrapper<T>& method(R(CT::*f)(ArgsT...))
TypeWrapper<T>& method(R(CT::*f)(ArgsT...), const std::string& doc = std::string())
{
m_module.method("operator()", [f](T& obj, ArgsT... args) -> R { return (obj.*f)(args...); } )
m_module.method("operator()", [f](T& obj, ArgsT... args) -> R { return (obj.*f)(args...); }, doc )
.set_name(detail::make_fname("CallOpOverload", m_box_dt));
return *this;
}
template<typename R, typename CT, typename... ArgsT>
TypeWrapper<T>& method(R(CT::*f)(ArgsT...) const)
TypeWrapper<T>& method(R(CT::*f)(ArgsT...) const, const std::string& doc = std::string())
{
m_module.method("operator()", [f](const T& obj, ArgsT... args) -> R { return (obj.*f)(args...); } )
m_module.method("operator()", [f](const T& obj, ArgsT... args) -> R { return (obj.*f)(args...); }, doc )
.set_name(detail::make_fname("CallOpOverload", m_box_dt));
return *this;
}

/// Overload operator() using a lambda
template<typename LambdaT>
TypeWrapper<T>& method(LambdaT&& lambda)
TypeWrapper<T>& method(LambdaT&& lambda, const std::string& doc = std::string())
{
m_module.method("operator()", std::forward<LambdaT>(lambda))
m_module.method("operator()", std::forward<LambdaT>(lambda), doc)
.set_name(detail::make_fname("CallOpOverload", m_box_dt));
return *this;
}
Expand Down
3 changes: 2 additions & 1 deletion src/c_interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,8 @@ JLCXX_API jl_array_t* get_module_functions(jl_module_t* jlmod)
julia_return_type,
boxed_f,
boxed_thunk,
f.override_module()
f.override_module(),
f.doc()
));

JL_GC_POP();
Expand Down

0 comments on commit c244461

Please sign in to comment.