|
107 | 107 | ('is_upper_bound', bool), # False => precise type
|
108 | 108 | ])
|
109 | 109 |
|
| 110 | +# Keeps track of partial types in a single scope. In fine-grained incremental |
| 111 | +# mode partial types initially defined at the top level cannot be completed in |
| 112 | +# a function, and we use the 'is_function' attribute to enforce this. |
| 113 | +PartialTypeScope = NamedTuple('PartialTypeScope', [('map', Dict[Var, Context]), |
| 114 | + ('is_function', bool)]) |
| 115 | + |
110 | 116 |
|
111 | 117 | class TypeChecker(NodeVisitor[None], CheckerPluginInterface):
|
112 | 118 | """Mypy type checker.
|
@@ -136,7 +142,7 @@ class TypeChecker(NodeVisitor[None], CheckerPluginInterface):
|
136 | 142 | # Flags; true for dynamically typed functions
|
137 | 143 | dynamic_funcs = None # type: List[bool]
|
138 | 144 | # Stack of collections of variables with partial types
|
139 |
| - partial_types = None # type: List[Dict[Var, Context]] |
| 145 | + partial_types = None # type: List[PartialTypeScope] |
140 | 146 | # Vars for which partial type errors are already reported
|
141 | 147 | # (to avoid logically duplicate errors with different error context).
|
142 | 148 | partial_reported = None # type: Set[Var]
|
@@ -632,7 +638,7 @@ def check_func_item(self, defn: FuncItem,
|
632 | 638 | self.dynamic_funcs.append(defn.is_dynamic() and not type_override)
|
633 | 639 |
|
634 | 640 | with self.errors.enter_function(fdef.name()) if fdef else nothing():
|
635 |
| - with self.enter_partial_types(): |
| 641 | + with self.enter_partial_types(is_function=True): |
636 | 642 | typ = self.function_type(defn)
|
637 | 643 | if type_override:
|
638 | 644 | typ = type_override.copy_modified(line=typ.line, column=typ.column)
|
@@ -1244,7 +1250,7 @@ def visit_class_def(self, defn: ClassDef) -> None:
|
1244 | 1250 | typ = defn.info
|
1245 | 1251 | if typ.is_protocol and typ.defn.type_vars:
|
1246 | 1252 | self.check_protocol_variance(defn)
|
1247 |
| - with self.errors.enter_type(defn.name), self.enter_partial_types(): |
| 1253 | + with self.errors.enter_type(defn.name), self.enter_partial_types(is_class=True): |
1248 | 1254 | old_binder = self.binder
|
1249 | 1255 | self.binder = ConditionalTypeBinder()
|
1250 | 1256 | with self.binder.top_frame_context():
|
@@ -1487,7 +1493,7 @@ def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type
|
1487 | 1493 | lvalue_type.item.type.is_protocol)):
|
1488 | 1494 | self.msg.concrete_only_assign(lvalue_type, rvalue)
|
1489 | 1495 | return
|
1490 |
| - if rvalue_type and infer_lvalue_type: |
| 1496 | + if rvalue_type and infer_lvalue_type and not isinstance(lvalue_type, PartialType): |
1491 | 1497 | self.binder.assign_type(lvalue, rvalue_type, lvalue_type, False)
|
1492 | 1498 | elif index_lvalue:
|
1493 | 1499 | self.check_indexed_assignment(index_lvalue, rvalue, lvalue)
|
@@ -1996,7 +2002,7 @@ def infer_partial_type(self, name: Var, lvalue: Lvalue, init_type: Type) -> bool
|
1996 | 2002 | else:
|
1997 | 2003 | return False
|
1998 | 2004 | self.set_inferred_type(name, lvalue, partial_type)
|
1999 |
| - self.partial_types[-1][name] = lvalue |
| 2005 | + self.partial_types[-1].map[name] = lvalue |
2000 | 2006 | return True
|
2001 | 2007 |
|
2002 | 2008 | def set_inferred_type(self, var: Var, lvalue: Lvalue, type: Type) -> None:
|
@@ -3111,33 +3117,99 @@ def lookup_qualified(self, name: str) -> SymbolTableNode:
|
3111 | 3117 | raise KeyError(msg.format(last, name))
|
3112 | 3118 |
|
3113 | 3119 | @contextmanager
|
3114 |
| - def enter_partial_types(self) -> Iterator[None]: |
| 3120 | + def enter_partial_types(self, *, is_function: bool = False, |
| 3121 | + is_class: bool = False) -> Iterator[None]: |
3115 | 3122 | """Enter a new scope for collecting partial types.
|
3116 | 3123 |
|
3117 |
| - Also report errors for variables which still have partial |
| 3124 | + Also report errors for (some) variables which still have partial |
3118 | 3125 | types, i.e. we couldn't infer a complete type.
|
3119 | 3126 | """
|
3120 |
| - self.partial_types.append({}) |
| 3127 | + self.partial_types.append(PartialTypeScope({}, is_function)) |
3121 | 3128 | yield
|
3122 | 3129 |
|
3123 |
| - partial_types = self.partial_types.pop() |
| 3130 | + partial_types, _ = self.partial_types.pop() |
3124 | 3131 | if not self.current_node_deferred:
|
3125 | 3132 | for var, context in partial_types.items():
|
3126 |
| - if isinstance(var.type, PartialType) and var.type.type is None: |
3127 |
| - # None partial type: assume variable is intended to have type None |
| 3133 | + # If we require local partial types, there are a few exceptions where |
| 3134 | + # we fall back to inferring just "None" as the type from a None initaliazer: |
| 3135 | + # |
| 3136 | + # 1. If all happens within a single function this is acceptable, since only |
| 3137 | + # the topmost function is a separate target in fine-grained incremental mode. |
| 3138 | + # We primarily want to avoid "splitting" partial types across targets. |
| 3139 | + # |
| 3140 | + # 2. A None initializer in the class body if the attribute is defined in a base |
| 3141 | + # class is fine, since the attribute is already defined and it's currently okay |
| 3142 | + # to vary the type of an attribute covariantly. The None type will still be |
| 3143 | + # checked for compatibility with base classes elsewhere. Without this exception |
| 3144 | + # mypy could require an annotation for an attribute that already has been |
| 3145 | + # declared in a base class, which would be bad. |
| 3146 | + allow_none = (not self.options.local_partial_types |
| 3147 | + or is_function |
| 3148 | + or (is_class and self.is_defined_in_base_class(var))) |
| 3149 | + if (allow_none |
| 3150 | + and isinstance(var.type, PartialType) |
| 3151 | + and var.type.type is None): |
3128 | 3152 | var.type = NoneTyp()
|
3129 | 3153 | else:
|
3130 | 3154 | if var not in self.partial_reported:
|
3131 | 3155 | self.msg.need_annotation_for_var(var, context)
|
3132 | 3156 | self.partial_reported.add(var)
|
| 3157 | + # Give the variable an 'Any' type to avoid generating multiple errors |
| 3158 | + # from a single missing annotation. |
3133 | 3159 | var.type = AnyType(TypeOfAny.from_error)
|
3134 | 3160 |
|
| 3161 | + def is_defined_in_base_class(self, var: Var) -> bool: |
| 3162 | + if var.info is not None: |
| 3163 | + for base in var.info.mro[1:]: |
| 3164 | + if base.get(var.name()) is not None: |
| 3165 | + return True |
| 3166 | + if var.info.fallback_to_any: |
| 3167 | + return True |
| 3168 | + return False |
| 3169 | + |
3135 | 3170 | def find_partial_types(self, var: Var) -> Optional[Dict[Var, Context]]:
|
3136 |
| - for partial_types in reversed(self.partial_types): |
3137 |
| - if var in partial_types: |
3138 |
| - return partial_types |
| 3171 | + """Look for an active partial type scope containing variable. |
| 3172 | +
|
| 3173 | + A scope is active if assignments in the current context can refine a partial |
| 3174 | + type originally defined in the scope. This is affected by the local_partial_types |
| 3175 | + configuration option. |
| 3176 | + """ |
| 3177 | + in_scope, partial_types = self.find_partial_types_in_all_scopes(var) |
| 3178 | + if in_scope: |
| 3179 | + return partial_types |
3139 | 3180 | return None
|
3140 | 3181 |
|
| 3182 | + def find_partial_types_in_all_scopes(self, var: Var) -> Tuple[bool, |
| 3183 | + Optional[Dict[Var, Context]]]: |
| 3184 | + """Look for partial type scope containing variable. |
| 3185 | +
|
| 3186 | + Return tuple (is the scope active, scope). |
| 3187 | + """ |
| 3188 | + active = self.partial_types |
| 3189 | + inactive = [] # type: List[PartialTypeScope] |
| 3190 | + if self.options.local_partial_types: |
| 3191 | + # All scopes within the outermost function are active. Scopes out of |
| 3192 | + # the outermost function are inactive to allow local reasoning (important |
| 3193 | + # for fine-grained incremental mode). |
| 3194 | + for i, t in enumerate(self.partial_types): |
| 3195 | + if t.is_function: |
| 3196 | + active = self.partial_types[i:] |
| 3197 | + inactive = self.partial_types[:i] |
| 3198 | + break |
| 3199 | + else: |
| 3200 | + # Not within a function -- only the innermost scope is in scope. |
| 3201 | + active = self.partial_types[-1:] |
| 3202 | + inactive = self.partial_types[:-1] |
| 3203 | + # First look within in-scope partial types. |
| 3204 | + for scope in reversed(active): |
| 3205 | + if var in scope.map: |
| 3206 | + return True, scope.map |
| 3207 | + # Then for out-of-scope partial types. |
| 3208 | + for scope in reversed(inactive): |
| 3209 | + if var in scope.map: |
| 3210 | + return False, scope.map |
| 3211 | + return False, None |
| 3212 | + |
3141 | 3213 | def temp_node(self, t: Type, context: Optional[Context] = None) -> TempNode:
|
3142 | 3214 | """Create a temporary node with the given, fixed type."""
|
3143 | 3215 | temp = TempNode(t)
|
|
0 commit comments