10000 New type inference: complete transitive closure by ilevkivskyi · Pull Request #15754 · python/mypy · GitHub
[go: up one dir, main page]

Skip to content

New type inference: complete transitive closure #15754

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

Merged
merged 16 commits into from
Aug 3, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Fix partial types (and TypeVarTuple)
  • Loading branch information
ilevkivskyi committed Jul 23, 2023
commit b43f286d1524f1a665b7ea8be8cdfe9bc5a44a90
11 changes: 6 additions & 5 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -3878,11 +3878,12 @@ def is_valid_defaultdict_partial_value_type(self, t: ProperType) -> bool:
return True
if len(t.args) == 1:
arg = get_proper_type(t.args[0])
# TODO: This is too permissive -- we only allow TypeVarType since
# they leak in cases like defaultdict(list) due to a bug.
# This can result in incorrect types being inferred, but only
# in rare cases.
if isinstance(arg, (TypeVarType, UninhabitedType, NoneType)):
if self.options.new_type_inference:
allowed = isinstance(arg, (UninhabitedType, NoneType))
else:
# Allow leaked TypeVars for legacy inference logic.
allowed = isinstance(arg, (UninhabitedType, NoneType, TypeVarType))
if allowed:
return True
return False

Expand Down
8 changes: 6 additions & 2 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -1965,11 +1965,15 @@ def infer_function_type_arguments(
freeze_all_type_vars(applied)
return applied
# If it didn't work, erase free variables as <nothing>, to avoid confusing errors.
unknown = UninhabitedType()
unknown.ambiguous = True
inferred_args = [
expand_type(a, {v.id: UninhabitedType() for v in callee_type.variables})
expand_type(
a, {v.id: unknown for v in list(callee_type.variables) + free_vars}
)
if a is not None
else None
for a in inferred_args
for a in poly_inferred_args
]
else:
# In dynamically typed functions use implicit 'Any' types for
Expand Down
11 changes: 7 additions & 4 deletions mypy/constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -661,13 +661,13 @@ def visit_instance(self, template: Instance) -> list[Constraint]:
assert mapped.type.type_var_tuple_prefix is not None
assert mapped.type.type_var_tuple_suffix is not None

unpack_constraints, mapped_args, instance_args = build_constraints_for_unpack(
mapped.args,
mapped.type.type_var_tuple_prefix,
mapped.type.type_var_tuple_suffix,
unpack_constraints, instance_args, mapped_args = build_constraints_for_unpack(
instance.args,
instance.type.type_var_tuple_prefix,
instance.type.type_var_tuple_suffix,
mapped.args,
mapped.type.type_var_tuple_prefix,
mapped.type.type_var_tuple_suffix,
self.direction,
)
res.extend(unpack_constraints)
Expand Down Expand Up @@ -1217,6 +1217,9 @@ def find_and_build_constraints_for_unpack(


def build_constraints_for_unpack(
# TODO: this naming is misleading, these should be "actual", not "mapped"
# both template and actual can be mapped before, depending on direction.
# Also the convention is to put template related args first.
mapped: tuple[Type, ...],
mapped_prefix_len: int | None,
mapped_suffix_len: int | None,
Expand Down
2 changes: 1 addition & 1 deletion mypy/expandtype.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ def visit_param_spec(self, t: ParamSpecType) -> Type:
return repl

def visit_type_var_tuple(self, t: TypeVarTupleType) -> Type:
# Sometimes solver may need to expand a type variable with itself
# Sometimes solver may need to expand a type variable with (a copy of) itself
# (usually for other TypeVars, and it is hard to filter out TypeVarTuples).
repl = self.variables[t.id]
if isinstance(repl, TypeVarTupleType):
Expand Down
9 changes: 6 additions & 3 deletions mypy/infer.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
)
from mypy.nodes import ArgKind
from mypy.solve import solve_constraints
from mypy.types import CallableType, Instance, Type, TypeVarId, TypeVarType, TypeVarLikeType
from mypy.types import CallableType, Instance, Type, TypeVarLikeType


class ArgumentInferContext(NamedTuple):
Expand All @@ -37,7 +37,7 @@ def infer_function_type_arguments(
context: ArgumentInferContext,
strict: bool = True,
allow_polymorphic: bool = False,
) -> tuple[list[Type | None], list[TypeVarType]]:
) -> tuple[list[Type | None], list[TypeVarLikeType]]:
"""Infer the type arguments of a generic function.

Return an array of lower bound types for the type variables -1 (at
Expand All @@ -62,7 +62,10 @@ def infer_function_type_arguments(


def infer_type_arguments(
type_var_ids: Sequence[TypeVarLikeType], template: Type, actual: Type, is_supertype: bool = False
type_var_ids: Sequence[TypeVarLikeType],
template: Type,
actual: Type,
is_supertype: bool = False,
) -> list[Type | None]:
# Like infer_function_type_arguments, but only match a single type
# against a generic type.
Expand Down
15 changes: 10 additions & 5 deletions mypy/solve.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from collections import defaultdict
from typing import Iterable, Sequence

from mypy.constraints import SUBTYPE_OF, SUPERTYPE_OF, Constraint, neg_op, infer_constraints
from mypy.constraints import SUBTYPE_OF, SUPERTYPE_OF, Constraint, infer_constraints, neg_op
from mypy.expandtype import expand_type
from mypy.graph_utils import prepare_sccs, strongly_connected_components, topsort
from mypy.join import join_types
Expand All @@ -18,12 +18,12 @@
Type,
TypeOfAny,
TypeVarId,
TypeVarLikeType,
TypeVarType,
UninhabitedType,
UnionType,
get_proper_type,
remove_dups,
TypeVarLikeType,
)
from mypy.typestate import type_state

Expand All @@ -33,7 +33,7 @@ def solve_constraints(
constraints: list[Constraint],
strict: bool = True,
allow_polymorphic: bool = False,
) -> tuple[list[Type | None], list[TypeVarType]]:
) -> tuple[list[Type | None], list[TypeVarLikeType]]:
"""Solve type constraints.

Return the best type(s) for type variables; each type can be None if the value of the variable
Expand Down Expand Up @@ -65,7 +65,13 @@ def solve_constraints(
cmap[con.type_var].append(con)

if allow_polymorphic:
solutions, free_vars = solve_non_linear(vars + extra_vars, constraints, vars, originals)
if constraints:
solutions, free_vars = solve_non_linear(
vars + extra_vars, constraints, vars, originals
)
else:
solutions = {}
free_vars = []
else:
solutions = {}
free_vars = []
Expand All @@ -77,7 +83,6 @@ def solve_constraints(
solution = solve_one(lowers, uppers, [])

# Do not leak type variables in non-polymorphic solutions.
# XXX: somehow this breaks defaultdict partial types.
if solution is None or not get_vars(
solution, [tv for tv in extra_vars if tv not in vars]
):
Expand Down
2 changes: 1 addition & 1 deletion mypy/test/testsolve.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from mypy.solve import solve_constraints
from mypy.test.helpers import Suite, assert_equal
from mypy.test.typefixture import TypeFixture
from mypy.types import Type, TypeVarId, TypeVarType, TypeVarLikeType
from mypy.types import Type, TypeVarLikeType, TypeVarType


class SolveSuite(Suite):
Expand Down
0