class documentation

The Project's state: container and accessors for analyses results.

Analyses

Node ancestors

Maps each node to their parents in the syntax-tree.

Accessors: get_parent(), get_parents(), get_parent_instance(), get_enclosing_scope(), get_all_enclosing_scopes().

Imports resolution

Resolved imports are made available directly in the Imp instances.

Accessors: Imp.orgmodule, Imp.orgname, Imp.target().

Chains of definitions

Accessors: get_def(), goto_defs(), goto_def(), goto_definition(), goto_references().

Reachability

Accessor: is_reachable().

Instance variables

Accessors: get_ivars(), get_ivar()

Class MROs

Accessor: get_mro().

Function parameters for humans

Accessors: Arg.default, Arg.kind, Arg.to_parameter().

Symbolic evaluation

Accessor: literal_eval.

Basic type inference

Accessor: get_type.

Method expand_expr Resove a name expression to it's qualified name. by using information only available in the current module.
Method expand_name Resove a dottedname to it's qualified name. by using information only available in the current module.
Method get_all_enclosing_scopes Returns all scopes enclosing this definition.
Method get_all_modules Iterate over all modules in the project. This include dependency modules.
Method get_all_names Returns all names bound when wildcard importing the given module.
Method get_attribute Get attributes definitions matching the name in the scope node. It fisrt call get_local() (get_ivar() if include_ivars=True); if no locals matches the name or ignore_locals=True and the scope is a module, it calls ...
Method get_def Def-Use chains accessor: returns the Def instance of this node. All ast nodes categorized as a use or a definition have a coresponding Def instance. Use this method to access it.
Method get_defs_from_qualname Finds the definitions having the given qualname.
Method get_dunder_all Get the computed value for the __all__ variable of this module.
Method get_enclosing_scope Get the first enclosing scope of this use or deinition. Returns None only of the definition is a Module.
Method get_filename Returns the filename of the given ast node. If the node does not exist in the system, it returns None.
Method get_ivar Get the instance variable definitions of the given name in class node. Only assigments present in methods directly in the body of the class are considered here. If you want to lookup instance variables in super classes as well, pass``include_inherited=True``.
Method get_ivars Get the mapping of instance variables of the given class.
Method get_local Get the local definitions of the given name in scope node.
Method get_locals Get the mapping of locals of the given node.
Method get_location Undocumented
Method get_module Returns the module with the given name if it's in the system, else None.
Method get_mro Get an iterator on the elements of the MRO of class node.
Method get_parent Returns the direct parent of the given node in the syntax tree.
Method get_parent_instance Returns the first parent of the node in the syntax tree matching the given type info.
Method get_parent_module Get the parent package of the given module.
Method get_parents Returns all syntax tree parents of the node in the syntax tree up to the root module.
Method get_qualname Returns the qualified name of this definition. If the definition is an imported name, returns the qualified name of the the imported symbol.
Method get_root If this node is a module, returns it's Mod instance, else find the parent Module and return it's Mod instance.
Method get_sibling Should only be called for statements.
Method get_sub_module Get a sub-module of the given module.
Method get_type Infer the type of the given node or definition.
Method goto_def Use-Def chains accessor (wraps goto_defs) that returns only one Def, or raise StaticException. It returns the first reachable definition in the list. It does not ensure that the list is only composed by one element, unless ...
Method goto_definition Go to the genuine definition of this expression. This is not a simple use-def chains accessor, it's recursive. By default it follows imports but not aliases.
Method goto_defs Use-Def chains accessor: returns the definition points of the use node.
Method goto_references Finds all Name and Attribute references pointing to the given definition.
Method goto_symbol_def Simple, lazy identifier -> defs resolving.
Method is_reachable Whether the node is reachable.
Method literal_eval Powerfull ast.literal_eval() function. Does not support dicts at the moment.
Instance Variable msg Undocumented
Method _get_public_names In the absence of definition of __all__, we use this function to compute names bound when wildcard importing the given module.
Method _goto_attr_references Find attribute references.
Method _goto_references Finds all Name or Import references, it follows imports, but bot aliases.
Method _raise_node_not_in_chains Undocumented
Method _softfilter_defs Undocumented
Method _softfilter_killed_defs Undocumented
Method _softfilter_unreachable_defs Undocumented
Instance Variable _ancestors Mapping of AST nodes to the list of their parents.
Instance Variable _def_use_chains Def-Use chains.
Instance Variable _dunder_all Mapping from Mod instances explicit __all__ values or None.
Instance Variable _ivars Mapping from class instances to instance variables definitions stored as mapping for fast name based access.
Instance Variable _locals Mapping of locals.
Instance Variable _modules Mapping from module names to Mod instances.
Instance Variable _mros Mapping from class instances to their resolved MRO. A warning is logged when the MRO of a class could not be computed or is ambiguous. Qualname as strings replaces unresolved classes in the MRO.
Instance Variable _unreachable Set of unreachable nodes.
Instance Variable _use_def_chains Use-Def chains.
def expand_expr(self, node: ast.AST) -> str | None: (source)

Resove a name expression to it's qualified name. by using information only available in the current module.

Only the first name in the expression is resolved, does not recurse in attributes definitions, simply append the rest of the names at the end.

>>> p = Project()
>>> node = ast.parse('from twisted.web.template import Tag as T; T')
>>> p.add_module(node, 'test')
<Mod(name=test)>
>>> p.analyze_project()
>>> use = node.body[-1].value
>>> p.state.expand_expr(use)
'twisted.web.template.Tag'

Returns None if the name is unbound or the expression is not composed by names.

def expand_name(self, scope: Scope, name: str, is_annotation: bool = False) -> str | None: (source)

Resove a dottedname to it's qualified name. by using information only available in the current module.

Only the first name in the dottedname is resolved, does not recurse in attributes definitions, simply append the rest of the names at the end.

>>> p = Project()
>>> m = p.add_module(ast.parse('from twisted.web.template import Tag as T'), 'test')
>>> p.analyze_project()
>>> p.state.expand_name(m, 'T.something') # expand 'T' in the context of m
'twisted.web.template.Tag.something'

Returns None is the name is unbound.

def get_all_enclosing_scopes(self, definition: Def | ast.AST) -> Sequence[Scope]: (source)

Returns all scopes enclosing this definition.

def get_all_modules(self) -> Iterable[Mod]: (source)

Iterate over all modules in the project. This include dependency modules.

def get_all_names(self, mod: Mod | ast.Module) -> Collection[str]: (source)

Returns all names bound when wildcard importing the given module.

Raises
StaticExceptionIf something went wrong.
Note
If __all__ is defined, it simply returns the computed literal value. No checks is done to verify if names are actually defined.
def get_attribute(self, node: ast.ClassDef | ast.Module | Mod | Cls, name: str, *, ignore_locals: bool = False, noraise: bool = False, filter_unreachable: bool = True, include_ivars: bool = False, include_inherited: bool = True) -> Sequence[NameDef]: (source)

Get attributes definitions matching the name in the scope node. It fisrt call get_local() (get_ivar() if include_ivars=True); if no locals matches the name or ignore_locals=True and the scope is a module, it calls get_sub_module().

Note

It always filter out killed definitions.

>>> src = '''
... class A:
...    def f(self, x):
...        self.x = x
... class B(A): ...
... class C(B): ...
... '''
>>> p = Project()
>>> m = p.add_module(ast.parse(src), 't')
>>> p.analyze_project()
>>> C = m.node.body[-1]
>>> p.state.get_attribute(C, 'f')
[<Func(name=f)>]
Parameters
node:ast.ClassDef | ast.Module | Mod | ClsThe AST or Def scope.
name:strThe name of the attribute we're looking-up.
ignore_locals:boolWhether to ignore the locals, this will only lookup in sub-modules.
noraise:boolDon't raise exceptions, returns an empty list in these cases.
filter_unreachable:boolWhether to filter unreachable definitions, True by default.
include_ivars:boolWhether to include instance context definitions, False by default.
include_inherited:boolWhether to include inherited definitions, True by default.
Returns
Sequence[NameDef]Undocumented
Raises
StaticTypeErrorIf the node is not a module or a class.
StaticAttributeErrorIf the attribute is not found.
StaticExceptionOther kind of exceptions can also be raised by callees.
@overload
def get_def(self, node: ast.alias) -> Imp:
@overload
def get_def(self, node: ast.Module) -> Mod:
@overload
def get_def(self, node: ast.FunctionDef) -> Func:
@overload
def get_def(self, node: ast.AsyncFunctionDef) -> Func:
@overload
def get_def(self, node: ast.ClassDef) -> Cls:
@overload
def get_def(self, node: ast.AST, noraise: Literal[False] = False) -> Def:
@overload
def get_def(self, node: ast.AST, noraise: Literal[True]) -> Def | None:
(source)

Def-Use chains accessor: returns the Def instance of this node. All ast nodes categorized as a use or a definition have a coresponding Def instance. Use this method to access it.

Parameters
node:ast.ASTThe AST node of a definition or use.
noraise:boolDon't raise exceptions if the node is not a definition or use in the system: simply returns None in these cases.
Returns
Def | Mod | NoneUndocumented
Raises
StaticValueErrorIf the node is not a use or definition.
StaticStateIncompleteIf the node is not in the system.
def get_defs_from_qualname(self, qualname: str) -> list[NameDef]: (source)

Finds the definitions having the given qualname.

>>> p = Project()
>>> node = ast.parse('class Reactor:\n class System:\n  target = "win32"')
>>> p.add_module(node, 'test')
<Mod(name=test)>
>>> p.analyze_project()
>>> p.state.get_defs_from_qualname('test.Reactor.System.target')
[<Var(name=target)>]
def get_dunder_all(self, mod: Mod | ast.Module) -> Collection[str] | None: (source)

Get the computed value for the __all__ variable of this module.

If __all__ variable is not defined or too complex returns None.

Raises
StaticTypeErrorIf mod is actually not a module.
StaticStateIncompleteIf no information is registered for the module mod.
@overload
def get_enclosing_scope(self, definition: Mod):
@overload
def get_enclosing_scope(self, definition: Def) -> Scope | None:
@overload
def get_enclosing_scope(self, definition: ast.AST) -> Scope:
@overload
def get_enclosing_scope(self, definition: ast.Module):
(source)

Get the first enclosing scope of this use or deinition. Returns None only of the definition is a Module.

def get_filename(self, node: ast.AST | Def) -> str | None: (source)

Returns the filename of the given ast node. If the node does not exist in the system, it returns None.

def get_ivar(self, node: Cls | ast.ClassDef, name: str, *, include_inherited: bool = False): (source)

Get the instance variable definitions of the given name in class node. Only assigments present in methods directly in the body of the class are considered here. If you want to lookup instance variables in super classes as well, pass``include_inherited=True``.

Returns
List of definitions matching the provided name. An empty list will be returned if the name is not defined.
def get_ivars(self, node: ast.ClassDef | Cls, *, include_inherited: bool = False) -> Mapping[str, Sequence[NameDef]]: (source)

Get the mapping of instance variables of the given class.

>>> p = Project()
>>> m = p.add_module(ast.parse('class C:\n'
... ' def __init__(self, x):\n'
... '  self._x = x'), 'test')
>>> p.analyze_project()
>>> C, = p.state.get_local(m, 'C')
>>> ivars = [f'{v.name()!r} {p.state.get_location(v)}' for v in chain(*p.state.get_ivars(C).values())]
>>> print('\n'.join(ivars))
'_x' ast.Attribute at test:3:2
def get_local(self, node: Mod | Def | ast.AST, name: str, *, include_inherited: bool = False) -> Sequence[NameDef | None]: (source)

Get the local definitions of the given name in scope node.

Returns
Sequence[NameDef | None]List of definitions matching the provided name. An empty list will be returned if the name is not defined.
def get_locals(self, node: Mod | Def | ast.AST, *, include_inherited: bool = False) -> Mapping[str, Sequence[NameDef | None]]: (source)

Get the mapping of locals of the given node.

>>> src = '''
... class A:
...    def f(self, x):
...        self.x = x
... class B(A): ...
... class C(B): ...
... '''
>>> p = Project()
>>> m = p.add_module(ast.parse(src), 't')
>>> p.analyze_project()
>>> A = m.node.body[-3]
>>> B = m.node.body[-2]
>>> C = m.node.body[-1]
>>> dict(p.state.get_locals(A))
{'f': [<Func(name=f)>]}
>>> dict(p.state.get_locals(B))
{}
>>> dict(p.state.get_locals(B, include_inherited=True))
{'f': [<Func(name=f)>]}
>>> dict(p.state.get_locals(C, include_inherited=True))
{'f': [<Func(name=f)>]}
Raises
StaticValueErrorIf the given node cannot have locals (it's not a scope definition).
def get_location(self, node: ast.AST | Def) -> NodeLocation: (source)

Undocumented

def get_module(self, name: str) -> Mod | None: (source)

Returns the module with the given name if it's in the system, else None.

Parameters
name:strThe full dotted name of the module.
Returns
Mod | NoneUndocumented
@overload
def get_mro(self, node: Cls | ast.ClassDef, *, include_unknown: Literal[True], include_self: bool = True) -> Iterator[Cls | str]:
@overload
def get_mro(self, node: Cls | ast.ClassDef, *, include_unknown: Literal[False] = False, include_self: bool = True) -> Iterator[Cls]:
(source)

Get an iterator on the elements of the MRO of class node.

>>> src = '''
... from x import thing
... class A(thing, object):...
... class B(A): ...
... class C(B): ...
... '''
>>> p = Project()
>>> m = p.add_module(ast.parse(src), 't')
>>> p.analyze_project()
>>> C = m.node.body[-1]
>>> list(p.state.get_mro(C))
[<Cls(name=C)>, <Cls(name=B)>, <Cls(name=A)>]
>>> list(p.state.get_mro(C, include_self=False))
[<Cls(name=B)>, <Cls(name=A)>]
>>> list(p.state.get_mro(C, include_self=False, include_unknown=True))
[<Cls(name=B)>, <Cls(name=A)>, 'x.thing', 'object']
>>> list(p.state.get_mro(C, include_unknown=True))
[<Cls(name=C)>, <Cls(name=B)>, <Cls(name=A)>, 'x.thing', 'object']
def get_parent(self, node: ast.AST | Def) -> ast.AST: (source)

Returns the direct parent of the given node in the syntax tree.

Raises
StaticValueErrorIf node is a module, is has no parents.
def get_parent_instance(self, node: ast.AST | Def, cls: type[T] | tuple[type[T], ...]) -> T: (source)

Returns the first parent of the node in the syntax tree matching the given type info.

Raises
StaticValueErrorIf the the node has no parents of the requested type.
def get_parent_module(self, mod: Mod | ast.Module) -> Mod | None: (source)

Get the parent package of the given module.

Returns None if the given module is a root module or the parent package is not found in the system.

def get_parents(self, node: ast.AST | Def) -> Sequence[ast.AST]: (source)

Returns all syntax tree parents of the node in the syntax tree up to the root module.

Raises
StaticStateIncompleteIf no parents informations is available.
def get_qualname(self, definition: NameDef | Scope | ast.AST) -> str: (source)

Returns the qualified name of this definition. If the definition is an imported name, returns the qualified name of the the imported symbol.

A qualified named a name by wich a definition can be found. The same object could have several qualified named depending on where it's imported.

def get_root(self, node: ast.AST | Def) -> Mod: (source)

If this node is a module, returns it's Mod instance, else find the parent Module and return it's Mod instance.

Raises
StaticExceptionIf something is wrong.
def get_sibling(self, node: ast.stmt, direction: int = 1) -> ast.AST | None: (source)

Should only be called for statements.

def get_sub_module(self, mod: Mod | ast.Module, name: str) -> Mod | None: (source)

Get a sub-module of the given module.

def get_type(self, node: ast.AST | Def) -> Type | None: (source)

Infer the type of the given node or definition.

While basic type inference is provided, libstatic does not carry the complexity to support full-featured type-checking.

It can infer the type of any literal expression, when a name is encountered all it's potential defintions are looked up and the inferred type is derived from a union of all the possible types. There is support for attribute annotations, functions, modules or attribute access, instance variables and methods, properties and other fundamentals.

Notably missing features includes:
  • Type aliases detection
  • TypedDict and a lot more of the typing features

Limited support for some of these items might be added in the future.

@overload
def goto_def(self, node: ast.AST, noraise: Literal[False] = False) -> Def:
@overload
def goto_def(self, node: ast.AST, noraise: Literal[True]) -> Def | None:
@overload
def goto_def(self, node: ast.AST, *, raise_on_ambiguity: Literal[True]) -> Def:
(source)

Use-Def chains accessor (wraps goto_defs) that returns only one Def, or raise StaticException. It returns the first reachable definition in the list. It does not ensure that the list is only composed by one element, unless raise_on_ambiguity=True.

Parameters
node:ast.ASTUndocumented
noraise:boolDon't raise exceptions, simply returns None in these cases.
raise_on_ambiguity:boolRaise StaticAmbiguity when the use has several reachable definitions. Cannot be used with noraise=True.
Returns
Def | NoneUndocumented
Raises
StaticAmbiguityIf raise_on_ambiguity=True and the symbol definition is ambiguous.
StaticExceptionAll other exceptions raised by goto_defs.
def goto_definition(self, node: ast.AST, *, raise_on_ambiguity: bool = False, follow_aliases: bool = False, follow_imports: bool = True) -> Def: (source)

Go to the genuine definition of this expression. This is not a simple use-def chains accessor, it's recursive. By default it follows imports but not aliases.

>>> p = Project()
>>> _ = p.add_module(ast.parse('def deprecated(f):...'), 'deprecated')
>>> src1 = p.add_module(ast.parse('''\
... from deprecated import deprecated
... @deprecated
... def f():...'''), 'src1')
>>> src2 = p.add_module(ast.parse('''\
... import src1
... @src1.deprecated
... class C:...'''), 'src2')
>>> p.analyze_project()
>>> func_dec = src1.node.body[-1].decorator_list[0]
>>> p.state.goto_definition(func_dec)
<Func(name=deprecated)>
>>> cls_dec = src1.node.body[-1].decorator_list[0]
>>> p.state.goto_definition(cls_dec)
<Func(name=deprecated)>
def goto_defs(self, node: ast.AST, noraise: bool = False) -> Sequence[Def]: (source)

Use-Def chains accessor: returns the definition points of the use node.

Note

  • It does not recurse on follow-up definitions in case of aliases.
  • It does not filter unreachable definitions
  • Builtins are supported only if the builtins module has been added to the project.
Parameters
node:ast.ASTThe AST node of a use.
noraise:boolDon't raise exception if the node is unbound or not a use in the system: simply returns an empty list in these cases. By default, the returned list always have at least one element, otherwise an exception is raised.
Returns
Sequence[Def]A collection of Def instances
Raises
StaticImportErrorIf the node is an unbound import.
StaticNameErrorIf the node is unbound.
StaticValueErrorIf the node is not a use.
StaticStateIncompleteIf the node is not in the system.
def goto_references(self, definition: NameDef | ast.AST, filter_unreachable: bool = False) -> Iterator[Def]: (source)

Finds all Name and Attribute references pointing to the given definition.

>>> p = Project()
>>> dep = p.add_module(ast.parse('def deprecated(f):...'), 'deprecated')
>>> _ = p.add_module(ast.parse('''\
... from deprecated import deprecated
... @deprecated
... def f():...'''), 'src1')
>>> _ = p.add_module(ast.parse('''\
... import src1
... @src1.deprecated
... class C:...'''), 'src2')
>>> p.analyze_project()
>>> dep_func = dep.node.body[-1]
>>> list(p.state.goto_references(dep_func))
[<Def(node=<Name>)>, <Def(node=<Attribute>)>]
def goto_symbol_def(self, scope: Scope, name: str, *, is_annotation: bool = False) -> Sequence[NameDef]: (source)

Simple, lazy identifier -> defs resolving.

"Lookup" a name in the context of the provided scope, it does not use the chains Note that nonlocal and global keywords are ignored by this function.

>>> p = Project()
>>> m = p.add_module(ast.parse('from twisted.web.template import Tag as T;'), 'test')
>>> p.analyze_project()
>>> p.state.goto_symbol_def(m, 'T')
[<Imp(name=T)>]
Raises
StaticNameErrorFor unbound names.
def is_reachable(self, node: ast.AST | Def) -> bool: (source)

Whether the node is reachable.

def literal_eval(self, node: ast.AST, *, known_values: Mapping[str, Any] | None = None, raise_on_ambiguity: bool = False, follow_imports: bool = False) -> LiteralValue: (source)

Powerfull ast.literal_eval() function. Does not support dicts at the moment.

>>> p = Project()
>>> node = ast.parse('from x import x;e="bar";x+["1", 2+3, e]')
>>> node2 = ast.parse('from test import e;"best "+e')
>>> p.add_module(node, 'test')
<Mod(name=test)>
>>> p.add_module(node2, 'test2')
<Mod(name=test2)>
>>> p.analyze_project()
>>> expr = node.body[-1].value
>>> p.state.literal_eval(expr, known_values={'x':['foo']})
['foo', '1', 5, 'bar']
>>> expr2 = node2.body[-1].value
>>> p.state.literal_eval(expr2)
Traceback (most recent call last):
libstatic.exceptions.StaticUnknownValue: test2:1:17: Unkown value: test.e
>>> p.state.literal_eval(expr2, follow_imports=True)
'best bar'
def _get_public_names(self, mod: Mod | ast.Module) -> Collection[str]: (source)

In the absence of definition of __all__, we use this function to compute names bound when wildcard importing the given module.

def _goto_attr_references(self, definition: NameDef, seen: set[Def], filter_unreachable: bool) -> Iterator[Def]: (source)

Find attribute references.

Pitfalls:
  • inherited attributes access in subclasses are not considered
def _goto_references(self, definition: NameDef, seen: set[Def], filter_unreachable: bool) -> Iterator[Def]: (source)

Finds all Name or Import references, it follows imports, but bot aliases.

def _raise_node_not_in_chains(self, e: KeyError, node: ast.AST) -> NoReturn: (source)

Undocumented

def _softfilter_defs(self, defs: Sequence[Def], *, unreachable: bool, killed: bool) -> Sequence[Def]: (source)

Undocumented

def _softfilter_killed_defs(self, defs: Sequence[Def]) -> Sequence[Def]: (source)

Undocumented

def _softfilter_unreachable_defs(self, defs: Sequence[Def]) -> Sequence[Def]: (source)

Undocumented

_ancestors: dict[ast.AST, Sequence[ast.AST]] = (source)

Mapping of AST nodes to the list of their parents.

_def_use_chains: dict[ast.AST, Def] = (source)

Def-Use chains.

_dunder_all: Mapping[Mod, Collection[str] | None] = (source)

Mapping from Mod instances explicit __all__ values or None.

_ivars: dict[ast.ClassDef, Mapping[str, Sequence[NameDef]]] = (source)

Mapping from class instances to instance variables definitions stored as mapping for fast name based access.

_locals: dict[ast.AST, Mapping[str, Sequence[NameDef | None]]] = (source)

Mapping of locals.

_modules: dict[str, Mod] = (source)

Mapping from module names to Mod instances.

_mros: Mapping[Cls, Sequence[Cls | str]] = (source)

Mapping from class instances to their resolved MRO. A warning is logged when the MRO of a class could not be computed or is ambiguous. Qualname as strings replaces unresolved classes in the MRO.

_unreachable: set[ast.AST] = (source)

Set of unreachable nodes.

_use_def_chains: dict[ast.AST, Sequence[Def]] = (source)

Use-Def chains.