Skip to content

Commit

Permalink
feat: Create stubs for public methods of inherited internal classes (#69
Browse files Browse the repository at this point in the history
)

Closes #64

### Summary of Changes

Now stubs for public methods of internal classes that are being
inherited will be created for the classes that inherit them.

---------

Co-authored-by: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com>
  • Loading branch information
Masara and megalinter-bot authored Mar 4, 2024
1 parent 522f38d commit 71b38d7
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 5 deletions.
64 changes: 60 additions & 4 deletions src/safeds_stubgen/stubs_generator/_generate_stubs.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,13 +254,25 @@ def _create_class_string(self, class_: Class, class_indentation: str = "") -> st
# Superclasses
superclasses = class_.superclasses
superclass_info = ""
superclass_methods_text = ""
if superclasses and not class_.is_abstract:
superclass_names = []
for superclass in superclasses:
superclass_names.append(superclass.split(".")[-1])
superclass_name = superclass.split(".")[-1]
self._add_to_imports(superclass)

superclass_info = f" sub {', '.join(superclass_names)}"
if superclass not in self.module_imports and is_internal(superclass_name):
# If the superclass was not added to the module_imports through the _add_to_imports method, it means
# that the superclass is a class from the same module.
# For internal superclasses, we have to add their public members to subclasses.
superclass_methods_text += self._create_internal_class_methods_string(
superclass=superclass,
inner_indentations=inner_indentations,
)
else:
superclass_names.append(superclass_name)

superclass_info = f" sub {', '.join(superclass_names)}" if superclass_names else ""

if len(superclasses) > 1:
self._current_todo_msgs.add("multiple_inheritance")
Expand Down Expand Up @@ -312,6 +324,9 @@ def _create_class_string(self, class_: Class, class_indentation: str = "") -> st
for inner_class in class_.classes:
class_text += f"\n{self._create_class_string(inner_class, inner_indentations)}\n"

# Superclass methods, if the superclass is an internal class
class_text += superclass_methods_text

# Methods
class_text += self._create_class_method_string(class_.methods, inner_indentations)

Expand All @@ -324,11 +339,17 @@ def _create_class_string(self, class_: Class, class_indentation: str = "") -> st

return f"{class_signature} {{{class_text}"

def _create_class_method_string(self, methods: list[Function], inner_indentations: str) -> str:
def _create_class_method_string(
self,
methods: list[Function],
inner_indentations: str,
is_internal_class: bool = False,
) -> str:
class_methods: list[str] = []
class_property_methods: list[str] = []
for method in methods:
if not method.is_public:
# Add methods of internal classes that are inherited if the methods themselfe are public
if not method.is_public and (not is_internal_class or (is_internal_class and is_internal(method.name))):
continue
elif method.is_property:
class_property_methods.append(
Expand Down Expand Up @@ -744,6 +765,26 @@ def _create_type_string(self, type_data: dict | None) -> str:

raise ValueError(f"Unexpected type: {kind}") # pragma: no cover

def _create_internal_class_methods_string(self, superclass: str, inner_indentations: str) -> str:
superclass_name = superclass.split(".")[-1]

superclass_class = self._get_class_in_module(superclass_name)
superclass_methods_text = self._create_class_method_string(
superclass_class.methods,
inner_indentations,
is_internal_class=True,
)

for superclass_superclass in superclass_class.superclasses:
name = superclass_superclass.split(".")[-1]
if is_internal(name):
superclass_methods_text += self._create_internal_class_methods_string(
superclass_superclass,
inner_indentations,
)

return superclass_methods_text

# ############################### Utilities ############################### #

def _add_to_imports(self, qname: str) -> None:
Expand Down Expand Up @@ -809,6 +850,17 @@ def _create_todo_msg(self, indentations: str) -> str:

return indentations + f"\n{indentations}".join(todo_msgs) + "\n"

def _get_class_in_module(self, class_name: str) -> Class:
if f"{self.module.id}/{class_name}" in self.api.classes:
return self.api.classes[f"{self.module.id}/{class_name}"]

# If the class is a nested class
for class_ in self.api.classes:
if class_.startswith(self.module.id) and class_.endswith(class_name):
return self.api.classes[class_]

raise LookupError(f"Expected finding class '{class_name}' in module '{self.module.id}'.") # pragma: no cover


def _callable_type_name_generator() -> Generator:
"""Generate a name for callable type parameters starting from 'a' until 'zz'."""
Expand Down Expand Up @@ -860,6 +912,10 @@ def _replace_if_safeds_keyword(keyword: str) -> str:
return keyword


def is_internal(name: str) -> bool:
return name.startswith("_")


def _convert_name_to_convention(
name: str,
naming_convention: NamingConvention,
Expand Down
35 changes: 35 additions & 0 deletions tests/data/various_modules_package/inheritance_module.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
class PublicSuperClass:
def public_superclass_method(self) -> str: ...


class PublicSubClass(PublicSuperClass):
...


class _PrivateInternalClass:
class _PrivateInternalNestedClass:
def public_internal_nested_class_method(self, a: None) -> bool: ...

def public_internal_class_method(self, a: int) -> str: ...

def _private_internal_class_method(self, b: list) -> None: ...


class PublicSubClass2(_PrivateInternalClass):
def public_subclass_method(self) -> str: ...


class PublicSubClassFromNested(_PrivateInternalClass._PrivateInternalNestedClass):
...


class _TransitiveInternalClassA:
def transitive_class_fun(self, c: list) -> list: ...


class _TransitiveInternalClassB(_TransitiveInternalClassA):
pass


class InheritTransitively(_TransitiveInternalClassB):
pass
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ from variousModulesPackage.aliasing.aliasingModule3 import ImportMeAliasingModul

class AliasingModuleClassB()

class AliasingModuleClassC() sub _AliasingModuleClassA {
class AliasingModuleClassC() {
@PythonName("typed_alias_attr")
static attr typedAliasAttr: AliasingModuleClassB
// TODO An internal class must not be used as a type in a public class.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
@PythonModule("various_modules_package.inheritance_module")
package variousModulesPackage.inheritanceModule

class PublicSuperClass() {
@Pure
@PythonName("public_superclass_method")
fun publicSuperclassMethod() -> result1: String
}

class PublicSubClass() sub PublicSuperClass

class PublicSubClass2() {
@Pure
@PythonName("public_internal_class_method")
fun publicInternalClassMethod(
a: Int
) -> result1: String

@Pure
@PythonName("public_subclass_method")
fun publicSubclassMethod() -> result1: String
}

class PublicSubClassFromNested() {
@Pure
@PythonName("public_internal_nested_class_method")
fun publicInternalNestedClassMethod(
a: Nothing?
) -> result1: Boolean
}

class InheritTransitively() {
@Pure
@PythonName("transitive_class_fun")
fun transitiveClassFun(
c: List<Any>
) -> result1: List<Any>
}

0 comments on commit 71b38d7

Please sign in to comment.