10000 Merge pull request #23813 from VadimLevin:dev/vlevin/runtime-typing-m… · opencv/opencv@003d048 · GitHub
[go: up one dir, main page]

Skip to content

Commit 003d048

Browse files
authored
Merge pull request #23813 from VadimLevin:dev/vlevin/runtime-typing-module
fix: typing module enums references
2 parents 4488159 + a3b6a5b commit 003d048

File tree

6 files changed

+94
-15
lines changed

6 files changed

+94
-15
lines changed

modules/python/src2/typing_stubs_generation/ast_utils.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
from typing import NamedTuple, Sequence, Tuple, Union, List, Dict
1+
from typing import (NamedTuple, Sequence, Tuple, Union, List,
2+
Dict, Callable, Optional)
23
import keyword
34

45
from .nodes import (ASTNode, NamespaceNode, ClassNode, FunctionNode,
@@ -323,12 +324,18 @@ class CV_EXPORTS TermCriteria {
323324
enum_node.parent = scope
324325

325326

326-
def get_enclosing_namespace(node: ASTNode) -> NamespaceNode:
327+
def get_enclosing_namespace(
328+
node: ASTNode,
329+
class_node_callback: Optional[Callable[[ClassNode], None]] = None
330+
) -> NamespaceNode:
327331
"""Traverses up nodes hierarchy to find closest enclosing namespace of the
328332
passed node
329333
330334
Args:
331335
node (ASTNode): Node to find a namespace for.
336+
class_node_callback (Optional[Callable[[ClassNode], None]]): Optional
337+
callable object invoked for each traversed class node in bottom-up
338+
order. Defaults: None.
332339
333340
Returns:
334341
NamespaceNode: Closest enclosing namespace of the provided node.
@@ -360,10 +367,32 @@ def get_enclosing_namespace(node: ASTNode) -> NamespaceNode:
360367
"Can't find enclosing namespace for '{}' known as: '{}'".format(
361368
node.full_export_name, node.native_name
362369
)
370+
if class_node_callback:
371+
class_node_callback(parent_node)
363372
parent_node = parent_node.parent
364373
return parent_node
365374

366375

376+
def get_enum_module_and_export_name(enum_node: EnumerationNode) -> Tuple[str, str]:
377+
"""Get export name of the enum node with its module name.
378+
379+
Note: Enumeration export names are prefixed with enclosing class names.
380+
381+
Args:
382+
enum_node (EnumerationNode): Enumeration node to construct name for.
383+
384+
Returns:
385+
Tuple[str, str]: a pair of enum export name and its full module name.
386+
"""
387+
def update_full_export_name(class_node: ClassNode) -> None:
388+
nonlocal enum_export_name
389+
enum_export_name = class_node.export_name + "_" + enum_export_name
390+
391+
enum_export_name = enum_node.export_name
392+
namespace_node = get_enclosing_namespace(enum_node, update_full_export_name)
393+
return enum_export_name, namespace_node.full_export_name
394+
395+
367396
if __name__ == '__main__':
368397
import doctest
369398
doctest.testmod()

modules/python/src2/typing_stubs_generation/generation.py

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@
66
Collection)
77
import warnings
88

9-
from .ast_utils import get_enclosing_namespace
9+
from .ast_utils import get_enclosing_namespace, g 67E6 et_enum_module_and_export_name
1010

1111
from .predefined_types import PREDEFINED_TYPES
1212

13-
from .nodes import (ASTNode, NamespaceNode, ClassNode, FunctionNode,
13+
from .nodes import (ASTNode, ASTNodeType, NamespaceNode, ClassNode, FunctionNode,
1414
EnumerationNode, ConstantNode)
1515

1616
from .nodes.type_node import (TypeNode, AliasTypeNode, AliasRefTypeNode,
17-
AggregatedTypeNode)
17+
AggregatedTypeNode, ASTNodeTypeNode)
1818

1919

2020
def generate_typing_stubs(root: NamespaceNode, output_path: Path):
@@ -616,29 +616,67 @@ def register_alias_links_from_aggregated_type(type_node: TypeNode) -> None:
616616
for item in filter(lambda i: isinstance(i, AliasRefTypeNode), type_node):
617617
register_alias(PREDEFINED_TYPES[item.ctype_name]) # type: ignore
618618

619+
def create_alias A3E2 _for_enum_node(enum_node: ASTNode) -> AliasTypeNode:
620+
"""Create int alias corresponding to the given enum node.
621+
622+
Args:
623+
enum_node (ASTNodeTypeNode): Enumeration node to create int alias for.
624+
625+
Returns:
626+
AliasTypeNode: int alias node with same export name as enum.
627+
"""
628+
assert enum_node.node_type == ASTNodeType.Enumeration, \
629+
f"{enum_node} has wrong node type. Expected type: Enumeration."
630+
631+
enum_export_name, enum_module_name = get_enum_module_and_export_name(
632+
enum_node
633+
)
634+
enum_full_export_name = f"{enum_module_name}.{enum_export_name}"
635+
alias_node = AliasTypeNode.int_(enum_full_export_name,
636+
enum_export_name)
637+
type_checking_time_definitions.add(alias_node)
638+
return alias_node
639+
619640
def register_alias(alias_node: AliasTypeNode) -> None:
620641
typename = alias_node.typename
621642
# Check if alias is already registered
622643
if typename in aliases:
623644
return
645+
646+
# Collect required imports for alias definition
647+
for required_import in alias_node.required_definition_imports:
648+
required_imports.add(required_import)
649+
624650
if isinstance(alias_node.value, AggregatedTypeNode):
625651
# Check if collection contains a link to another alias
626652
register_alias_links_from_aggregated_type(alias_node.value)
627653

654+
# Remove references to alias nodes
655+
for i, item in enumerate(alias_node.value.items):
656+
# Process enumerations only
657+
if not isinstance(item, ASTNodeTypeNode) or item.ast_node is None:
658+
continue
659+
if item.ast_node.node_type != ASTNodeType.Enumeration:
660+
continue
661+
alias_node.value.items[i] = create_alias_for_enum_node(item.ast_node)
662+
663+
if isinstance(alias_node.value, ASTNodeTypeNode) \
664+
and alias_node.value.ast_node == ASTNodeType.Enumeration:
665+
alias_node.value = create_alias_for_enum_node(alias_node.ast_node)
666+
628667
# Strip module prefix from aliased types
629668
aliases[typename] = alias_node.value.full_typename.replace(
630669
root.export_name + ".typing.", ""
631670
)
632671
if alias_node.doc is not None:
633672
aliases[typename] += f'\n"""{alias_node.doc}"""'
634-
for required_import in alias_node.required_definition_imports:
635-
required_imports.add(required_import)
636673

637674
output_path = Path(output_path) / root.export_name / "typing"
638675
output_path.mkdir(parents=True, exist_ok=True)
639676

640677
required_imports: Set[str] = set()
641678
aliases: Dict[str, str] = {}
679+
type_checking_time_definitions: Set[AliasTypeNode] = set()
642680

643681
# Resolve each node and register aliases
644682
TypeNode.compatible_to_runtime_usage = True
@@ -655,11 +693,16 @@ def register_alias(alias_node: AliasTypeNode) -> None:
655693

656694
_write_required_imports(required_imports, output_stream)
657695

696+
# Add type checking time definitions as generated __init__.py content
697+
for alias in type_checking_time_definitions:
698+
output_stream.write("if typing.TYPE_CHECKING:\n ")
699+
output_stream.write(f"{alias.typename} = {alias.ctype_name}\nelse:\n")
700+
output_stream.write(f" {alias.typename} = {alias.value.ctype_name}\n")
701+
if type_checking_time_definitions:
702+
output_stream.write("\n\n")
703+
658704
for alias_name, alias_type in aliases.items():
659-
output_stream.write(alias_name)
660-
output_stream.write(" = ")
661-
output_stream.write(alias_type)
662-
output_stream.write("\n")
705+
output_stream.write(f"{alias_name} = {alias_type}\n")
663706

664707
TypeNode.compatible_to_runtime_usage = False
665708
(output_path / "__init__.py").write_text(output_stream.getvalue())

modules/python/src2/typing_stubs_generation/nodes/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from .node import ASTNode
1+
from .node import ASTNode, ASTNodeType
22
from .namespace_node import NamespaceNode
33
from .class_node import ClassNode, ClassProperty
44
from .function_node import FunctionNode

modules/python/src2/typing_stubs_generation/nodes/node.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import abc
22
import enum
33
import itertools
4-
from typing import (Iterator, Type, TypeVar, Iterable, Dict,
4+
from typing import (Iterator, Type, TypeVar, Dict,
55
Optional, Tuple, DefaultDict)
66
from collections import defaultdict
77

modules/python/src2/typing_stubs_generation/nodes/type_node.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,10 @@ def __init__(self, ctype_name: str, typename: Optional[str] = None,
417417
self._module_name = module_name
418418
self._ast_node: Optional[weakref.ProxyType[ASTNode]] = None
419419

420+
@property
421+
def ast_node(self):
422+
return self._ast_node
423+
420424
@property
421425
def typename(self) -> str:
422426
if self._ast_node is None:

modules/python/src2/typing_stubs_generator.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from contextlib import contextmanager
1313

1414
from typing import Dict, Set, Any, Sequence, Generator, Union
15+
import traceback
1516

1617
from pathlib import Path
1718

@@ -46,10 +47,12 @@ def wrapped_func(*args, **kwargs):
4647

4748
try:
4849
ret_type = func(*args, **kwargs)
49-
except Exception as e:
50+
except Exception:
5051
self.has_failure = True
5152
warnings.warn(
52-
'Typing stubs generation has failed. Reason: {}'.format(e)
53+
"Typing stubs generation has failed.\n{}".format(
54+
traceback.format_exc()
55+
)
5356
)
5457
if ret_type_on_failure is None:
5558
return None

0 commit comments

Comments
 (0)
0