From 25b6e79fe067586b2526f1216f165686bfce6ce8 Mon Sep 17 00:00:00 2001 From: Valentin Stanciu <250871+svalentin@users.noreply.github.com> Date: Thu, 13 Feb 2025 13:51:20 +0000 Subject: [PATCH 1/3] Fix mypyc crash with enum type aliases mypyc was crashing because it couldn't find the type in the type map. This PR adds a generic AnyType to the type map if an expression isn't in the map already. Tried actually changing mypy to accept these type alias expressions, but ran into problems with nested type aliases where the inner one doesn't have the "analyzed" value and ending up with wrong results. fixes https://github.com/mypyc/mypyc/issues/1064 --- mypyc/irbuild/main.py | 2 +- mypyc/irbuild/missingtypevisitor.py | 20 ++++++++++++++++++++ mypyc/irbuild/prebuildvisitor.py | 14 ++++++++++++++ mypyc/test-data/irbuild-classes.test | 9 +++++++++ 4 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 mypyc/irbuild/missingtypevisitor.py diff --git a/mypyc/irbuild/main.py b/mypyc/irbuild/main.py index 15928d939cbf..7cdc6b686778 100644 --- a/mypyc/irbuild/main.py +++ b/mypyc/irbuild/main.py @@ -73,7 +73,7 @@ def build_ir( for module in modules: # First pass to determine free symbols. - pbv = PreBuildVisitor(errors, module, singledispatch_info.decorators_to_remove) + pbv = PreBuildVisitor(errors, module, singledispatch_info.decorators_to_remove, types) module.accept(pbv) # Construct and configure builder objects (cyclic runtime dependency). diff --git a/mypyc/irbuild/missingtypevisitor.py b/mypyc/irbuild/missingtypevisitor.py new file mode 100644 index 000000000000..77402232e061 --- /dev/null +++ b/mypyc/irbuild/missingtypevisitor.py @@ -0,0 +1,20 @@ +from __future__ import annotations + +from mypy.nodes import Expression, Node +from mypy.traverser import ExtendedTraverserVisitor +from mypy.types import Type, AnyType, TypeOfAny + + +class MissingTypesVisitor(ExtendedTraverserVisitor): + """AST visitor that can be used to add any missing types as a generic AnyType.""" + + def __init__(self, types: dict[Expression, Type]) -> None: + super().__init__() + self.types: dict[Expression, Type] = types + + def visit(self, o: Node) -> bool: + if isinstance(o, Expression) and o not in self.types: + self.types[o] = AnyType(TypeOfAny.special_form) + + # If returns True, will continue to nested nodes. + return True diff --git a/mypyc/irbuild/prebuildvisitor.py b/mypyc/irbuild/prebuildvisitor.py index 5f178a290138..6f3c8bf3528e 100644 --- a/mypyc/irbuild/prebuildvisitor.py +++ b/mypyc/irbuild/prebuildvisitor.py @@ -1,12 +1,14 @@ from __future__ import annotations from mypy.nodes import ( + AssignmentStmt, Block, Decorator, Expression, FuncDef, FuncItem, Import, + IndexExpr, LambdaExpr, MemberExpr, MypyFile, @@ -16,7 +18,9 @@ Var, ) from mypy.traverser import ExtendedTraverserVisitor +from mypy.types import Type from mypyc.errors import Errors +from mypyc.irbuild.missingtypevisitor import MissingTypesVisitor class PreBuildVisitor(ExtendedTraverserVisitor): @@ -39,6 +43,7 @@ def __init__( errors: Errors, current_file: MypyFile, decorators_to_remove: dict[FuncDef, list[int]], + types: dict[Expression, Type], ) -> None: super().__init__() # Dict from a function to symbols defined directly in the @@ -82,11 +87,20 @@ def __init__( self.current_file: MypyFile = current_file + self.missing_types_visitor = MissingTypesVisitor(types) + def visit(self, o: Node) -> bool: if not isinstance(o, Import): self._current_import_group = None return True + def visit_assignment_stmt(self, stmt: AssignmentStmt) -> None: + # These are cases where mypy may not have types for certain expressions, + # but mypyc needs some form type to exist. + if isinstance(stmt.rvalue, IndexExpr) and stmt.rvalue.analyzed: + stmt.rvalue.accept(self.missing_types_visitor) + return super().visit_assignment_stmt(stmt) + def visit_block(self, block: Block) -> None: self._current_import_group = None super().visit_block(block) diff --git a/mypyc/test-data/irbuild-classes.test b/mypyc/test-data/irbuild-classes.test index 605ab46181e2..1b13f99e1ae8 100644 --- a/mypyc/test-data/irbuild-classes.test +++ b/mypyc/test-data/irbuild-classes.test @@ -1335,3 +1335,12 @@ def outer(): if True: class OtherInner: # E: Nested class definitions not supported pass + +[case testEnumClassAlias] +from enum import Enum +from typing import Literal + +class SomeEnum(Enum): + AVALUE = "a" + +ALIAS = Literal[SomeEnum.AVALUE] From c6afe2b5cade002997d262bb412da391dd521d71 Mon Sep 17 00:00:00 2001 From: Valentin Stanciu <250871+svalentin@users.noreply.github.com> Date: Mon, 24 Feb 2025 15:50:20 +0000 Subject: [PATCH 2/3] comments --- mypyc/irbuild/missingtypevisitor.py | 2 +- mypyc/irbuild/prebuildvisitor.py | 2 +- mypyc/test-data/irbuild-classes.test | 3 ++- mypyc/test-data/run-python312.test | 10 +++++++++- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/mypyc/irbuild/missingtypevisitor.py b/mypyc/irbuild/missingtypevisitor.py index 77402232e061..e655d270a4a4 100644 --- a/mypyc/irbuild/missingtypevisitor.py +++ b/mypyc/irbuild/missingtypevisitor.py @@ -2,7 +2,7 @@ from mypy.nodes import Expression, Node from mypy.traverser import ExtendedTraverserVisitor -from mypy.types import Type, AnyType, TypeOfAny +from mypy.types import AnyType, Type, TypeOfAny class MissingTypesVisitor(ExtendedTraverserVisitor): diff --git a/mypyc/irbuild/prebuildvisitor.py b/mypyc/irbuild/prebuildvisitor.py index 6f3c8bf3528e..75966bcea2b8 100644 --- a/mypyc/irbuild/prebuildvisitor.py +++ b/mypyc/irbuild/prebuildvisitor.py @@ -97,7 +97,7 @@ def visit(self, o: Node) -> bool: def visit_assignment_stmt(self, stmt: AssignmentStmt) -> None: # These are cases where mypy may not have types for certain expressions, # but mypyc needs some form type to exist. - if isinstance(stmt.rvalue, IndexExpr) and stmt.rvalue.analyzed: + if stmt.is_alias_def: stmt.rvalue.accept(self.missing_types_visitor) return super().visit_assignment_stmt(stmt) diff --git a/mypyc/test-data/irbuild-classes.test b/mypyc/test-data/irbuild-classes.test index 1b13f99e1ae8..ed7c167d8621 100644 --- a/mypyc/test-data/irbuild-classes.test +++ b/mypyc/test-data/irbuild-classes.test @@ -1338,9 +1338,10 @@ if True: [case testEnumClassAlias] from enum import Enum -from typing import Literal +from typing import Literal, Union class SomeEnum(Enum): AVALUE = "a" ALIAS = Literal[SomeEnum.AVALUE] +ALIAS2 = Union[Literal[SomeEnum.AVALUE], None] diff --git a/mypyc/test-data/run-python312.test b/mypyc/test-data/run-python312.test index a5a3f058d1e2..5c0a807c375a 100644 --- a/mypyc/test-data/run-python312.test +++ b/mypyc/test-data/run-python312.test @@ -1,5 +1,6 @@ [case testPEP695Basics] -from typing import Any, TypeAliasType, cast +from enum import Enum +from typing import Any, Literal, TypeAliasType, cast from testutil import assertRaises @@ -188,6 +189,13 @@ type R = int | list[R] def test_recursive_type_alias() -> None: assert isinstance(R, TypeAliasType) assert getattr(R, "__value__") == (int | list[R]) + +class SomeEnum(Enum): + AVALUE = "a" + +type EnumLiteralAlias1 = Literal[SomeEnum.AVALUE] +type EnumLiteralAlias2 = Literal[SomeEnum.AVALUE] | None +EnumLiteralAlias3 = Literal[SomeEnum.AVALUE] | None [typing fixtures/typing-full.pyi] [case testPEP695GenericTypeAlias] From 441bca764965ff0a382b012efb27e5b5ef41567f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 24 Feb 2025 15:51:48 +0000 Subject: [PATCH 3/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypyc/irbuild/prebuildvisitor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mypyc/irbuild/prebuildvisitor.py b/mypyc/irbuild/prebuildvisitor.py index 75966bcea2b8..e630fed0d85a 100644 --- a/mypyc/irbuild/prebuildvisitor.py +++ b/mypyc/irbuild/prebuildvisitor.py @@ -8,7 +8,6 @@ FuncDef, FuncItem, Import, - IndexExpr, LambdaExpr, MemberExpr, MypyFile,