Tipragot
628be439b8
Cela permet de ne pas avoir de problèmes de compatibilité car python est dans le git.
665 lines
28 KiB
Python
665 lines
28 KiB
Python
"""Semantic analysis of named tuple definitions.
|
|
|
|
This is conceptually part of mypy.semanal.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from contextlib import contextmanager
|
|
from typing import Final, Iterator, List, Mapping, cast
|
|
|
|
from mypy.exprtotype import TypeTranslationError, expr_to_unanalyzed_type
|
|
from mypy.nodes import (
|
|
ARG_NAMED_OPT,
|
|
ARG_OPT,
|
|
ARG_POS,
|
|
MDEF,
|
|
Argument,
|
|
AssignmentStmt,
|
|
Block,
|
|
CallExpr,
|
|
ClassDef,
|
|
Context,
|
|
Decorator,
|
|
EllipsisExpr,
|
|
Expression,
|
|
ExpressionStmt,
|
|
FuncBase,
|
|
FuncDef,
|
|
ListExpr,
|
|
NamedTupleExpr,
|
|
NameExpr,
|
|
PassStmt,
|
|
RefExpr,
|
|
Statement,
|
|
StrExpr,
|
|
SymbolTable,
|
|
SymbolTableNode,
|
|
TempNode,
|
|
TupleExpr,
|
|
TypeInfo,
|
|
TypeVarExpr,
|
|
Var,
|
|
is_StrExpr_list,
|
|
)
|
|
from mypy.options import Options
|
|
from mypy.semanal_shared import (
|
|
PRIORITY_FALLBACKS,
|
|
SemanticAnalyzerInterface,
|
|
calculate_tuple_fallback,
|
|
has_placeholder,
|
|
set_callable_name,
|
|
)
|
|
from mypy.types import (
|
|
TYPED_NAMEDTUPLE_NAMES,
|
|
AnyType,
|
|
CallableType,
|
|
LiteralType,
|
|
TupleType,
|
|
Type,
|
|
TypeOfAny,
|
|
TypeType,
|
|
TypeVarLikeType,
|
|
TypeVarType,
|
|
UnboundType,
|
|
has_type_vars,
|
|
)
|
|
from mypy.util import get_unique_redefinition_name
|
|
|
|
# Matches "_prohibited" in typing.py, but adds __annotations__, which works at runtime but can't
|
|
# easily be supported in a static checker.
|
|
NAMEDTUPLE_PROHIBITED_NAMES: Final = (
|
|
"__new__",
|
|
"__init__",
|
|
"__slots__",
|
|
"__getnewargs__",
|
|
"_fields",
|
|
"_field_defaults",
|
|
"_field_types",
|
|
"_make",
|
|
"_replace",
|
|
"_asdict",
|
|
"_source",
|
|
"__annotations__",
|
|
)
|
|
|
|
NAMEDTUP_CLASS_ERROR: Final = (
|
|
"Invalid statement in NamedTuple definition; " 'expected "field_name: field_type [= default]"'
|
|
)
|
|
|
|
SELF_TVAR_NAME: Final = "_NT"
|
|
|
|
|
|
class NamedTupleAnalyzer:
|
|
def __init__(self, options: Options, api: SemanticAnalyzerInterface) -> None:
|
|
self.options = options
|
|
self.api = api
|
|
|
|
def analyze_namedtuple_classdef(
|
|
self, defn: ClassDef, is_stub_file: bool, is_func_scope: bool
|
|
) -> tuple[bool, TypeInfo | None]:
|
|
"""Analyze if given class definition can be a named tuple definition.
|
|
|
|
Return a tuple where first item indicates whether this can possibly be a named tuple,
|
|
and the second item is the corresponding TypeInfo (may be None if not ready and should be
|
|
deferred).
|
|
"""
|
|
for base_expr in defn.base_type_exprs:
|
|
if isinstance(base_expr, RefExpr):
|
|
self.api.accept(base_expr)
|
|
if base_expr.fullname in TYPED_NAMEDTUPLE_NAMES:
|
|
result = self.check_namedtuple_classdef(defn, is_stub_file)
|
|
if result is None:
|
|
# This is a valid named tuple, but some types are incomplete.
|
|
return True, None
|
|
items, types, default_items, statements = result
|
|
if is_func_scope and "@" not in defn.name:
|
|
defn.name += "@" + str(defn.line)
|
|
existing_info = None
|
|
if isinstance(defn.analyzed, NamedTupleExpr):
|
|
existing_info = defn.analyzed.info
|
|
info = self.build_namedtuple_typeinfo(
|
|
defn.name, items, types, default_items, defn.line, existing_info
|
|
)
|
|
defn.analyzed = NamedTupleExpr(info, is_typed=True)
|
|
defn.analyzed.line = defn.line
|
|
defn.analyzed.column = defn.column
|
|
defn.defs.body = statements
|
|
# All done: this is a valid named tuple with all types known.
|
|
return True, info
|
|
# This can't be a valid named tuple.
|
|
return False, None
|
|
|
|
def check_namedtuple_classdef(
|
|
self, defn: ClassDef, is_stub_file: bool
|
|
) -> tuple[list[str], list[Type], dict[str, Expression], list[Statement]] | None:
|
|
"""Parse and validate fields in named tuple class definition.
|
|
|
|
Return a four tuple:
|
|
* field names
|
|
* field types
|
|
* field default values
|
|
* valid statements
|
|
or None, if any of the types are not ready.
|
|
"""
|
|
if len(defn.base_type_exprs) > 1:
|
|
self.fail("NamedTuple should be a single base", defn)
|
|
items: list[str] = []
|
|
types: list[Type] = []
|
|
default_items: dict[str, Expression] = {}
|
|
statements: list[Statement] = []
|
|
for stmt in defn.defs.body:
|
|
statements.append(stmt)
|
|
if not isinstance(stmt, AssignmentStmt):
|
|
# Still allow pass or ... (for empty namedtuples).
|
|
if isinstance(stmt, PassStmt) or (
|
|
isinstance(stmt, ExpressionStmt) and isinstance(stmt.expr, EllipsisExpr)
|
|
):
|
|
continue
|
|
# Also allow methods, including decorated ones.
|
|
if isinstance(stmt, (Decorator, FuncBase)):
|
|
continue
|
|
# And docstrings.
|
|
if isinstance(stmt, ExpressionStmt) and isinstance(stmt.expr, StrExpr):
|
|
continue
|
|
statements.pop()
|
|
defn.removed_statements.append(stmt)
|
|
self.fail(NAMEDTUP_CLASS_ERROR, stmt)
|
|
elif len(stmt.lvalues) > 1 or not isinstance(stmt.lvalues[0], NameExpr):
|
|
# An assignment, but an invalid one.
|
|
statements.pop()
|
|
defn.removed_statements.append(stmt)
|
|
self.fail(NAMEDTUP_CLASS_ERROR, stmt)
|
|
else:
|
|
# Append name and type in this case...
|
|
name = stmt.lvalues[0].name
|
|
items.append(name)
|
|
if stmt.type is None:
|
|
types.append(AnyType(TypeOfAny.unannotated))
|
|
else:
|
|
# We never allow recursive types at function scope. Although it is
|
|
# possible to support this for named tuples, it is still tricky, and
|
|
# it would be inconsistent with type aliases.
|
|
analyzed = self.api.anal_type(
|
|
stmt.type,
|
|
allow_placeholder=not self.options.disable_recursive_aliases
|
|
and not self.api.is_func_scope(),
|
|
prohibit_self_type="NamedTuple item type",
|
|
)
|
|
if analyzed is None:
|
|
# Something is incomplete. We need to defer this named tuple.
|
|
return None
|
|
types.append(analyzed)
|
|
# ...despite possible minor failures that allow further analyzis.
|
|
if name.startswith("_"):
|
|
self.fail(
|
|
f"NamedTuple field name cannot start with an underscore: {name}", stmt
|
|
)
|
|
if stmt.type is None or hasattr(stmt, "new_syntax") and not stmt.new_syntax:
|
|
self.fail(NAMEDTUP_CLASS_ERROR, stmt)
|
|
elif isinstance(stmt.rvalue, TempNode):
|
|
# x: int assigns rvalue to TempNode(AnyType())
|
|
if default_items:
|
|
self.fail(
|
|
"Non-default NamedTuple fields cannot follow default fields", stmt
|
|
)
|
|
else:
|
|
default_items[name] = stmt.rvalue
|
|
return items, types, default_items, statements
|
|
|
|
def check_namedtuple(
|
|
self, node: Expression, var_name: str | None, is_func_scope: bool
|
|
) -> tuple[str | None, TypeInfo | None, list[TypeVarLikeType]]:
|
|
"""Check if a call defines a namedtuple.
|
|
|
|
The optional var_name argument is the name of the variable to
|
|
which this is assigned, if any.
|
|
|
|
Return a tuple of two items:
|
|
* Internal name of the named tuple (e.g. the name passed as an argument to namedtuple)
|
|
or None if it is not a valid named tuple
|
|
* Corresponding TypeInfo, or None if not ready.
|
|
|
|
If the definition is invalid but looks like a namedtuple,
|
|
report errors but return (some) TypeInfo.
|
|
"""
|
|
if not isinstance(node, CallExpr):
|
|
return None, None, []
|
|
call = node
|
|
callee = call.callee
|
|
if not isinstance(callee, RefExpr):
|
|
return None, None, []
|
|
fullname = callee.fullname
|
|
if fullname == "collections.namedtuple":
|
|
is_typed = False
|
|
elif fullname in TYPED_NAMEDTUPLE_NAMES:
|
|
is_typed = True
|
|
else:
|
|
return None, None, []
|
|
result = self.parse_namedtuple_args(call, fullname)
|
|
if result:
|
|
items, types, defaults, typename, tvar_defs, ok = result
|
|
else:
|
|
# Error. Construct dummy return value.
|
|
if var_name:
|
|
name = var_name
|
|
if is_func_scope:
|
|
name += "@" + str(call.line)
|
|
else:
|
|
name = var_name = "namedtuple@" + str(call.line)
|
|
info = self.build_namedtuple_typeinfo(name, [], [], {}, node.line, None)
|
|
self.store_namedtuple_info(info, var_name, call, is_typed)
|
|
if name != var_name or is_func_scope:
|
|
# NOTE: we skip local namespaces since they are not serialized.
|
|
self.api.add_symbol_skip_local(name, info)
|
|
return var_name, info, []
|
|
if not ok:
|
|
# This is a valid named tuple but some types are not ready.
|
|
return typename, None, []
|
|
|
|
# We use the variable name as the class name if it exists. If
|
|
# it doesn't, we use the name passed as an argument. We prefer
|
|
# the variable name because it should be unique inside a
|
|
# module, and so we don't need to disambiguate it with a line
|
|
# number.
|
|
if var_name:
|
|
name = var_name
|
|
else:
|
|
name = typename
|
|
|
|
if var_name is None or is_func_scope:
|
|
# There are two special cases where need to give it a unique name derived
|
|
# from the line number:
|
|
# * This is a base class expression, since it often matches the class name:
|
|
# class NT(NamedTuple('NT', [...])):
|
|
# ...
|
|
# * This is a local (function or method level) named tuple, since
|
|
# two methods of a class can define a named tuple with the same name,
|
|
# and they will be stored in the same namespace (see below).
|
|
name += "@" + str(call.line)
|
|
if defaults:
|
|
default_items = {
|
|
arg_name: default for arg_name, default in zip(items[-len(defaults) :], defaults)
|
|
}
|
|
else:
|
|
default_items = {}
|
|
|
|
existing_info = None
|
|
if isinstance(node.analyzed, NamedTupleExpr):
|
|
existing_info = node.analyzed.info
|
|
info = self.build_namedtuple_typeinfo(
|
|
name, items, types, default_items, node.line, existing_info
|
|
)
|
|
|
|
# If var_name is not None (i.e. this is not a base class expression), we always
|
|
# store the generated TypeInfo under var_name in the current scope, so that
|
|
# other definitions can use it.
|
|
if var_name:
|
|
self.store_namedtuple_info(info, var_name, call, is_typed)
|
|
else:
|
|
call.analyzed = NamedTupleExpr(info, is_typed=is_typed)
|
|
call.analyzed.set_line(call)
|
|
# There are three cases where we need to store the generated TypeInfo
|
|
# second time (for the purpose of serialization):
|
|
# * If there is a name mismatch like One = NamedTuple('Other', [...])
|
|
# we also store the info under name 'Other@lineno', this is needed
|
|
# because classes are (de)serialized using their actual fullname, not
|
|
# the name of l.h.s.
|
|
# * If this is a method level named tuple. It can leak from the method
|
|
# via assignment to self attribute and therefore needs to be serialized
|
|
# (local namespaces are not serialized).
|
|
# * If it is a base class expression. It was not stored above, since
|
|
# there is no var_name (but it still needs to be serialized
|
|
# since it is in MRO of some class).
|
|
if name != var_name or is_func_scope:
|
|
# NOTE: we skip local namespaces since they are not serialized.
|
|
self.api.add_symbol_skip_local(name, info)
|
|
return typename, info, tvar_defs
|
|
|
|
def store_namedtuple_info(
|
|
self, info: TypeInfo, name: str, call: CallExpr, is_typed: bool
|
|
) -> None:
|
|
self.api.add_symbol(name, info, call)
|
|
call.analyzed = NamedTupleExpr(info, is_typed=is_typed)
|
|
call.analyzed.set_line(call)
|
|
|
|
def parse_namedtuple_args(
|
|
self, call: CallExpr, fullname: str
|
|
) -> None | (tuple[list[str], list[Type], list[Expression], str, list[TypeVarLikeType], bool]):
|
|
"""Parse a namedtuple() call into data needed to construct a type.
|
|
|
|
Returns a 6-tuple:
|
|
- List of argument names
|
|
- List of argument types
|
|
- List of default values
|
|
- First argument of namedtuple
|
|
- All typevars found in the field definition
|
|
- Whether all types are ready.
|
|
|
|
Return None if the definition didn't typecheck.
|
|
"""
|
|
type_name = "NamedTuple" if fullname in TYPED_NAMEDTUPLE_NAMES else "namedtuple"
|
|
# TODO: Share code with check_argument_count in checkexpr.py?
|
|
args = call.args
|
|
if len(args) < 2:
|
|
self.fail(f'Too few arguments for "{type_name}()"', call)
|
|
return None
|
|
defaults: list[Expression] = []
|
|
if len(args) > 2:
|
|
# Typed namedtuple doesn't support additional arguments.
|
|
if fullname in TYPED_NAMEDTUPLE_NAMES:
|
|
self.fail('Too many arguments for "NamedTuple()"', call)
|
|
return None
|
|
for i, arg_name in enumerate(call.arg_names[2:], 2):
|
|
if arg_name == "defaults":
|
|
arg = args[i]
|
|
# We don't care what the values are, as long as the argument is an iterable
|
|
# and we can count how many defaults there are.
|
|
if isinstance(arg, (ListExpr, TupleExpr)):
|
|
defaults = list(arg.items)
|
|
else:
|
|
self.fail(
|
|
"List or tuple literal expected as the defaults argument to "
|
|
"{}()".format(type_name),
|
|
arg,
|
|
)
|
|
break
|
|
if call.arg_kinds[:2] != [ARG_POS, ARG_POS]:
|
|
self.fail(f'Unexpected arguments to "{type_name}()"', call)
|
|
return None
|
|
if not isinstance(args[0], StrExpr):
|
|
self.fail(f'"{type_name}()" expects a string literal as the first argument', call)
|
|
return None
|
|
typename = args[0].value
|
|
types: list[Type] = []
|
|
tvar_defs = []
|
|
if not isinstance(args[1], (ListExpr, TupleExpr)):
|
|
if fullname == "collections.namedtuple" and isinstance(args[1], StrExpr):
|
|
str_expr = args[1]
|
|
items = str_expr.value.replace(",", " ").split()
|
|
else:
|
|
self.fail(
|
|
'List or tuple literal expected as the second argument to "{}()"'.format(
|
|
type_name
|
|
),
|
|
call,
|
|
)
|
|
return None
|
|
else:
|
|
listexpr = args[1]
|
|
if fullname == "collections.namedtuple":
|
|
# The fields argument contains just names, with implicit Any types.
|
|
if not is_StrExpr_list(listexpr.items):
|
|
self.fail('String literal expected as "namedtuple()" item', call)
|
|
return None
|
|
items = [item.value for item in listexpr.items]
|
|
else:
|
|
type_exprs = [
|
|
t.items[1]
|
|
for t in listexpr.items
|
|
if isinstance(t, TupleExpr) and len(t.items) == 2
|
|
]
|
|
tvar_defs = self.api.get_and_bind_all_tvars(type_exprs)
|
|
# The fields argument contains (name, type) tuples.
|
|
result = self.parse_namedtuple_fields_with_types(listexpr.items, call)
|
|
if result is None:
|
|
# One of the types is not ready, defer.
|
|
return None
|
|
items, types, _, ok = result
|
|
if not ok:
|
|
return [], [], [], typename, [], False
|
|
if not types:
|
|
types = [AnyType(TypeOfAny.unannotated) for _ in items]
|
|
underscore = [item for item in items if item.startswith("_")]
|
|
if underscore:
|
|
self.fail(
|
|
f'"{type_name}()" field names cannot start with an underscore: '
|
|
+ ", ".join(underscore),
|
|
call,
|
|
)
|
|
if len(defaults) > len(items):
|
|
self.fail(f'Too many defaults given in call to "{type_name}()"', call)
|
|
defaults = defaults[: len(items)]
|
|
return items, types, defaults, typename, tvar_defs, True
|
|
|
|
def parse_namedtuple_fields_with_types(
|
|
self, nodes: list[Expression], context: Context
|
|
) -> tuple[list[str], list[Type], list[Expression], bool] | None:
|
|
"""Parse typed named tuple fields.
|
|
|
|
Return (names, types, defaults, whether types are all ready), or None if error occurred.
|
|
"""
|
|
items: list[str] = []
|
|
types: list[Type] = []
|
|
for item in nodes:
|
|
if isinstance(item, TupleExpr):
|
|
if len(item.items) != 2:
|
|
self.fail('Invalid "NamedTuple()" field definition', item)
|
|
return None
|
|
name, type_node = item.items
|
|
if isinstance(name, StrExpr):
|
|
items.append(name.value)
|
|
else:
|
|
self.fail('Invalid "NamedTuple()" field name', item)
|
|
return None
|
|
try:
|
|
type = expr_to_unanalyzed_type(type_node, self.options, self.api.is_stub_file)
|
|
except TypeTranslationError:
|
|
self.fail("Invalid field type", type_node)
|
|
return None
|
|
# We never allow recursive types at function scope.
|
|
analyzed = self.api.anal_type(
|
|
type,
|
|
allow_placeholder=not self.options.disable_recursive_aliases
|
|
and not self.api.is_func_scope(),
|
|
prohibit_self_type="NamedTuple item type",
|
|
)
|
|
# Workaround #4987 and avoid introducing a bogus UnboundType
|
|
if isinstance(analyzed, UnboundType):
|
|
analyzed = AnyType(TypeOfAny.from_error)
|
|
# These should be all known, otherwise we would defer in visit_assignment_stmt().
|
|
if analyzed is None:
|
|
return [], [], [], False
|
|
types.append(analyzed)
|
|
else:
|
|
self.fail('Tuple expected as "NamedTuple()" field', item)
|
|
return None
|
|
return items, types, [], True
|
|
|
|
def build_namedtuple_typeinfo(
|
|
self,
|
|
name: str,
|
|
items: list[str],
|
|
types: list[Type],
|
|
default_items: Mapping[str, Expression],
|
|
line: int,
|
|
existing_info: TypeInfo | None,
|
|
) -> TypeInfo:
|
|
strtype = self.api.named_type("builtins.str")
|
|
implicit_any = AnyType(TypeOfAny.special_form)
|
|
basetuple_type = self.api.named_type("builtins.tuple", [implicit_any])
|
|
dictype = self.api.named_type("builtins.dict", [strtype, implicit_any])
|
|
# Actual signature should return OrderedDict[str, Union[types]]
|
|
ordereddictype = self.api.named_type("builtins.dict", [strtype, implicit_any])
|
|
fallback = self.api.named_type("builtins.tuple", [implicit_any])
|
|
# Note: actual signature should accept an invariant version of Iterable[UnionType[types]].
|
|
# but it can't be expressed. 'new' and 'len' should be callable types.
|
|
iterable_type = self.api.named_type_or_none("typing.Iterable", [implicit_any])
|
|
function_type = self.api.named_type("builtins.function")
|
|
|
|
literals: list[Type] = [LiteralType(item, strtype) for item in items]
|
|
match_args_type = TupleType(literals, basetuple_type)
|
|
|
|
info = existing_info or self.api.basic_new_typeinfo(name, fallback, line)
|
|
info.is_named_tuple = True
|
|
tuple_base = TupleType(types, fallback)
|
|
if info.special_alias and has_placeholder(info.special_alias.target):
|
|
self.api.process_placeholder(
|
|
None, "NamedTuple item", info, force_progress=tuple_base != info.tuple_type
|
|
)
|
|
info.update_tuple_type(tuple_base)
|
|
info.line = line
|
|
# For use by mypyc.
|
|
info.metadata["namedtuple"] = {"fields": items.copy()}
|
|
|
|
# We can't calculate the complete fallback type until after semantic
|
|
# analysis, since otherwise base classes might be incomplete. Postpone a
|
|
# callback function that patches the fallback.
|
|
if not has_placeholder(tuple_base) and not has_type_vars(tuple_base):
|
|
self.api.schedule_patch(
|
|
PRIORITY_FALLBACKS, lambda: calculate_tuple_fallback(tuple_base)
|
|
)
|
|
|
|
def add_field(
|
|
var: Var, is_initialized_in_class: bool = False, is_property: bool = False
|
|
) -> None:
|
|
var.info = info
|
|
var.is_initialized_in_class = is_initialized_in_class
|
|
var.is_property = is_property
|
|
var._fullname = f"{info.fullname}.{var.name}"
|
|
info.names[var.name] = SymbolTableNode(MDEF, var)
|
|
|
|
fields = [Var(item, typ) for item, typ in zip(items, types)]
|
|
for var in fields:
|
|
add_field(var, is_property=True)
|
|
# We can't share Vars between fields and method arguments, since they
|
|
# have different full names (the latter are normally used as local variables
|
|
# in functions, so their full names are set to short names when generated methods
|
|
# are analyzed).
|
|
vars = [Var(item, typ) for item, typ in zip(items, types)]
|
|
|
|
tuple_of_strings = TupleType([strtype for _ in items], basetuple_type)
|
|
add_field(Var("_fields", tuple_of_strings), is_initialized_in_class=True)
|
|
add_field(Var("_field_types", dictype), is_initialized_in_class=True)
|
|
add_field(Var("_field_defaults", dictype), is_initialized_in_class=True)
|
|
add_field(Var("_source", strtype), is_initialized_in_class=True)
|
|
add_field(Var("__annotations__", ordereddictype), is_initialized_in_class=True)
|
|
add_field(Var("__doc__", strtype), is_initialized_in_class=True)
|
|
if self.options.python_version >= (3, 10):
|
|
add_field(Var("__match_args__", match_args_type), is_initialized_in_class=True)
|
|
|
|
assert info.tuple_type is not None # Set by update_tuple_type() above.
|
|
tvd = TypeVarType(
|
|
name=SELF_TVAR_NAME,
|
|
fullname=info.fullname + "." + SELF_TVAR_NAME,
|
|
id=self.api.tvar_scope.new_unique_func_id(),
|
|
values=[],
|
|
upper_bound=info.tuple_type,
|
|
default=AnyType(TypeOfAny.from_omitted_generics),
|
|
)
|
|
selftype = tvd
|
|
|
|
def add_method(
|
|
funcname: str,
|
|
ret: Type,
|
|
args: list[Argument],
|
|
is_classmethod: bool = False,
|
|
is_new: bool = False,
|
|
) -> None:
|
|
if is_classmethod or is_new:
|
|
first = [Argument(Var("_cls"), TypeType.make_normalized(selftype), None, ARG_POS)]
|
|
else:
|
|
first = [Argument(Var("_self"), selftype, None, ARG_POS)]
|
|
args = first + args
|
|
|
|
types = [arg.type_annotation for arg in args]
|
|
items = [arg.variable.name for arg in args]
|
|
arg_kinds = [arg.kind for arg in args]
|
|
assert None not in types
|
|
signature = CallableType(cast(List[Type], types), arg_kinds, items, ret, function_type)
|
|
signature.variables = [tvd]
|
|
func = FuncDef(funcname, args, Block([]))
|
|
func.info = info
|
|
func.is_class = is_classmethod
|
|
func.type = set_callable_name(signature, func)
|
|
func._fullname = info.fullname + "." + funcname
|
|
func.line = line
|
|
if is_classmethod:
|
|
v = Var(funcname, func.type)
|
|
v.is_classmethod = True
|
|
v.info = info
|
|
v._fullname = func._fullname
|
|
func.is_decorated = True
|
|
dec = Decorator(func, [NameExpr("classmethod")], v)
|
|
dec.line = line
|
|
sym = SymbolTableNode(MDEF, dec)
|
|
else:
|
|
sym = SymbolTableNode(MDEF, func)
|
|
sym.plugin_generated = True
|
|
info.names[funcname] = sym
|
|
|
|
add_method(
|
|
"_replace",
|
|
ret=selftype,
|
|
args=[Argument(var, var.type, EllipsisExpr(), ARG_NAMED_OPT) for var in vars],
|
|
)
|
|
|
|
def make_init_arg(var: Var) -> Argument:
|
|
default = default_items.get(var.name, None)
|
|
kind = ARG_POS if default is None else ARG_OPT
|
|
return Argument(var, var.type, default, kind)
|
|
|
|
add_method("__new__", ret=selftype, args=[make_init_arg(var) for var in vars], is_new=True)
|
|
add_method("_asdict", args=[], ret=ordereddictype)
|
|
add_method(
|
|
"_make",
|
|
ret=selftype,
|
|
is_classmethod=True,
|
|
args=[Argument(Var("iterable", iterable_type), iterable_type, None, ARG_POS)],
|
|
)
|
|
|
|
self_tvar_expr = TypeVarExpr(
|
|
SELF_TVAR_NAME,
|
|
info.fullname + "." + SELF_TVAR_NAME,
|
|
[],
|
|
info.tuple_type,
|
|
AnyType(TypeOfAny.from_omitted_generics),
|
|
)
|
|
info.names[SELF_TVAR_NAME] = SymbolTableNode(MDEF, self_tvar_expr)
|
|
return info
|
|
|
|
@contextmanager
|
|
def save_namedtuple_body(self, named_tuple_info: TypeInfo) -> Iterator[None]:
|
|
"""Preserve the generated body of class-based named tuple and then restore it.
|
|
|
|
Temporarily clear the names dict so we don't get errors about duplicate names
|
|
that were already set in build_namedtuple_typeinfo (we already added the tuple
|
|
field names while generating the TypeInfo, and actual duplicates are
|
|
already reported).
|
|
"""
|
|
nt_names = named_tuple_info.names
|
|
named_tuple_info.names = SymbolTable()
|
|
|
|
yield
|
|
|
|
# Make sure we didn't use illegal names, then reset the names in the typeinfo.
|
|
for prohibited in NAMEDTUPLE_PROHIBITED_NAMES:
|
|
if prohibited in named_tuple_info.names:
|
|
if nt_names.get(prohibited) is named_tuple_info.names[prohibited]:
|
|
continue
|
|
ctx = named_tuple_info.names[prohibited].node
|
|
assert ctx is not None
|
|
self.fail(f'Cannot overwrite NamedTuple attribute "{prohibited}"', ctx)
|
|
|
|
# Restore the names in the original symbol table. This ensures that the symbol
|
|
# table contains the field objects created by build_namedtuple_typeinfo. Exclude
|
|
# __doc__, which can legally be overwritten by the class.
|
|
for key, value in nt_names.items():
|
|
if key in named_tuple_info.names:
|
|
if key == "__doc__":
|
|
continue
|
|
sym = named_tuple_info.names[key]
|
|
if isinstance(sym.node, (FuncBase, Decorator)) and not sym.plugin_generated:
|
|
# Keep user-defined methods as is.
|
|
continue
|
|
# Keep existing (user-provided) definitions under mangled names, so they
|
|
# get semantically analyzed.
|
|
r_key = get_unique_redefinition_name(key, named_tuple_info.names)
|
|
named_tuple_info.names[r_key] = sym
|
|
named_tuple_info.names[key] = value
|
|
|
|
# Helpers
|
|
|
|
def fail(self, msg: str, ctx: Context) -> None:
|
|
self.api.fail(msg, ctx)
|