Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Static type checking à la mypy #3893

Open
saada opened this issue Apr 5, 2023 · 11 comments
Open

Static type checking à la mypy #3893

saada opened this issue Apr 5, 2023 · 11 comments
Labels
type-inference Requires more advanced type inference.

Comments

@saada
Copy link

saada commented Apr 5, 2023

Would be amazing if Ruff had static type checking. Mypy is a well known solution in this area as well as pytype, pyre, and others.

As a follow up, we could have some way of auto-fixing type annotations.

Thank you for this incredible project 😍

@charliermarsh charliermarsh added the type-inference Requires more advanced type inference. label Apr 13, 2023
@charliermarsh
Copy link
Member

I will admit that I'm interested in this and it fits into the vision of what we're trying to do, but it would be irresponsible of me to promise or commit to anything yet :)

@NeilGirdhar
Copy link

I'm not sure if you are aware, but Pylyzer is a Python type checker that's written in Rust. It might be worth seeing if there's a way to have all the desired features with less effort.

@zanieb
Copy link
Member

zanieb commented Aug 15, 2023

Oh that's interesting! It uses Erg and isn't quite feature complete, I'm not sure if we could use it but it's great to see another Rust Python tool.

@NeilGirdhar
Copy link

I don't know much about Rust, but would it be possible for them to expose an API that Ruff could call into? If so, the Ruff and Pylyzer teams could work out an API that Pylyzer exposes and Rust could call into? Even if you don't end up using Pylyzer, knowing the API that Ruff needs would be useful if Ruff were to implement its own type analysis. (Just thinking out loud.)

@thibaut-st
Copy link

Is there any dev started on that feature? It would be so awesome to have all the essential python code quality tools in one fast as hell dependency!

@nolanking90
Copy link
Contributor

I'm interested in moving forward on this. I would like to begin working on features that could be exposed to ruff-lsp to add more of the traditional LSP capabilities, and it sounds like getting some type checking/inference working is going to be necessary before something like 'textDocument/hover' can be handled (by ruff-lsp) in a useful way.

Some input from the maintainers about implementation would be helpful, if this is still something the core team is interested in.

@zanieb
Copy link
Member

zanieb commented Apr 3, 2024

Thanks for expressing your interest! Unfortunately the scope of this feature is far too large for external contribution, it will require major architectural changes to Ruff and extensive collaboration with our team. We're moving in this direction though. We can mark any related issues that would be good for external contribution with a "help wanted" label, as I'm sure there will be lots of smaller tasks that arise.

We're also recently kicked off a rewrite of ruff-lsp in Rust, see #10158. Once it's established, I'm sure there will be issues in that project that are good for contribution.

@johnthagen
Copy link

For those interested, looks like initial work for this is here

@KotlinIsland
Copy link
Contributor

à la mypy

please don't make it like mypy, closer to pyright would be much more tolerable. although considering how based the maintainers are here, i'm confident they would know what they are doing

@kszlim
Copy link

kszlim commented Jul 30, 2024

Curious if there's a tracking issue for the progression of this feature? Would be great to gain some visibility into this (with the caveat that I certainly don't have any expectations around the arrival date).

@leontrolski
Copy link

leontrolski commented Nov 1, 2024

An API that I'd love to see from a future typechecker (I feel this is sorely missing with mypy and friends):

Given a module `my.module` like:
class Foo:
    def call_db(self) -> int:
        return 42


class Bar:
    foos: list[Foo]


def no_call_db(f: Any) -> Any:
    return f


@no_call_db
def f() -> None:
    bar = Bar()
    for foo in bar.foos:
        print(foo.call_db())

I'd like to be able to use the results of typing-checking to perform further static analysis (or maybe even insane runtime stuff/documentation generation).

In this case, I'd like to check there are no calls to my.module.Foo.call_db in any function decorated with @my.module.no_call_db.

The API would look something like this:

from ruff.typechecker import ast_extended as ast, types, typecheck, parse_ast_with_types

top_level_types = typecheck(Path("."), use_cache=True)
assert top_level_types == {
    "my.module.Foo": types.Class(methods={"call_db": types.Method(...)}),
    "my.module.no_call_db": types.Function(args=[types.Any], ret=types.Any),
    ...,
}

More useful would be coupling the above with the AST:

node = parse_ast_with_types(Path("my/module.py"), use_cache=True)
assert node == ast.FunctionDef(
    name="f",
    decorator_list=[
        ast.Name(
            id="no_call_db",
            type=types.TypeRef("my.module.no_call_db"),
        )
    ],
    body=[
        ast.Assign(
            targets=[ast.Name(id="bar")],
            value=ast.Call(
                func=ast.Name(
                    id="Bar",
                    type=types.Type(types.TypeRef("my.module.Bar")),
                ),
                args=[],
                type=types.TypeRef("my.module.Bar"),
            ),
            type=types.TypeRef("my.module.Bar"),
        ),
        ast.For(
            target=ast.Name(
                id="foo",
                type=types.TypeRef("my.module.Foo"),
            ),
            iter=ast.Attribute(
                value=ast.Name(
                    id="bar",
                    type=types.TypeRef("my.module.Bar"),
                ),
                attr="foos",
                type=types.List(types.TypeRef("my.module.Foo")),
            ),
            body=[
                ast.Expr(
                    value=ast.Call(
                        func=ast.Name(id="print"),
                        args=[
                            ast.Call(
                                func=ast.Attribute(
                                    value=ast.Name(id="foo"),
                                    attr="call_db",
                                    type=types.MethodRef("my.module.Foo", "call_db"),
                                ),
                                args=[],
                                type=types.None_,
                            )
                        ],
                        type=types.TypeRef("builtins.print"),
                    )
                )
            ],
        ),
    ],
)

A checker for "no db calls where decorator disallows" is then just:

def is_decorated_no_call_db(node: ast.AST) -> TypeIs[ast.FunctionDef]:
    return (
        isinstance(node, ast.FunctionDef)
        and any(d.type == types.TypeRef("my.module.no_call_db") for d in node.decorator_list)
    )

def is_call_to_call_db(node: ast.AST) -> TypeIs[ast.Call]:
    return (
        isinstance(child, ast.Call)
        and child.func.type == types.MethodRef("my.module.Foo", "call_db")
    )

for node in walk(module):
    if is_decorated(node) and if any(is_call_to_call_db(child) for child in walk(node)):
        yield Error(...)

Reliably achieving this kind of thing right now is really difficult as I can't easily eg. hook into mypy's internals.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type-inference Requires more advanced type inference.
Projects
None yet
Development

No branches or pull requests

10 participants