| 60 | |
| 61 | |
| 62 | class TypedDictAnalyzer: |
| 63 | def __init__( |
| 64 | self, options: Options, api: SemanticAnalyzerInterface, msg: MessageBuilder |
| 65 | ) -> None: |
| 66 | self.options = options |
| 67 | self.api = api |
| 68 | self.msg = msg |
| 69 | |
| 70 | def analyze_typeddict_classdef(self, defn: ClassDef) -> tuple[bool, TypeInfo | None]: |
| 71 | """Analyze a class that may define a TypedDict. |
| 72 | |
| 73 | Assume that base classes have been analyzed already. |
| 74 | |
| 75 | Note: Unlike normal classes, we won't create a TypeInfo until |
| 76 | the whole definition of the TypeDict (including the body and all |
| 77 | key names and types) is complete. This is mostly because we |
| 78 | store the corresponding TypedDictType in the TypeInfo. |
| 79 | |
| 80 | Return (is this a TypedDict, new TypeInfo). Specifics: |
| 81 | * If we couldn't finish due to incomplete reference anywhere in |
| 82 | the definition, return (True, None). |
| 83 | * If this is not a TypedDict, return (False, None). |
| 84 | """ |
| 85 | possible = False |
| 86 | for base_expr in defn.base_type_exprs: |
| 87 | if isinstance(base_expr, CallExpr): |
| 88 | base_expr = base_expr.callee |
| 89 | if isinstance(base_expr, IndexExpr): |
| 90 | base_expr = base_expr.base |
| 91 | if isinstance(base_expr, RefExpr): |
| 92 | self.api.accept(base_expr) |
| 93 | if base_expr.fullname in TPDICT_NAMES or self.is_typeddict(base_expr): |
| 94 | possible = True |
| 95 | if isinstance(base_expr.node, TypeInfo) and base_expr.node.is_final: |
| 96 | err = message_registry.CANNOT_INHERIT_FROM_FINAL |
| 97 | self.fail(err.format(base_expr.node.name).value, defn, code=err.code) |
| 98 | if not possible: |
| 99 | return False, None |
| 100 | existing_info = None |
| 101 | if isinstance(defn.analyzed, TypedDictExpr): |
| 102 | existing_info = defn.analyzed.info |
| 103 | |
| 104 | field_types: dict[str, Type] | None |
| 105 | if ( |
| 106 | len(defn.base_type_exprs) == 1 |
| 107 | and isinstance(defn.base_type_exprs[0], RefExpr) |
| 108 | and defn.base_type_exprs[0].fullname in TPDICT_NAMES |
| 109 | ): |
| 110 | # Building a new TypedDict |
| 111 | field_types, statements, required_keys, readonly_keys = ( |
| 112 | self.analyze_typeddict_classdef_fields(defn) |
| 113 | ) |
| 114 | if field_types is None: |
| 115 | return True, None # Defer |
| 116 | if self.api.is_func_scope() and "@" not in defn.name: |
| 117 | defn.name += "@" + str(defn.line) |
| 118 | info = self.build_typeddict_typeinfo( |
| 119 | defn.name, field_types, required_keys, readonly_keys, defn.line, existing_info |
no outgoing calls
no test coverage detected
searching dependent graphs…