Skip to content
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
1 change: 1 addition & 0 deletions mypy/message_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ def with_additional_msg(self, info: str) -> ErrorMessage:
'Implicit generic "Any". Use "{}" and specify generic parameters'
)
NO_CYCLIC_DEFAULT: Final = "Cyclic type variable defaults are not supported"
NO_DEFAULT_AFTER_TYPEVAR_TUPLE: Final = "A type variable with default cannot follow TypVarTuple"
INVALID_UNPACK: Final = "{} cannot be unpacked (must be tuple or TypeVarTuple)"
INVALID_UNPACK_POSITION: Final = "Unpack is only valid in a variadic position"
INVALID_PARAM_SPEC_LOCATION: Final = "Invalid location for ParamSpec {}"
Expand Down
60 changes: 47 additions & 13 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -546,8 +546,13 @@ def __init__(
# to create the set lazily.
self.types_fixed: set[TypeInfo | TypeAlias] | None = None

# Stack of type variables that have been removed from current class because they
# cannot be bound unambiguously. This can happen if a (regular) type variable
# with a default follows a type variable tuple.
self.removed_type_vars: list[list[TypeVarType]] = [[]]

# mypyc doesn't properly handle implementing an abstractproperty
# with a regular attribute so we make them properties
# with a regular attribute, so we make them properties
@property
def type(self) -> TypeInfo | None:
return self._type
Expand Down Expand Up @@ -1836,8 +1841,9 @@ def visit_class_def(self, defn: ClassDef) -> None:
if self.push_type_args(defn.type_args, defn) is None:
self.mark_incomplete(defn.name, defn)
return

self.removed_type_vars.append([])
self.analyze_class(defn)
self.removed_type_vars.pop()
self.pop_type_args(defn.type_args)
self.incomplete_type_stack.pop()

Expand Down Expand Up @@ -2084,7 +2090,21 @@ def check_type_alias_bases(self, bases: list[Expression]) -> None:
)

def setup_type_vars(self, defn: ClassDef, tvar_defs: list[TypeVarLikeType]) -> None:
defn.type_vars = tvar_defs
seen_tvt = False
valid_tvar_defs = []
for tv in tvar_defs:
if seen_tvt and isinstance(tv, TypeVarType) and tv.has_default():
self.fail(
message_registry.NO_DEFAULT_AFTER_TYPEVAR_TUPLE, defn, code=codes.TYPE_VAR
)
# Remove the ambiguous type variable, and record it, so that we can replace
# all its uses with Any.
self.removed_type_vars[-1].append(tv)
continue
if isinstance(tv, TypeVarTupleType):
seen_tvt = True
valid_tvar_defs.append(tv)
defn.type_vars = valid_tvar_defs
defn.info.type_vars = []
# we want to make sure any additional logic in add_type_vars gets run
defn.info.add_type_vars()
Expand Down Expand Up @@ -4017,6 +4037,28 @@ def analyze_alias(
with self.allow_unbound_tvars_set():
rvalue.accept(self)

new_tvar_defs = []
erase_tvar_defs = []
variadic = False
for td in tvar_defs:
if variadic and isinstance(td, TypeVarType) and td.has_default():
self.fail(
message_registry.NO_DEFAULT_AFTER_TYPEVAR_TUPLE,
rvalue,
code=codes.TYPE_VAR,
)
# Remove the ambiguous type variable, and record it, so that we can
# replace all its uses with Any.
erase_tvar_defs.append(td)
continue
if isinstance(td, TypeVarTupleType):
# There can be only one variadic variable at most,
# the error is reported elsewhere.
if variadic:
continue
variadic = True
new_tvar_defs.append(td)

analyzed, depends_on = analyze_type_alias(
typ,
self,
Expand All @@ -4029,20 +4071,11 @@ def analyze_alias(
in_dynamic_func=dynamic,
global_scope=global_scope,
allowed_alias_tvars=tvar_defs,
erase_tvar_defs=erase_tvar_defs,
alias_type_params_names=all_declared_type_params_names,
python_3_12_type_alias=python_3_12_type_alias,
)

# There can be only one variadic variable at most, the error is reported elsewhere.
new_tvar_defs = []
variadic = False
for td in tvar_defs:
if isinstance(td, TypeVarTupleType):
if variadic:
continue
variadic = True
new_tvar_defs.append(td)

indexed = bool(isinstance(typ, UnboundType) and (typ.args or typ.empty_tuple_index))
default_depends = {}
for _, tv in alias_type_vars:
Expand Down Expand Up @@ -7789,6 +7822,7 @@ def type_analyzer(
prohibit_special_class_field_types=prohibit_special_class_field_types,
allow_type_any=allow_type_any,
analyzing_tvar_def=analyzing_tvar_def,
erase_tvar_defs=self.removed_type_vars[-1],
)
tpan.in_dynamic_func = bool(self.function_stack and self.function_stack[-1].is_dynamic())
tpan.global_scope = not self.type and not self.function_stack
Expand Down
71 changes: 54 additions & 17 deletions mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ def analyze_type_alias(
in_dynamic_func: bool = False,
global_scope: bool = True,
allowed_alias_tvars: list[TypeVarLikeType] | None = None,
erase_tvar_defs: list[TypeVarType] | None = None,
alias_type_params_names: list[str] | None = None,
python_3_12_type_alias: bool = False,
) -> tuple[Type, set[str]]:
Expand All @@ -174,6 +175,7 @@ def analyze_type_alias(
allow_placeholder=allow_placeholder,
prohibit_self_type="type alias target",
allowed_alias_tvars=allowed_alias_tvars,
erase_tvar_defs=erase_tvar_defs,
alias_type_params_names=alias_type_params_names,
python_3_12_type_alias=python_3_12_type_alias,
)
Expand Down Expand Up @@ -220,6 +222,7 @@ def __init__(
prohibit_self_type: str | None = None,
prohibit_special_class_field_types: str | None = None,
allowed_alias_tvars: list[TypeVarLikeType] | None = None,
erase_tvar_defs: list[TypeVarType] | None = None,
allow_type_any: bool = False,
alias_type_params_names: list[str] | None = None,
analyzing_tvar_def: bool = False,
Expand All @@ -240,6 +243,11 @@ def __init__(
if allowed_alias_tvars is None:
allowed_alias_tvars = []
self.allowed_alias_tvars = allowed_alias_tvars
# Should we erase some type variables? This can be used to mass-erase type
# variables that were found to be invalid at the class/alias definition.
if erase_tvar_defs is None:
erase_tvar_defs = []
self.erase_tvar_defs = erase_tvar_defs
self.alias_type_params_names = alias_type_params_names
# If false, record incomplete ref if we generate PlaceholderType.
self.allow_placeholder = allow_placeholder
Expand Down Expand Up @@ -403,6 +411,13 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool)
msg = f'Can\'t use bound type variable "{t.name}" to define generic alias'
self.fail(msg, t, code=codes.VALID_TYPE)
return AnyType(TypeOfAny.from_error)
if (
isinstance(sym.node, TypeVarExpr)
and tvar_def is not None
and tvar_def in self.erase_tvar_defs
):
# The caller should have already given a relevant error.
return AnyType(TypeOfAny.from_error)
if isinstance(sym.node, TypeVarExpr) and tvar_def is not None:
assert isinstance(tvar_def, TypeVarType)
if len(t.args) > 0:
Expand Down Expand Up @@ -2105,7 +2120,10 @@ def fix_instance(
"""
used_default = False
arg_count = len(t.args)
min_tv_count = sum(not tv.has_default() for tv in t.type.defn.type_vars)
min_tv_count = sum(
not tv.has_default() and not isinstance(tv, TypeVarTupleType)
for tv in t.type.defn.type_vars
)
max_tv_count = len(t.type.type_vars)
if arg_count < min_tv_count or arg_count > max_tv_count:
# Don't use existing args if arg_count doesn't match
Expand All @@ -2114,9 +2132,10 @@ def fix_instance(
disallow_any = False
t.args = ()

args: list[Type] = [*(t.args[:max_tv_count])]
args: list[Type] = list(t.args)
any_type: AnyType | None = None
env: dict[TypeVarId, Type] = {}
tvt_no_default = False

for tv, arg in itertools.zip_longest(t.type.defn.type_vars, t.args, fillvalue=None):
if tv is None:
Expand Down Expand Up @@ -2149,16 +2168,27 @@ def fix_instance(
arg = any_type
else:
assert arg is not None
with state.strict_optional_set(options.strict_optional):
# Gradually expand defaults, as they may depend on previous variables.
if tv.has_default():
arg = expand_type(arg, env)
env[tv.id] = arg
args.append(arg)
if use_any and isinstance(tv, TypeVarTupleType):
tvt_no_default = True
# Default such as *tuple[int, str] should be unpacked into individual items.
if isinstance(arg, UnpackType) and isinstance(
unpack := get_proper_type(arg.type), TupleType
):
unpacked = unpack.items
else:
unpacked = [arg]
for arg in unpacked:
with state.strict_optional_set(options.strict_optional):
# Gradually expand defaults, as they may depend on previous variables.
if tv.has_default():
arg = expand_type(arg, env)
env[tv.id] = arg
args.append(arg)
else:
env[tv.id] = arg
t.args = tuple(args)
fix_type_var_tuple_argument(t)
if tvt_no_default:
fix_type_var_tuple_argument(t)
return used_default


Expand Down Expand Up @@ -2374,15 +2404,22 @@ def set_any_tvars(
else:
arg = any_type
used_any_type = True
if isinstance(tv, TypeVarTupleType):
# TODO Handle TypeVarTuple defaults
if used_any_type and isinstance(tv, TypeVarTupleType):
arg = UnpackType(Instance(tv.tuple_fallback.type, [any_type]))
with state.strict_optional_set(options.strict_optional):
# Gradually expand defaults, as they may depend on previous variables.
if tv.has_default():
arg = expand_type(arg, env)
env[tv.id] = arg
args.append(arg)
# Default such as *tuple[int, str] should be unpacked into individual items.
if isinstance(arg, UnpackType) and isinstance(
unpack := get_proper_type(arg.type), TupleType
):
unpacked = unpack.items
else:
unpacked = [arg]
for arg in unpacked:
with state.strict_optional_set(options.strict_optional):
# Gradually expand defaults, as they may depend on previous variables.
if tv.has_default():
arg = expand_type(arg, env)
env[tv.id] = arg
args.append(arg)
else:
env[tv.id] = arg
t = TypeAliasType(node, args, newline, newcolumn)
Expand Down
6 changes: 3 additions & 3 deletions test-data/unit/check-python313.test
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ reveal_type(func_b1(callback1)) # N: Revealed type is "def (x: builtins.str)"
reveal_type(func_b1(2)) # N: Revealed type is "def (builtins.int, builtins.str)"

def func_c1[*Ts = *tuple[int, str]](x: int | Callable[[*Ts], None]) -> tuple[*Ts]: ...
# reveal_type(func_c1(callback1)) # Revealed type is "Tuple[str]" # TODO
reveal_type(func_c1(callback1)) # N: Revealed type is "tuple[builtins.str]"
reveal_type(func_c1(2)) # N: Revealed type is "tuple[builtins.int, builtins.str]"
[builtins fixtures/tuple.pyi]

Expand Down Expand Up @@ -195,7 +195,7 @@ def func_c1(
a: ClassC1,
b: ClassC1[float],
) -> None:
# reveal_type(a) # Revealed type is "__main__.ClassC1[builtins.int, builtins.str]" # TODO
reveal_type(a) # N: Revealed type is "__main__.ClassC1[builtins.int, builtins.str]"
reveal_type(b) # N: Revealed type is "__main__.ClassC1[builtins.float]"

k = ClassC1()
Expand Down Expand Up @@ -250,7 +250,7 @@ def func_c1(
a: TC1,
b: TC1[float],
) -> None:
# reveal_type(a) # Revealed type is "Tuple[builtins.int, builtins.str]" # TODO
reveal_type(a) # N: Revealed type is "tuple[builtins.int, builtins.str]"
reveal_type(b) # N: Revealed type is "tuple[builtins.float]"

[builtins fixtures/tuple.pyi]
Expand Down
Loading
Loading