Skip to content

Commit

Permalink
Delete recursive aliases flags (#16346)
Browse files Browse the repository at this point in the history
FWIW I decided to keep the old tests (where possible), just to be sure
we will not re-introduce various crashes at function scope, where
recursive aliases are not allowed.
  • Loading branch information
ilevkivskyi authored Oct 27, 2023
1 parent 42f7cf1 commit 9011ca8
Show file tree
Hide file tree
Showing 15 changed files with 216 additions and 230 deletions.
14 changes: 0 additions & 14 deletions mypy/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -999,15 +999,6 @@ def add_invertible_flag(
action="store_true",
help="Enable new experimental type inference algorithm",
)
internals_group.add_argument(
"--disable-recursive-aliases",
action="store_true",
help="Disable experimental support for recursive type aliases",
)
# Deprecated reverse variant of the above.
internals_group.add_argument(
"--enable-recursive-aliases", action="store_true", help=argparse.SUPPRESS
)
parser.add_argument(
"--enable-incomplete-feature",
action="append",
Expand Down Expand Up @@ -1392,11 +1383,6 @@ def set_strict_flags() -> None:
if options.logical_deps:
options.cache_fine_grained = True

if options.enable_recursive_aliases:
print(
"Warning: --enable-recursive-aliases is deprecated;"
" recursive types are enabled by default"
)
if options.strict_concatenate and not strict_option_set:
print("Warning: --strict-concatenate is deprecated; use --extra-checks instead")

Expand Down
4 changes: 0 additions & 4 deletions mypy/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -362,10 +362,6 @@ def __init__(self) -> None:
self.many_errors_threshold = defaults.MANY_ERRORS_THRESHOLD
# Enable new experimental type inference algorithm.
self.new_type_inference = False
# Disable recursive type aliases (currently experimental)
self.disable_recursive_aliases = False
# Deprecated reverse version of the above, do not use.
self.enable_recursive_aliases = False
# Export line-level, limited, fine-grained dependency information in cache data
# (undocumented feature).
self.export_ref_info = False
Expand Down
4 changes: 2 additions & 2 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -3608,7 +3608,7 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:
)
if not res:
return False
if not self.options.disable_recursive_aliases and not self.is_func_scope():
if not self.is_func_scope():
# Only marking incomplete for top-level placeholders makes recursive aliases like
# `A = Sequence[str | A]` valid here, similar to how we treat base classes in class
# definitions, allowing `class str(Sequence[str]): ...`
Expand Down Expand Up @@ -6296,7 +6296,7 @@ def process_placeholder(
def cannot_resolve_name(self, name: str | None, kind: str, ctx: Context) -> None:
name_format = f' "{name}"' if name else ""
self.fail(f"Cannot resolve {kind}{name_format} (possible cyclic definition)", ctx)
if not self.options.disable_recursive_aliases and self.is_func_scope():
if self.is_func_scope():
self.note("Recursive types are not allowed at function scope", ctx)

def qualified_name(self, name: str) -> str:
Expand Down
6 changes: 2 additions & 4 deletions mypy/semanal_namedtuple.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,7 @@ def check_namedtuple_classdef(
# it would be inconsistent with type aliases.
analyzed = self.api.anal_type(
stmt.type,
allow_placeholder=not self.options.disable_recursive_aliases
and not self.api.is_func_scope(),
allow_placeholder=not self.api.is_func_scope(),
prohibit_self_type="NamedTuple item type",
)
if analyzed is None:
Expand Down Expand Up @@ -450,8 +449,7 @@ def parse_namedtuple_fields_with_types(
# We never allow recursive types at function scope.
analyzed = self.api.anal_type(
type,
allow_placeholder=not self.options.disable_recursive_aliases
and not self.api.is_func_scope(),
allow_placeholder=not self.api.is_func_scope(),
prohibit_self_type="NamedTuple item type",
)
# Workaround #4987 and avoid introducing a bogus UnboundType
Expand Down
3 changes: 1 addition & 2 deletions mypy/semanal_newtype.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,8 +207,7 @@ def check_newtype_args(
self.api.anal_type(
unanalyzed_type,
report_invalid_types=False,
allow_placeholder=not self.options.disable_recursive_aliases
and not self.api.is_func_scope(),
allow_placeholder=not self.api.is_func_scope(),
)
)
should_defer = False
Expand Down
11 changes: 3 additions & 8 deletions mypy/semanal_typeddict.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,10 +228,7 @@ def analyze_base_args(self, base: IndexExpr, ctx: Context) -> list[Type] | None:
self.fail("Invalid TypedDict type argument", ctx)
return None
analyzed = self.api.anal_type(
type,
allow_required=True,
allow_placeholder=not self.options.disable_recursive_aliases
and not self.api.is_func_scope(),
type, allow_required=True, allow_placeholder=not self.api.is_func_scope()
)
if analyzed is None:
return None
Expand Down Expand Up @@ -307,8 +304,7 @@ def analyze_typeddict_classdef_fields(
analyzed = self.api.anal_type(
stmt.type,
allow_required=True,
allow_placeholder=not self.options.disable_recursive_aliases
and not self.api.is_func_scope(),
allow_placeholder=not self.api.is_func_scope(),
prohibit_self_type="TypedDict item type",
)
if analyzed is None:
Expand Down Expand Up @@ -504,8 +500,7 @@ def parse_typeddict_fields_with_types(
analyzed = self.api.anal_type(
type,
allow_required=True,
allow_placeholder=not self.options.disable_recursive_aliases
and not self.api.is_func_scope(),
allow_placeholder=not self.api.is_func_scope(),
prohibit_self_type="TypedDict item type",
)
if analyzed is None:
Expand Down
2 changes: 1 addition & 1 deletion mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,7 @@ def cannot_resolve_type(self, t: UnboundType) -> None:
# need access to MessageBuilder here. Also move the similar
# message generation logic in semanal.py.
self.api.fail(f'Cannot resolve name "{t.name}" (possible cyclic definition)', t)
if not self.options.disable_recursive_aliases and self.api.is_func_scope():
if self.api.is_func_scope():
self.note("Recursive types are not allowed at function scope", t)

def apply_concatenate_operator(self, t: UnboundType) -> Type:
Expand Down
31 changes: 17 additions & 14 deletions test-data/unit/check-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -5002,12 +5002,13 @@ class A(Tuple[int, str]): pass
-- -----------------------

[case testCrashOnSelfRecursiveNamedTupleVar]
# flags: --disable-recursive-aliases
from typing import NamedTuple

N = NamedTuple('N', [('x', N)]) # E: Cannot resolve name "N" (possible cyclic definition)
n: N
reveal_type(n) # N: Revealed type is "Tuple[Any, fallback=__main__.N]"
def test() -> None:
N = NamedTuple('N', [('x', N)]) # E: Cannot resolve name "N" (possible cyclic definition) \
# N: Recursive types are not allowed at function scope
n: N
reveal_type(n) # N: Revealed type is "Tuple[Any, fallback=__main__.N@4]"
[builtins fixtures/tuple.pyi]

[case testCrashOnSelfRecursiveTypedDictVar]
Expand All @@ -5032,18 +5033,20 @@ lst = [n, m]
[builtins fixtures/isinstancelist.pyi]

[case testCorrectJoinOfSelfRecursiveTypedDicts]
# flags: --disable-recursive-aliases
from mypy_extensions import TypedDict

class N(TypedDict):
x: N # E: Cannot resolve name "N" (possible cyclic definition)
class M(TypedDict):
x: M # E: Cannot resolve name "M" (possible cyclic definition)

n: N
m: M
lst = [n, m]
reveal_type(lst[0]['x']) # N: Revealed type is "Any"
def test() -> None:
class N(TypedDict):
x: N # E: Cannot resolve name "N" (possible cyclic definition) \
# N: Recursive types are not allowed at function scope
class M(TypedDict):
x: M # E: Cannot resolve name "M" (possible cyclic definition) \
# N: Recursive types are not allowed at function scope

n: N
m: M
lst = [n, m]
reveal_type(lst[0]['x']) # N: Revealed type is "Any"
[builtins fixtures/isinstancelist.pyi]

[case testCrashInForwardRefToNamedTupleWithIsinstance]
Expand Down
15 changes: 4 additions & 11 deletions test-data/unit/check-incremental.test
Original file line number Diff line number Diff line change
Expand Up @@ -4594,7 +4594,6 @@ def outer() -> None:
[out2]

[case testRecursiveAliasImported]
# flags: --disable-recursive-aliases
import a

[file a.py]
Expand All @@ -4620,16 +4619,10 @@ B = List[A]

[builtins fixtures/list.pyi]
[out]
tmp/lib.pyi:4: error: Module "other" has no attribute "B"
tmp/other.pyi:3: error: Cannot resolve name "B" (possible cyclic definition)
[out2]
tmp/lib.pyi:4: error: Module "other" has no attribute "B"
tmp/other.pyi:3: error: Cannot resolve name "B" (possible cyclic definition)
tmp/a.py:3: note: Revealed type is "builtins.list[Any]"

[case testRecursiveNamedTupleTypedDict-skip]
# https://github.com/python/mypy/issues/7125
tmp/a.py:3: note: Revealed type is "builtins.list[builtins.list[...]]"

[case testRecursiveNamedTupleTypedDict]
import a
[file a.py]
import lib
Expand All @@ -4641,15 +4634,15 @@ reveal_type(x.x['x'])
[file lib.pyi]
from typing import NamedTuple
from other import B
A = NamedTuple('A', [('x', B)]) # type: ignore
A = NamedTuple('A', [('x', B)])
[file other.pyi]
from mypy_extensions import TypedDict
from lib import A
B = TypedDict('B', {'x': A})
[builtins fixtures/dict.pyi]
[out]
[out2]
tmp/a.py:3: note: Revealed type is "Tuple[TypedDict('other.B', {'x': Any}), fallback=lib.A]"
tmp/a.py:3: note: Revealed type is "Tuple[TypedDict('other.B', {'x': Tuple[..., fallback=lib.A]}), fallback=lib.A]"

[case testFollowImportSkipNotInvalidatedOnPresent]
# flags: --follow-imports=skip
Expand Down
Loading

0 comments on commit 9011ca8

Please sign in to comment.