Skip to content

Commit

Permalink
Translate known namespaces in stub
Browse files Browse the repository at this point in the history
  • Loading branch information
pthom committed Nov 15, 2024
1 parent 7199fc5 commit f869933
Show file tree
Hide file tree
Showing 9 changed files with 81 additions and 27 deletions.
2 changes: 1 addition & 1 deletion src/litgen/internal/adapted_types/adapted_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -890,7 +890,7 @@ def _tpl_instantiate_template_for_type(self, cpp_type: CppType) -> AdaptedClass:
new_class_name_python = template_options._apply_template_naming(
tpl_class_name_python, instantiated_cpp_type_str
)
new_class_name_python = cpp_to_python.type_to_python(self.options, new_class_name_python)
new_class_name_python = cpp_to_python.type_to_python(self.lg_context, new_class_name_python)

new_adapted_class.template_specialization = AdaptedClass.TemplateSpecialization(new_class_name_python, cpp_type)

Expand Down
2 changes: 1 addition & 1 deletion src/litgen/internal/adapted_types/adapted_decl.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def decl_value_python(self) -> str:

def decl_type_python(self) -> str:
decl_type_cpp = self.cpp_element().cpp_type.str_code()
decl_type_python = cpp_to_python.type_to_python(self.options, decl_type_cpp)
decl_type_python = cpp_to_python.type_to_python(self.lg_context, decl_type_cpp)
return decl_type_python

def is_immutable_for_python(self) -> bool:
Expand Down
5 changes: 3 additions & 2 deletions src/litgen/internal/adapted_types/adapted_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -1218,14 +1218,15 @@ def _stub_return_type_python(self) -> str:
else:
cpp_adapted_function_terse = self.cpp_adapted_function.with_terse_types()
return_type_cpp = cpp_adapted_function_terse.str_full_return_type()
return_type_python = cpp_to_python.type_to_python(self.options, return_type_cpp)
return_type_python = cpp_to_python.type_to_python(self.lg_context, return_type_cpp)
return_type_python = self._add_class_hierarchy_to_python_type__fixme(return_type_python)
return return_type_python

def _stub_params_list_signature(self) -> list[str]:
current_scope = self.cpp_element().cpp_scope(include_self=False)
cpp_adapted_function_terse = self.cpp_adapted_function.with_terse_types(current_scope=current_scope)
cpp_parameters = cpp_adapted_function_terse.parameter_list.parameters

r = []
for i, param in enumerate(cpp_parameters):
# For nanobind, skip first "self" argument in constructors
Expand Down Expand Up @@ -1255,7 +1256,7 @@ def _stub_params_list_signature(self) -> list[str]:
r.append("**kwargs")
continue

param_type_python = cpp_to_python.type_to_python(self.options, param_type_cpp)
param_type_python = cpp_to_python.type_to_python(self.lg_context, param_type_cpp)
# Add Optional to param_type_python if cpp_type is a pointer with default = nullptr or NULL
if "*" in param_decl.cpp_type.modifiers and param_decl.initial_value_code in ["NULL", "nullptr"]:
param_type_python = f"Optional[{param_type_python}]"
Expand Down
7 changes: 7 additions & 0 deletions src/litgen/internal/context/litgen_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
)
from litgen.internal.context.replacements_cache import ReplacementsCache
from litgen.internal.context.type_synonyms import CppTypeName
from litgen.internal.context.type_synonyms import CppNamespaceName, CppQualifiedNamespaceName
from srcmlcpp.cpp_types.cpp_enum import CppEnum

if TYPE_CHECKING:
Expand Down Expand Up @@ -46,3 +47,9 @@ def __init__(self, options: LitgenOptions):
def clear_namespaces_code_tree(self) -> None:
self.namespaces_stub = NamespacesCodeTree(self.options, PydefOrStub.Stub)
self.namespaces_pydef = NamespacesCodeTree(self.options, PydefOrStub.Pydef)

def qualified_stub_namespaces(self) -> set[CppQualifiedNamespaceName]:
return self.namespaces_stub.qualified_namespaces()

def unqualified_stub_namespaces(self) -> set[CppNamespaceName]:
return self.namespaces_stub.unqualified_namespaces()
9 changes: 9 additions & 0 deletions src/litgen/internal/context/namespaces_code_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,12 @@ def was_namespace_created(self, qualified_namespace_name: CppQualifiedNamespaceN

def register_namespace_creation(self, qualified_namespace_name: CppQualifiedNamespaceName) -> None:
self._created_namespaces.add(qualified_namespace_name)

def qualified_namespaces(self) -> set[CppQualifiedNamespaceName]:
return self._created_namespaces

def unqualified_namespaces(self) -> set[CppNamespaceName]:
r = set()
for ns in self._created_namespaces:
r.add(ns.split("::")[-1])
return r
44 changes: 31 additions & 13 deletions src/litgen/internal/cpp_to_python.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ def comment_pydef_one_line(options: LitgenOptions, title_cpp: str) -> str:
return r


def type_to_python(options: LitgenOptions, cpp_type_str: str) -> str:
def type_to_python(lg_context: LitgenContext, cpp_type_str: str) -> str:
options = lg_context.options
specialized_type_python_name = options.class_template_options.specialized_type_python_name_str(
cpp_type_str, options.type_replacements
)
Expand All @@ -68,6 +69,14 @@ def normalize_whitespace(s: str) -> str:
# Fix for std::optional (issue origin unknown)
r = r.replace("Optional[" + " ", "Optional[")

# Translate known namespaces
for cpp_namespace_name in lg_context.unqualified_stub_namespaces():
python_namespace_name = namespace_name_to_python(options, cpp_namespace_name)
if python_namespace_name != cpp_namespace_name:
r = r.replace(cpp_namespace_name + ".", python_namespace_name + ".")
for cpp_namespace_name in options.namespaces_root:
r = r.replace(cpp_namespace_name + ".", "")

return r


Expand Down Expand Up @@ -122,20 +131,14 @@ def var_name_to_python(options: LitgenOptions, name: str) -> str:
return r


def var_value_to_python(context: LitgenContext, default_value_cpp: str) -> str:
def var_value_to_python(lg_context: LitgenContext, default_value_cpp: str) -> str:
options = lg_context.options
r = default_value_cpp
r = context.options.type_replacements.apply(r)
r = context.options.value_replacements.apply(r)
for number_macro, value in context.options.srcmlcpp_options.named_number_macros.items():
r = options.type_replacements.apply(r)
r = options.value_replacements.apply(r)
for number_macro, value in lg_context.options.srcmlcpp_options.named_number_macros.items():
r = r.replace(number_macro, str(value))
r = context.var_values_replacements_cache.apply(r)

# If this default value uses a bound template type, try to translate it
specialized_type_python_default_value = (
context.options.class_template_options.specialized_type_python_default_value(
default_value_cpp, context.options.type_replacements
)
)
r = lg_context.var_values_replacements_cache.apply(r)

# If this value is a std::initializer_list, try to translate it
if r.startswith("{") and r.endswith("}"):
Expand All @@ -145,6 +148,21 @@ def var_value_to_python(context: LitgenContext, default_value_cpp: str) -> str:
else:
r = f"initialized with {inner}"

# Translate known namespaces
for cpp_namespace_name in lg_context.unqualified_stub_namespaces():
python_namespace_name = namespace_name_to_python(options, cpp_namespace_name)
if python_namespace_name != cpp_namespace_name:
r = r.replace(cpp_namespace_name + ".", python_namespace_name + ".")
for cpp_namespace_name in options.namespaces_root:
r = r.replace(cpp_namespace_name + ".", "")


# If this default value uses a bound template type, try to translate it
specialized_type_python_default_value = (
options.class_template_options.specialized_type_python_default_value(
default_value_cpp, lg_context.options.type_replacements
)
)
if specialized_type_python_default_value is not None:
r = specialized_type_python_default_value

Expand Down
2 changes: 1 addition & 1 deletion src/litgen/tests/internal/context/litgen_context_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ class MyEnumNonClass(enum.Enum):
value_a = enum.auto() # (= 0)
def foo_inner(
x: int = Inner.FooValue(),
x: int = inner.FooValue(),
a: MyEnumClass = MyEnumClass.value_a,
b: MyEnumNonClass = MyEnumNonClass.value_a
) -> int:
Expand Down
4 changes: 3 additions & 1 deletion src/litgen/tests/internal/cpp_to_python_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@ def test_std_array():


def test_type_to_python() -> None:
from litgen.internal.context.litgen_context import LitgenContext
options = litgen.LitgenOptions()
lg_context = LitgenContext(options)
def my_type_to_python(s: str) -> str:
return cpp_to_python.type_to_python(options, s)
return cpp_to_python.type_to_python(lg_context, s)

assert my_type_to_python("unsigned int") == "int"
assert my_type_to_python("std::vector<std::optional<int>>") == "List[Optional[int]]"
Expand Down
33 changes: 25 additions & 8 deletions src/litgen/tests/litgen_generator_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,20 +154,37 @@ def __init__(self, e: EC = EC.a) -> None:
)


def test_naming() -> None:
def test_namespace_adapt_in_stub() -> None:
code = """
//namespace CamelCase { // should be converted to snake_case in Python
enum Foo { a, b, c };
void UseFoo(Foo f = Foo::a);
//}
namespace CamelCase { // should be converted to snake_case in Python
enum Foo { a };
}
// should have this signature in Python:
// def use_foo(f: camel_case.Foo = camel_case.Foo.a) -> None:
// void UseFoo(CamelCase::Foo f = CamelCase::Foo::a);
void UseFoo(CamelCase::Foo f = CamelCase::Foo::a);
"""
options = litgen.LitgenOptions()
options.fn_params_adapt_mutable_param_with_default_value__regex = r".*"
generated_code = litgen.generate_code(options, code)
print(generated_code.stub_code)
# print(generated_code.stub_code)
code_utils.assert_are_codes_equal(
generated_code.stub_code,
'''
def use_foo(f: camel_case.Foo = camel_case.Foo.a) -> None:
""" should have this signature in Python:
def use_foo(f: camel_case.Foo = camel_case.Foo.a) -> None:
"""
pass
# <submodule camel_case>
class camel_case: # Proxy class that introduces typings for the *submodule* camel_case
pass # (This corresponds to a C++ namespace. All method are static!)
class Foo(enum.Enum):
""" should be converted to snake_case in Python"""
a = enum.auto() # (= 0)
# </submodule camel_case>
'''
)

0 comments on commit f869933

Please sign in to comment.