Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stop TypedDictAnalyzer from leaking synthetic types #13732

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions mypy/semanal_typeddict.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,18 @@ def analyze_typeddict_classdef_fields(
)
if analyzed is None:
return None, [], [], set() # Need to defer
# TypedDictAnalyzer sets the AssignmentStmt type here, but
# NamedTupleAnalyzer doesn't and instead has semanal.py set it
# by calling analyze_class_body_common after.
#
# This is because unlike TypedDicts, NamedTuples support method
# definitions. So, we must handle some of what analyze_class_body_common
# does here -- including modifying `stmt.type`.
#
# TODO: Find some way of refactoring and partially unifying
# these two codepaths?
if not has_placeholder(analyzed):
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think #13516 might have been the root cause of the previous mypy_primer failures -- the previous version of this diff probably just never worked when we enabled recursive types, so it broke once I rebased pass the diff flipping the defaults.

I'm not really sure this is the correct fix though; it feels sort of ad-hoc.

I also need to figure out what's up with the bokeh error from mypy_primer -- I'm having a difficult time repro-ing those new failures locally.

stmt.type = analyzed
types.append(analyzed)
# ...despite possible minor failures that allow further analysis.
if stmt.type is None or hasattr(stmt, "new_syntax") and not stmt.new_syntax:
Expand Down
30 changes: 30 additions & 0 deletions test-data/unit/check-semanal-error.test
Original file line number Diff line number Diff line change
Expand Up @@ -152,3 +152,33 @@ class C:
x: P[int] = C()
[builtins fixtures/tuple.pyi]
[out]

[case testSemanalDoesNotLeakSyntheticTypes]
# flags: --cache-fine-grained
from typing import Generic, NamedTuple, TypedDict, TypeVar
from dataclasses import dataclass

T = TypeVar('T')
class Wrap(Generic[T]): pass

invalid_1: 1 + 2 # E: Invalid type comment or annotation
invalid_2: Wrap[1 + 2] # E: Invalid type comment or annotation

class A:
invalid_1: 1 + 2 # E: Invalid type comment or annotation
invalid_2: Wrap[1 + 2] # E: Invalid type comment or annotation

class B(NamedTuple):
invalid_1: 1 + 2 # E: Invalid type comment or annotation
invalid_2: Wrap[1 + 2] # E: Invalid type comment or annotation

class C(TypedDict):
invalid_1: 1 + 2 # E: Invalid type comment or annotation
invalid_2: Wrap[1 + 2] # E: Invalid type comment or annotation

@dataclass
class D:
invalid_1: 1 + 2 # E: Invalid type comment or annotation
invalid_2: Wrap[1 + 2] # E: Invalid type comment or annotation
[builtins fixtures/dict.pyi]
[typing fixtures/typing-typeddict.pyi]
21 changes: 21 additions & 0 deletions test-data/unit/check-typeddict.test
Original file line number Diff line number Diff line change
Expand Up @@ -2189,6 +2189,27 @@ Foo = TypedDict('Foo', {'camelCaseKey': str})
value: Foo = {} # E: Missing key "camelCaseKey" for TypedDict "Foo"
[builtins fixtures/dict.pyi]

[case testTypedDictWithDeferredFieldTypeEval]
from typing import Generic, TypeVar, TypedDict, NamedTuple

class Foo(TypedDict):
# Inner[ForceDeferredEval] will be a placeholder type on first pass;
# confirm we infer the correct type on the second.
x: Outer[Inner[ForceDeferredEval]]

var: Foo
reveal_type(var) # N: Revealed type is "TypedDict('__main__.Foo', {'x': __main__.Outer[__main__.Inner[__main__.ForceDeferredEval]]})"

T1 = TypeVar("T1")
class Outer(Generic[T1]): pass

T2 = TypeVar("T2", bound=ForceDeferredEval)
class Inner(Generic[T2]): pass

class ForceDeferredEval: pass
[builtins fixtures/dict.pyi]
[typing fixtures/typing-typeddict.pyi]

-- Required[]

[case testDoesRecognizeRequiredInTypedDictWithClass]
Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/semanal-typeddict.test
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,4 @@ MypyFile:1(
NameExpr(x)
TempNode:4(
Any)
str?)))
builtins.str)))