robot-piglet 10 месяцев назад
Родитель
Сommit
95043960ea

+ 448 - 0
contrib/libs/pybind11/include/pybind11/stl.h

@@ -0,0 +1,448 @@
+/*
+    pybind11/stl.h: Transparent conversion for STL data types
+
+    Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
+
+    All rights reserved. Use of this source code is governed by a
+    BSD-style license that can be found in the LICENSE file.
+*/
+
+#pragma once
+
+#include "pybind11.h"
+#include "detail/common.h"
+
+#include <deque>
+#include <list>
+#include <map>
+#include <ostream>
+#include <set>
+#include <unordered_map>
+#include <unordered_set>
+#include <valarray>
+
+// See `detail/common.h` for implementation of these guards.
+#if defined(PYBIND11_HAS_OPTIONAL)
+#    include <optional>
+#elif defined(PYBIND11_HAS_EXP_OPTIONAL)
+#    error #include <experimental/optional>
+#endif
+
+#if defined(PYBIND11_HAS_VARIANT)
+#    include <variant>
+#endif
+
+PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
+PYBIND11_NAMESPACE_BEGIN(detail)
+
+/// Extracts an const lvalue reference or rvalue reference for U based on the type of T (e.g. for
+/// forwarding a container element).  Typically used indirect via forwarded_type(), below.
+template <typename T, typename U>
+using forwarded_type = conditional_t<std::is_lvalue_reference<T>::value,
+                                     remove_reference_t<U> &,
+                                     remove_reference_t<U> &&>;
+
+/// Forwards a value U as rvalue or lvalue according to whether T is rvalue or lvalue; typically
+/// used for forwarding a container's elements.
+template <typename T, typename U>
+constexpr forwarded_type<T, U> forward_like(U &&u) {
+    return std::forward<detail::forwarded_type<T, U>>(std::forward<U>(u));
+}
+
+// Checks if a container has a STL style reserve method.
+// This will only return true for a `reserve()` with a `void` return.
+template <typename C>
+using has_reserve_method = std::is_same<decltype(std::declval<C>().reserve(0)), void>;
+
+template <typename Type, typename Key>
+struct set_caster {
+    using type = Type;
+    using key_conv = make_caster<Key>;
+
+private:
+    template <typename T = Type, enable_if_t<has_reserve_method<T>::value, int> = 0>
+    void reserve_maybe(const anyset &s, Type *) {
+        value.reserve(s.size());
+    }
+    void reserve_maybe(const anyset &, void *) {}
+
+public:
+    bool load(handle src, bool convert) {
+        if (!isinstance<anyset>(src)) {
+            return false;
+        }
+        auto s = reinterpret_borrow<anyset>(src);
+        value.clear();
+        reserve_maybe(s, &value);
+        for (auto entry : s) {
+            key_conv conv;
+            if (!conv.load(entry, convert)) {
+                return false;
+            }
+            value.insert(cast_op<Key &&>(std::move(conv)));
+        }
+        return true;
+    }
+
+    template <typename T>
+    static handle cast(T &&src, return_value_policy policy, handle parent) {
+        if (!std::is_lvalue_reference<T>::value) {
+            policy = return_value_policy_override<Key>::policy(policy);
+        }
+        pybind11::set s;
+        for (auto &&value : src) {
+            auto value_ = reinterpret_steal<object>(
+                key_conv::cast(detail::forward_like<T>(value), policy, parent));
+            if (!value_ || !s.add(std::move(value_))) {
+                return handle();
+            }
+        }
+        return s.release();
+    }
+
+    PYBIND11_TYPE_CASTER(type, const_name("set[") + key_conv::name + const_name("]"));
+};
+
+template <typename Type, typename Key, typename Value>
+struct map_caster {
+    using key_conv = make_caster<Key>;
+    using value_conv = make_caster<Value>;
+
+private:
+    template <typename T = Type, enable_if_t<has_reserve_method<T>::value, int> = 0>
+    void reserve_maybe(const dict &d, Type *) {
+        value.reserve(d.size());
+    }
+    void reserve_maybe(const dict &, void *) {}
+
+public:
+    bool load(handle src, bool convert) {
+        if (!isinstance<dict>(src)) {
+            return false;
+        }
+        auto d = reinterpret_borrow<dict>(src);
+        value.clear();
+        reserve_maybe(d, &value);
+        for (auto it : d) {
+            key_conv kconv;
+            value_conv vconv;
+            if (!kconv.load(it.first.ptr(), convert) || !vconv.load(it.second.ptr(), convert)) {
+                return false;
+            }
+            value.emplace(cast_op<Key &&>(std::move(kconv)), cast_op<Value &&>(std::move(vconv)));
+        }
+        return true;
+    }
+
+    template <typename T>
+    static handle cast(T &&src, return_value_policy policy, handle parent) {
+        dict d;
+        return_value_policy policy_key = policy;
+        return_value_policy policy_value = policy;
+        if (!std::is_lvalue_reference<T>::value) {
+            policy_key = return_value_policy_override<Key>::policy(policy_key);
+            policy_value = return_value_policy_override<Value>::policy(policy_value);
+        }
+        for (auto &&kv : src) {
+            auto key = reinterpret_steal<object>(
+                key_conv::cast(detail::forward_like<T>(kv.first), policy_key, parent));
+            auto value = reinterpret_steal<object>(
+                value_conv::cast(detail::forward_like<T>(kv.second), policy_value, parent));
+            if (!key || !value) {
+                return handle();
+            }
+            d[std::move(key)] = std::move(value);
+        }
+        return d.release();
+    }
+
+    PYBIND11_TYPE_CASTER(Type,
+                         const_name("dict[") + key_conv::name + const_name(", ") + value_conv::name
+                             + const_name("]"));
+};
+
+template <typename Type, typename Value>
+struct list_caster {
+    using value_conv = make_caster<Value>;
+
+    bool load(handle src, bool convert) {
+        if (!isinstance<sequence>(src) || isinstance<bytes>(src) || isinstance<str>(src)) {
+            return false;
+        }
+        auto s = reinterpret_borrow<sequence>(src);
+        value.clear();
+        reserve_maybe(s, &value);
+        for (const auto &it : s) {
+            value_conv conv;
+            if (!conv.load(it, convert)) {
+                return false;
+            }
+            value.push_back(cast_op<Value &&>(std::move(conv)));
+        }
+        return true;
+    }
+
+private:
+    template <typename T = Type, enable_if_t<has_reserve_method<T>::value, int> = 0>
+    void reserve_maybe(const sequence &s, Type *) {
+        value.reserve(s.size());
+    }
+    void reserve_maybe(const sequence &, void *) {}
+
+public:
+    template <typename T>
+    static handle cast(T &&src, return_value_policy policy, handle parent) {
+        if (!std::is_lvalue_reference<T>::value) {
+            policy = return_value_policy_override<Value>::policy(policy);
+        }
+        list l(src.size());
+        ssize_t index = 0;
+        for (auto &&value : src) {
+            auto value_ = reinterpret_steal<object>(
+                value_conv::cast(detail::forward_like<T>(value), policy, parent));
+            if (!value_) {
+                return handle();
+            }
+            PyList_SET_ITEM(l.ptr(), index++, value_.release().ptr()); // steals a reference
+        }
+        return l.release();
+    }
+
+    PYBIND11_TYPE_CASTER(Type, const_name("list[") + value_conv::name + const_name("]"));
+};
+
+template <typename Type, typename Alloc>
+struct type_caster<std::vector<Type, Alloc>> : list_caster<std::vector<Type, Alloc>, Type> {};
+
+template <typename Type, typename Alloc>
+struct type_caster<std::deque<Type, Alloc>> : list_caster<std::deque<Type, Alloc>, Type> {};
+
+template <typename Type, typename Alloc>
+struct type_caster<std::list<Type, Alloc>> : list_caster<std::list<Type, Alloc>, Type> {};
+
+template <typename ArrayType, typename Value, bool Resizable, size_t Size = 0>
+struct array_caster {
+    using value_conv = make_caster<Value>;
+
+private:
+    template <bool R = Resizable>
+    bool require_size(enable_if_t<R, size_t> size) {
+        if (value.size() != size) {
+            value.resize(size);
+        }
+        return true;
+    }
+    template <bool R = Resizable>
+    bool require_size(enable_if_t<!R, size_t> size) {
+        return size == Size;
+    }
+
+public:
+    bool load(handle src, bool convert) {
+        if (!isinstance<sequence>(src)) {
+            return false;
+        }
+        auto l = reinterpret_borrow<sequence>(src);
+        if (!require_size(l.size())) {
+            return false;
+        }
+        size_t ctr = 0;
+        for (const auto &it : l) {
+            value_conv conv;
+            if (!conv.load(it, convert)) {
+                return false;
+            }
+            value[ctr++] = cast_op<Value &&>(std::move(conv));
+        }
+        return true;
+    }
+
+    template <typename T>
+    static handle cast(T &&src, return_value_policy policy, handle parent) {
+        list l(src.size());
+        ssize_t index = 0;
+        for (auto &&value : src) {
+            auto value_ = reinterpret_steal<object>(
+                value_conv::cast(detail::forward_like<T>(value), policy, parent));
+            if (!value_) {
+                return handle();
+            }
+            PyList_SET_ITEM(l.ptr(), index++, value_.release().ptr()); // steals a reference
+        }
+        return l.release();
+    }
+
+    PYBIND11_TYPE_CASTER(ArrayType,
+                         const_name<Resizable>(const_name(""), const_name("Annotated["))
+                             + const_name("list[") + value_conv::name + const_name("]")
+                             + const_name<Resizable>(const_name(""),
+                                                     const_name(", FixedSize(")
+                                                         + const_name<Size>() + const_name(")]")));
+};
+
+template <typename Type, size_t Size>
+struct type_caster<std::array<Type, Size>>
+    : array_caster<std::array<Type, Size>, Type, false, Size> {};
+
+template <typename Type>
+struct type_caster<std::valarray<Type>> : array_caster<std::valarray<Type>, Type, true> {};
+
+template <typename Key, typename Compare, typename Alloc>
+struct type_caster<std::set<Key, Compare, Alloc>>
+    : set_caster<std::set<Key, Compare, Alloc>, Key> {};
+
+template <typename Key, typename Hash, typename Equal, typename Alloc>
+struct type_caster<std::unordered_set<Key, Hash, Equal, Alloc>>
+    : set_caster<std::unordered_set<Key, Hash, Equal, Alloc>, Key> {};
+
+template <typename Key, typename Value, typename Compare, typename Alloc>
+struct type_caster<std::map<Key, Value, Compare, Alloc>>
+    : map_caster<std::map<Key, Value, Compare, Alloc>, Key, Value> {};
+
+template <typename Key, typename Value, typename Hash, typename Equal, typename Alloc>
+struct type_caster<std::unordered_map<Key, Value, Hash, Equal, Alloc>>
+    : map_caster<std::unordered_map<Key, Value, Hash, Equal, Alloc>, Key, Value> {};
+
+// This type caster is intended to be used for std::optional and std::experimental::optional
+template <typename Type, typename Value = typename Type::value_type>
+struct optional_caster {
+    using value_conv = make_caster<Value>;
+
+    template <typename T>
+    static handle cast(T &&src, return_value_policy policy, handle parent) {
+        if (!src) {
+            return none().release();
+        }
+        if (!std::is_lvalue_reference<T>::value) {
+            policy = return_value_policy_override<Value>::policy(policy);
+        }
+        // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
+        return value_conv::cast(*std::forward<T>(src), policy, parent);
+    }
+
+    bool load(handle src, bool convert) {
+        if (!src) {
+            return false;
+        }
+        if (src.is_none()) {
+            return true; // default-constructed value is already empty
+        }
+        value_conv inner_caster;
+        if (!inner_caster.load(src, convert)) {
+            return false;
+        }
+
+        value.emplace(cast_op<Value &&>(std::move(inner_caster)));
+        return true;
+    }
+
+    PYBIND11_TYPE_CASTER(Type, const_name("Optional[") + value_conv::name + const_name("]"));
+};
+
+#if defined(PYBIND11_HAS_OPTIONAL)
+template <typename T>
+struct type_caster<std::optional<T>> : public optional_caster<std::optional<T>> {};
+
+template <>
+struct type_caster<std::nullopt_t> : public void_caster<std::nullopt_t> {};
+#endif
+
+#if defined(PYBIND11_HAS_EXP_OPTIONAL)
+template <typename T>
+struct type_caster<std::experimental::optional<T>>
+    : public optional_caster<std::experimental::optional<T>> {};
+
+template <>
+struct type_caster<std::experimental::nullopt_t>
+    : public void_caster<std::experimental::nullopt_t> {};
+#endif
+
+/// Visit a variant and cast any found type to Python
+struct variant_caster_visitor {
+    return_value_policy policy;
+    handle parent;
+
+    using result_type = handle; // required by boost::variant in C++11
+
+    template <typename T>
+    result_type operator()(T &&src) const {
+        return make_caster<T>::cast(std::forward<T>(src), policy, parent);
+    }
+};
+
+/// Helper class which abstracts away variant's `visit` function. `std::variant` and similar
+/// `namespace::variant` types which provide a `namespace::visit()` function are handled here
+/// automatically using argument-dependent lookup. Users can provide specializations for other
+/// variant-like classes, e.g. `boost::variant` and `boost::apply_visitor`.
+template <template <typename...> class Variant>
+struct visit_helper {
+    template <typename... Args>
+    static auto call(Args &&...args) -> decltype(visit(std::forward<Args>(args)...)) {
+        return visit(std::forward<Args>(args)...);
+    }
+};
+
+/// Generic variant caster
+template <typename Variant>
+struct variant_caster;
+
+template <template <typename...> class V, typename... Ts>
+struct variant_caster<V<Ts...>> {
+    static_assert(sizeof...(Ts) > 0, "Variant must consist of at least one alternative.");
+
+    template <typename U, typename... Us>
+    bool load_alternative(handle src, bool convert, type_list<U, Us...>) {
+        auto caster = make_caster<U>();
+        if (caster.load(src, convert)) {
+            value = cast_op<U>(std::move(caster));
+            return true;
+        }
+        return load_alternative(src, convert, type_list<Us...>{});
+    }
+
+    bool load_alternative(handle, bool, type_list<>) { return false; }
+
+    bool load(handle src, bool convert) {
+        // Do a first pass without conversions to improve constructor resolution.
+        // E.g. `py::int_(1).cast<variant<double, int>>()` needs to fill the `int`
+        // slot of the variant. Without two-pass loading `double` would be filled
+        // because it appears first and a conversion is possible.
+        if (convert && load_alternative(src, false, type_list<Ts...>{})) {
+            return true;
+        }
+        return load_alternative(src, convert, type_list<Ts...>{});
+    }
+
+    template <typename Variant>
+    static handle cast(Variant &&src, return_value_policy policy, handle parent) {
+        return visit_helper<V>::call(variant_caster_visitor{policy, parent},
+                                     std::forward<Variant>(src));
+    }
+
+    using Type = V<Ts...>;
+    PYBIND11_TYPE_CASTER(Type,
+                         const_name("Union[")
+                             + ::pybind11::detail::concat(make_caster<Ts>::name...)
+                             + const_name("]"));
+};
+
+#if defined(PYBIND11_HAS_VARIANT)
+template <typename... Ts>
+struct type_caster<std::variant<Ts...>> : variant_caster<std::variant<Ts...>> {};
+
+template <>
+struct type_caster<std::monostate> : public void_caster<std::monostate> {};
+#endif
+
+PYBIND11_NAMESPACE_END(detail)
+
+inline std::ostream &operator<<(std::ostream &os, const handle &obj) {
+#ifdef PYBIND11_HAS_STRING_VIEW
+    os << str(obj).cast<std::string_view>();
+#else
+    os << (std::string) str(obj);
+#endif
+    return os;
+}
+
+PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

+ 30 - 0
contrib/python/beniget/.dist-info/METADATA

@@ -0,0 +1,30 @@
+Metadata-Version: 2.1
+Name: beniget
+Version: 0.4.1
+Summary: Extract semantic information about static Python code
+Home-page: https://github.com/serge-sans-paille/beniget/
+Author: serge-sans-paille
+Author-email: serge.guelton@telecom-bretagne.eu
+License: BSD 3-Clause
+Platform: UNKNOWN
+Classifier: Development Status :: 4 - Beta
+Classifier: Environment :: Console
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: BSD License
+Classifier: Natural Language :: English
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
+Requires-Dist: gast (~=0.5.0)
+
+
+A static analyzer for Python2 and Python3 code.
+
+Beniget provides a static over-approximation of the global and
+local definitions inside Python Module/Class/Function.
+It can also compute def-use chains from each definition.
+

+ 1 - 0
contrib/python/beniget/.dist-info/top_level.txt

@@ -0,0 +1 @@
+beniget

+ 29 - 0
contrib/python/beniget/LICENSE

@@ -0,0 +1,29 @@
+Copyright (c) 2019, Serge Guelton
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+	Redistributions of source code must retain the above copyright notice, this
+	list of conditions and the following disclaimer.
+
+	Redistributions in binary form must reproduce the above copyright notice,
+	this list of conditions and the following disclaimer in the documentation
+	and/or other materials provided with the distribution.
+
+	Neither the name of HPCProject, Serge Guelton nor the names of its
+	contributors may be used to endorse or promote products derived from this
+	software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+

+ 237 - 0
contrib/python/beniget/README.rst

@@ -0,0 +1,237 @@
+Gast, Beniget!
+==============
+
+Beniget is a collection of Compile-time analyse on Python Abstract Syntax Tree(AST).
+It's a building block to write static analyzer or compiler for Python.
+
+Beniget relies on `gast <https://pypi.org/project/gast/>`_ to provide a cross
+version abstraction of the AST, effectively working on both Python2 and
+Python3.
+
+API
+---
+
+Basically Beniget provides three analyse:
+
+- ``beniget.Ancestors`` that maps each node to the list of enclosing nodes;
+- ``beniget.DefUseChains`` that maps each node to the list of definition points in that node;
+- ``beniget.UseDefChains`` that maps each node to the list of possible definition of that node.
+
+See sample usages and/or run ``pydoc beniget`` for more information :-).
+
+
+Sample Usages
+-------------
+
+Detect unused imports
+*********************
+
+This is a very basic usage: look for def without any use, and warn about them, focusing on imported values.
+
+.. code:: python
+
+    >>> import beniget, gast as ast
+
+    # parse some simple statements
+    >>> code = "from math import cos, sin; print(cos(3))"
+    >>> module = ast.parse(code)
+
+    # compute the def-use chains at module level
+    >>> duc = beniget.DefUseChains()
+    >>> duc.visit(module)
+
+    # grab the import statement
+    >>> imported = module.body[0].names
+
+    # inspect the users of each imported name
+    >>> for name in imported:
+    ...   ud = duc.chains[name]
+    ...   if not ud.users():
+    ...     print("Unused import: {}".format(ud.name()))
+    Unused import: sin
+
+*NOTE*: Due to the dynamic nature of Python, one can fool this analysis by
+calling the ``eval`` function, eventually through an indirection, or by performing a lookup
+into ``globals()``.
+
+Find all functions marked with a given decorator
+************************************************
+
+Let's assume we've got a ``@nice`` decorator applied to some functions. We can traverse the users
+of this decorator to find which functions are decorated.
+
+.. code:: python
+
+    # parse some simple statements
+    >>> code = """
+    ... nice = lambda x: x
+    ... @nice
+    ... def aw(): pass
+    ... def some(): pass"""
+    >>> module = ast.parse(code)
+
+    # compute the def-use chains at module level
+    >>> duc = beniget.DefUseChains()
+    >>> duc.visit(module)
+
+    # analysis to find parent of a node
+    >>> ancestors = beniget.Ancestors()
+    >>> ancestors.visit(module)
+
+    # find the nice definition
+    >>> nice = [d for d in duc.locals[module] if d.name() == "nice"][0]
+
+    # walkthrough its users
+    >>> for use in nice.users():
+    ...   # we're interested in the parent of the decorator
+    ...   parents = ancestors.parents(use.node)
+    ...   # direct parent of the decorator is the function
+    ...   fdef = parents[-1]
+    ...   print(fdef.name)
+    aw
+
+Gather attributes of ``self``
+*****************************
+
+This analysis gathers all attributes of a class, by going through all methods and checking
+the users of the first method parameter, investigating the one used in attribute lookup.
+
+.. code:: python
+
+    >>> import gast as ast
+    >>> import beniget
+
+    >>> class Attributes(ast.NodeVisitor):
+    ...
+    ...     def __init__(self, module_node):
+    ...         # compute the def-use of the module
+    ...         self.chains = beniget.DefUseChains()
+    ...         self.chains.visit(module_node)
+    ...         self.users = set()  # all users of `self`
+    ...         self.attributes = set()  # attributes of current class
+    ...
+    ...     def visit_ClassDef(self, node):
+    ...         # walk methods and fill users of `self`
+    ...         for stmt in node.body:
+    ...             if isinstance(stmt, ast.FunctionDef):
+    ...                 self_def = self.chains.chains[stmt.args.args[0]]
+    ...                 self.users.update(use.node for use in self_def.users())
+    ...         self.generic_visit(node)
+    ...
+    ...     def visit_Attribute(self, node):
+    ...         # any attribute of `self` is registered
+    ...         if node.value in self.users:
+    ...             self.attributes.add(node.attr)
+
+    >>> code = "class My(object):\n def __init__(self, x): self.x = x"
+    >>> module = ast.parse(code)
+    >>> classdef = module.body[0]
+    >>> attr = Attributes(module)
+    >>> attr.visit(classdef)
+    >>> list(attr.attributes)
+    ['x']
+
+*NOTE*: This is *not* an alias analysis, so assigning ``self`` to another variable, or
+setting it in a tuple is not captured by this analysis. It's still possible to write such an
+a analysis using def-use chains though ;-)
+
+Compute the identifiers captured by a function
+**********************************************
+
+In Python, inner functions (and lambdas) can capture identifiers defined in the outer scope.
+This analysis computes such identifiers by registering each identifier defined in the function,
+then walking through all loaded identifier and checking whether it's local or not.
+
+.. code:: python
+
+    >>> import gast as ast
+    >>> import beniget
+    >>> class Capture(ast.NodeVisitor):
+    ...
+    ...     def __init__(self, module_node):
+    ...         # initialize def-use chains
+    ...         self.chains = beniget.DefUseChains()
+    ...         self.chains.visit(module_node)
+    ...         self.users = set()  # users of local definitions
+    ...         self.captured = set()  # identifiers that don't belong to local users
+    ...
+    ...     def visit_FunctionDef(self, node):
+    ...         # initialize the set of node using a local variable
+    ...         for def_ in self.chains.locals[node]:
+    ...             self.users.update(use.node for use in def_.users())
+    ...         self.generic_visit(node)
+    ...
+    ...     def visit_Name(self, node):
+    ...         # register load of identifiers not locally definied
+    ...         if isinstance(node.ctx, ast.Load):
+    ...             if node not in self.users:
+    ...                 self.captured.add(node.id)
+
+    >>> code = 'def foo(x):\n def bar(): return x\n return bar'
+    >>> module = ast.parse(code)
+    >>> inner_function = module.body[0].body[0]
+    >>> capture = Capture(module)
+    >>> capture.visit(inner_function)
+    >>> list(capture.captured)
+    ['x']
+
+Compute the set of instructions required to compute a function
+**************************************************************
+
+This is actually very similar to the computation of the closure, but this time
+let's use the UseDef chains combined with the ancestors.
+
+.. code:: python
+
+    >>> import gast as ast
+    >>> import beniget
+    >>> class CaptureX(ast.NodeVisitor):
+    ...
+    ...     def __init__(self, module_node, fun):
+    ...         self.fun = fun
+    ...         # initialize use-def chains
+    ...         du = beniget.DefUseChains()
+    ...         du.visit(module_node)
+    ...         self.chains = beniget.UseDefChains(du)
+    ...         self.ancestors = beniget.Ancestors()
+    ...         self.ancestors.visit(module_node)
+    ...         self.external = list()
+    ...         self.visited_external = set()
+    ...
+    ...     def visit_Name(self, node):
+    ...         # register load of identifiers not locally defined
+    ...         if isinstance(node.ctx, ast.Load):
+    ...             uses = self.chains.chains[node]
+    ...             for use in uses:
+    ...                 try:
+    ...                     parents = self.ancestors.parents(use.node)
+    ...                 except KeyError:
+    ...                     return # a builtin
+    ...                 if self.fun not in parents:
+    ...                         parent = self.ancestors.parentStmt(use.node)
+    ...                         if parent not in self.visited_external:
+    ...                             self.visited_external.add(parent)
+    ...                             self.external.append(parent)
+    ...                             self.rec(parent)
+    ...
+    ...     def rec(self, node):
+    ...         "walk definitions to find their operands's def"
+    ...         if isinstance(node, ast.Assign):
+    ...             self.visit(node.value)
+    ...         # TODO: implement this for AugAssign etc
+
+
+    >>> code = 'a = 1; b = [a, a]; c = len(b)\ndef foo():\n return c'
+    >>> module = ast.parse(code)
+    >>> function = module.body[3]
+    >>> capturex = CaptureX(module, function)
+    >>> capturex.visit(function)
+    >>> # the three top level assignments have been captured!
+    >>> list(map(type, capturex.external))
+    [<class 'gast.gast.Assign'>, <class 'gast.gast.Assign'>, <class 'gast.gast.Assign'>]
+
+Acknowledgments
+---------------
+
+Beniget is in Pierre Augier's debt, for he triggered the birth of beniget and provided
+countless meaningful bug reports and advices. Trugarez!

+ 2 - 0
contrib/python/beniget/beniget/__init__.py

@@ -0,0 +1,2 @@
+from __future__ import absolute_import
+from beniget.beniget import Ancestors, DefUseChains, UseDefChains

+ 1062 - 0
contrib/python/beniget/beniget/beniget.py

@@ -0,0 +1,1062 @@
+from collections import defaultdict, OrderedDict
+from contextlib import contextmanager
+import sys
+
+import gast as ast
+
+
+class ordered_set(object):
+    def __init__(self, elements=None):
+        self.values = OrderedDict.fromkeys(elements or [])
+
+    def add(self, value):
+        self.values[value] = None
+
+    def update(self, values):
+        self.values.update((k, None) for k in values)
+
+    def __iter__(self):
+        return iter(self.values.keys())
+
+    def __contains__(self, value):
+        return value in self.values
+
+    def __add__(self, other):
+        out = self.values.copy()
+        out.update(other.values)
+        return out
+
+    def __len__(self):
+        return len(self.values)
+
+
+class Ancestors(ast.NodeVisitor):
+    """
+    Build the ancestor tree, that associates a node to the list of node visited
+    from the root node (the Module) to the current node
+
+    >>> import gast as ast
+    >>> code = 'def foo(x): return x + 1'
+    >>> module = ast.parse(code)
+
+    >>> from beniget import Ancestors
+    >>> ancestors = Ancestors()
+    >>> ancestors.visit(module)
+
+    >>> binop = module.body[0].body[0].value
+    >>> for n in ancestors.parents(binop):
+    ...    print(type(n))
+    <class 'gast.gast.Module'>
+    <class 'gast.gast.FunctionDef'>
+    <class 'gast.gast.Return'>
+    """
+
+    def __init__(self):
+        self._parents = dict()
+        self._current = list()
+
+    def generic_visit(self, node):
+        self._parents[node] = list(self._current)
+        self._current.append(node)
+        super(Ancestors, self).generic_visit(node)
+        self._current.pop()
+
+    def parent(self, node):
+        return self._parents[node][-1]
+
+    def parents(self, node):
+        return self._parents[node]
+
+    def parentInstance(self, node, cls):
+        for n in reversed(self._parents[node]):
+            if isinstance(n, cls):
+                return n
+        raise ValueError("{} has no parent of type {}".format(node, cls))
+
+    def parentFunction(self, node):
+        return self.parentInstance(node, (ast.FunctionDef,
+                                          ast.AsyncFunctionDef))
+
+    def parentStmt(self, node):
+        return self.parentInstance(node, ast.stmt)
+
+
+class Def(object):
+    """
+    Model a definition, either named or unnamed, and its users.
+    """
+
+    __slots__ = "node", "_users"
+
+    def __init__(self, node):
+        self.node = node
+        self._users = ordered_set()
+
+    def add_user(self, node):
+        assert isinstance(node, Def)
+        self._users.add(node)
+
+    def name(self):
+        """
+        If the node associated to this Def has a name, returns this name.
+        Otherwise returns its type
+        """
+        if isinstance(self.node, (ast.ClassDef,
+                                  ast.FunctionDef,
+                                  ast.AsyncFunctionDef)):
+            return self.node.name
+        elif isinstance(self.node, ast.Name):
+            return self.node.id
+        elif isinstance(self.node, ast.alias):
+            base = self.node.name.split(".", 1)[0]
+            return self.node.asname or base
+        elif isinstance(self.node, tuple):
+            return self.node[1]
+        else:
+            return type(self.node).__name__
+
+    def users(self):
+        """
+        The list of ast entity that holds a reference to this node
+        """
+        return self._users
+
+    def __repr__(self):
+        return self._repr({})
+
+    def _repr(self, nodes):
+        if self in nodes:
+            return "(#{})".format(nodes[self])
+        else:
+            nodes[self] = len(nodes)
+            return "{} -> ({})".format(
+                self.node, ", ".join(u._repr(nodes.copy())
+                                     for u in self._users)
+            )
+
+    def __str__(self):
+        return self._str({})
+
+    def _str(self, nodes):
+        if self in nodes:
+            return "(#{})".format(nodes[self])
+        else:
+            nodes[self] = len(nodes)
+            return "{} -> ({})".format(
+                self.name(), ", ".join(u._str(nodes.copy())
+                                       for u in self._users)
+            )
+
+
+Builtins = {}
+
+if sys.version_info.major == 2:
+    BuiltinsSrc = __builtins__
+else:
+    import builtins
+
+    BuiltinsSrc = builtins.__dict__
+
+Builtins = {k: v for k, v in BuiltinsSrc.items()}
+
+Builtins["__file__"] = __file__
+
+DeclarationStep, DefinitionStep = object(), object()
+
+
+class CollectGlobals(ast.NodeVisitor):
+    def __init__(self):
+        self.Globals = defaultdict(list)
+
+    def visit_Global(self, node):
+        for name in node.names:
+            self.Globals[name].append((node, name))
+
+
+class DefUseChains(ast.NodeVisitor):
+    """
+    Module visitor that gathers two kinds of informations:
+        - locals: Dict[node, List[Def]], a mapping between a node and the list
+          of variable defined in this node,
+        - chains: Dict[node, Def], a mapping between nodes and their chains.
+
+    >>> import gast as ast
+    >>> module = ast.parse("from b import c, d; c()")
+    >>> duc = DefUseChains()
+    >>> duc.visit(module)
+    >>> for head in duc.locals[module]:
+    ...     print("{}: {}".format(head.name(), len(head.users())))
+    c: 1
+    d: 0
+    >>> alias_def = duc.chains[module.body[0].names[0]]
+    >>> print(alias_def)
+    c -> (c -> (Call -> ()))
+    """
+
+    def __init__(self, filename=None):
+        """
+            - filename: str, included in error messages if specified
+        """
+        self.chains = {}
+        self.locals = defaultdict(list)
+        self.filename = filename
+
+        # deep copy of builtins, to remain reentrant
+        self._builtins = {k: Def(v) for k, v in Builtins.items()}
+
+        # function body are not executed when the function definition is met
+        # this holds a stack of the functions met during body processing
+        self._defered = []
+
+        # stack of mapping between an id and Names
+        self._definitions = []
+
+        # stack of variable defined with the global keywords
+        self._promoted_locals = []
+
+        # stack of variable that were undefined when we met them, but that may
+        # be defined in another path of the control flow (esp. in loop)
+        self._undefs = []
+
+        # stack of current node holding definitions: class, module, function...
+        self._currenthead = []
+
+        self._breaks = []
+        self._continues = []
+
+        # dead code levels
+        self.deadcode = 0
+
+    # helpers
+
+    def dump_definitions(self, node, ignore_builtins=True):
+        if isinstance(node, ast.Module) and not ignore_builtins:
+            builtins = {d for d in self._builtins.values()}
+            return sorted(d.name()
+                          for d in self.locals[node] if d not in builtins)
+        else:
+            return sorted(d.name() for d in self.locals[node])
+
+    def dump_chains(self, node):
+        chains = []
+        for d in self.locals[node]:
+            chains.append(str(d))
+        return chains
+
+    def unbound_identifier(self, name, node):
+        if hasattr(node, "lineno"):
+            filename = "{}:".format(
+                "<unknown>" if self.filename is None else self.filename
+            )
+            location = " at {}{}:{}".format(filename,
+                                            node.lineno,
+                                            node.col_offset)
+        else:
+            location = ""
+        print("W: unbound identifier '{}'{}".format(name, location))
+
+    def lookup_identifier(self, name):
+        for d in reversed(self._definitions):
+            if name in d:
+                return d[name]
+        return []
+
+    def defs(self, node):
+        name = node.id
+        stars = []
+        for d in reversed(self._definitions):
+            if name in d:
+                return d[name] if not stars else stars + list(d[name])
+            if "*" in d:
+                stars.extend(d["*"])
+
+        d = self.chains.setdefault(node, Def(node))
+
+        if self._undefs:
+            self._undefs[-1][name].append((d, stars))
+
+        if stars:
+            return stars + [d]
+        else:
+            if not self._undefs:
+                self.unbound_identifier(name, node)
+            return [d]
+
+    def process_body(self, stmts):
+        deadcode = False
+        for stmt in stmts:
+            if isinstance(stmt, (ast.Break, ast.Continue, ast.Raise)):
+                if not deadcode:
+                    deadcode = True
+                    self.deadcode += 1
+            self.visit(stmt)
+        if deadcode:
+            self.deadcode -= 1
+
+    def process_undefs(self):
+        for undef_name, _undefs in self._undefs[-1].items():
+            if undef_name in self._definitions[-1]:
+                for newdef in self._definitions[-1][undef_name]:
+                    for undef, _ in _undefs:
+                        for user in undef.users():
+                            newdef.add_user(user)
+            else:
+                for undef, stars in _undefs:
+                    if not stars:
+                        self.unbound_identifier(undef_name, undef.node)
+        self._undefs.pop()
+
+    @contextmanager
+    def DefinitionContext(self, node):
+        self._currenthead.append(node)
+        self._definitions.append(defaultdict(ordered_set))
+        self._promoted_locals.append(set())
+        yield
+        self._promoted_locals.pop()
+        self._definitions.pop()
+        self._currenthead.pop()
+
+    @contextmanager
+    def CompDefinitionContext(self, node):
+        if sys.version_info.major >= 3:
+            self._currenthead.append(node)
+            self._definitions.append(defaultdict(ordered_set))
+            self._promoted_locals.append(set())
+        yield
+        if sys.version_info.major >= 3:
+            self._promoted_locals.pop()
+            self._definitions.pop()
+            self._currenthead.pop()
+
+    # stmt
+    def visit_Module(self, node):
+        self.module = node
+        with self.DefinitionContext(node):
+
+            self._definitions[-1].update(
+                {k: ordered_set((v,)) for k, v in self._builtins.items()}
+            )
+
+            self._defered.append([])
+            self.process_body(node.body)
+
+            # handle `global' keyword specifically
+            cg = CollectGlobals()
+            cg.visit(node)
+            for nodes in cg.Globals.values():
+                for n, name in nodes:
+                    if name not in self._definitions[-1]:
+                        dnode = Def((n, name))
+                        self.set_definition(name, dnode)
+                        self.locals[node].append(dnode)
+
+            # handle function bodies
+            for fnode, ctx in self._defered[-1]:
+                visitor = getattr(self,
+                                  "visit_{}".format(type(fnode).__name__))
+                defs, self._definitions = self._definitions, ctx
+                visitor(fnode, step=DefinitionStep)
+                self._definitions = defs
+            self._defered.pop()
+
+            # various sanity checks
+            if __debug__:
+                overloaded_builtins = set()
+                for d in self.locals[node]:
+                    name = d.name()
+                    if name in self._builtins:
+                        overloaded_builtins.add(name)
+                    assert name in self._definitions[0], (name, d.node)
+
+                nb_defs = len(self._definitions[0])
+                nb_bltns = len(self._builtins)
+                nb_overloaded_bltns = len(overloaded_builtins)
+                nb_heads = len({d.name() for d in self.locals[node]})
+                assert nb_defs == nb_heads + nb_bltns - nb_overloaded_bltns
+
+        assert not self._definitions
+        assert not self._defered
+
+    def set_definition(self, name, dnode_or_dnodes):
+        if self.deadcode:
+            return
+        if isinstance(dnode_or_dnodes, Def):
+            self._definitions[-1][name] = ordered_set((dnode_or_dnodes,))
+        else:
+            self._definitions[-1][name] = ordered_set(dnode_or_dnodes)
+
+    @staticmethod
+    def add_to_definition(definition, name, dnode_or_dnodes):
+        if isinstance(dnode_or_dnodes, Def):
+            definition[name].add(dnode_or_dnodes)
+        else:
+            definition[name].update(dnode_or_dnodes)
+
+    def extend_definition(self, name, dnode_or_dnodes):
+        if self.deadcode:
+            return
+        DefUseChains.add_to_definition(self._definitions[-1], name,
+                                       dnode_or_dnodes)
+
+    def visit_FunctionDef(self, node, step=DeclarationStep):
+        if step is DeclarationStep:
+            dnode = self.chains.setdefault(node, Def(node))
+            self.set_definition(node.name, dnode)
+            self.locals[self._currenthead[-1]].append(dnode)
+
+            for kw_default in filter(None, node.args.kw_defaults):
+                self.visit(kw_default).add_user(dnode)
+            for default in node.args.defaults:
+                self.visit(default).add_user(dnode)
+            for decorator in node.decorator_list:
+                self.visit(decorator)
+
+            definitions = list(self._definitions)
+            if isinstance(self._currenthead[-1], ast.ClassDef):
+                definitions.pop()
+            self._defered[-1].append((node, definitions))
+        elif step is DefinitionStep:
+            # function is not considered as defined when evaluating returns
+            if node.returns:
+                self.visit(node.returns)
+            with self.DefinitionContext(node):
+                self.visit(node.args)
+                self.process_body(node.body)
+        else:
+            raise NotImplementedError()
+
+    visit_AsyncFunctionDef = visit_FunctionDef
+
+    def visit_ClassDef(self, node):
+        dnode = self.chains.setdefault(node, Def(node))
+        self.locals[self._currenthead[-1]].append(dnode)
+        self.set_definition(node.name, dnode)
+        for base in node.bases:
+            self.visit(base).add_user(dnode)
+        for keyword in node.keywords:
+            self.visit(keyword.value).add_user(dnode)
+        for decorator in node.decorator_list:
+            self.visit(decorator).add_user(dnode)
+
+        with self.DefinitionContext(node):
+            self.set_definition("__class__", Def("__class__"))
+            self.process_body(node.body)
+
+    def visit_Return(self, node):
+        if node.value:
+            self.visit(node.value)
+
+    def visit_Break(self, _):
+        for k, v in self._definitions[-1].items():
+            DefUseChains.add_to_definition(self._breaks[-1], k, v)
+        self._definitions[-1].clear()
+
+    def visit_Continue(self, _):
+        for k, v in self._definitions[-1].items():
+            DefUseChains.add_to_definition(self._continues[-1], k, v)
+        self._definitions[-1].clear()
+
+    def visit_Delete(self, node):
+        for target in node.targets:
+            self.visit(target)
+
+    def visit_Assign(self, node):
+        # link is implicit through ctx
+        self.visit(node.value)
+        for target in node.targets:
+            self.visit(target)
+
+    def visit_AnnAssign(self, node):
+        if node.value:
+            dvalue = self.visit(node.value)
+        dannotation = self.visit(node.annotation)
+        dtarget = self.visit(node.target)
+        dtarget.add_user(dannotation)
+        if node.value:
+            dvalue.add_user(dtarget)
+
+    def visit_AugAssign(self, node):
+        dvalue = self.visit(node.value)
+        if isinstance(node.target, ast.Name):
+            ctx, node.target.ctx = node.target.ctx, ast.Load()
+            dtarget = self.visit(node.target)
+            dvalue.add_user(dtarget)
+            node.target.ctx = ctx
+            if node.target.id in self._promoted_locals[-1]:
+                self.extend_definition(node.target.id, dtarget)
+            else:
+                loaded_from = [d.name() for d in self.defs(node.target)]
+                self.set_definition(node.target.id, dtarget)
+                # If we augassign from a value that comes from '*', let's use
+                # this node as the definition point.
+                if '*' in loaded_from:
+                    self.locals[self._currenthead[-1]].append(dtarget)
+        else:
+            self.visit(node.target).add_user(dvalue)
+
+    def visit_Print(self, node):
+        if node.dest:
+            self.visit(node.dest)
+        for value in node.values:
+            self.visit(value)
+
+    def visit_For(self, node):
+        self.visit(node.iter)
+
+        self._breaks.append(defaultdict(ordered_set))
+        self._continues.append(defaultdict(ordered_set))
+
+        self._undefs.append(defaultdict(list))
+        self._definitions.append(self._definitions[-1].copy())
+        self.visit(node.target)
+        self.process_body(node.body)
+        self.process_undefs()
+
+        continue_defs = self._continues.pop()
+        for d, u in continue_defs.items():
+            self.extend_definition(d, u)
+        self._continues.append(defaultdict(ordered_set))
+
+        # extra round to ``emulate'' looping
+        self.visit(node.target)
+        self.process_body(node.body)
+
+        # process else clause in case of late break
+        self._definitions.append(defaultdict(ordered_set))
+        self.process_body(node.orelse)
+        orelse_defs = self._definitions.pop()
+
+        break_defs = self._breaks.pop()
+        continue_defs = self._continues.pop()
+
+        body_defs = self._definitions.pop()
+
+        for d, u in orelse_defs.items():
+            self.extend_definition(d, u)
+
+        for d, u in continue_defs.items():
+            self.extend_definition(d, u)
+
+        for d, u in break_defs.items():
+            self.extend_definition(d, u)
+
+        for d, u in body_defs.items():
+            self.extend_definition(d, u)
+
+    visit_AsyncFor = visit_For
+
+    def visit_While(self, node):
+
+        self._definitions.append(self._definitions[-1].copy())
+        self._undefs.append(defaultdict(list))
+        self._breaks.append(defaultdict(ordered_set))
+        self._continues.append(defaultdict(ordered_set))
+
+        self.process_body(node.orelse)
+
+        self._definitions.pop()
+
+        self._definitions.append(self._definitions[-1].copy())
+
+        self.visit(node.test)
+        self.process_body(node.body)
+
+        self.process_undefs()
+
+        continue_defs = self._continues.pop()
+        for d, u in continue_defs.items():
+            self.extend_definition(d, u)
+        self._continues.append(defaultdict(ordered_set))
+
+        # extra round to simulate loop
+        self.visit(node.test)
+        self.process_body(node.body)
+
+        # the false branch of the eval
+        self.visit(node.test)
+
+        self._definitions.append(self._definitions[-1].copy())
+        self.process_body(node.orelse)
+
+        orelse_defs = self._definitions.pop()
+        body_defs = self._definitions.pop()
+        break_defs = self._breaks.pop()
+        continue_defs = self._continues.pop()
+
+        for d, u in continue_defs.items():
+            self.extend_definition(d, u)
+
+        for d, u in break_defs.items():
+            self.extend_definition(d, u)
+
+        for d, u in orelse_defs.items():
+            self.extend_definition(d, u)
+
+        for d, u in body_defs.items():
+            self.extend_definition(d, u)
+
+    def visit_If(self, node):
+        self.visit(node.test)
+
+        # putting a copy of current level to handle nested conditions
+        self._definitions.append(self._definitions[-1].copy())
+        self.process_body(node.body)
+        body_defs = self._definitions.pop()
+
+        self._definitions.append(self._definitions[-1].copy())
+        self.process_body(node.orelse)
+        orelse_defs = self._definitions.pop()
+        for d in body_defs:
+            if d in orelse_defs:
+                self.set_definition(d, body_defs[d] + orelse_defs[d])
+            else:
+                self.extend_definition(d, body_defs[d])
+
+        for d in orelse_defs:
+            if d in body_defs:
+                pass  # already done in the previous loop
+            else:
+                self.extend_definition(d, orelse_defs[d])
+
+    def visit_With(self, node):
+        for withitem in node.items:
+            self.visit(withitem)
+        self.process_body(node.body)
+
+    visit_AsyncWith = visit_With
+
+    def visit_Raise(self, node):
+        if node.exc:
+            self.visit(node.exc)
+        if node.cause:
+            self.visit(node.cause)
+
+    def visit_Try(self, node):
+        self._definitions.append(self._definitions[-1].copy())
+        self.process_body(node.body)
+        self.process_body(node.orelse)
+        failsafe_defs = self._definitions.pop()
+
+        # handle the fact that definitions may have fail
+        for d in failsafe_defs:
+            self.extend_definition(d, failsafe_defs[d])
+
+        for excepthandler in node.handlers:
+            self._definitions.append(defaultdict(ordered_set))
+            self.visit(excepthandler)
+            handler_def = self._definitions.pop()
+            for hd in handler_def:
+                self.extend_definition(hd, handler_def[hd])
+
+        self.process_body(node.finalbody)
+
+    def visit_Assert(self, node):
+        self.visit(node.test)
+        if node.msg:
+            self.visit(node.msg)
+
+    def visit_Import(self, node):
+        for alias in node.names:
+            dalias = self.chains.setdefault(alias, Def(alias))
+            base = alias.name.split(".", 1)[0]
+            self.set_definition(alias.asname or base, dalias)
+            self.locals[self._currenthead[-1]].append(dalias)
+
+    def visit_ImportFrom(self, node):
+        for alias in node.names:
+            dalias = self.chains.setdefault(alias, Def(alias))
+            self.set_definition(alias.asname or alias.name, dalias)
+            self.locals[self._currenthead[-1]].append(dalias)
+
+    def visit_Exec(self, node):
+        dnode = self.chains.setdefault(node, Def(node))
+        self.visit(node.body)
+
+        if node.globals:
+            self.visit(node.globals)
+        else:
+            # any global may be used by this exec!
+            for defs in self._definitions[0].values():
+                for d in defs:
+                    d.add_user(dnode)
+
+        if node.locals:
+            self.visit(node.locals)
+        else:
+            # any local may be used by this exec!
+            visible_locals = set()
+            for _definitions in reversed(self._definitions[1:]):
+                for dname, defs in _definitions.items():
+                    if dname not in visible_locals:
+                        visible_locals.add(dname)
+                        for d in defs:
+                            d.add_user(dnode)
+
+        self.extend_definition("*", dnode)
+
+    def visit_Global(self, node):
+        for name in node.names:
+            self._promoted_locals[-1].add(name)
+
+    def visit_Nonlocal(self, node):
+        for name in node.names:
+            for d in reversed(self._definitions[:-1]):
+                if name not in d:
+                    continue
+                else:
+                    # this rightfully creates aliasing
+                    self.set_definition(name, d[name])
+                    break
+            else:
+                self.unbound_identifier(name, node)
+
+    def visit_Expr(self, node):
+        self.generic_visit(node)
+
+    # expr
+    def visit_BoolOp(self, node):
+        dnode = self.chains.setdefault(node, Def(node))
+        for value in node.values:
+            self.visit(value).add_user(dnode)
+        return dnode
+
+    def visit_BinOp(self, node):
+        dnode = self.chains.setdefault(node, Def(node))
+        self.visit(node.left).add_user(dnode)
+        self.visit(node.right).add_user(dnode)
+        return dnode
+
+    def visit_UnaryOp(self, node):
+        dnode = self.chains.setdefault(node, Def(node))
+        self.visit(node.operand).add_user(dnode)
+        return dnode
+
+    def visit_Lambda(self, node, step=DeclarationStep):
+        if step is DeclarationStep:
+            dnode = self.chains.setdefault(node, Def(node))
+            self._defered[-1].append((node, list(self._definitions)))
+            return dnode
+        elif step is DefinitionStep:
+            dnode = self.chains[node]
+            with self.DefinitionContext(node):
+                self.visit(node.args)
+                self.visit(node.body).add_user(dnode)
+            return dnode
+        else:
+            raise NotImplementedError()
+
+    def visit_IfExp(self, node):
+        dnode = self.chains.setdefault(node, Def(node))
+        self.visit(node.test).add_user(dnode)
+        self.visit(node.body).add_user(dnode)
+        self.visit(node.orelse).add_user(dnode)
+        return dnode
+
+    def visit_Dict(self, node):
+        dnode = self.chains.setdefault(node, Def(node))
+        for key in filter(None, node.keys):
+            self.visit(key).add_user(dnode)
+        for value in node.values:
+            self.visit(value).add_user(dnode)
+        return dnode
+
+    def visit_Set(self, node):
+        dnode = self.chains.setdefault(node, Def(node))
+        for elt in node.elts:
+            self.visit(elt).add_user(dnode)
+        return dnode
+
+    def visit_ListComp(self, node):
+        dnode = self.chains.setdefault(node, Def(node))
+
+        with self.CompDefinitionContext(node):
+            for comprehension in node.generators:
+                self.visit(comprehension).add_user(dnode)
+            self.visit(node.elt).add_user(dnode)
+
+        return dnode
+
+    visit_SetComp = visit_ListComp
+
+    def visit_DictComp(self, node):
+        dnode = self.chains.setdefault(node, Def(node))
+
+        with self.CompDefinitionContext(node):
+            for comprehension in node.generators:
+                self.visit(comprehension).add_user(dnode)
+            self.visit(node.key).add_user(dnode)
+            self.visit(node.value).add_user(dnode)
+
+        return dnode
+
+    visit_GeneratorExp = visit_ListComp
+
+    def visit_Await(self, node):
+        dnode = self.chains.setdefault(node, Def(node))
+        self.visit(node.value).add_user(dnode)
+        return dnode
+
+    def visit_Yield(self, node):
+        dnode = self.chains.setdefault(node, Def(node))
+        if node.value:
+            self.visit(node.value).add_user(dnode)
+        return dnode
+
+    visit_YieldFrom = visit_Await
+
+    def visit_Compare(self, node):
+        dnode = self.chains.setdefault(node, Def(node))
+        self.visit(node.left).add_user(dnode)
+        for expr in node.comparators:
+            self.visit(expr).add_user(dnode)
+        return dnode
+
+    def visit_Call(self, node):
+        dnode = self.chains.setdefault(node, Def(node))
+        self.visit(node.func).add_user(dnode)
+        for arg in node.args:
+            self.visit(arg).add_user(dnode)
+        for kw in node.keywords:
+            self.visit(kw.value).add_user(dnode)
+        return dnode
+
+    visit_Repr = visit_Await
+
+    def visit_Constant(self, node):
+        dnode = self.chains.setdefault(node, Def(node))
+        return dnode
+
+    def visit_FormattedValue(self, node):
+        dnode = self.chains.setdefault(node, Def(node))
+        self.visit(node.value).add_user(dnode)
+        if node.format_spec:
+            self.visit(node.format_spec).add_user(dnode)
+        return dnode
+
+    def visit_JoinedStr(self, node):
+        dnode = self.chains.setdefault(node, Def(node))
+        for value in node.values:
+            self.visit(value).add_user(dnode)
+        return dnode
+
+    visit_Attribute = visit_Await
+
+    def visit_Subscript(self, node):
+        dnode = self.chains.setdefault(node, Def(node))
+        self.visit(node.value).add_user(dnode)
+        self.visit(node.slice).add_user(dnode)
+        return dnode
+
+    visit_Starred = visit_Await
+
+    def visit_NamedExpr(self, node):
+        dnode = self.chains.setdefault(node, Def(node))
+        self.visit(node.value).add_user(dnode)
+        self.visit(node.target)
+        return dnode
+
+    def visit_Name(self, node):
+
+        if isinstance(node.ctx, (ast.Param, ast.Store)):
+            dnode = self.chains.setdefault(node, Def(node))
+            if node.id in self._promoted_locals[-1]:
+                self.extend_definition(node.id, dnode)
+                if dnode not in self.locals[self.module]:
+                    self.locals[self.module].append(dnode)
+            else:
+                self.set_definition(node.id, dnode)
+                if dnode not in self.locals[self._currenthead[-1]]:
+                    self.locals[self._currenthead[-1]].append(dnode)
+
+            if node.annotation is not None:
+                self.visit(node.annotation)
+
+        elif isinstance(node.ctx, (ast.Load, ast.Del)):
+            node_in_chains = node in self.chains
+            if node_in_chains:
+                dnode = self.chains[node]
+            else:
+                dnode = Def(node)
+            for d in self.defs(node):
+                d.add_user(dnode)
+            if not node_in_chains:
+                self.chains[node] = dnode
+            # currently ignore the effect of a del
+        else:
+            raise NotImplementedError()
+        return dnode
+
+    def visit_Destructured(self, node):
+        dnode = self.chains.setdefault(node, Def(node))
+        tmp_store = ast.Store()
+        for elt in node.elts:
+            if isinstance(elt, ast.Name):
+                tmp_store, elt.ctx = elt.ctx, tmp_store
+                self.visit(elt)
+                tmp_store, elt.ctx = elt.ctx, tmp_store
+            elif isinstance(elt, ast.Subscript):
+                self.visit(elt)
+            elif isinstance(elt, (ast.List, ast.Tuple)):
+                self.visit_Destructured(elt)
+        return dnode
+
+    def visit_List(self, node):
+        if isinstance(node.ctx, ast.Load):
+            dnode = self.chains.setdefault(node, Def(node))
+            for elt in node.elts:
+                self.visit(elt).add_user(dnode)
+            return dnode
+        # unfortunately, destructured node are marked as Load,
+        # only the parent List/Tuple is marked as Store
+        elif isinstance(node.ctx, ast.Store):
+            return self.visit_Destructured(node)
+
+    visit_Tuple = visit_List
+
+    # slice
+
+    def visit_Slice(self, node):
+        dnode = self.chains.setdefault(node, Def(node))
+        if node.lower:
+            self.visit(node.lower).add_user(dnode)
+        if node.upper:
+            self.visit(node.upper).add_user(dnode)
+        if node.step:
+            self.visit(node.step).add_user(dnode)
+        return dnode
+
+    # misc
+
+    def visit_comprehension(self, node):
+        dnode = self.chains.setdefault(node, Def(node))
+        self.visit(node.iter).add_user(dnode)
+        self.visit(node.target)
+        for if_ in node.ifs:
+            self.visit(if_).add_user(dnode)
+        return dnode
+
+    def visit_excepthandler(self, node):
+        dnode = self.chains.setdefault(node, Def(node))
+        if node.type:
+            self.visit(node.type).add_user(dnode)
+        if node.name:
+            self.visit(node.name).add_user(dnode)
+        self.process_body(node.body)
+        return dnode
+
+    def visit_arguments(self, node):
+        for arg in node.args:
+            self.visit(arg)
+
+        for arg in node.posonlyargs:
+            self.visit(arg)
+
+        if node.vararg:
+            self.visit(node.vararg)
+
+        for arg in node.kwonlyargs:
+            self.visit(arg)
+        if node.kwarg:
+            self.visit(node.kwarg)
+
+    def visit_withitem(self, node):
+        dnode = self.chains.setdefault(node, Def(node))
+        self.visit(node.context_expr).add_user(dnode)
+        if node.optional_vars:
+            self.visit(node.optional_vars)
+        return dnode
+
+
+class UseDefChains(object):
+    """
+    DefUseChains adaptor that builds a mapping between each user
+    and the Def that defines this user:
+        - chains: Dict[node, List[Def]], a mapping between nodes and the Defs
+          that define it.
+    """
+
+    def __init__(self, defuses):
+        self.chains = {}
+        for chain in defuses.chains.values():
+            if isinstance(chain.node, ast.Name):
+                self.chains.setdefault(chain.node, [])
+            for use in chain.users():
+                self.chains.setdefault(use.node, []).append(chain)
+
+        for chain in defuses._builtins.values():
+            for use in chain.users():
+                self.chains.setdefault(use.node, []).append(chain)
+
+    def __str__(self):
+        out = []
+        for k, uses in self.chains.items():
+            kname = Def(k).name()
+            kstr = "{} <- {{{}}}".format(
+                kname, ", ".join(sorted(use.name() for use in uses))
+            )
+            out.append((kname, kstr))
+        out.sort()
+        return ", ".join(s for k, s in out)
+
+
+if __name__ == "__main__":
+    import sys
+
+    class Beniget(ast.NodeVisitor):
+        def __init__(self, filename, module):
+            super(Beniget, self).__init__()
+
+            self.filename = filename or "<stdin>"
+
+            self.ancestors = Ancestors()
+            self.ancestors.visit(module)
+
+            self.defuses = DefUseChains(self.filename)
+            self.defuses.visit(module)
+
+            self.visit(module)
+
+        def check_unused(self, node, skipped_types=()):
+            for local_def in self.defuses.locals[node]:
+                if not local_def.users():
+                    if local_def.name() == "_":
+                        continue  # typical naming by-pass
+                    if isinstance(local_def.node, skipped_types):
+                        continue
+
+                    location = local_def.node
+                    while not hasattr(location, "lineno"):
+                        location = self.ancestors.parent(location)
+
+                    if isinstance(location, ast.ImportFrom):
+                        if location.module == "__future__":
+                            continue
+
+                    print(
+                        "W: '{}' is defined but not used at {}:{}:{}".format(
+                            local_def.name(),
+                            self.filename,
+                            location.lineno,
+                            location.col_offset,
+                        )
+                    )
+
+        def visit_Module(self, node):
+            self.generic_visit(node)
+            if self.filename.endswith("__init__.py"):
+                return
+            self.check_unused(
+                node, skipped_types=(ast.FunctionDef, ast.AsyncFunctionDef,
+                                     ast.ClassDef, ast.Name)
+            )
+
+        def visit_FunctionDef(self, node):
+            self.generic_visit(node)
+            self.check_unused(node)
+
+    paths = sys.argv[1:] or (None,)
+
+    for path in paths:
+        with open(path) if path else sys.stdin as target:
+            module = ast.parse(target.read())
+            Beniget(path, module)

+ 27 - 0
contrib/python/beniget/ya.make

@@ -0,0 +1,27 @@
+# Generated by devtools/yamaker (pypi).
+
+PY3_LIBRARY()
+
+VERSION(0.4.1)
+
+LICENSE(BSD-3-Clause)
+
+PEERDIR(
+    contrib/python/gast
+)
+
+NO_LINT()
+
+PY_SRCS(
+    TOP_LEVEL
+    beniget/__init__.py
+    beniget/beniget.py
+)
+
+RESOURCE_FILES(
+    PREFIX contrib/python/beniget/
+    .dist-info/METADATA
+    .dist-info/top_level.txt
+)
+
+END()

+ 32 - 0
contrib/python/gast/.dist-info/METADATA

@@ -0,0 +1,32 @@
+Metadata-Version: 2.1
+Name: gast
+Version: 0.5.4
+Summary: Python AST that abstracts the underlying Python version
+Home-page: https://github.com/serge-sans-paille/gast/
+Author: serge-sans-paille
+Author-email: serge.guelton@telecom-bretagne.eu
+License: BSD 3-Clause
+Classifier: Development Status :: 4 - Beta
+Classifier: Environment :: Console
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: BSD License
+Classifier: Natural Language :: English
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
+Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
+License-File: LICENSE
+
+
+A generic AST to represent Python2 and Python3's Abstract Syntax Tree(AST).
+
+GAST provides a compatibility layer between the AST of various Python versions,
+as produced by ``ast.parse`` from the standard ``ast`` module.

+ 1 - 0
contrib/python/gast/.dist-info/top_level.txt

@@ -0,0 +1 @@
+gast

Некоторые файлы не были показаны из-за большого количества измененных файлов