semanal_classprop.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. """Calculate some properties of classes.
  2. These happen after semantic analysis and before type checking.
  3. """
  4. from __future__ import annotations
  5. from typing import Final
  6. from mypy.errors import Errors
  7. from mypy.nodes import (
  8. IMPLICITLY_ABSTRACT,
  9. IS_ABSTRACT,
  10. CallExpr,
  11. Decorator,
  12. FuncDef,
  13. Node,
  14. OverloadedFuncDef,
  15. PromoteExpr,
  16. SymbolTable,
  17. TypeInfo,
  18. Var,
  19. )
  20. from mypy.options import Options
  21. from mypy.types import MYPYC_NATIVE_INT_NAMES, Instance, ProperType
  22. # Hard coded type promotions (shared between all Python versions).
  23. # These add extra ad-hoc edges to the subtyping relation. For example,
  24. # int is considered a subtype of float, even though there is no
  25. # subclass relationship.
  26. # Note that the bytearray -> bytes promotion is a little unsafe
  27. # as some functions only accept bytes objects. Here convenience
  28. # trumps safety.
  29. TYPE_PROMOTIONS: Final = {
  30. "builtins.int": "float",
  31. "builtins.float": "complex",
  32. "builtins.bytearray": "bytes",
  33. "builtins.memoryview": "bytes",
  34. }
  35. def calculate_class_abstract_status(typ: TypeInfo, is_stub_file: bool, errors: Errors) -> None:
  36. """Calculate abstract status of a class.
  37. Set is_abstract of the type to True if the type has an unimplemented
  38. abstract attribute. Also compute a list of abstract attributes.
  39. Report error is required ABCMeta metaclass is missing.
  40. """
  41. if typ.typeddict_type:
  42. return # TypedDict can't be abstract
  43. concrete: set[str] = set()
  44. # List of abstract attributes together with their abstract status
  45. abstract: list[tuple[str, int]] = []
  46. abstract_in_this_class: list[str] = []
  47. if typ.is_newtype:
  48. # Special case: NewTypes are considered as always non-abstract, so they can be used as:
  49. # Config = NewType('Config', Mapping[str, str])
  50. # default = Config({'cannot': 'modify'}) # OK
  51. typ.abstract_attributes = []
  52. return
  53. for base in typ.mro:
  54. for name, symnode in base.names.items():
  55. node = symnode.node
  56. if isinstance(node, OverloadedFuncDef):
  57. # Unwrap an overloaded function definition. We can just
  58. # check arbitrarily the first overload item. If the
  59. # different items have a different abstract status, there
  60. # should be an error reported elsewhere.
  61. if node.items: # can be empty for invalid overloads
  62. func: Node | None = node.items[0]
  63. else:
  64. func = None
  65. else:
  66. func = node
  67. if isinstance(func, Decorator):
  68. func = func.func
  69. if isinstance(func, FuncDef):
  70. if (
  71. func.abstract_status in (IS_ABSTRACT, IMPLICITLY_ABSTRACT)
  72. and name not in concrete
  73. ):
  74. typ.is_abstract = True
  75. abstract.append((name, func.abstract_status))
  76. if base is typ:
  77. abstract_in_this_class.append(name)
  78. elif isinstance(node, Var):
  79. if node.is_abstract_var and name not in concrete:
  80. typ.is_abstract = True
  81. abstract.append((name, IS_ABSTRACT))
  82. if base is typ:
  83. abstract_in_this_class.append(name)
  84. concrete.add(name)
  85. # In stubs, abstract classes need to be explicitly marked because it is too
  86. # easy to accidentally leave a concrete class abstract by forgetting to
  87. # implement some methods.
  88. typ.abstract_attributes = sorted(abstract)
  89. if is_stub_file:
  90. if typ.declared_metaclass and typ.declared_metaclass.type.has_base("abc.ABCMeta"):
  91. return
  92. if typ.is_protocol:
  93. return
  94. if abstract and not abstract_in_this_class:
  95. def report(message: str, severity: str) -> None:
  96. errors.report(typ.line, typ.column, message, severity=severity)
  97. attrs = ", ".join(f'"{attr}"' for attr, _ in sorted(abstract))
  98. report(f"Class {typ.fullname} has abstract attributes {attrs}", "error")
  99. report(
  100. "If it is meant to be abstract, add 'abc.ABCMeta' as an explicit metaclass", "note"
  101. )
  102. if typ.is_final and abstract:
  103. attrs = ", ".join(f'"{attr}"' for attr, _ in sorted(abstract))
  104. errors.report(
  105. typ.line, typ.column, f"Final class {typ.fullname} has abstract attributes {attrs}"
  106. )
  107. def check_protocol_status(info: TypeInfo, errors: Errors) -> None:
  108. """Check that all classes in MRO of a protocol are protocols"""
  109. if info.is_protocol:
  110. for type in info.bases:
  111. if not type.type.is_protocol and type.type.fullname != "builtins.object":
  112. def report(message: str, severity: str) -> None:
  113. errors.report(info.line, info.column, message, severity=severity)
  114. report("All bases of a protocol must be protocols", "error")
  115. def calculate_class_vars(info: TypeInfo) -> None:
  116. """Try to infer additional class variables.
  117. Subclass attribute assignments with no type annotation are assumed
  118. to be classvar if overriding a declared classvar from the base
  119. class.
  120. This must happen after the main semantic analysis pass, since
  121. this depends on base class bodies having been fully analyzed.
  122. """
  123. for name, sym in info.names.items():
  124. node = sym.node
  125. if isinstance(node, Var) and node.info and node.is_inferred and not node.is_classvar:
  126. for base in info.mro[1:]:
  127. member = base.names.get(name)
  128. if member is not None and isinstance(member.node, Var) and member.node.is_classvar:
  129. node.is_classvar = True
  130. def add_type_promotion(
  131. info: TypeInfo, module_names: SymbolTable, options: Options, builtin_names: SymbolTable
  132. ) -> None:
  133. """Setup extra, ad-hoc subtyping relationships between classes (promotion).
  134. This includes things like 'int' being compatible with 'float'.
  135. """
  136. defn = info.defn
  137. promote_targets: list[ProperType] = []
  138. for decorator in defn.decorators:
  139. if isinstance(decorator, CallExpr):
  140. analyzed = decorator.analyzed
  141. if isinstance(analyzed, PromoteExpr):
  142. # _promote class decorator (undocumented feature).
  143. promote_targets.append(analyzed.type)
  144. if not promote_targets:
  145. if defn.fullname in TYPE_PROMOTIONS:
  146. target_sym = module_names.get(TYPE_PROMOTIONS[defn.fullname])
  147. if defn.fullname == "builtins.bytearray" and options.disable_bytearray_promotion:
  148. target_sym = None
  149. elif defn.fullname == "builtins.memoryview" and options.disable_memoryview_promotion:
  150. target_sym = None
  151. # With test stubs, the target may not exist.
  152. if target_sym:
  153. target_info = target_sym.node
  154. assert isinstance(target_info, TypeInfo)
  155. promote_targets.append(Instance(target_info, []))
  156. # Special case the promotions between 'int' and native integer types.
  157. # These have promotions going both ways, such as from 'int' to 'i64'
  158. # and 'i64' to 'int', for convenience.
  159. if defn.fullname in MYPYC_NATIVE_INT_NAMES:
  160. int_sym = builtin_names["int"]
  161. assert isinstance(int_sym.node, TypeInfo)
  162. int_sym.node._promote.append(Instance(defn.info, []))
  163. defn.info.alt_promote = Instance(int_sym.node, [])
  164. if promote_targets:
  165. defn.info._promote.extend(promote_targets)