Skip to content

Conversation

AlexWaygood
Copy link
Member

@AlexWaygood AlexWaygood commented Jan 28, 2022

These were all discovered with the help of the following, fairly diabolical, script. I haven't tested the script at all, so it likely has a few bugs, but was surprisingly accurate in the results it produced. (I manually checked each result that it gave me, and there was only 1 false positive).

import ast
import inspect
import sys
import textwrap
from pathlib import Path

def check_syntax(tree: ast.AST, path: Path) -> list[str]:
    errors = []

    class RuntimeReturnStatementFinder(ast.NodeVisitor):
        def __init__(self) -> None:
            self.return_statements_are_self = []

        def visit_Return(self, node: ast.Return):
            self.return_statements_are_self.append(isinstance(node.value, ast.Name) and node.value.id == "self")
            self.generic_visit(node)
    
    class FunctionFinder(ast.NodeVisitor):
        def __init__(self) -> None:
            self.current_class_name = ""

        def visit_ClassDef(self, node: ast.ClassDef):
            old_name = self.current_class_name
            self.current_class_name = node.name
            self.generic_visit(node)
            self.current_class_name = old_name
        
        def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
            return_annotation = node.returns
            if isinstance(return_annotation, ast.Name) and return_annotation.id == "Self":
                return
            try:
                runtime_module_as_string = '.'.join(path.parts[1:-1])
                if runtime_module_as_string:
                    runtime_module_as_string += '.'
                runtime_module_as_string += path.parts[-1].split('.')[0]
                if runtime_module_as_string == "antigravity":  # It was amusing the first time it happened, but no
                    return
                runtime_module = __import__(runtime_module_as_string)
                if self.current_class_name:
                    runtime_class = getattr(runtime_module, self.current_class_name)
                else:
                    runtime_class = runtime_module
                runtime_function = getattr(runtime_class, node.name)
                cleaned_runtime_source_code = textwrap.dedent(inspect.getsource(runtime_function))
                runtime_ast = ast.parse(cleaned_runtime_source_code)
                return_finder = RuntimeReturnStatementFinder()
                return_finder.visit(runtime_ast)
            
                if return_finder.return_statements_are_self and all(return_finder.return_statements_are_self):
                    errors.append(f'{path}:{node.lineno} Runtime only ever returns self but typeshed stub does not return "Self"')
            except:
                pass
            self.generic_visit(node)

    FunctionFinder().visit(tree)
    return errors


def main() -> None:
    errors = []
    for path in Path("stdlib").rglob("*.pyi"):
        if "@python2" in path.parts:
            continue
        with open(path) as f:
            tree = ast.parse(f.read())
        errors.extend(check_syntax(tree, path))

    if errors:
        print("\n".join(errors))
        sys.exit(1)


if __name__ == "__main__":
    main()

@github-actions
Copy link
Contributor

According to mypy_primer, this change has no effect on the checked open source code. 🤖🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants