Skip to content

Commit

Permalink
Flip the default for recursive aliases flag (#13516)
Browse files Browse the repository at this point in the history
I don't think we need to wait long time for this. As soon as next
release goes out, I think we can flip the default. Otherwise, this
feature may degrade, because there are just several dozen tests with
this flag on.

(Also I am curious to see `mypy_primer` on this.)

I manually checked each of couple dozen tests where I currently disable
recursive aliases (they essentially just test that there is no crash and
emit various errors like `Cannot resolve name`).
  • Loading branch information
ilevkivskyi authored Sep 29, 2022
1 parent 0f4e0fb commit ddd9177
Show file tree
Hide file tree
Showing 18 changed files with 57 additions and 97 deletions.
4 changes: 2 additions & 2 deletions mypy/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -975,9 +975,9 @@ def add_invertible_flag(
help="Use a custom typing module",
)
internals_group.add_argument(
"--enable-recursive-aliases",
"--disable-recursive-aliases",
action="store_true",
help="Experimental support for recursive type aliases",
help="Disable experimental support for recursive type aliases",
)
internals_group.add_argument(
"--custom-typeshed-dir", metavar="DIR", help="Use the custom typeshed in DIR"
Expand Down
4 changes: 2 additions & 2 deletions mypy/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,8 +312,8 @@ def __init__(self) -> None:
# skip most errors after this many messages have been reported.
# -1 means unlimited.
self.many_errors_threshold = defaults.MANY_ERRORS_THRESHOLD
# Enable recursive type aliases (currently experimental)
self.enable_recursive_aliases = False
# Disable recursive type aliases (currently experimental)
self.disable_recursive_aliases = False

# To avoid breaking plugin compatibility, keep providing new_semantic_analyzer
@property
Expand Down
4 changes: 2 additions & 2 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -3238,7 +3238,7 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:
)
if not res:
return False
if self.options.enable_recursive_aliases and not self.is_func_scope():
if not self.options.disable_recursive_aliases and 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 @@ -5749,7 +5749,7 @@ def process_placeholder(self, name: str, kind: str, ctx: Context) -> None:

def cannot_resolve_name(self, name: str, kind: str, ctx: Context) -> None:
self.fail(f'Cannot resolve {kind} "{name}" (possible cyclic definition)', ctx)
if self.options.enable_recursive_aliases and self.is_func_scope():
if not self.options.disable_recursive_aliases and self.is_func_scope():
self.note("Recursive types are not allowed at function scope", ctx)

def qualified_name(self, name: str) -> str:
Expand Down
4 changes: 2 additions & 2 deletions mypy/semanal_namedtuple.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ def check_namedtuple_classdef(
# it would be inconsistent with type aliases.
analyzed = self.api.anal_type(
stmt.type,
allow_placeholder=self.options.enable_recursive_aliases
allow_placeholder=not self.options.disable_recursive_aliases
and not self.api.is_func_scope(),
)
if analyzed is None:
Expand Down Expand Up @@ -443,7 +443,7 @@ def parse_namedtuple_fields_with_types(
# We never allow recursive types at function scope.
analyzed = self.api.anal_type(
type,
allow_placeholder=self.options.enable_recursive_aliases
allow_placeholder=not self.options.disable_recursive_aliases
and not self.api.is_func_scope(),
)
# Workaround #4987 and avoid introducing a bogus UnboundType
Expand Down
2 changes: 1 addition & 1 deletion mypy/semanal_newtype.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ def check_newtype_args(
self.api.anal_type(
unanalyzed_type,
report_invalid_types=False,
allow_placeholder=self.options.enable_recursive_aliases
allow_placeholder=not self.options.disable_recursive_aliases
and not self.api.is_func_scope(),
)
)
Expand Down
6 changes: 3 additions & 3 deletions mypy/semanal_typeddict.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ def analyze_base_args(self, base: IndexExpr, ctx: Context) -> list[Type] | None:
analyzed = self.api.anal_type(
type,
allow_required=True,
allow_placeholder=self.options.enable_recursive_aliases
allow_placeholder=not self.options.disable_recursive_aliases
and not self.api.is_func_scope(),
)
if analyzed is None:
Expand Down Expand Up @@ -289,7 +289,7 @@ def analyze_typeddict_classdef_fields(
analyzed = self.api.anal_type(
stmt.type,
allow_required=True,
allow_placeholder=self.options.enable_recursive_aliases
allow_placeholder=not self.options.disable_recursive_aliases
and not self.api.is_func_scope(),
)
if analyzed is None:
Expand Down Expand Up @@ -484,7 +484,7 @@ def parse_typeddict_fields_with_types(
analyzed = self.api.anal_type(
type,
allow_required=True,
allow_placeholder=self.options.enable_recursive_aliases
allow_placeholder=not self.options.disable_recursive_aliases
and not self.api.is_func_scope(),
)
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 @@ -402,7 +402,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 self.options.enable_recursive_aliases and self.api.is_func_scope():
if not self.options.disable_recursive_aliases and 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
4 changes: 2 additions & 2 deletions test-data/unit/check-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -4779,7 +4779,7 @@ 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)
Expand Down Expand Up @@ -4809,7 +4809,7 @@ lst = [n, m]
[builtins fixtures/isinstancelist.pyi]

[case testCorrectJoinOfSelfRecursiveTypedDicts]

# flags: --disable-recursive-aliases
from mypy_extensions import TypedDict

class N(TypedDict):
Expand Down
1 change: 0 additions & 1 deletion test-data/unit/check-dataclasses.test
Original file line number Diff line number Diff line change
Expand Up @@ -1915,7 +1915,6 @@ takes_cp(MyDataclass)
[builtins fixtures/dataclasses.pyi]

[case testDataclassTypeAnnotationAliasUpdated]
# flags: --enable-recursive-aliases
import a
[file a.py]
from dataclasses import dataclass
Expand Down
10 changes: 5 additions & 5 deletions test-data/unit/check-incremental.test
Original file line number Diff line number Diff line change
Expand Up @@ -4600,7 +4600,7 @@ def outer() -> None:
[out2]

[case testRecursiveAliasImported]

# flags: --disable-recursive-aliases
import a

[file a.py]
Expand Down Expand Up @@ -5759,7 +5759,7 @@ class C:
[builtins fixtures/tuple.pyi]

[case testNamedTupleUpdateNonRecursiveToRecursiveCoarse]
# flags: --enable-recursive-aliases
# flags: --strict-optional
import c
[file a.py]
from b import M
Expand Down Expand Up @@ -5802,7 +5802,7 @@ tmp/c.py:5: error: Incompatible types in assignment (expression has type "Option
tmp/c.py:7: note: Revealed type is "Tuple[Union[Tuple[Union[..., None], builtins.int, fallback=b.M], None], builtins.int, fallback=a.N]"

[case testTupleTypeUpdateNonRecursiveToRecursiveCoarse]
# flags: --enable-recursive-aliases
# flags: --strict-optional
import c
[file a.py]
from b import M
Expand Down Expand Up @@ -5835,7 +5835,7 @@ tmp/c.py:4: note: Revealed type is "Tuple[Union[Tuple[Union[..., None], builtins
tmp/c.py:5: error: Incompatible types in assignment (expression has type "Optional[N]", variable has type "int")

[case testTypeAliasUpdateNonRecursiveToRecursiveCoarse]
# flags: --enable-recursive-aliases
# flags: --strict-optional
import c
[file a.py]
from b import M
Expand Down Expand Up @@ -5868,7 +5868,7 @@ tmp/c.py:4: note: Revealed type is "Tuple[Union[Tuple[Union[..., None], builtins
tmp/c.py:5: error: Incompatible types in assignment (expression has type "Optional[N]", variable has type "int")

[case testTypedDictUpdateNonRecursiveToRecursiveCoarse]
# flags: --enable-recursive-aliases
# flags: --strict-optional
import c
[file a.py]
from b import M
Expand Down
16 changes: 8 additions & 8 deletions test-data/unit/check-namedtuple.test
Original file line number Diff line number Diff line change
Expand Up @@ -617,7 +617,7 @@ tmp/b.py:4: note: Revealed type is "Tuple[Any, fallback=a.N]"
tmp/b.py:7: note: Revealed type is "Tuple[Any, fallback=a.N]"

[case testSimpleSelfReferentialNamedTuple]

# flags: --disable-recursive-aliases
from typing import NamedTuple
class MyNamedTuple(NamedTuple):
parent: 'MyNamedTuple' # E: Cannot resolve name "MyNamedTuple" (possible cyclic definition)
Expand Down Expand Up @@ -655,7 +655,7 @@ class B:
[out]

[case testSelfRefNT1]

# flags: --disable-recursive-aliases
from typing import Tuple, NamedTuple

Node = NamedTuple('Node', [
Expand All @@ -667,7 +667,7 @@ reveal_type(n) # N: Revealed type is "Tuple[builtins.str, builtins.tuple[Any, ..
[builtins fixtures/tuple.pyi]

[case testSelfRefNT2]

# flags: --disable-recursive-aliases
from typing import Tuple, NamedTuple

A = NamedTuple('A', [
Expand All @@ -683,7 +683,7 @@ reveal_type(n) # N: Revealed type is "Tuple[builtins.str, builtins.tuple[Any, ..
[builtins fixtures/tuple.pyi]

[case testSelfRefNT3]

# flags: --disable-recursive-aliases
from typing import NamedTuple, Tuple

class B(NamedTuple):
Expand All @@ -703,7 +703,7 @@ reveal_type(lst[0]) # N: Revealed type is "Tuple[builtins.object, builtins.objec
[builtins fixtures/tuple.pyi]

[case testSelfRefNT4]

# flags: --disable-recursive-aliases
from typing import NamedTuple

class B(NamedTuple):
Expand All @@ -719,7 +719,7 @@ reveal_type(n.y[0]) # N: Revealed type is "Any"
[builtins fixtures/tuple.pyi]

[case testSelfRefNT5]

# flags: --disable-recursive-aliases
from typing import NamedTuple

B = NamedTuple('B', [
Expand All @@ -737,7 +737,7 @@ reveal_type(f) # N: Revealed type is "def (m: Tuple[Any, builtins.int, fallback=
[builtins fixtures/tuple.pyi]

[case testRecursiveNamedTupleInBases]

# flags: --disable-recursive-aliases
from typing import List, NamedTuple, Union

Exp = Union['A', 'B'] # E: Cannot resolve name "Exp" (possible cyclic definition) \
Expand Down Expand Up @@ -781,7 +781,7 @@ tp = NamedTuple('tp', [('x', int)])
[out]

[case testSubclassOfRecursiveNamedTuple]

# flags: --disable-recursive-aliases
from typing import List, NamedTuple

class Command(NamedTuple):
Expand Down
24 changes: 15 additions & 9 deletions test-data/unit/check-newsemanal.test
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,7 @@ def main() -> None:
x # E: Name "x" is not defined

[case testNewAnalyzerCyclicDefinitions]
# flags: --disable-recursive-aliases
gx = gy # E: Cannot resolve name "gy" (possible cyclic definition)
gy = gx
def main() -> None:
Expand Down Expand Up @@ -1499,6 +1500,7 @@ reveal_type(x[0][0]) # N: Revealed type is "__main__.C"
[builtins fixtures/list.pyi]

[case testNewAnalyzerAliasToNotReadyDirectBase]
# flags: --disable-recursive-aliases
from typing import List

x: B
Expand All @@ -1509,11 +1511,11 @@ reveal_type(x)
reveal_type(x[0][0])
[builtins fixtures/list.pyi]
[out]
main:3: error: Cannot resolve name "B" (possible cyclic definition)
main:4: error: Cannot resolve name "B" (possible cyclic definition)
main:4: error: Cannot resolve name "C" (possible cyclic definition)
main:7: note: Revealed type is "Any"
main:5: error: Cannot resolve name "B" (possible cyclic definition)
main:5: error: Cannot resolve name "C" (possible cyclic definition)
main:8: note: Revealed type is "Any"
main:9: note: Revealed type is "Any"

[case testNewAnalyzerAliasToNotReadyTwoDeferralsFunction]
import a
Expand All @@ -1532,6 +1534,7 @@ reveal_type(f) # N: Revealed type is "def (x: builtins.list[a.C]) -> builtins.l
[builtins fixtures/list.pyi]

[case testNewAnalyzerAliasToNotReadyDirectBaseFunction]
# flags: --disable-recursive-aliases
import a
[file a.py]
from typing import List
Expand Down Expand Up @@ -2119,6 +2122,7 @@ class B(List[C]):
[builtins fixtures/list.pyi]

[case testNewAnalyzerNewTypeForwardClassAliasDirect]
# flags: --disable-recursive-aliases
from typing import NewType, List

x: D
Expand All @@ -2131,12 +2135,12 @@ class B(D):
pass
[builtins fixtures/list.pyi]
[out]
main:3: error: Cannot resolve name "D" (possible cyclic definition)
main:4: note: Revealed type is "Any"
main:6: error: Cannot resolve name "D" (possible cyclic definition)
main:6: error: Cannot resolve name "C" (possible cyclic definition)
main:7: error: Argument 2 to NewType(...) must be a valid type
main:7: error: Cannot resolve name "B" (possible cyclic definition)
main:4: error: Cannot resolve name "D" (possible cyclic definition)
main:5: note: Revealed type is "Any"
main:7: error: Cannot resolve name "D" (possible cyclic definition)
main:7: error: Cannot resolve name "C" (possible cyclic definition)
main:8: error: Argument 2 to NewType(...) must be a valid type
main:8: error: Cannot resolve name "B" (possible cyclic definition)

-- Copied from check-classes.test (tricky corner cases).
[case testNewAnalyzerNoCrashForwardRefToBrokenDoubleNewTypeClass]
Expand All @@ -2153,6 +2157,7 @@ class C:
[builtins fixtures/dict.pyi]

[case testNewAnalyzerForwardTypeAliasInBase]
# flags: --disable-recursive-aliases
from typing import List, Generic, TypeVar, NamedTuple
T = TypeVar('T')

Expand Down Expand Up @@ -2593,6 +2598,7 @@ import n
def __getattr__(x): pass

[case testNewAnalyzerReportLoopInMRO2]
# flags: --disable-recursive-aliases
def f() -> None:
class A(A): ... # E: Cannot resolve name "A" (possible cyclic definition)

Expand Down
Loading

0 comments on commit ddd9177

Please sign in to comment.