Code Monkey home page Code Monkey logo

awfutils's People

Contributors

awf avatar

Stargazers

 avatar  avatar  avatar

Watchers

 avatar  avatar

awfutils's Issues

[Feature Request] Generalize @typecheck to support PEP-compliant type hints

Extremely impressive. @awfutils.typecheck is the first practical attempt I've seen at performing static type-checking at runtime. Take all my thunderous clapping! ๐Ÿ‘ ๐Ÿ‘

The current approach is outrageously useful, but appears to currently only support isinstance()-able classes rather than PEP-compliant type hints: e.g.,

# I suspect this fails hard, but am lazy and thus did not test.
@typecheck
def foo(x : List[int], y : int):
  z : List[int] = x * y
  w : float = z[0] * 3.2
  return w

foo([3, 2, 1], 1.3)

Is that right? If so, that's still impressive tech for a several hundred-line decorator. Still, it'd be even more outrageously useful if we could generalize your @typecheck decorator to support arbitrary PEP-compliant type hints. Can we? Yes, we can.

You Know What @leycec Is Gonna Suggest Next...

That's right. We're talkin' @beartype, because of course we are. Implementing full-blown type-checking for all PEP standards past and present probably isn't somewhere you want to willingly go. Thankfully, @beartype already went there for you. ๐Ÿฅณ

The core issue appears to be the current usage of the isinstance() builtin in the TypeCheckVisitor.visit_FunctionDef() method. Specifically, this AST transform:

        node_assert = ast.Assert(
            test=ast.Call(
                ast.Name("isinstance", ctx=ast.Load()),
                [
                    ast.Name(
                        node.target.id, ctx=ast.Load()
                    ),  # Convert ctx from Store to Load
                    node.annotation,
                ],
                [],
            ),
            msg=ast.Constant(value=f"{node.target.id} not a {annot_str}", kind=None),
        )

I'm fairly certain (...but technically uncertain, because lazy and thus untested) that replacing the above with the below should generalize @typecheck to support PEP-compliant type hints:

class TypeCheckVisitor(ast.NodeTransformer):
    def visit_Module(self, node: Module) -> Module:
        '''
        Add a new abstract syntax tree (AST) child node to the passed AST module
        parent node encapsulating the module currently being loaded, importing
        the :func:`beartype.abby.die_if_unbearable` runtime type-checker for
        subsequent use by the other visitor methods defined by this class.

        Parameters
        ----------
        node : Module
            AST module parent node to be transformed.

        Returns
        ----------
        Module
            That same AST module parent node.
        '''

        # 0-based index of the first safe position of the list of all AST child
        # nodes of this AST module parent node to insert an import statement
        # importing our beartype decorator, initialized to the erroneous index
        # "-1" to enable detection of empty modules (i.e., modules whose AST
        # module nodes containing *NO* child nodes) below.
        import_beartype_index = -1

        # AST child node of this AST module parent node immediately preceding
        # the AST import child node to be added below, defaulting to this AST
        # module parent node to ensure that the _copy_node_code_metadata()
        # function below *ALWAYS* copies from a valid AST node for sanity.
        module_child: AST = node

        # Efficiently find this index. Since, this iteration is guaranteed to
        # exhibit worst-case O(1) time complexity despite superficially
        # appearing to perform a linear search of all n child nodes of this
        # module parent node and thus exhibit worst-case O(n) time complexity.
        #
        # For the 0-based index and value of each direct AST child node of this
        # AST module parent node...
        for import_beartype_index, module_child in enumerate(node.body):
            # If this child node signifies either...
            if (
                # A module docstring...
                #
                # If that module defines a docstring, that docstring *MUST* be
                # the first expression of that module. That docstring *MUST* be
                # explicitly found and iterated past to ensure that the import
                # statement added below appears *AFTER* rather than *BEFORE* any
                # docstring. (The latter would destroy the semantics of that
                # docstring by reducing that docstring to an ignorable string.)
                (
                    isinstance(module_child, Expr) and
                    isinstance(module_child.value, Str)
                ) or
                # A future import (i.e., import of the form
                # "from __future__ ...") *OR*...
                #
                # If that module performs one or more future imports, these
                # imports *MUST* necessarily be the first non-docstring
                # statement of that module and thus appear *BEFORE* all import
                # statements that are actually imports -- including the import
                # statement added below.
                (
                    isinstance(module_child, ImportFrom) and
                    module_child.module == '__future__'
                )
            ):
                # Then continue past this child node to the next child node.
                continue

        # If the 0-based index of the first safe position of the list of all AST
        # child nodes of this AST module parent node to insert an import
        # statement importing our beartype decorator is *NOT* the erroneous
        # index to which this index was initialized above, this module contains
        # one or more child nodes and is thus non-empty. In this case...
        if import_beartype_index != -1:
            # AST import child node importing our private
            # beartype._decor.decorcore.beartype_object_nonfatal() decorator for
            # subsequent use by the other visitor methods defined by this class.
            import_beartype = ImportFrom(
                module='beartype.abby',
                names=[alias('die_if_unbearable')],
            )

            # Copy all source code metadata from the AST child node of this AST
            # module parent node immediately preceding this AST import child
            # node onto this AST import child node.
            _copy_node_code_metadata(
                node_src=node, node_trg=import_beartype)

            # Insert this AST import child node at this safe position of the
            # list of all AST child nodes of this AST module parent node.
            node.body.insert(import_beartype_index, import_beartype)
        # Else, this module is empty. In this case, silently reduce to a noop.
        # Since this edge case is *EXTREMELY* uncommon, avoid optimizing for
        # this edge case (here or elsewhere).

        # Recursively transform *ALL* AST child nodes of this AST module node.
        self.generic_visit(node)

        # Return this AST module node as is.
        return node

    ...


    def visit_AnnAssign(self, node):
        # An assignment with a type annotation.
        # node.target : single node and can be a Name, a Attribute or a Subscript.
        # node.annotation : annotation, such as a Str or Name node.
        # node.value : single optional node.
        # node.simple : True for a Name node in target that do not appear between
        #               parentheses and are hence pure names and not expressions.

        if not node.simple:
            return node

        assert isinstance(node.target, ast.Name)  # Should be guaranteed by node.simple

        node_typecheck = ast.Call(
            ast.Name('die_if_unbearable', ctx=ast.Load()),
            [
                ast.Name(
                    node.target.id, ctx=ast.Load()
                ),  # Convert ctx from Store to Load
                node.annotation,
            ],
            [],
        )

        node_typecheck = ast.copy_location(node_typecheck, node)
        ast.fix_missing_locations(node_typecheck)
        return [node, node_typecheck]

The visit_Module() implementation is copied almost verbatim from a similar AST transform in the @beartype codebase itself. So, possibly working?

Regardless of where you choose to take this awesomeness, this has been a considerable inspiration. @beartype will probably end up assimilating this into itself, because everyone over at beartype/beartype#105 really wants this to happen.

In short, you're amazing.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.