Skip to content

Remove duplicated constraints before solving and deduplicate fast_container_type items #19409

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

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
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
28 changes: 22 additions & 6 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -5064,11 +5064,16 @@ def fast_container_type(
Limitations:
- no active type context
- no star expressions
- the joined type of all entries must be an Instance or Tuple type
- not after deferral
- either exactly one distinct type inside,
or the joined type of all entries must be an Instance or Tuple type
"""
ctx = self.type_context[-1]
if ctx:
return None
if self.chk.current_node_deferred:
# Guarantees that all items will be Any, we'll reject it anyway.
return None
rt = self.resolved_type.get(e, None)
if rt is not None:
return rt if isinstance(rt, Instance) else None
Expand All @@ -5078,11 +5083,22 @@ def fast_container_type(
# fallback to slow path
self.resolved_type[e] = NoneType()
return None
values.append(self.accept(item))
vt = join.join_type_list(values)
if not allow_fast_container_literal(vt):
self.resolved_type[e] = NoneType()
return None

typ = self.accept(item)
if typ not in values:
values.append(typ)

if len(values) == 1 and not self.chk.current_node_deferred:
Copy link
Collaborator

@hauntsaninja hauntsaninja Jul 10, 2025

Choose a reason for hiding this comment

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

second half of clause is now always true. also dumb question: what is the "inference cycle" in the else branch?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

second half of clause is now always true

Huh, why? We had self.accept after previous check, it might have changed

what is the "inference cycle" in the else branch

Do you mean what happens when this method returns None? We run a full typecheck on container_type.__init__(*args), which is significantly slower than this happy path where all arguments are already "good enough".

# If only one non-duplicate item remains, there's no need to run the whole
# inference cycle over it. This helps in pathological cases where items
# are complex overloads.
# https://github.com/python/mypy/issues/14718
vt = values[0]
else:
vt = join.join_type_list(values)
if not allow_fast_container_literal(vt):
self.resolved_type[e] = NoneType()
return None
ct = self.chk.named_generic_type(container_fullname, [vt])
self.resolved_type[e] = ct
return ct
Expand Down
3 changes: 3 additions & 0 deletions mypy/constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ def infer_constraints_for_callable(
)
c = infer_constraints(callee.arg_types[i], actual_type, SUPERTYPE_OF)
constraints.extend(c)

if (
param_spec
and not any(c.type_var == param_spec.id for c in constraints)
Expand All @@ -270,6 +271,8 @@ def infer_constraints_for_callable(
),
)
)

constraints = list(dict.fromkeys(constraints))
if any(isinstance(v, ParamSpecType) for v in callee.variables):
# As a perf optimization filter imprecise constraints only when we can have them.
constraints = filter_imprecise_kinds(constraints)
Expand Down
10 changes: 5 additions & 5 deletions test-data/unit/check-generics.test
Original file line number Diff line number Diff line change
Expand Up @@ -2929,8 +2929,8 @@ def mix(fs: List[Callable[[S], T]]) -> Callable[[S], List[T]]:
def id(__x: U) -> U:
...
fs = [id, id, id]
reveal_type(mix(fs)) # N: Revealed type is "def [S] (S`11) -> builtins.list[S`11]"
reveal_type(mix([id, id, id])) # N: Revealed type is "def [S] (S`13) -> builtins.list[S`13]"
reveal_type(mix(fs)) # N: Revealed type is "def [S] (S`2) -> builtins.list[S`2]"
reveal_type(mix([id, id, id])) # N: Revealed type is "def [S] (S`4) -> builtins.list[S`4]"
[builtins fixtures/list.pyi]

[case testInferenceAgainstGenericCurry]
Expand Down Expand Up @@ -3118,11 +3118,11 @@ def dec4_bound(f: Callable[[I], List[T]]) -> Callable[[I], T]:
reveal_type(dec1(lambda x: x)) # N: Revealed type is "def [T] (T`3) -> builtins.list[T`3]"
reveal_type(dec2(lambda x: x)) # N: Revealed type is "def [S] (S`5) -> builtins.list[S`5]"
reveal_type(dec3(lambda x: x[0])) # N: Revealed type is "def [S] (S`8) -> S`8"
reveal_type(dec4(lambda x: [x])) # N: Revealed type is "def [S] (S`12) -> S`12"
reveal_type(dec4(lambda x: [x])) # N: Revealed type is "def [S] (S`11) -> S`11"
reveal_type(dec1(lambda x: 1)) # N: Revealed type is "def (builtins.int) -> builtins.list[builtins.int]"
reveal_type(dec5(lambda x: x)) # N: Revealed type is "def (builtins.int) -> builtins.list[builtins.int]"
reveal_type(dec3(lambda x: x)) # N: Revealed type is "def [S] (S`20) -> builtins.list[S`20]"
reveal_type(dec4(lambda x: x)) # N: Revealed type is "def [T] (builtins.list[T`24]) -> T`24"
reveal_type(dec3(lambda x: x)) # N: Revealed type is "def [S] (S`19) -> builtins.list[S`19]"
reveal_type(dec4(lambda x: x)) # N: Revealed type is "def [T] (builtins.list[T`23]) -> T`23"
dec4_bound(lambda x: x) # E: Value of type variable "I" of "dec4_bound" cannot be "list[T]"
[builtins fixtures/list.pyi]

Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/check-redefine2.test
Original file line number Diff line number Diff line change
Expand Up @@ -1073,7 +1073,7 @@ def f() -> None:
while int():
x = [x]

reveal_type(x) # N: Revealed type is "Union[Any, builtins.list[Any], builtins.list[Union[Any, builtins.list[Any]]], builtins.list[Union[Any, builtins.list[Any], builtins.list[Union[Any, builtins.list[Any]]]]], builtins.list[Union[Any, builtins.list[Any], builtins.list[Union[Any, builtins.list[Any]]], builtins.list[Union[Any, builtins.list[Any], builtins.list[Union[Any, builtins.list[Any]]]]]]]]"
reveal_type(x) # N: Revealed type is "Union[Any, builtins.list[Any]]"

[case testNewRedefinePartialNoneEmptyList]
# flags: --allow-redefinition-new --local-partial-types
Expand Down
19 changes: 9 additions & 10 deletions test-data/unit/check-typeddict.test
Original file line number Diff line number Diff line change
Expand Up @@ -1164,22 +1164,21 @@ fc(b) # E: Argument 1 to "fc" has incompatible type "B"; expected "C"
from typing import TypedDict, TypeVar
A = TypedDict('A', {'x': int})
B = TypedDict('B', {'x': int}, total=False)
B2 = TypedDict('B2', {'x': int}, total=False)
C = TypedDict('C', {'x': int, 'y': str}, total=False)
C2 = TypedDict('C2', {'x': int, 'y': str}, total=False)
T = TypeVar('T')
def j(x: T, y: T) -> T: return x
a: A
b: B
b2: B2
c: C
reveal_type(j(a, b)) \
# N: Revealed type is "TypedDict({})"
reveal_type(j(b, b)) \
# N: Revealed type is "TypedDict({'x'?: builtins.int})"
reveal_type(j(c, c)) \
# N: Revealed type is "TypedDict({'x'?: builtins.int, 'y'?: builtins.str})"
reveal_type(j(b, c)) \
# N: Revealed type is "TypedDict({'x'?: builtins.int})"
reveal_type(j(c, b)) \
# N: Revealed type is "TypedDict({'x'?: builtins.int})"
c2: C2
reveal_type(j(a, b)) # N: Revealed type is "TypedDict({})"
reveal_type(j(b, b2)) # N: Revealed type is "TypedDict({'x'?: builtins.int})"
reveal_type(j(c, c2)) # N: Revealed type is "TypedDict({'x'?: builtins.int, 'y'?: builtins.str})"
reveal_type(j(b, c)) # N: Revealed type is "TypedDict({'x'?: builtins.int})"
reveal_type(j(c, b)) # N: Revealed type is "TypedDict({'x'?: builtins.int})"
[builtins fixtures/dict.pyi]
[typing fixtures/typing-typeddict.pyi]

Expand Down