blob: d894e2cc8c514564c7095687ac167067c839bf73 [file] [log] [blame]
"""Semantic analysis of types"""
from __future__ import annotations
import itertools
from contextlib import contextmanager
from typing import Callable, Final, Iterable, Iterator, List, Sequence, Tuple, TypeVar
from typing_extensions import Protocol
from mypy import errorcodes as codes, message_registry, nodes
from mypy.errorcodes import ErrorCode
from mypy.messages import MessageBuilder, format_type_bare, quote_type_string, wrong_type_arg_count
from mypy.nodes import (
ARG_NAMED,
ARG_NAMED_OPT,
ARG_OPT,
ARG_POS,
ARG_STAR,
ARG_STAR2,
SYMBOL_FUNCBASE_TYPES,
ArgKind,
Context,
Decorator,
MypyFile,
ParamSpecExpr,
PlaceholderNode,
SymbolTableNode,
TypeAlias,
TypeInfo,
TypeVarExpr,
TypeVarLikeExpr,
TypeVarTupleExpr,
Var,
check_arg_kinds,
check_arg_names,
get_nongen_builtins,
)
from mypy.options import UNPACK, Options
from mypy.plugin import AnalyzeTypeContext, Plugin, TypeAnalyzerPluginInterface
from mypy.semanal_shared import SemanticAnalyzerCoreInterface, paramspec_args, paramspec_kwargs
from mypy.tvar_scope import TypeVarLikeScope
from mypy.types import (
ANNOTATED_TYPE_NAMES,
ANY_STRATEGY,
FINAL_TYPE_NAMES,
LITERAL_TYPE_NAMES,
NEVER_NAMES,
TYPE_ALIAS_NAMES,
AnyType,
BoolTypeQuery,
CallableArgument,
CallableType,
DeletedType,
EllipsisType,
ErasedType,
Instance,
LiteralType,
NoneType,
Overloaded,
Parameters,
ParamSpecFlavor,
ParamSpecType,
PartialType,
PlaceholderType,
RawExpressionType,
RequiredType,
SyntheticTypeVisitor,
TrivialSyntheticTypeTranslator,
TupleType,
Type,
TypeAliasType,
TypedDictType,
TypeList,
TypeOfAny,
TypeQuery,
TypeType,
TypeVarLikeType,
TypeVarTupleType,
TypeVarType,
UnboundType,
UninhabitedType,
UnionType,
UnpackType,
callable_with_ellipsis,
flatten_nested_unions,
get_proper_type,
has_type_vars,
)
from mypy.types_utils import is_bad_type_type_item
from mypy.typetraverser import TypeTraverserVisitor
from mypy.typevars import fill_typevars
T = TypeVar("T")
type_constructors: Final = {
"typing.Callable",
"typing.Optional",
"typing.Tuple",
"typing.Type",
"typing.Union",
*LITERAL_TYPE_NAMES,
*ANNOTATED_TYPE_NAMES,
}
ARG_KINDS_BY_CONSTRUCTOR: Final = {
"mypy_extensions.Arg": ARG_POS,
"mypy_extensions.DefaultArg": ARG_OPT,
"mypy_extensions.NamedArg": ARG_NAMED,
"mypy_extensions.DefaultNamedArg": ARG_NAMED_OPT,
"mypy_extensions.VarArg": ARG_STAR,
"mypy_extensions.KwArg": ARG_STAR2,
}
GENERIC_STUB_NOT_AT_RUNTIME_TYPES: Final = {
"queue.Queue",
"builtins._PathLike",
"asyncio.futures.Future",
}
SELF_TYPE_NAMES: Final = {"typing.Self", "typing_extensions.Self"}
def analyze_type_alias(
type: Type,
api: SemanticAnalyzerCoreInterface,
tvar_scope: TypeVarLikeScope,
plugin: Plugin,
options: Options,
is_typeshed_stub: bool,
allow_placeholder: bool = False,
in_dynamic_func: bool = False,
global_scope: bool = True,
allowed_alias_tvars: list[TypeVarLikeType] | None = None,
) -> tuple[Type, set[str]]:
"""Analyze r.h.s. of a (potential) type alias definition.
If `node` is valid as a type alias rvalue, return the resulting type and a set of
full names of type aliases it depends on (directly or indirectly).
'node' must have been semantically analyzed.
"""
analyzer = TypeAnalyser(
api,
tvar_scope,
plugin,
options,
is_typeshed_stub,
defining_alias=True,
allow_placeholder=allow_placeholder,
prohibit_self_type="type alias target",
allowed_alias_tvars=allowed_alias_tvars,
)
analyzer.in_dynamic_func = in_dynamic_func
analyzer.global_scope = global_scope
res = type.accept(analyzer)
return res, analyzer.aliases_used
def no_subscript_builtin_alias(name: str, propose_alt: bool = True) -> str:
class_name = name.split(".")[-1]
msg = f'"{class_name}" is not subscriptable'
# This should never be called if the python_version is 3.9 or newer
nongen_builtins = get_nongen_builtins((3, 8))
replacement = nongen_builtins[name]
if replacement and propose_alt:
msg += f', use "{replacement}" instead'
return msg
class TypeAnalyser(SyntheticTypeVisitor[Type], TypeAnalyzerPluginInterface):
"""Semantic analyzer for types.
Converts unbound types into bound types. This is a no-op for already
bound types.
If an incomplete reference is encountered, this does a defer. The
caller never needs to defer.
"""
# Is this called from an untyped function definition?
in_dynamic_func: bool = False
# Is this called from global scope?
global_scope: bool = True
def __init__(
self,
api: SemanticAnalyzerCoreInterface,
tvar_scope: TypeVarLikeScope,
plugin: Plugin,
options: Options,
is_typeshed_stub: bool,
*,
defining_alias: bool = False,
allow_tuple_literal: bool = False,
allow_unbound_tvars: bool = False,
allow_placeholder: bool = False,
allow_required: bool = False,
allow_param_spec_literals: bool = False,
report_invalid_types: bool = True,
prohibit_self_type: str | None = None,
allowed_alias_tvars: list[TypeVarLikeType] | None = None,
allow_type_any: bool = False,
) -> None:
self.api = api
self.fail_func = api.fail
self.note_func = api.note
self.tvar_scope = tvar_scope
# Are we analysing a type alias definition rvalue?
self.defining_alias = defining_alias
self.allow_tuple_literal = allow_tuple_literal
# Positive if we are analyzing arguments of another (outer) type
self.nesting_level = 0
# Should we allow new type syntax when targeting older Python versions
# like 'list[int]' or 'X | Y' (allowed in stubs and with `__future__` import)?
self.always_allow_new_syntax = self.api.is_stub_file or self.api.is_future_flag_set(
"annotations"
)
# Should we accept unbound type variables? This is currently used for class bases,
# and alias right hand sides (before they are analyzed as type aliases).
self.allow_unbound_tvars = allow_unbound_tvars
if allowed_alias_tvars is None:
allowed_alias_tvars = []
self.allowed_alias_tvars = allowed_alias_tvars
# If false, record incomplete ref if we generate PlaceholderType.
self.allow_placeholder = allow_placeholder
# Are we in a context where Required[] is allowed?
self.allow_required = allow_required
# Are we in a context where ParamSpec literals are allowed?
self.allow_param_spec_literals = allow_param_spec_literals
# Should we report an error whenever we encounter a RawExpressionType outside
# of a Literal context: e.g. whenever we encounter an invalid type? Normally,
# we want to report an error, but the caller may want to do more specialized
# error handling.
self.report_invalid_types = report_invalid_types
self.plugin = plugin
self.options = options
self.is_typeshed_stub = is_typeshed_stub
# Names of type aliases encountered while analysing a type will be collected here.
self.aliases_used: set[str] = set()
self.prohibit_self_type = prohibit_self_type
# Allow variables typed as Type[Any] and type (useful for base classes).
self.allow_type_any = allow_type_any
def lookup_qualified(
self, name: str, ctx: Context, suppress_errors: bool = False
) -> SymbolTableNode | None:
return self.api.lookup_qualified(name, ctx, suppress_errors)
def lookup_fully_qualified(self, name: str) -> SymbolTableNode:
return self.api.lookup_fully_qualified(name)
def visit_unbound_type(self, t: UnboundType, defining_literal: bool = False) -> Type:
typ = self.visit_unbound_type_nonoptional(t, defining_literal)
if t.optional:
# We don't need to worry about double-wrapping Optionals or
# wrapping Anys: Union simplification will take care of that.
return make_optional_type(typ)
return typ
def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) -> Type:
sym = self.lookup_qualified(t.name, t)
if sym is not None:
node = sym.node
if isinstance(node, PlaceholderNode):
if node.becomes_typeinfo:
# Reference to placeholder type.
if self.api.final_iteration:
self.cannot_resolve_type(t)
return AnyType(TypeOfAny.from_error)
elif self.allow_placeholder:
self.api.defer()
else:
self.api.record_incomplete_ref()
# Always allow ParamSpec for placeholders, if they are actually not valid,
# they will be reported later, after we resolve placeholders.
return PlaceholderType(
node.fullname,
self.anal_array(
t.args, allow_param_spec=True, allow_param_spec_literals=True
),
t.line,
)
else:
if self.api.final_iteration:
self.cannot_resolve_type(t)
return AnyType(TypeOfAny.from_error)
else:
# Reference to an unknown placeholder node.
self.api.record_incomplete_ref()
return AnyType(TypeOfAny.special_form)
if node is None:
self.fail(f"Internal error (node is None, kind={sym.kind})", t)
return AnyType(TypeOfAny.special_form)
fullname = node.fullname
hook = self.plugin.get_type_analyze_hook(fullname)
if hook is not None:
return hook(AnalyzeTypeContext(t, t, self))
if (
fullname in get_nongen_builtins(self.options.python_version)
and t.args
and not self.always_allow_new_syntax
):
self.fail(
no_subscript_builtin_alias(fullname, propose_alt=not self.defining_alias), t
)
tvar_def = self.tvar_scope.get_binding(sym)
if isinstance(sym.node, ParamSpecExpr):
if tvar_def is None:
if self.allow_unbound_tvars:
return t
self.fail(f'ParamSpec "{t.name}" is unbound', t, code=codes.VALID_TYPE)
return AnyType(TypeOfAny.from_error)
assert isinstance(tvar_def, ParamSpecType)
if len(t.args) > 0:
self.fail(
f'ParamSpec "{t.name}" used with arguments', t, code=codes.VALID_TYPE
)
# Change the line number
return ParamSpecType(
tvar_def.name,
tvar_def.fullname,
tvar_def.id,
tvar_def.flavor,
tvar_def.upper_bound,
tvar_def.default,
line=t.line,
column=t.column,
)
if (
isinstance(sym.node, TypeVarExpr)
and self.defining_alias
and not defining_literal
and (tvar_def is None or tvar_def not in self.allowed_alias_tvars)
):
self.fail(
f'Can\'t use bound type variable "{t.name}" to define generic alias',
t,
code=codes.VALID_TYPE,
)
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:
self.fail(
f'Type variable "{t.name}" used with arguments', t, code=codes.VALID_TYPE
)
# Change the line number
return tvar_def.copy_modified(line=t.line, column=t.column)
if isinstance(sym.node, TypeVarTupleExpr) and (
tvar_def is not None
and self.defining_alias
and tvar_def not in self.allowed_alias_tvars
):
self.fail(
f'Can\'t use bound type variable "{t.name}" to define generic alias',
t,
code=codes.VALID_TYPE,
)
return AnyType(TypeOfAny.from_error)
if isinstance(sym.node, TypeVarTupleExpr):
if tvar_def is None:
if self.allow_unbound_tvars:
return t
self.fail(f'TypeVarTuple "{t.name}" is unbound', t, code=codes.VALID_TYPE)
return AnyType(TypeOfAny.from_error)
assert isinstance(tvar_def, TypeVarTupleType)
if len(t.args) > 0:
self.fail(
f'Type variable "{t.name}" used with arguments', t, code=codes.VALID_TYPE
)
# Change the line number
return TypeVarTupleType(
tvar_def.name,
tvar_def.fullname,
tvar_def.id,
tvar_def.upper_bound,
sym.node.tuple_fallback,
tvar_def.default,
line=t.line,
column=t.column,
)
special = self.try_analyze_special_unbound_type(t, fullname)
if special is not None:
return special
if isinstance(node, TypeAlias):
self.aliases_used.add(fullname)
an_args = self.anal_array(
t.args,
allow_param_spec=True,
allow_param_spec_literals=node.has_param_spec_type,
)
if node.has_param_spec_type and len(node.alias_tvars) == 1:
an_args = self.pack_paramspec_args(an_args)
disallow_any = self.options.disallow_any_generics and not self.is_typeshed_stub
res = instantiate_type_alias(
node,
an_args,
self.fail,
node.no_args,
t,
self.options,
unexpanded_type=t,
disallow_any=disallow_any,
empty_tuple_index=t.empty_tuple_index,
)
# The only case where instantiate_type_alias() can return an incorrect instance is
# when it is top-level instance, so no need to recurse.
if (
isinstance(res, Instance) # type: ignore[misc]
and len(res.args) != len(res.type.type_vars)
and not self.defining_alias
and not res.type.has_type_var_tuple_type
):
fix_instance(
res,
self.fail,
self.note,
disallow_any=disallow_any,
options=self.options,
use_generic_error=True,
unexpanded_type=t,
)
if node.eager:
res = get_proper_type(res)
return res
elif isinstance(node, TypeInfo):
return self.analyze_type_with_type_info(node, t.args, t)
elif node.fullname in TYPE_ALIAS_NAMES:
return AnyType(TypeOfAny.special_form)
# Concatenate is an operator, no need for a proper type
elif node.fullname in ("typing_extensions.Concatenate", "typing.Concatenate"):
# We check the return type further up the stack for valid use locations
return self.apply_concatenate_operator(t)
else:
return self.analyze_unbound_type_without_type_info(t, sym, defining_literal)
else: # sym is None
return AnyType(TypeOfAny.special_form)
def pack_paramspec_args(self, an_args: Sequence[Type]) -> list[Type]:
# "Aesthetic" ParamSpec literals for single ParamSpec: C[int, str] -> C[[int, str]].
# These do not support mypy_extensions VarArgs, etc. as they were already analyzed
# TODO: should these be re-analyzed to get rid of this inconsistency?
count = len(an_args)
if count > 0:
first_arg = get_proper_type(an_args[0])
if not (count == 1 and isinstance(first_arg, (Parameters, ParamSpecType, AnyType))):
return [Parameters(an_args, [ARG_POS] * count, [None] * count)]
return list(an_args)
def cannot_resolve_type(self, t: UnboundType) -> None:
# TODO: Move error message generation to messages.py. We'd first
# 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 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:
if len(t.args) == 0:
self.api.fail("Concatenate needs type arguments", t, code=codes.VALID_TYPE)
return AnyType(TypeOfAny.from_error)
# last argument has to be ParamSpec
ps = self.anal_type(t.args[-1], allow_param_spec=True)
if not isinstance(ps, ParamSpecType):
if isinstance(ps, UnboundType) and self.allow_unbound_tvars:
sym = self.lookup_qualified(ps.name, t)
if sym is not None and isinstance(sym.node, ParamSpecExpr):
return ps
self.api.fail(
"The last parameter to Concatenate needs to be a ParamSpec",
t,
code=codes.VALID_TYPE,
)
return AnyType(TypeOfAny.from_error)
# TODO: this may not work well with aliases, if those worked.
# Those should be special-cased.
elif ps.prefix.arg_types:
self.api.fail("Nested Concatenates are invalid", t, code=codes.VALID_TYPE)
args = self.anal_array(t.args[:-1])
pre = ps.prefix
# mypy can't infer this :(
names: list[str | None] = [None] * len(args)
pre = Parameters(
args + pre.arg_types, [ARG_POS] * len(args) + pre.arg_kinds, names + pre.arg_names
)
return ps.copy_modified(prefix=pre)
def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Type | None:
"""Bind special type that is recognized through magic name such as 'typing.Any'.
Return the bound type if successful, and return None if the type is a normal type.
"""
if fullname == "builtins.None":
return NoneType()
elif fullname == "typing.Any" or fullname == "builtins.Any":
return AnyType(TypeOfAny.explicit, line=t.line, column=t.column)
elif fullname in FINAL_TYPE_NAMES:
self.fail(
"Final can be only used as an outermost qualifier in a variable annotation",
t,
code=codes.VALID_TYPE,
)
return AnyType(TypeOfAny.from_error)
elif fullname == "typing.Tuple" or (
fullname == "builtins.tuple"
and (self.always_allow_new_syntax or self.options.python_version >= (3, 9))
):
# Tuple is special because it is involved in builtin import cycle
# and may be not ready when used.
sym = self.api.lookup_fully_qualified_or_none("builtins.tuple")
if not sym or isinstance(sym.node, PlaceholderNode):
if self.api.is_incomplete_namespace("builtins"):
self.api.record_incomplete_ref()
else:
self.fail('Name "tuple" is not defined', t)
return AnyType(TypeOfAny.special_form)
if len(t.args) == 0 and not t.empty_tuple_index:
# Bare 'Tuple' is same as 'tuple'
any_type = self.get_omitted_any(t)
return self.named_type("builtins.tuple", [any_type], line=t.line, column=t.column)
if len(t.args) == 2 and isinstance(t.args[1], EllipsisType):
# Tuple[T, ...] (uniform, variable-length tuple)
instance = self.named_type("builtins.tuple", [self.anal_type(t.args[0])])
instance.line = t.line
return instance
return self.tuple_type(self.anal_array(t.args))
elif fullname == "typing.Union":
items = self.anal_array(t.args)
return UnionType.make_union(items)
elif fullname == "typing.Optional":
if len(t.args) != 1:
self.fail(
"Optional[...] must have exactly one type argument", t, code=codes.VALID_TYPE
)
return AnyType(TypeOfAny.from_error)
item = self.anal_type(t.args[0])
return make_optional_type(item)
elif fullname == "typing.Callable":
return self.analyze_callable_type(t)
elif fullname == "typing.Type" or (
fullname == "builtins.type"
and (self.always_allow_new_syntax or self.options.python_version >= (3, 9))
):
if len(t.args) == 0:
if fullname == "typing.Type":
any_type = self.get_omitted_any(t)
return TypeType(any_type, line=t.line, column=t.column)
else:
# To prevent assignment of 'builtins.type' inferred as 'builtins.object'
# See https://github.com/python/mypy/issues/9476 for more information
return None
if len(t.args) != 1:
type_str = "Type[...]" if fullname == "typing.Type" else "type[...]"
self.fail(
type_str + " must have exactly one type argument", t, code=codes.VALID_TYPE
)
item = self.anal_type(t.args[0])
if is_bad_type_type_item(item):
self.fail("Type[...] can't contain another Type[...]", t, code=codes.VALID_TYPE)
item = AnyType(TypeOfAny.from_error)
return TypeType.make_normalized(item, line=t.line, column=t.column)
elif fullname == "typing.ClassVar":
if self.nesting_level > 0:
self.fail(
"Invalid type: ClassVar nested inside other type", t, code=codes.VALID_TYPE
)
if len(t.args) == 0:
return AnyType(TypeOfAny.from_omitted_generics, line=t.line, column=t.column)
if len(t.args) != 1:
self.fail(
"ClassVar[...] must have at most one type argument", t, code=codes.VALID_TYPE
)
return AnyType(TypeOfAny.from_error)
return self.anal_type(t.args[0])
elif fullname in NEVER_NAMES:
return UninhabitedType(is_noreturn=True)
elif fullname in LITERAL_TYPE_NAMES:
return self.analyze_literal_type(t)
elif fullname in ANNOTATED_TYPE_NAMES:
if len(t.args) < 2:
self.fail(
"Annotated[...] must have exactly one type argument"
" and at least one annotation",
t,
code=codes.VALID_TYPE,
)
return AnyType(TypeOfAny.from_error)
return self.anal_type(t.args[0])
elif fullname in ("typing_extensions.Required", "typing.Required"):
if not self.allow_required:
self.fail(
"Required[] can be only used in a TypedDict definition",
t,
code=codes.VALID_TYPE,
)
return AnyType(TypeOfAny.from_error)
if len(t.args) != 1:
self.fail(
"Required[] must have exactly one type argument", t, code=codes.VALID_TYPE
)
return AnyType(TypeOfAny.from_error)
return RequiredType(self.anal_type(t.args[0]), required=True)
elif fullname in ("typing_extensions.NotRequired", "typing.NotRequired"):
if not self.allow_required:
self.fail(
"NotRequired[] can be only used in a TypedDict definition",
t,
code=codes.VALID_TYPE,
)
return AnyType(TypeOfAny.from_error)
if len(t.args) != 1:
self.fail(
"NotRequired[] must have exactly one type argument", t, code=codes.VALID_TYPE
)
return AnyType(TypeOfAny.from_error)
return RequiredType(self.anal_type(t.args[0]), required=False)
elif self.anal_type_guard_arg(t, fullname) is not None:
# In most contexts, TypeGuard[...] acts as an alias for bool (ignoring its args)
return self.named_type("builtins.bool")
elif fullname in ("typing.Unpack", "typing_extensions.Unpack"):
if not self.api.incomplete_feature_enabled(UNPACK, t):
return AnyType(TypeOfAny.from_error)
if len(t.args) != 1:
self.fail("Unpack[...] requires exactly one type argument", t)
return AnyType(TypeOfAny.from_error)
return UnpackType(self.anal_type(t.args[0]), line=t.line, column=t.column)
elif fullname in SELF_TYPE_NAMES:
if t.args:
self.fail("Self type cannot have type arguments", t)
if self.prohibit_self_type is not None:
self.fail(f"Self type cannot be used in {self.prohibit_self_type}", t)
return AnyType(TypeOfAny.from_error)
if self.api.type is None:
self.fail("Self type is only allowed in annotations within class definition", t)
return AnyType(TypeOfAny.from_error)
if self.api.type.has_base("builtins.type"):
self.fail("Self type cannot be used in a metaclass", t)
if self.api.type.self_type is not None:
if self.api.type.is_final:
return fill_typevars(self.api.type)
return self.api.type.self_type.copy_modified(line=t.line, column=t.column)
# TODO: verify this is unreachable and replace with an assert?
self.fail("Unexpected Self type", t)
return AnyType(TypeOfAny.from_error)
return None
def get_omitted_any(self, typ: Type, fullname: str | None = None) -> AnyType:
disallow_any = not self.is_typeshed_stub and self.options.disallow_any_generics
return get_omitted_any(disallow_any, self.fail, self.note, typ, self.options, fullname)
def analyze_type_with_type_info(
self, info: TypeInfo, args: Sequence[Type], ctx: Context
) -> Type:
"""Bind unbound type when were able to find target TypeInfo.
This handles simple cases like 'int', 'modname.UserClass[str]', etc.
"""
if len(args) > 0 and info.fullname == "builtins.tuple":
fallback = Instance(info, [AnyType(TypeOfAny.special_form)], ctx.line)
return TupleType(self.anal_array(args), fallback, ctx.line)
# Analyze arguments and (usually) construct Instance type. The
# number of type arguments and their values are
# checked only later, since we do not always know the
# valid count at this point. Thus we may construct an
# Instance with an invalid number of type arguments.
#
# We allow ParamSpec literals based on a heuristic: it will be
# checked later anyways but the error message may be worse.
instance = Instance(
info,
self.anal_array(
args, allow_param_spec=True, allow_param_spec_literals=info.has_param_spec_type
),
ctx.line,
ctx.column,
)
if len(info.type_vars) == 1 and info.has_param_spec_type:
instance.args = tuple(self.pack_paramspec_args(instance.args))
if info.has_type_var_tuple_type:
if instance.args:
# -1 to account for empty tuple
valid_arg_length = len(instance.args) >= len(info.type_vars) - 1
# Empty case is special cased and we want to infer a Tuple[Any, ...]
# instead of the empty tuple, so no - 1 here.
else:
valid_arg_length = False
else:
valid_arg_length = len(instance.args) == len(info.type_vars)
# Check type argument count.
if not valid_arg_length and not self.defining_alias:
fix_instance(
instance,
self.fail,
self.note,
disallow_any=self.options.disallow_any_generics and not self.is_typeshed_stub,
options=self.options,
)
tup = info.tuple_type
if tup is not None:
# The class has a Tuple[...] base class so it will be
# represented as a tuple type.
if info.special_alias:
return instantiate_type_alias(
info.special_alias,
# TODO: should we allow NamedTuples generic in ParamSpec?
self.anal_array(args),
self.fail,
False,
ctx,
self.options,
use_standard_error=True,
)
return tup.copy_modified(items=self.anal_array(tup.items), fallback=instance)
td = info.typeddict_type
if td is not None:
# The class has a TypedDict[...] base class so it will be
# represented as a typeddict type.
if info.special_alias:
return instantiate_type_alias(
info.special_alias,
# TODO: should we allow TypedDicts generic in ParamSpec?
self.anal_array(args),
self.fail,
False,
ctx,
self.options,
use_standard_error=True,
)
# Create a named TypedDictType
return td.copy_modified(
item_types=self.anal_array(list(td.items.values())), fallback=instance
)
if info.fullname == "types.NoneType":
self.fail(
"NoneType should not be used as a type, please use None instead",
ctx,
code=codes.VALID_TYPE,
)
return NoneType(ctx.line, ctx.column)
return instance
def analyze_unbound_type_without_type_info(
self, t: UnboundType, sym: SymbolTableNode, defining_literal: bool
) -> Type:
"""Figure out what an unbound type that doesn't refer to a TypeInfo node means.
This is something unusual. We try our best to find out what it is.
"""
name = sym.fullname
if name is None:
assert sym.node is not None
name = sym.node.name
# Option 1:
# Something with an Any type -- make it an alias for Any in a type
# context. This is slightly problematic as it allows using the type 'Any'
# as a base class -- however, this will fail soon at runtime so the problem
# is pretty minor.
if isinstance(sym.node, Var):
typ = get_proper_type(sym.node.type)
if isinstance(typ, AnyType):
return AnyType(
TypeOfAny.from_unimported_type, missing_import_name=typ.missing_import_name
)
elif self.allow_type_any:
if isinstance(typ, Instance) and typ.type.fullname == "builtins.type":
return AnyType(TypeOfAny.special_form)
if isinstance(typ, TypeType) and isinstance(typ.item, AnyType):
return AnyType(TypeOfAny.from_another_any, source_any=typ.item)
# Option 2:
# Unbound type variable. Currently these may be still valid,
# for example when defining a generic type alias.
unbound_tvar = (
isinstance(sym.node, (TypeVarExpr, TypeVarTupleExpr))
and self.tvar_scope.get_binding(sym) is None
)
if self.allow_unbound_tvars and unbound_tvar:
return t
# Option 3:
# Enum value. Note: we only want to return a LiteralType when
# we're using this enum value specifically within context of
# a "Literal[...]" type. So, if `defining_literal` is not set,
# we bail out early with an error.
#
# If, in the distant future, we decide to permit things like
# `def foo(x: Color.RED) -> None: ...`, we can remove that
# check entirely.
if isinstance(sym.node, Var) and sym.node.info and sym.node.info.is_enum:
value = sym.node.name
base_enum_short_name = sym.node.info.name
if not defining_literal:
msg = message_registry.INVALID_TYPE_RAW_ENUM_VALUE.format(
base_enum_short_name, value
)
self.fail(msg.value, t, code=msg.code)
return AnyType(TypeOfAny.from_error)
return LiteralType(
value=value,
fallback=Instance(sym.node.info, [], line=t.line, column=t.column),
line=t.line,
column=t.column,
)
# None of the above options worked. We parse the args (if there are any)
# to make sure there are no remaining semanal-only types, then give up.
t = t.copy_modified(args=self.anal_array(t.args))
# TODO: Move this message building logic to messages.py.
notes: list[str] = []
if isinstance(sym.node, Var):
notes.append(
"See https://mypy.readthedocs.io/en/"
"stable/common_issues.html#variables-vs-type-aliases"
)
message = 'Variable "{}" is not valid as a type'
elif isinstance(sym.node, (SYMBOL_FUNCBASE_TYPES, Decorator)):
message = 'Function "{}" is not valid as a type'
if name == "builtins.any":
notes.append('Perhaps you meant "typing.Any" instead of "any"?')
elif name == "builtins.callable":
notes.append('Perhaps you meant "typing.Callable" instead of "callable"?')
else:
notes.append('Perhaps you need "Callable[...]" or a callback protocol?')
elif isinstance(sym.node, MypyFile):
message = 'Module "{}" is not valid as a type'
notes.append("Perhaps you meant to use a protocol matching the module structure?")
elif unbound_tvar:
message = 'Type variable "{}" is unbound'
short = name.split(".")[-1]
notes.append(
(
'(Hint: Use "Generic[{}]" or "Protocol[{}]" base class'
' to bind "{}" inside a class)'
).format(short, short, short)
)
notes.append(
'(Hint: Use "{}" in function signature to bind "{}"'
" inside a function)".format(short, short)
)
else:
message = 'Cannot interpret reference "{}" as a type'
if not defining_literal:
# Literal check already gives a custom error. Avoid duplicating errors.
self.fail(message.format(name), t, code=codes.VALID_TYPE)
for note in notes:
self.note(note, t, code=codes.VALID_TYPE)
# TODO: Would it be better to always return Any instead of UnboundType
# in case of an error? On one hand, UnboundType has a name so error messages
# are more detailed, on the other hand, some of them may be bogus,
# see https://github.com/python/mypy/issues/4987.
return t
def visit_any(self, t: AnyType) -> Type:
return t
def visit_none_type(self, t: NoneType) -> Type:
return t
def visit_uninhabited_type(self, t: UninhabitedType) -> Type:
return t
def visit_erased_type(self, t: ErasedType) -> Type:
# This type should exist only temporarily during type inference
assert False, "Internal error: Unexpected erased type"
def visit_deleted_type(self, t: DeletedType) -> Type:
return t
def visit_type_list(self, t: TypeList) -> Type:
# paramspec literal (Z[[int, str, Whatever]])
if self.allow_param_spec_literals:
params = self.analyze_callable_args(t)
if params:
ts, kinds, names = params
# bind these types
return Parameters(self.anal_array(ts), kinds, names)
else:
return AnyType(TypeOfAny.from_error)
else:
self.fail(
'Bracketed expression "[...]" is not valid as a type', t, code=codes.VALID_TYPE
)
self.note('Did you mean "List[...]"?', t)
return AnyType(TypeOfAny.from_error)
def visit_callable_argument(self, t: CallableArgument) -> Type:
self.fail("Invalid type", t, code=codes.VALID_TYPE)
return AnyType(TypeOfAny.from_error)
def visit_instance(self, t: Instance) -> Type:
return t
def visit_type_alias_type(self, t: TypeAliasType) -> Type:
# TODO: should we do something here?
return t
def visit_type_var(self, t: TypeVarType) -> Type:
return t
def visit_param_spec(self, t: ParamSpecType) -> Type:
return t
def visit_type_var_tuple(self, t: TypeVarTupleType) -> Type:
return t
def visit_unpack_type(self, t: UnpackType) -> Type:
raise NotImplementedError
def visit_parameters(self, t: Parameters) -> Type:
raise NotImplementedError("ParamSpec literals cannot have unbound TypeVars")
def visit_callable_type(self, t: CallableType, nested: bool = True) -> Type:
# Every Callable can bind its own type variables, if they're not in the outer scope
with self.tvar_scope_frame():
if self.defining_alias:
variables = t.variables
else:
variables, _ = self.bind_function_type_variables(t, t)
special = self.anal_type_guard(t.ret_type)
arg_kinds = t.arg_kinds
if len(arg_kinds) >= 2 and arg_kinds[-2] == ARG_STAR and arg_kinds[-1] == ARG_STAR2:
arg_types = self.anal_array(t.arg_types[:-2], nested=nested) + [
self.anal_star_arg_type(t.arg_types[-2], ARG_STAR, nested=nested),
self.anal_star_arg_type(t.arg_types[-1], ARG_STAR2, nested=nested),
]
else:
arg_types = self.anal_array(t.arg_types, nested=nested)
# If there were multiple (invalid) unpacks, the arg types list will become shorter,
# we need to trim the kinds/names as well to avoid crashes.
arg_kinds = t.arg_kinds[: len(arg_types)]
arg_names = t.arg_names[: len(arg_types)]
ret = t.copy_modified(
arg_types=arg_types,
arg_kinds=arg_kinds,
arg_names=arg_names,
ret_type=self.anal_type(t.ret_type, nested=nested),
# If the fallback isn't filled in yet,
# its type will be the falsey FakeInfo
fallback=(t.fallback if t.fallback.type else self.named_type("builtins.function")),
variables=self.anal_var_defs(variables),
type_guard=special,
)
return ret
def anal_type_guard(self, t: Type) -> Type | None:
if isinstance(t, UnboundType):
sym = self.lookup_qualified(t.name, t)
if sym is not None and sym.node is not None:
return self.anal_type_guard_arg(t, sym.node.fullname)
# TODO: What if it's an Instance? Then use t.type.fullname?
return None
def anal_type_guard_arg(self, t: UnboundType, fullname: str) -> Type | None:
if fullname in ("typing_extensions.TypeGuard", "typing.TypeGuard"):
if len(t.args) != 1:
self.fail(
"TypeGuard must have exactly one type argument", t, code=codes.VALID_TYPE
)
return AnyType(TypeOfAny.from_error)
return self.anal_type(t.args[0])
return None
def anal_star_arg_type(self, t: Type, kind: ArgKind, nested: bool) -> Type:
"""Analyze signature argument type for *args and **kwargs argument."""
if isinstance(t, UnboundType) and t.name and "." in t.name and not t.args:
components = t.name.split(".")
tvar_name = ".".join(components[:-1])
sym = self.lookup_qualified(tvar_name, t)
if sym is not None and isinstance(sym.node, ParamSpecExpr):
tvar_def = self.tvar_scope.get_binding(sym)
if isinstance(tvar_def, ParamSpecType):
if kind == ARG_STAR:
make_paramspec = paramspec_args
if components[-1] != "args":
self.fail(
f'Use "{tvar_name}.args" for variadic "*" parameter',
t,
code=codes.VALID_TYPE,
)
elif kind == ARG_STAR2:
make_paramspec = paramspec_kwargs
if components[-1] != "kwargs":
self.fail(
f'Use "{tvar_name}.kwargs" for variadic "**" parameter',
t,
code=codes.VALID_TYPE,
)
else:
assert False, kind
return make_paramspec(
tvar_def.name,
tvar_def.fullname,
tvar_def.id,
named_type_func=self.named_type,
line=t.line,
column=t.column,
)
return self.anal_type(t, nested=nested)
def visit_overloaded(self, t: Overloaded) -> Type:
# Overloaded types are manually constructed in semanal.py by analyzing the
# AST and combining together the Callable types this visitor converts.
#
# So if we're ever asked to reanalyze an Overloaded type, we know it's
# fine to just return it as-is.
return t
def visit_tuple_type(self, t: TupleType) -> Type:
# Types such as (t1, t2, ...) only allowed in assignment statements. They'll
# generate errors elsewhere, and Tuple[t1, t2, ...] must be used instead.
if t.implicit and not self.allow_tuple_literal:
self.fail("Syntax error in type annotation", t, code=codes.SYNTAX)
if len(t.items) == 0:
self.note(
"Suggestion: Use Tuple[()] instead of () for an empty tuple, or "
"None for a function without a return value",
t,
code=codes.SYNTAX,
)
elif len(t.items) == 1:
self.note("Suggestion: Is there a spurious trailing comma?", t, code=codes.SYNTAX)
else:
self.note(
"Suggestion: Use Tuple[T1, ..., Tn] instead of (T1, ..., Tn)",
t,
code=codes.SYNTAX,
)
return AnyType(TypeOfAny.from_error)
any_type = AnyType(TypeOfAny.special_form)
# If the fallback isn't filled in yet, its type will be the falsey FakeInfo
fallback = (
t.partial_fallback
if t.partial_fallback.type
else self.named_type("builtins.tuple", [any_type])
)
return TupleType(self.anal_array(t.items), fallback, t.line)
def visit_typeddict_type(self, t: TypedDictType) -> Type:
items = {
item_name: self.anal_type(item_type) for (item_name, item_type) in t.items.items()
}
return TypedDictType(items, set(t.required_keys), t.fallback)
def visit_raw_expression_type(self, t: RawExpressionType) -> Type:
# We should never see a bare Literal. We synthesize these raw literals
# in the earlier stages of semantic analysis, but those
# "fake literals" should always be wrapped in an UnboundType
# corresponding to 'Literal'.
#
# Note: if at some point in the distant future, we decide to
# make signatures like "foo(x: 20) -> None" legal, we can change
# this method so it generates and returns an actual LiteralType
# instead.
if self.report_invalid_types:
if t.base_type_name in ("builtins.int", "builtins.bool"):
# The only time it makes sense to use an int or bool is inside of
# a literal type.
msg = f"Invalid type: try using Literal[{repr(t.literal_value)}] instead?"
elif t.base_type_name in ("builtins.float", "builtins.complex"):
# We special-case warnings for floats and complex numbers.
msg = f"Invalid type: {t.simple_name()} literals cannot be used as a type"
else:
# And in all other cases, we default to a generic error message.
# Note: the reason why we use a generic error message for strings
# but not ints or bools is because whenever we see an out-of-place
# string, it's unclear if the user meant to construct a literal type
# or just misspelled a regular type. So we avoid guessing.
msg = "Invalid type comment or annotation"
self.fail(msg, t, code=codes.VALID_TYPE)
if t.note is not None:
self.note(t.note, t, code=codes.VALID_TYPE)
return AnyType(TypeOfAny.from_error, line=t.line, column=t.column)
def visit_literal_type(self, t: LiteralType) -> Type:
return t
def visit_union_type(self, t: UnionType) -> Type:
if (
t.uses_pep604_syntax is True
and t.is_evaluated is True
and not self.always_allow_new_syntax
and not self.options.python_version >= (3, 10)
):
self.fail("X | Y syntax for unions requires Python 3.10", t, code=codes.SYNTAX)
return UnionType(self.anal_array(t.items), t.line)
def visit_partial_type(self, t: PartialType) -> Type:
assert False, "Internal error: Unexpected partial type"
def visit_ellipsis_type(self, t: EllipsisType) -> Type:
if self.allow_param_spec_literals:
any_type = AnyType(TypeOfAny.explicit)
return Parameters(
[any_type, any_type], [ARG_STAR, ARG_STAR2], [None, None], is_ellipsis_args=True
)
else:
self.fail('Unexpected "..."', t)
return AnyType(TypeOfAny.from_error)
def visit_type_type(self, t: TypeType) -> Type:
return TypeType.make_normalized(self.anal_type(t.item), line=t.line)
def visit_placeholder_type(self, t: PlaceholderType) -> Type:
n = (
None
# No dot in fullname indicates we are at function scope, and recursive
# types are not supported there anyway, so we just give up.
if not t.fullname or "." not in t.fullname
else self.api.lookup_fully_qualified(t.fullname)
)
if not n or isinstance(n.node, PlaceholderNode):
self.api.defer() # Still incomplete
return t
else:
# TODO: Handle non-TypeInfo
assert isinstance(n.node, TypeInfo)
return self.analyze_type_with_type_info(n.node, t.args, t)
def analyze_callable_args_for_paramspec(
self, callable_args: Type, ret_type: Type, fallback: Instance
) -> CallableType | None:
"""Construct a 'Callable[P, RET]', where P is ParamSpec, return None if we cannot."""
if not isinstance(callable_args, UnboundType):
return None
sym = self.lookup_qualified(callable_args.name, callable_args)
if sym is None:
return None
tvar_def = self.tvar_scope.get_binding(sym)
if not isinstance(tvar_def, ParamSpecType):
if (
tvar_def is None
and self.allow_unbound_tvars
and isinstance(sym.node, ParamSpecExpr)
):
# We are analyzing this type in runtime context (e.g. as type application).
# If it is not valid as a type in this position an error will be given later.
return callable_with_ellipsis(
AnyType(TypeOfAny.explicit), ret_type=ret_type, fallback=fallback
)
return None
return CallableType(
[
paramspec_args(
tvar_def.name, tvar_def.fullname, tvar_def.id, named_type_func=self.named_type
),
paramspec_kwargs(
tvar_def.name, tvar_def.fullname, tvar_def.id, named_type_func=self.named_type
),
],
[nodes.ARG_STAR, nodes.ARG_STAR2],
[None, None],
ret_type=ret_type,
fallback=fallback,
)
def analyze_callable_args_for_concatenate(
self, callable_args: Type, ret_type: Type, fallback: Instance
) -> CallableType | None:
"""Construct a 'Callable[C, RET]', where C is Concatenate[..., P], returning None if we
cannot.
"""
if not isinstance(callable_args, UnboundType):
return None
sym = self.lookup_qualified(callable_args.name, callable_args)
if sym is None:
return None
if sym.node is None:
return None
if sym.node.fullname not in ("typing_extensions.Concatenate", "typing.Concatenate"):
return None
tvar_def = self.anal_type(callable_args, allow_param_spec=True)
if not isinstance(tvar_def, ParamSpecType):
if self.allow_unbound_tvars and isinstance(tvar_def, UnboundType):
sym = self.lookup_qualified(tvar_def.name, callable_args)
if sym is not None and isinstance(sym.node, ParamSpecExpr):
# We are analyzing this type in runtime context (e.g. as type application).
# If it is not valid as a type in this position an error will be given later.
return callable_with_ellipsis(
AnyType(TypeOfAny.explicit), ret_type=ret_type, fallback=fallback
)
return None
# ick, CallableType should take ParamSpecType
prefix = tvar_def.prefix
# we don't set the prefix here as generic arguments will get updated at some point
# in the future. CallableType.param_spec() accounts for this.
return CallableType(
[
*prefix.arg_types,
paramspec_args(
tvar_def.name, tvar_def.fullname, tvar_def.id, named_type_func=self.named_type
),
paramspec_kwargs(
tvar_def.name, tvar_def.fullname, tvar_def.id, named_type_func=self.named_type
),
],
[*prefix.arg_kinds, nodes.ARG_STAR, nodes.ARG_STAR2],
[*prefix.arg_names, None, None],
ret_type=ret_type,
fallback=fallback,
from_concatenate=True,
)
def analyze_callable_type(self, t: UnboundType) -> Type:
fallback = self.named_type("builtins.function")
if len(t.args) == 0:
# Callable (bare). Treat as Callable[..., Any].
any_type = self.get_omitted_any(t)
ret = callable_with_ellipsis(any_type, any_type, fallback)
elif len(t.args) == 2:
callable_args = t.args[0]
ret_type = t.args[1]
if isinstance(callable_args, TypeList):
# Callable[[ARG, ...], RET] (ordinary callable type)
analyzed_args = self.analyze_callable_args(callable_args)
if analyzed_args is None:
return AnyType(TypeOfAny.from_error)
args, kinds, names = analyzed_args
ret = CallableType(args, kinds, names, ret_type=ret_type, fallback=fallback)
elif isinstance(callable_args, EllipsisType):
# Callable[..., RET] (with literal ellipsis; accept arbitrary arguments)
ret = callable_with_ellipsis(
AnyType(TypeOfAny.explicit), ret_type=ret_type, fallback=fallback
)
else:
# Callable[P, RET] (where P is ParamSpec)
maybe_ret = self.analyze_callable_args_for_paramspec(
callable_args, ret_type, fallback
) or self.analyze_callable_args_for_concatenate(callable_args, ret_type, fallback)
if maybe_ret is None:
# Callable[?, RET] (where ? is something invalid)
self.fail(
"The first argument to Callable must be a "
'list of types, parameter specification, or "..."',
t,
code=codes.VALID_TYPE,
)
self.note(
"See https://mypy.readthedocs.io/en/stable/kinds_of_types.html#callable-types-and-lambdas",
t,
)
return AnyType(TypeOfAny.from_error)
ret = maybe_ret
else:
if self.options.disallow_any_generics:
self.fail('Please use "Callable[[<parameters>], <return type>]"', t)
else:
self.fail('Please use "Callable[[<parameters>], <return type>]" or "Callable"', t)
return AnyType(TypeOfAny.from_error)
assert isinstance(ret, CallableType)
return ret.accept(self)
def analyze_callable_args(
self, arglist: TypeList
) -> tuple[list[Type], list[ArgKind], list[str | None]] | None:
args: list[Type] = []
kinds: list[ArgKind] = []
names: list[str | None] = []
for arg in arglist.items:
if isinstance(arg, CallableArgument):
args.append(arg.typ)
names.append(arg.name)
if arg.constructor is None:
return None
found = self.lookup_qualified(arg.constructor, arg)
if found is None:
# Looking it up already put an error message in
return None
elif found.fullname not in ARG_KINDS_BY_CONSTRUCTOR:
self.fail(f'Invalid argument constructor "{found.fullname}"', arg)
return None
else:
assert found.fullname is not None
kind = ARG_KINDS_BY_CONSTRUCTOR[found.fullname]
kinds.append(kind)
if arg.name is not None and kind.is_star():
self.fail(f"{arg.constructor} arguments should not have names", arg)
return None
elif isinstance(arg, UnboundType):
kind = ARG_POS
# Potentially a unpack.
sym = self.lookup_qualified(arg.name, arg)
if sym is not None:
if sym.fullname in ("typing_extensions.Unpack", "typing.Unpack"):
kind = ARG_STAR
args.append(arg)
kinds.append(kind)
names.append(None)
else:
args.append(arg)
kinds.append(ARG_POS)
names.append(None)
# Note that arglist below is only used for error context.
check_arg_names(names, [arglist] * len(args), self.fail, "Callable")
check_arg_kinds(kinds, [arglist] * len(args), self.fail)
return args, kinds, names
def analyze_literal_type(self, t: UnboundType) -> Type:
if len(t.args) == 0:
self.fail("Literal[...] must have at least one parameter", t, code=codes.VALID_TYPE)
return AnyType(TypeOfAny.from_error)
output: list[Type] = []
for i, arg in enumerate(t.args):
analyzed_types = self.analyze_literal_param(i + 1, arg, t)
if analyzed_types is None:
return AnyType(TypeOfAny.from_error)
else:
output.extend(analyzed_types)
return UnionType.make_union(output, line=t.line)
def analyze_literal_param(self, idx: int, arg: Type, ctx: Context) -> list[Type] | None:
# This UnboundType was originally defined as a string.
if isinstance(arg, UnboundType) and arg.original_str_expr is not None:
assert arg.original_str_fallback is not None
return [
LiteralType(
value=arg.original_str_expr,
fallback=self.named_type(arg.original_str_fallback),
line=arg.line,
column=arg.column,
)
]
# If arg is an UnboundType that was *not* originally defined as
# a string, try expanding it in case it's a type alias or something.
if isinstance(arg, UnboundType):
self.nesting_level += 1
try:
arg = self.visit_unbound_type(arg, defining_literal=True)
finally:
self.nesting_level -= 1
# Literal[...] cannot contain Any. Give up and add an error message
# (if we haven't already).
arg = get_proper_type(arg)
if isinstance(arg, AnyType):
# Note: We can encounter Literals containing 'Any' under three circumstances:
#
# 1. If the user attempts use an explicit Any as a parameter
# 2. If the user is trying to use an enum value imported from a module with
# no type hints, giving it an implicit type of 'Any'
# 3. If there's some other underlying problem with the parameter.
#
# We report an error in only the first two cases. In the third case, we assume
# some other region of the code has already reported a more relevant error.
#
# TODO: Once we start adding support for enums, make sure we report a custom
# error for case 2 as well.
if arg.type_of_any not in (TypeOfAny.from_error, TypeOfAny.special_form):
self.fail(
f'Parameter {idx} of Literal[...] cannot be of type "Any"',
ctx,
code=codes.VALID_TYPE,
)
return None
elif isinstance(arg, RawExpressionType):
# A raw literal. Convert it directly into a literal if we can.
if arg.literal_value is None:
name = arg.simple_name()
if name in ("float", "complex"):
msg = f'Parameter {idx} of Literal[...] cannot be of type "{name}"'
else:
msg = "Invalid type: Literal[...] cannot contain arbitrary expressions"
self.fail(msg, ctx, code=codes.VALID_TYPE)
# Note: we deliberately ignore arg.note here: the extra info might normally be
# helpful, but it generally won't make sense in the context of a Literal[...].
return None
# Remap bytes and unicode into the appropriate type for the correct Python version
fallback = self.named_type(arg.base_type_name)
assert isinstance(fallback, Instance)
return [LiteralType(arg.literal_value, fallback, line=arg.line, column=arg.column)]
elif isinstance(arg, (NoneType, LiteralType)):
# Types that we can just add directly to the literal/potential union of literals.
return [arg]
elif isinstance(arg, Instance) and arg.last_known_value is not None:
# Types generated from declarations like "var: Final = 4".
return [arg.last_known_value]
elif isinstance(arg, UnionType):
out = []
for union_arg in arg.items:
union_result = self.analyze_literal_param(idx, union_arg, ctx)
if union_result is None:
return None
out.extend(union_result)
return out
else:
self.fail(f"Parameter {idx} of Literal[...] is invalid", ctx, code=codes.VALID_TYPE)
return None
def analyze_type(self, t: Type) -> Type:
return t.accept(self)
def fail(self, msg: str, ctx: Context, *, code: ErrorCode | None = None) -> None:
self.fail_func(msg, ctx, code=code)
def note(self, msg: str, ctx: Context, *, code: ErrorCode | None = None) -> None:
self.note_func(msg, ctx, code=code)
@contextmanager
def tvar_scope_frame(self) -> Iterator[None]:
old_scope = self.tvar_scope
self.tvar_scope = self.tvar_scope.method_frame()
yield
self.tvar_scope = old_scope
def find_type_var_likes(self, t: Type, include_callables: bool = True) -> TypeVarLikeList:
return t.accept(
TypeVarLikeQuery(self.api, self.tvar_scope, include_callables=include_callables)
)
def infer_type_variables(self, type: CallableType) -> list[tuple[str, TypeVarLikeExpr]]:
"""Return list of unique type variables referred to in a callable."""
names: list[str] = []
tvars: list[TypeVarLikeExpr] = []
for arg in type.arg_types:
for name, tvar_expr in self.find_type_var_likes(arg):
if name not in names:
names.append(name)
tvars.append(tvar_expr)
# When finding type variables in the return type of a function, don't
# look inside Callable types. Type variables only appearing in
# functions in the return type belong to those functions, not the
# function we're currently analyzing.
for name, tvar_expr in self.find_type_var_likes(type.ret_type, include_callables=False):
if name not in names:
names.append(name)
tvars.append(tvar_expr)
if not names:
return [] # Fast path
return list(zip(names, tvars))
def bind_function_type_variables(
self, fun_type: CallableType, defn: Context
) -> tuple[Sequence[TypeVarLikeType], bool]:
"""Find the type variables of the function type and bind them in our tvar_scope"""
has_self_type = False
if fun_type.variables:
defs = []
for var in fun_type.variables:
if self.api.type and self.api.type.self_type and var == self.api.type.self_type:
has_self_type = True
continue
var_node = self.lookup_qualified(var.name, defn)
assert var_node, "Binding for function type variable not found within function"
var_expr = var_node.node
assert isinstance(var_expr, TypeVarLikeExpr)
binding = self.tvar_scope.bind_new(var.name, var_expr)
defs.append(binding)
return defs, has_self_type
typevars = self.infer_type_variables(fun_type)
has_self_type = find_self_type(
fun_type, lambda name: self.api.lookup_qualified(name, defn, suppress_errors=True)
)
# Do not define a new type variable if already defined in scope.
typevars = [
(name, tvar) for name, tvar in typevars if not self.is_defined_type_var(name, defn)
]
defs = []
for name, tvar in typevars:
if not self.tvar_scope.allow_binding(tvar.fullname):
self.fail(
f'Type variable "{name}" is bound by an outer class',
defn,
code=codes.VALID_TYPE,
)
binding = self.tvar_scope.bind_new(name, tvar)
defs.append(binding)
return defs, has_self_type
def is_defined_type_var(self, tvar: str, context: Context) -> bool:
tvar_node = self.lookup_qualified(tvar, context)
if not tvar_node:
return False
return self.tvar_scope.get_binding(tvar_node) is not None
def anal_array(
self,
a: Iterable[Type],
nested: bool = True,
*,
allow_param_spec: bool = False,
allow_param_spec_literals: bool = False,
) -> list[Type]:
old_allow_param_spec_literals = self.allow_param_spec_literals
self.allow_param_spec_literals = allow_param_spec_literals
res: list[Type] = []
for t in a:
res.append(self.anal_type(t, nested, allow_param_spec=allow_param_spec))
self.allow_param_spec_literals = old_allow_param_spec_literals
return self.check_unpacks_in_list(res)
def anal_type(self, t: Type, nested: bool = True, *, allow_param_spec: bool = False) -> Type:
if nested:
self.nesting_level += 1
old_allow_required = self.allow_required
self.allow_required = False
try:
analyzed = t.accept(self)
finally:
if nested:
self.nesting_level -= 1
self.allow_required = old_allow_required
if (
not allow_param_spec
and isinstance(analyzed, ParamSpecType)
and analyzed.flavor == ParamSpecFlavor.BARE
):
if analyzed.prefix.arg_types:
self.fail("Invalid location for Concatenate", t, code=codes.VALID_TYPE)
self.note("You can use Concatenate as the first argument to Callable", t)
else:
self.fail(
f'Invalid location for ParamSpec "{analyzed.name}"', t, code=codes.VALID_TYPE
)
self.note(
"You can use ParamSpec as the first argument to Callable, e.g., "
"'Callable[{}, int]'".format(analyzed.name),
t,
)
return analyzed
def anal_var_def(self, var_def: TypeVarLikeType) -> TypeVarLikeType:
if isinstance(var_def, TypeVarType):
return TypeVarType(
name=var_def.name,
fullname=var_def.fullname,
id=var_def.id.raw_id,
values=self.anal_array(var_def.values),
upper_bound=var_def.upper_bound.accept(self),
default=var_def.default.accept(self),
variance=var_def.variance,
line=var_def.line,
column=var_def.column,
)
else:
return var_def
def anal_var_defs(self, var_defs: Sequence[TypeVarLikeType]) -> list[TypeVarLikeType]:
return [self.anal_var_def(vd) for vd in var_defs]
def named_type(
self,
fully_qualified_name: str,
args: list[Type] | None = None,
line: int = -1,
column: int = -1,
) -> Instance:
node = self.lookup_fully_qualified(fully_qualified_name)
assert isinstance(node.node, TypeInfo)
any_type = AnyType(TypeOfAny.special_form)
if args is not None:
args = self.check_unpacks_in_list(args)
return Instance(
node.node, args or [any_type] * len(node.node.defn.type_vars), line=line, column=column
)
def check_unpacks_in_list(self, items: list[Type]) -> list[Type]:
new_items: list[Type] = []
num_unpacks = 0
final_unpack = None
for item in items:
if isinstance(item, UnpackType) and not isinstance(
get_proper_type(item.type), TupleType
):
if not num_unpacks:
new_items.append(item)
num_unpacks += 1
final_unpack = item
else:
new_items.append(item)
if num_unpacks > 1:
assert final_unpack is not None
self.fail("More than one Unpack in a type is not allowed", final_unpack)
return new_items
def tuple_type(self, items: list[Type]) -> TupleType:
any_type = AnyType(TypeOfAny.special_form)
return TupleType(items, fallback=self.named_type("builtins.tuple", [any_type]))
TypeVarLikeList = List[Tuple[str, TypeVarLikeExpr]]
class MsgCallback(Protocol):
def __call__(self, __msg: str, __ctx: Context, *, code: ErrorCode | None = None) -> None:
...
def get_omitted_any(
disallow_any: bool,
fail: MsgCallback,
note: MsgCallback,
orig_type: Type,
options: Options,
fullname: str | None = None,
unexpanded_type: Type | None = None,
) -> AnyType:
if disallow_any:
nongen_builtins = get_nongen_builtins(options.python_version)
if fullname in nongen_builtins:
typ = orig_type
# We use a dedicated error message for builtin generics (as the most common case).
alternative = nongen_builtins[fullname]
fail(
message_registry.IMPLICIT_GENERIC_ANY_BUILTIN.format(alternative),
typ,
code=codes.TYPE_ARG,
)
else:
typ = unexpanded_type or orig_type
type_str = typ.name if isinstance(typ, UnboundType) else format_type_bare(typ, options)
fail(
message_registry.BARE_GENERIC.format(quote_type_string(type_str)),
typ,
code=codes.TYPE_ARG,
)
base_type = get_proper_type(orig_type)
base_fullname = (
base_type.type.fullname if isinstance(base_type, Instance) else fullname
)
# Ideally, we'd check whether the type is quoted or `from __future__ annotations`
# is set before issuing this note
if (
options.python_version < (3, 9)
and base_fullname in GENERIC_STUB_NOT_AT_RUNTIME_TYPES
):
# Recommend `from __future__ import annotations` or to put type in quotes
# (string literal escaping) for classes not generic at runtime
note(
"Subscripting classes that are not generic at runtime may require "
"escaping, see https://mypy.readthedocs.io/en/stable/runtime_troubles.html"
"#not-generic-runtime",
typ,
code=codes.TYPE_ARG,
)
any_type = AnyType(TypeOfAny.from_error, line=typ.line, column=typ.column)
else:
any_type = AnyType(
TypeOfAny.from_omitted_generics, line=orig_type.line, column=orig_type.column
)
return any_type
def fix_type_var_tuple_argument(any_type: Type, t: Instance) -> None:
if t.type.has_type_var_tuple_type:
args = list(t.args)
assert t.type.type_var_tuple_prefix is not None
tvt = t.type.defn.type_vars[t.type.type_var_tuple_prefix]
assert isinstance(tvt, TypeVarTupleType)
args[t.type.type_var_tuple_prefix] = UnpackType(
Instance(tvt.tuple_fallback.type, [any_type])
)
t.args = tuple(args)
def fix_instance(
t: Instance,
fail: MsgCallback,
note: MsgCallback,
disallow_any: bool,
options: Options,
use_generic_error: bool = False,
unexpanded_type: Type | None = None,
) -> None:
"""Fix a malformed instance by replacing all type arguments with Any.
Also emit a suitable error if this is not due to implicit Any's.
"""
if len(t.args) == 0:
if use_generic_error:
fullname: str | None = None
else:
fullname = t.type.fullname
any_type = get_omitted_any(disallow_any, fail, note, t, options, fullname, unexpanded_type)
t.args = (any_type,) * len(t.type.type_vars)
fix_type_var_tuple_argument(any_type, t)
return
# Invalid number of type parameters.
fail(
wrong_type_arg_count(len(t.type.type_vars), str(len(t.args)), t.type.name),
t,
code=codes.TYPE_ARG,
)
# Construct the correct number of type arguments, as
# otherwise the type checker may crash as it expects
# things to be right.
t.args = tuple(AnyType(TypeOfAny.from_error) for _ in t.type.type_vars)
fix_type_var_tuple_argument(AnyType(TypeOfAny.from_error), t)
t.invalid = True
def instantiate_type_alias(
node: TypeAlias,
args: list[Type],
fail: MsgCallback,
no_args: bool,
ctx: Context,
options: Options,
*,
unexpanded_type: Type | None = None,
disallow_any: bool = False,
use_standard_error: bool = False,
empty_tuple_index: bool = False,
) -> Type:
"""Create an instance of a (generic) type alias from alias node and type arguments.
We are following the rules outlined in TypeAlias docstring.
Here:
node: type alias node (definition)
args: type arguments (types to be substituted in place of type variables
when expanding the alias)
fail: error reporter callback
no_args: whether original definition used a bare generic `A = List`
ctx: context where expansion happens
unexpanded_type, disallow_any, use_standard_error: used to customize error messages
"""
exp_len = len(node.alias_tvars)
act_len = len(args)
if (
exp_len > 0
and act_len == 0
and not (empty_tuple_index and node.tvar_tuple_index is not None)
):
# Interpret bare Alias same as normal generic, i.e., Alias[Any, Any, ...]
return set_any_tvars(
node,
ctx.line,
ctx.column,
options,
disallow_any=disallow_any,
fail=fail,
unexpanded_type=unexpanded_type,
)
if exp_len == 0 and act_len == 0:
if no_args:
assert isinstance(node.target, Instance) # type: ignore[misc]
# Note: this is the only case where we use an eager expansion. See more info about
# no_args aliases like L = List in the docstring for TypeAlias class.
return Instance(node.target.type, [], line=ctx.line, column=ctx.column)
return TypeAliasType(node, [], line=ctx.line, column=ctx.column)
if (
exp_len == 0
and act_len > 0
and isinstance(node.target, Instance) # type: ignore[misc]
and no_args
):
tp = Instance(node.target.type, args)
tp.line = ctx.line
tp.column = ctx.column
return tp
if act_len != exp_len and node.tvar_tuple_index is None:
if use_standard_error:
# This is used if type alias is an internal representation of another type,
# for example a generic TypedDict or NamedTuple.
msg = wrong_type_arg_count(exp_len, str(act_len), node.name)
else:
msg = f"Bad number of arguments for type alias, expected: {exp_len}, given: {act_len}"
fail(msg, ctx, code=codes.TYPE_ARG)
return set_any_tvars(node, ctx.line, ctx.column, options, from_error=True)
# TODO: we need to check args validity w.r.t alias.alias_tvars.
# Otherwise invalid instantiations will be allowed in runtime context.
# Note: in type context, these will be still caught by semanal_typeargs.
typ = TypeAliasType(node, args, ctx.line, ctx.column)
assert typ.alias is not None
# HACK: Implement FlexibleAlias[T, typ] by expanding it to typ here.
if (
isinstance(typ.alias.target, Instance) # type: ignore[misc]
and typ.alias.target.type.fullname == "mypy_extensions.FlexibleAlias"
):
exp = get_proper_type(typ)
assert isinstance(exp, Instance)
return exp.args[-1]
return typ
def set_any_tvars(
node: TypeAlias,
newline: int,
newcolumn: int,
options: Options,
*,
from_error: bool = False,
disallow_any: bool = False,
fail: MsgCallback | None = None,
unexpanded_type: Type | None = None,
) -> TypeAliasType:
if from_error or disallow_any:
type_of_any = TypeOfAny.from_error
else:
type_of_any = TypeOfAny.from_omitted_generics
if disallow_any and node.alias_tvars:
assert fail is not None
if unexpanded_type:
type_str = (
unexpanded_type.name
if isinstance(unexpanded_type, UnboundType)
else format_type_bare(unexpanded_type, options)
)
else:
type_str = node.name
fail(
message_registry.BARE_GENERIC.format(quote_type_string(type_str)),
Context(newline, newcolumn),
code=codes.TYPE_ARG,
)
any_type = AnyType(type_of_any, line=newline, column=newcolumn)
args: list[Type] = []
for tv in node.alias_tvars:
if isinstance(tv, TypeVarTupleType):
args.append(UnpackType(Instance(tv.tuple_fallback.type, [any_type])))
else:
args.append(any_type)
return TypeAliasType(node, args, newline, newcolumn)
def flatten_tvars(lists: list[list[T]]) -> list[T]:
result: list[T] = []
for lst in lists:
for item in lst:
if item not in result:
result.append(item)
return result
class TypeVarLikeQuery(TypeQuery[TypeVarLikeList]):
"""Find TypeVar and ParamSpec references in an unbound type."""
def __init__(
self,
api: SemanticAnalyzerCoreInterface,
scope: TypeVarLikeScope,
*,
include_callables: bool = True,
) -> None:
super().__init__(flatten_tvars)
self.api = api
self.scope = scope
self.include_callables = include_callables
# Only include type variables in type aliases args. This would be anyway
# that case if we expand (as target variables would be overridden with args)
# and it may cause infinite recursion on invalid (diverging) recursive aliases.
self.skip_alias_target = True
def _seems_like_callable(self, type: UnboundType) -> bool:
if not type.args:
return False
return isinstance(type.args[0], (EllipsisType, TypeList, ParamSpecType))
def visit_unbound_type(self, t: UnboundType) -> TypeVarLikeList:
name = t.name
node = None
# Special case P.args and P.kwargs for ParamSpecs only.
if name.endswith("args"):
if name.endswith(".args") or name.endswith(".kwargs"):
base = ".".join(name.split(".")[:-1])
n = self.api.lookup_qualified(base, t)
if n is not None and isinstance(n.node, ParamSpecExpr):
node = n
name = base
if node is None:
node = self.api.lookup_qualified(name, t)
if (
node
and isinstance(node.node, TypeVarLikeExpr)
and self.scope.get_binding(node) is None
):
assert isinstance(node.node, TypeVarLikeExpr)
return [(name, node.node)]
elif not self.include_callables and self._seems_like_callable(t):
return []
elif node and node.fullname in LITERAL_TYPE_NAMES:
return []
elif node and node.fullname in ANNOTATED_TYPE_NAMES and t.args:
# Don't query the second argument to Annotated for TypeVars
return self.query_types([t.args[0]])
else:
return super().visit_unbound_type(t)
def visit_callable_type(self, t: CallableType) -> TypeVarLikeList:
if self.include_callables:
return super().visit_callable_type(t)
else:
return []
class DivergingAliasDetector(TrivialSyntheticTypeTranslator):
"""See docstring of detect_diverging_alias() for details."""
# TODO: this doesn't really need to be a translator, but we don't have a trivial visitor.
def __init__(
self,
seen_nodes: set[TypeAlias],
lookup: Callable[[str, Context], SymbolTableNode | None],
scope: TypeVarLikeScope,
) -> None:
self.seen_nodes = seen_nodes
self.lookup = lookup
self.scope = scope
self.diverging = False
def visit_type_alias_type(self, t: TypeAliasType) -> Type:
assert t.alias is not None, f"Unfixed type alias {t.type_ref}"
if t.alias in self.seen_nodes:
for arg in t.args:
if not (
isinstance(arg, TypeVarLikeType)
or isinstance(arg, UnpackType)
and isinstance(arg.type, TypeVarLikeType)
) and has_type_vars(arg):
self.diverging = True
return t
# All clear for this expansion chain.
return t
new_nodes = self.seen_nodes | {t.alias}
visitor = DivergingAliasDetector(new_nodes, self.lookup, self.scope)
_ = get_proper_type(t).accept(visitor)
if visitor.diverging:
self.diverging = True
return t
def detect_diverging_alias(
node: TypeAlias,
target: Type,
lookup: Callable[[str, Context], SymbolTableNode | None],
scope: TypeVarLikeScope,
) -> bool:
"""This detects type aliases that will diverge during type checking.
For example F = Something[..., F[List[T]]]. At each expansion step this will produce
*new* type aliases: e.g. F[List[int]], F[List[List[int]]], etc. So we can't detect
recursion. It is a known problem in the literature, recursive aliases and generic types
don't always go well together. It looks like there is no known systematic solution yet.
# TODO: should we handle such aliases using type_recursion counter and some large limit?
They may be handy in rare cases, e.g. to express a union of non-mixed nested lists:
Nested = Union[T, Nested[List[T]]] ~> Union[T, List[T], List[List[T]], ...]
"""
visitor = DivergingAliasDetector({node}, lookup, scope)
_ = target.accept(visitor)
return visitor.diverging
def check_for_explicit_any(
typ: Type | None,
options: Options,
is_typeshed_stub: bool,
msg: MessageBuilder,
context: Context,
) -> None:
if options.disallow_any_explicit and not is_typeshed_stub and typ and has_explicit_any(typ):
msg.explicit_any(context)
def has_explicit_any(t: Type) -> bool:
"""
Whether this type is or type it contains is an Any coming from explicit type annotation
"""
return t.accept(HasExplicitAny())
class HasExplicitAny(TypeQuery[bool]):
def __init__(self) -> None:
super().__init__(any)
def visit_any(self, t: AnyType) -> bool:
return t.type_of_any == TypeOfAny.explicit
def visit_typeddict_type(self, t: TypedDictType) -> bool:
# typeddict is checked during TypedDict declaration, so don't typecheck it here.
return False
def has_any_from_unimported_type(t: Type) -> bool:
"""Return true if this type is Any because an import was not followed.
If type t is such Any type or has type arguments that contain such Any type
this function will return true.
"""
return t.accept(HasAnyFromUnimportedType())
class HasAnyFromUnimportedType(BoolTypeQuery):
def __init__(self) -> None:
super().__init__(ANY_STRATEGY)
def visit_any(self, t: AnyType) -> bool:
return t.type_of_any == TypeOfAny.from_unimported_type
def visit_typeddict_type(self, t: TypedDictType) -> bool:
# typeddict is checked during TypedDict declaration, so don't typecheck it here
return False
def collect_all_inner_types(t: Type) -> list[Type]:
"""
Return all types that `t` contains
"""
return t.accept(CollectAllInnerTypesQuery())
class CollectAllInnerTypesQuery(TypeQuery[List[Type]]):
def __init__(self) -> None:
super().__init__(self.combine_lists_strategy)
def query_types(self, types: Iterable[Type]) -> list[Type]:
return self.strategy([t.accept(self) for t in types]) + list(types)
@classmethod
def combine_lists_strategy(cls, it: Iterable[list[Type]]) -> list[Type]:
return list(itertools.chain.from_iterable(it))
def make_optional_type(t: Type) -> Type:
"""Return the type corresponding to Optional[t].
Note that we can't use normal union simplification, since this function
is called during semantic analysis and simplification only works during
type checking.
"""
p_t = get_proper_type(t)
if isinstance(p_t, NoneType):
return t
elif isinstance(p_t, UnionType):
# Eagerly expanding aliases is not safe during semantic analysis.
items = [
item
for item in flatten_nested_unions(p_t.items, handle_type_alias_type=False)
if not isinstance(get_proper_type(item), NoneType)
]
return UnionType(items + [NoneType()], t.line, t.column)
else:
return UnionType([t, NoneType()], t.line, t.column)
def fix_instance_types(t: Type, fail: MsgCallback, note: MsgCallback, options: Options) -> None:
"""Recursively fix all instance types (type argument count) in a given type.
For example 'Union[Dict, List[str, int]]' will be transformed into
'Union[Dict[Any, Any], List[Any]]' in place.
"""
t.accept(InstanceFixer(fail, note, options))
class InstanceFixer(TypeTraverserVisitor):
def __init__(self, fail: MsgCallback, note: MsgCallback, options: Options) -> None:
self.fail = fail
self.note = note
self.options = options
def visit_instance(self, typ: Instance) -> None:
super().visit_instance(typ)
if len(typ.args) != len(typ.type.type_vars) and not typ.type.has_type_var_tuple_type:
fix_instance(
typ,
self.fail,
self.note,
disallow_any=False,
options=self.options,
use_generic_error=True,
)
def find_self_type(typ: Type, lookup: Callable[[str], SymbolTableNode | None]) -> bool:
return typ.accept(HasSelfType(lookup))
class HasSelfType(BoolTypeQuery):
def __init__(self, lookup: Callable[[str], SymbolTableNode | None]) -> None:
self.lookup = lookup
super().__init__(ANY_STRATEGY)
def visit_unbound_type(self, t: UnboundType) -> bool:
sym = self.lookup(t.name)
if sym and sym.fullname in SELF_TYPE_NAMES:
return True
return super().visit_unbound_type(t)