def _run_and_apply_changes(cls, kwargs, autofix=False): changes = collections.defaultdict(list) with qcore.override(cls, "_changes_for_fixer", changes): try: had_failure = cls._run(**kwargs) except VisitorError: had_failure = True # ignore run_fixer if autofix is enabled if autofix: cls._apply_changes(changes) else: patches = [] for filename in changes: for change in changes[filename]: linenos = sorted(change.linenos_to_delete) additions = change.lines_to_add if len(linenos) > 1: start_lineno, end_lineno = linenos[0], linenos[-1] else: start_lineno, end_lineno = linenos[0], linenos[0] patches.append( _PatchWithDescription( start_lineno - 1, end_lineno, new_lines=additions, path=filename, description=change.error_str, )) if patches: # poor man's version of https://github.com/facebook/codemod/pull/113 with qcore.override(builtins, "print", _flushing_print): codemod.run_interactive(_Query(patches)) return had_failure
def visit(self, node): """Save the node if it is a statement.""" # This code is also inlined in NameCheckVisitor (a subclass of this class) for speed. if isinstance(node, ast.stmt): with qcore.override(self, "current_statement", node): return super(ReplacingNodeVisitor, self).visit(node) else: return super(ReplacingNodeVisitor, self).visit(node)
def test_override(): class TestObject(object): def __init__(self): self.v = None o = TestObject() o.v = "a" with qcore.override(o, "v", "b"): assert_eq(o.v, "b") try: with qcore.override(o, "v", "c"): assert_eq(o.v, "c") raise NotImplementedError() except NotImplementedError: pass assert_eq(o.v, "b") assert_eq(o.v, "a")
def set_func_name( self, name, async_kind=AsyncFunctionKind.non_async, is_classmethod=False ): """Sets the current function name for async data collection.""" # Override current_func_name only if this is the outermost function, so that data access # within nested functions is attributed to the outer function. However, for async inner # functions, check batching within the function separately. with qcore.override(self, "current_async_kind", async_kind), qcore.override( self, "is_classmethod", is_classmethod ): if ( self.current_func_name is None or async_kind != AsyncFunctionKind.non_async ): with qcore.override(self, "current_func_name", name): yield else: yield
def test_override(): class TestObject(object): def __init__(self): self.v = None o = TestObject() o.v = 'a' with qcore.override(o, 'v', 'b'): assert_eq(o.v, 'b') try: with qcore.override(o, 'v', 'c'): assert_eq(o.v, 'c') raise NotImplementedError() except NotImplementedError: pass assert_eq(o.v, 'b') assert_eq(o.v, 'a')
def loop_scope(self): loop_scopes = [] with self.subscope() as main_scope: loop_scopes.append(main_scope) with qcore.override(self, "current_loop_scopes", loop_scopes): yield self.combine_subscopes([{ name: values for name, values in scope.items() if name != LEAVES_LOOP } for scope in loop_scopes])
def check_for_test(self, apply_changes=False): """Entry point for testing. Also applies all changes if apply_changes is on.""" if not apply_changes: return self.check() changes = collections.defaultdict(list) with qcore.override(self.__class__, "_changes_for_fixer", changes): result = self.check() lines = [line + "\n" for line in self.contents.splitlines()] if self.filename in changes: lines = self._apply_changes_to_lines(changes[self.filename], lines) return result, "".join(lines)
def with_implementation(fn: object, implementation_fn: Impl) -> Iterable[None]: """Temporarily sets the implementation of fn to be implementation_fn. This is useful for invoking test_scope to aggregate all calls to a particular function. For example, the following can be used to find the names of all scribe categories we log to: categories = set() def _scribe_log_impl(variables, visitor, node): if isinstance(variables['category'], pyanalyze.value.KnownValue): categories.add(variables['category'].val) with pyanalyze.arg_spec.with_implementation(qclient.scribe.log, _scribe_log_impl): test_scope.test_all() print(categories) """ if fn in ArgSpecCache.DEFAULT_ARGSPECS: with qcore.override(ArgSpecCache.DEFAULT_ARGSPECS[fn], "impl", implementation_fn): yield else: argspec = ArgSpecCache(Config()).get_argspec(fn, impl=implementation_fn) if argspec is None: # builtin or something, just use a generic argspec argspec = Signature.make( [ SigParameter("args", SigParameter.VAR_POSITIONAL), SigParameter("kwargs", SigParameter.VAR_KEYWORD), ], callable=fn, impl=implementation_fn, ) known_argspecs = dict(ArgSpecCache.DEFAULT_ARGSPECS) known_argspecs[fn] = argspec with qcore.override(ArgSpecCache, "DEFAULT_ARGSPECS", known_argspecs): yield
def subscope(self): """Create a new subscope, to be used for conditional branches.""" # Ignore LEAVES_SCOPE if it's already there, so that we type check code after the # assert False correctly. Without this, test_after_assert_false fails. new_name_to_nodes = defaultdict( list, { key: value for key, value in self.name_to_current_definition_nodes.items( ) if key != LEAVES_SCOPE }, ) with qcore.override(self, "name_to_current_definition_nodes", new_name_to_nodes): yield new_name_to_nodes
def check_yield(self, node, current_statement): assert current_statement is not None if self.visitor.async_kind == AsyncFunctionKind.normal: self._check_for_duplicate_yields(node, self.visitor.current_statement) in_non_async_yield = self.visitor.async_kind == AsyncFunctionKind.non_async with qcore.override(self, "in_non_async_yield", in_non_async_yield): yield if self.visitor.async_kind == AsyncFunctionKind.normal: self._maybe_show_unnecessary_yield_error(node, current_statement) if self.last_yield_in_aug_assign: self._maybe_show_unnecessary_yield_error(node, current_statement) self.last_yield_in_aug_assign = self._is_augassign_target(node) self.variables_from_yield_result = {} self.previous_yield = node self.statement_for_previous_yield = current_statement
def check_yield_result_assignment(self, in_yield): return qcore.override(self, "in_yield_result_assignment", in_yield)
def _has_import_star_usage(self, module, attr): with qcore.override(self, "_recursive_stack", set()): return self._has_import_star_usage_inner(module, attr)
def catch_errors(self): caught_errors = [] with qcore.override(self, "caught_errors", caught_errors): yield caught_errors
def _type_from_runtime(val, ctx): if isinstance(val, str): return _eval_forward_ref(val, ctx) elif isinstance(val, tuple): # This happens under some Python versions for types # nested in tuples, e.g. on 3.6: # > typing_inspect.get_args(Union[Set[int], List[str]]) # ((typing.Set, int), (typing.List, str)) origin = val[0] if len(val) == 2: args = (val[1], ) else: args = val[1:] return _value_of_origin_args(origin, args, val, ctx) elif typing_inspect.is_literal_type(val): args = typing_inspect.get_args(val) if len(args) == 0: return KnownValue(args[0]) else: return unite_values(*[KnownValue(arg) for arg in args]) elif typing_inspect.is_union_type(val): args = typing_inspect.get_args(val) return unite_values(*[_type_from_runtime(arg, ctx) for arg in args]) elif typing_inspect.is_tuple_type(val): args = typing_inspect.get_args(val) if not args: return TypedValue(tuple) elif len(args) == 2 and args[1] is Ellipsis: return GenericValue(tuple, [_type_from_runtime(args[0], ctx)]) else: args_vals = [_type_from_runtime(arg, ctx) for arg in args] return SequenceIncompleteValue(tuple, args_vals) elif is_instance_of_typing_name(val, "_TypedDictMeta"): return TypedDictValue({ key: _type_from_runtime(value, ctx) for key, value in val.__annotations__.items() }) elif typing_inspect.is_callable_type(val): return TypedValue(Callable) elif typing_inspect.is_generic_type(val): origin = typing_inspect.get_origin(val) args = typing_inspect.get_args(val) return _value_of_origin_args(origin, args, val, ctx) elif GenericAlias is not None and isinstance(val, GenericAlias): origin = get_origin(val) args = get_args(val) return GenericValue(origin, [_type_from_runtime(arg, ctx) for arg in args]) elif isinstance(val, type): if val is type(None): return KnownValue(None) return TypedValue(val) elif val is None: return KnownValue(None) elif is_typing_name(val, "NoReturn"): return NO_RETURN_VALUE elif val is typing.Any: return UNRESOLVED_VALUE elif hasattr(val, "__supertype__"): if isinstance(val.__supertype__, type): # NewType return NewTypeValue(val) elif typing_inspect.is_tuple_type(val.__supertype__): # TODO figure out how to make NewTypes over tuples work return UNRESOLVED_VALUE else: ctx.show_error("Invalid NewType %s" % (val, )) return UNRESOLVED_VALUE elif typing_inspect.is_typevar(val): # TypeVar; not supported yet return UNRESOLVED_VALUE elif typing_inspect.is_classvar(val): return UNRESOLVED_VALUE elif is_instance_of_typing_name( val, "_ForwardRef") or is_instance_of_typing_name( val, "ForwardRef"): # This has issues because the forward ref may be defined in a different file, in # which case we don't know which names are valid in it. with qcore.override(ctx, "suppress_undefined_name", True): return UNRESOLVED_VALUE elif val is Ellipsis: # valid in Callable[..., ] return UNRESOLVED_VALUE elif is_instance_of_typing_name(val, "_TypeAlias"): # typing.Pattern and Match, which are not normal generic types for some reason return GenericValue(val.impl_type, [_type_from_runtime(val.type_var, ctx)]) else: origin = get_origin(val) if origin is not None: return TypedValue(origin) ctx.show_error("Invalid type annotation %s" % (val, )) return UNRESOLVED_VALUE
def check_yield_result_assignment(self, in_yield: bool) -> ContextManager[None]: return qcore.override(self, "in_yield_result_assignment", in_yield)