From 07992e12887dda230015ba1ad05bcc4eaf1fc098 Mon Sep 17 00:00:00 2001 From: marcoeilers Date: Thu, 5 Feb 2026 13:34:44 +0100 Subject: [PATCH 1/3] Nested classes, WIP --- src/nagini_translation/analyzer.py | 42 ++++++++++++++------- src/nagini_translation/lib/program_nodes.py | 9 +++++ src/nagini_translation/lib/typeinfo.py | 3 ++ 3 files changed, 40 insertions(+), 14 deletions(-) diff --git a/src/nagini_translation/analyzer.py b/src/nagini_translation/analyzer.py index aae9e0b35..ddd6346a9 100644 --- a/src/nagini_translation/analyzer.py +++ b/src/nagini_translation/analyzer.py @@ -428,8 +428,17 @@ def find_or_create_class(self, name: str, module=None) -> PythonClass: name = aliases.get(name, name) if self.current_class and name in self.current_class.type_vars: return self.current_class.type_vars[name] - if not module: + if module and module != self.module: + superscope = module + else: + superscope = self.current_class or self.module module = self.module + class_scope = superscope + while isinstance(class_scope, PythonClass): + if class_scope.name == name: + return class_scope + class_scope = class_scope.superscope + # Check all imported modules for the class. for visible_module in module.get_included_modules((), True): if name in visible_module.classes: @@ -438,10 +447,10 @@ def find_or_create_class(self, name: str, module=None) -> PythonClass: else: # Class doesn't exist yet, create it. superclass = self.global_module.classes[OBJECT_TYPE] if name != OBJECT_TYPE else None - cls = self.node_factory.create_python_class(name, module, + cls = self.node_factory.create_python_class(name, superscope, self.node_factory, superclass=superclass) - module.classes[name] = cls + superscope.classes[name] = cls return cls def find_or_create_target_class(self, node: ast.AST) -> PythonClass: @@ -538,13 +547,15 @@ def _visit_ADT(self, cls: PythonClass, actual_bases: List[ast.AST], return actual_bases def visit_ClassDef(self, node: ast.ClassDef) -> None: - if self.current_function or self.current_class: + if self.current_function: raise InvalidProgramException(node, 'nested.class.declaration') name = node.name - self.define_new(self.module, name, node) + container = self.module if self.current_class is None else self.current_class + self.define_new(container, name, node) cls = self.find_or_create_class(name) cls.defined = True cls.node = node + old_class = self.current_class self.current_class = cls actual_bases = [] current_index = 0 @@ -591,7 +602,7 @@ def visit_ClassDef(self, node: ast.ClassDef) -> None: for member in node.body: self.visit(member, node) - self.current_class = None + self.current_class = old_class def _is_illegal_magic_method_name(self, name: str) -> bool: """ @@ -1311,7 +1322,10 @@ def convert_type(self, mypy_type, node, bound_type_vars: Dict[str, PythonType] = msg = f'Type could not be fully inferred (this usually means that a type argument is unknown)' raise InvalidProgramException(node, 'partial.type', message=msg) else: - msg = 'Unsupported type: {}'.format(mypy_type.__class__.__name__) + if mypy_type is None: + msg = 'Internal error: Could not determine type.' + else: + msg = 'Unsupported type: {}'.format(mypy_type.__class__.__name__) raise UnsupportedException(node, desc=msg) return result @@ -1408,8 +1422,8 @@ def typeof(self, node: ast.AST) -> PythonType: if node.id in self.module.classes: return self.module.classes[node.id] context = [] - if self.current_class is not None: - context.append(self.current_class.name) + if self.current_class: + context.extend(self.current_class.full_name) if self.current_function is not None: context.append(self.current_function.name) context.extend(self.current_scopes) @@ -1434,15 +1448,15 @@ def typeof(self, node: ast.AST) -> PythonType: return UnionType(list(set_of_types)) if len(set_of_types) > 1 else set_of_types.pop() contexts = [] if isinstance(receiver, OptionalType): - contexts.append([receiver.optional_type.name]) + contexts.append(receiver.optional_type.full_name) rec_super = receiver.optional_type.superclass module = receiver.optional_type.module else: - contexts.append([receiver.name]) + contexts.append(receiver.full_name) rec_super = receiver.superclass module = receiver.module while rec_super is not None: - contexts.append([rec_super.name]) + contexts.append(rec_super.full_name) rec_super = rec_super.superclass bound_type_vars = None if isinstance(receiver, GenericType) or (isinstance(receiver, OptionalType) and isinstance(receiver.optional_type, GenericType)): @@ -1464,8 +1478,8 @@ def typeof(self, node: ast.AST) -> PythonType: cls = self.module.global_module.classes['type'] return GenericType(cls, [self.current_class]) context = [] - if self.current_class is not None: - context.append(self.current_class.name) + if self.current_class: + context.extend(self.current_class.full_name) context.append(self.current_function.name) context.extend(self.current_scopes) type, _ = self.module.get_type(context, node.arg) diff --git a/src/nagini_translation/lib/program_nodes.py b/src/nagini_translation/lib/program_nodes.py index a503ebf1b..3d3621508 100644 --- a/src/nagini_translation/lib/program_nodes.py +++ b/src/nagini_translation/lib/program_nodes.py @@ -408,6 +408,7 @@ def __init__(self, name: str, superscope: PythonScope, self.predicates = OrderedDict() self.fields = OrderedDict() self.static_fields = OrderedDict() + self.classes = OrderedDict() self.type = None # infer, domain type self.interface = interface self.defined = False @@ -767,6 +768,14 @@ def try_unbox(self) -> 'PythonClass': def python_class(self) -> 'PythonClass': return self + @property + def full_name(self) -> List[str]: + result = [] + if isinstance(self.superscope, PythonClass): + result.extend(self.superscope.full_name) + result.append(self.name) + return result + class GenericType(PythonType): """ diff --git a/src/nagini_translation/lib/typeinfo.py b/src/nagini_translation/lib/typeinfo.py index 3c83bb61d..21390619e 100644 --- a/src/nagini_translation/lib/typeinfo.py +++ b/src/nagini_translation/lib/typeinfo.py @@ -233,6 +233,9 @@ def type_of(self, node): key = (node.name,) if key in self.all_types: return self.all_types[key] + full_key = tuple(self.prefix) + key + if full_key in self.all_types: + return self.all_types[full_key] elif isinstance(node, mypy.nodes.CallExpr): if isinstance(node.callee, mypy.nodes.NameExpr) and node.callee.name == 'Result': key = tuple(self.prefix) From 26f22153841a121d339bb801723f3406660c20a7 Mon Sep 17 00:00:00 2001 From: marcoeilers Date: Tue, 10 Feb 2026 09:49:26 +0100 Subject: [PATCH 2/3] Fixing all sorts of things that were going wrong with imports, including #284 --- src/nagini_translation/analyzer.py | 14 ++++++++----- src/nagini_translation/lib/program_nodes.py | 23 ++++++++++++++++++++- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/nagini_translation/analyzer.py b/src/nagini_translation/analyzer.py index aae9e0b35..1a8a8cb43 100644 --- a/src/nagini_translation/analyzer.py +++ b/src/nagini_translation/analyzer.py @@ -1324,11 +1324,15 @@ def _convert_normal_type(self, mypy_type) -> PythonType: name = 'list' if prefix.endswith('.' + name): prefix = prefix[:-(len(name) + 1)] - target_module = self.module for module in self.modules.values(): - if module.type_prefix == prefix: + if module.full_module_name == prefix or module.type_prefix == prefix: target_module = module break + else: + if prefix in IGNORED_IMPORTS: + target_module = self.module.global_module + else: + raise Exception("Internal error: Could not find module for type.") result = self.find_or_create_class(name, module=target_module) return result @@ -1434,15 +1438,15 @@ def typeof(self, node: ast.AST) -> PythonType: return UnionType(list(set_of_types)) if len(set_of_types) > 1 else set_of_types.pop() contexts = [] if isinstance(receiver, OptionalType): - contexts.append([receiver.optional_type.name]) + contexts.append(receiver.optional_type.python_class.full_name) rec_super = receiver.optional_type.superclass module = receiver.optional_type.module else: - contexts.append([receiver.name]) + contexts.append(receiver.python_class.full_name) rec_super = receiver.superclass module = receiver.module while rec_super is not None: - contexts.append([rec_super.name]) + contexts.append(rec_super.python_class.full_name) rec_super = rec_super.superclass bound_type_vars = None if isinstance(receiver, GenericType) or (isinstance(receiver, OptionalType) and isinstance(receiver.optional_type, GenericType)): diff --git a/src/nagini_translation/lib/program_nodes.py b/src/nagini_translation/lib/program_nodes.py index a503ebf1b..4319ac02c 100644 --- a/src/nagini_translation/lib/program_nodes.py +++ b/src/nagini_translation/lib/program_nodes.py @@ -174,11 +174,20 @@ def full_module_name(self) -> str: return self.types.module_name return self.type_prefix + @property + def full_name(self) -> List[str]: + if self.type_prefix is None: + return [] # ???? + return self.type_prefix.split(".") + def get_relative_import_name(self, name: str, level: int) -> str: module_name = name if level > 0: current_module_name = self.full_module_name - module_name_to_add = current_module_name.split(".")[:-level] + actual_level = level if not (self.module.file.endswith('__init__.py') or self.module.file.endswith('__init__.pyi')) else level - 1 + module_name_to_add = current_module_name.split(".") + if actual_level != 0: + module_name_to_add = module_name_to_add[:-actual_level] if module_name is not None: module_name_to_add.append(module_name) module_name = ".".join(module_name_to_add) @@ -245,6 +254,11 @@ def get_type(self, prefixes: List[str], name: str, """ if self in previous: return None, None + + local_type, local_alts = self.types.get_type(prefixes, name) + if local_type is not None: + return local_type, local_alts + actual_prefix = self.type_prefix.split('.') if self.type_prefix else [] actual_prefix.extend(prefixes) local_type, local_alts = self.types.get_type(actual_prefix, name) @@ -767,6 +781,13 @@ def try_unbox(self) -> 'PythonClass': def python_class(self) -> 'PythonClass': return self + @property + def full_name(self) -> List[str]: + result = [] + result.extend(self.superscope.full_name) + result.append(self.name) + return result + class GenericType(PythonType): """ From c1c68a69d681544645bbb1fb1812561eefe38b0a Mon Sep 17 00:00:00 2001 From: marcoeilers Date: Tue, 10 Feb 2026 23:01:33 +0100 Subject: [PATCH 3/3] Fixing crash for type aliases --- src/nagini_translation/lib/program_nodes.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/nagini_translation/lib/program_nodes.py b/src/nagini_translation/lib/program_nodes.py index f90079221..ff70b4c50 100644 --- a/src/nagini_translation/lib/program_nodes.py +++ b/src/nagini_translation/lib/program_nodes.py @@ -235,7 +235,8 @@ def scope_prefix(self) -> List[str]: def all_classes(self) -> OrderedDict[str, 'PythonClass']: res = OrderedDict() for cls_name, cls in self.classes.items(): - res.update(cls.all_classes) + if cls_name == cls.name: + res.update(cls.all_classes) return res def get_func_or_method(self, name: str) -> 'PythonMethod':