| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186 |
- """Calculate some properties of classes.
- These happen after semantic analysis and before type checking.
- """
- from __future__ import annotations
- from typing import Final
- from mypy.errors import Errors
- from mypy.nodes import (
- IMPLICITLY_ABSTRACT,
- IS_ABSTRACT,
- CallExpr,
- Decorator,
- FuncDef,
- Node,
- OverloadedFuncDef,
- PromoteExpr,
- SymbolTable,
- TypeInfo,
- Var,
- )
- from mypy.options import Options
- from mypy.types import MYPYC_NATIVE_INT_NAMES, Instance, ProperType
- # Hard coded type promotions (shared between all Python versions).
- # These add extra ad-hoc edges to the subtyping relation. For example,
- # int is considered a subtype of float, even though there is no
- # subclass relationship.
- # Note that the bytearray -> bytes promotion is a little unsafe
- # as some functions only accept bytes objects. Here convenience
- # trumps safety.
- TYPE_PROMOTIONS: Final = {
- "builtins.int": "float",
- "builtins.float": "complex",
- "builtins.bytearray": "bytes",
- "builtins.memoryview": "bytes",
- }
- def calculate_class_abstract_status(typ: TypeInfo, is_stub_file: bool, errors: Errors) -> None:
- """Calculate abstract status of a class.
- Set is_abstract of the type to True if the type has an unimplemented
- abstract attribute. Also compute a list of abstract attributes.
- Report error is required ABCMeta metaclass is missing.
- """
- if typ.typeddict_type:
- return # TypedDict can't be abstract
- concrete: set[str] = set()
- # List of abstract attributes together with their abstract status
- abstract: list[tuple[str, int]] = []
- abstract_in_this_class: list[str] = []
- if typ.is_newtype:
- # Special case: NewTypes are considered as always non-abstract, so they can be used as:
- # Config = NewType('Config', Mapping[str, str])
- # default = Config({'cannot': 'modify'}) # OK
- typ.abstract_attributes = []
- return
- for base in typ.mro:
- for name, symnode in base.names.items():
- node = symnode.node
- if isinstance(node, OverloadedFuncDef):
- # Unwrap an overloaded function definition. We can just
- # check arbitrarily the first overload item. If the
- # different items have a different abstract status, there
- # should be an error reported elsewhere.
- if node.items: # can be empty for invalid overloads
- func: Node | None = node.items[0]
- else:
- func = None
- else:
- func = node
- if isinstance(func, Decorator):
- func = func.func
- if isinstance(func, FuncDef):
- if (
- func.abstract_status in (IS_ABSTRACT, IMPLICITLY_ABSTRACT)
- and name not in concrete
- ):
- typ.is_abstract = True
- abstract.append((name, func.abstract_status))
- if base is typ:
- abstract_in_this_class.append(name)
- elif isinstance(node, Var):
- if node.is_abstract_var and name not in concrete:
- typ.is_abstract = True
- abstract.append((name, IS_ABSTRACT))
- if base is typ:
- abstract_in_this_class.append(name)
- concrete.add(name)
- # In stubs, abstract classes need to be explicitly marked because it is too
- # easy to accidentally leave a concrete class abstract by forgetting to
- # implement some methods.
- typ.abstract_attributes = sorted(abstract)
- if is_stub_file:
- if typ.declared_metaclass and typ.declared_metaclass.type.has_base("abc.ABCMeta"):
- return
- if typ.is_protocol:
- return
- if abstract and not abstract_in_this_class:
- def report(message: str, severity: str) -> None:
- errors.report(typ.line, typ.column, message, severity=severity)
- attrs = ", ".join(f'"{attr}"' for attr, _ in sorted(abstract))
- report(f"Class {typ.fullname} has abstract attributes {attrs}", "error")
- report(
- "If it is meant to be abstract, add 'abc.ABCMeta' as an explicit metaclass", "note"
- )
- if typ.is_final and abstract:
- attrs = ", ".join(f'"{attr}"' for attr, _ in sorted(abstract))
- errors.report(
- typ.line, typ.column, f"Final class {typ.fullname} has abstract attributes {attrs}"
- )
- def check_protocol_status(info: TypeInfo, errors: Errors) -> None:
- """Check that all classes in MRO of a protocol are protocols"""
- if info.is_protocol:
- for type in info.bases:
- if not type.type.is_protocol and type.type.fullname != "builtins.object":
- def report(message: str, severity: str) -> None:
- errors.report(info.line, info.column, message, severity=severity)
- report("All bases of a protocol must be protocols", "error")
- def calculate_class_vars(info: TypeInfo) -> None:
- """Try to infer additional class variables.
- Subclass attribute assignments with no type annotation are assumed
- to be classvar if overriding a declared classvar from the base
- class.
- This must happen after the main semantic analysis pass, since
- this depends on base class bodies having been fully analyzed.
- """
- for name, sym in info.names.items():
- node = sym.node
- if isinstance(node, Var) and node.info and node.is_inferred and not node.is_classvar:
- for base in info.mro[1:]:
- member = base.names.get(name)
- if member is not None and isinstance(member.node, Var) and member.node.is_classvar:
- node.is_classvar = True
- def add_type_promotion(
- info: TypeInfo, module_names: SymbolTable, options: Options, builtin_names: SymbolTable
- ) -> None:
- """Setup extra, ad-hoc subtyping relationships between classes (promotion).
- This includes things like 'int' being compatible with 'float'.
- """
- defn = info.defn
- promote_targets: list[ProperType] = []
- for decorator in defn.decorators:
- if isinstance(decorator, CallExpr):
- analyzed = decorator.analyzed
- if isinstance(analyzed, PromoteExpr):
- # _promote class decorator (undocumented feature).
- promote_targets.append(analyzed.type)
- if not promote_targets:
- if defn.fullname in TYPE_PROMOTIONS:
- target_sym = module_names.get(TYPE_PROMOTIONS[defn.fullname])
- if defn.fullname == "builtins.bytearray" and options.disable_bytearray_promotion:
- target_sym = None
- elif defn.fullname == "builtins.memoryview" and options.disable_memoryview_promotion:
- target_sym = None
- # With test stubs, the target may not exist.
- if target_sym:
- target_info = target_sym.node
- assert isinstance(target_info, TypeInfo)
- promote_targets.append(Instance(target_info, []))
- # Special case the promotions between 'int' and native integer types.
- # These have promotions going both ways, such as from 'int' to 'i64'
- # and 'i64' to 'int', for convenience.
- if defn.fullname in MYPYC_NATIVE_INT_NAMES:
- int_sym = builtin_names["int"]
- assert isinstance(int_sym.node, TypeInfo)
- int_sym.node._promote.append(Instance(defn.info, []))
- defn.info.alt_promote = Instance(int_sym.node, [])
- if promote_targets:
- defn.info._promote.extend(promote_targets)
|