From 81286d993df6e62ab17b4692dce416102fdd9fae Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Thu, 6 Jun 2024 23:18:10 -0700 Subject: [PATCH] Improve pyright support --- CONTRIBUTING.md | 2 +- coconut/command/command.py | 44 +++++++++++-------- coconut/command/util.py | 6 ++- coconut/tests/__main__.py | 5 ++- coconut/tests/main_test.py | 2 + .../src/cocotest/agnostic/primary_2.coco | 2 +- 6 files changed, 36 insertions(+), 25 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 12b79fd46..53f4cdbf7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -155,7 +155,7 @@ After you've tested your changes locally, you'll want to add more permanent test 1. Preparation: 1. Run `make check-reqs` and update dependencies as necessary 2. Run `sudo make format` - 3. Make sure `make test`, `make test-py2`, and `make test-easter-eggs` are passing + 3. Make sure `make test`, `make test-pyright`, and `make test-easter-eggs` are passing 4. Ensure that `coconut --watch` can successfully compile files when they're modified 5. Check changes in [`compiled-cocotest`](https://github.com/evhub/compiled-cocotest), [`pyprover`](https://github.com/evhub/pyprover), and [`coconut-prelude`](https://github.com/evhub/coconut-prelude) 6. Check [Codebeat](https://codebeat.co/a/evhub/projects) and [LGTM](https://lgtm.com/dashboard) for `coconut` and `compiled-cocotest` diff --git a/coconut/command/command.py b/coconut/command/command.py index 6549d4c34..0b9ff536a 100644 --- a/coconut/command/command.py +++ b/coconut/command/command.py @@ -293,7 +293,6 @@ def execute_args(self, args, interact=True, original_args=None): set_recursion_limit(args.recursion_limit) self.fail_fast = args.fail_fast self.display = args.display - self.pyright = args.pyright self.prompt.vi_mode = args.vi_mode if args.style is not None: self.prompt.set_style(args.style) @@ -344,9 +343,11 @@ def execute_args(self, args, interact=True, original_args=None): ), ) - # process mypy args and print timing info (must come after compiler setup) + # process mypy + pyright args and print timing info (must come after compiler setup) if args.mypy is not None: self.set_mypy_args(args.mypy) + if args.pyright: + self.enable_pyright() logger.log_compiler_stats(self.comp) # do compilation, keeping track of compiled filepaths @@ -697,15 +698,15 @@ def register_exit_code(self, code=1, errmsg=None, err=None): errmsg = format_error(err) else: errmsg = err.__class__.__name__ - if errmsg is not None: - if self.errmsg is None: - self.errmsg = errmsg - elif errmsg not in self.errmsg: - if logger.verbose: - self.errmsg += "\nAnd error: " + errmsg - else: - self.errmsg += "; " + errmsg - if code is not None: + if code: + if errmsg is not None: + if self.errmsg is None: + self.errmsg = errmsg + elif errmsg not in self.errmsg: + if logger.verbose: + self.errmsg += "\nAnd error: " + errmsg + else: + self.errmsg += "; " + errmsg self.exit_code = code or self.exit_code @contextmanager @@ -888,7 +889,7 @@ def execute(self, compiled=None, path=None, use_eval=False, allow_show=True): if no_str_code is not None: result = mypy_builtin_regex.search(no_str_code) if result: - logger.warn("found mypy-only built-in " + repr(result.group(0)) + "; pass --mypy to use mypy-only built-ins at the interpreter") + logger.warn("found type-checking-only built-in " + repr(result.group(0)) + "; pass --mypy to use such built-ins at the interpreter") else: # header is included compiled = rem_encoding(compiled) @@ -935,10 +936,11 @@ def set_mypy_args(self, mypy_args=None): if mypy_args is None: self.mypy_args = None - elif mypy_install_arg in mypy_args: + stub_dir = set_mypy_path() + + if mypy_install_arg in mypy_args: if mypy_args != [mypy_install_arg]: raise CoconutException("'--mypy install' cannot be used alongside other --mypy arguments") - stub_dir = set_mypy_path() logger.show_sig("Successfully installed MyPy stubs into " + repr(stub_dir)) self.mypy_args = None @@ -968,10 +970,15 @@ def set_mypy_args(self, mypy_args=None): logger.log("MyPy args:", self.mypy_args) self.mypy_errs = [] + def enable_pyright(self): + """Enable the use of Pyright for type-checking.""" + update_pyright_config() + self.pyright = True + def run_type_checking(self, paths=(), code=None): """Run type-checking on the given paths / code.""" if self.mypy_args is not None: - set_mypy_path() + set_mypy_path(ensure_stubs=False) from coconut.command.mypy import mypy_run args = list(paths) + self.mypy_args if code is not None: # interpreter @@ -996,9 +1003,8 @@ def run_type_checking(self, paths=(), code=None): logger.printerr(line) self.mypy_errs.append(line) if self.pyright: - config_file = update_pyright_config() if code is not None: - logger.warn("--pyright only works on files, not code snippets or at the interpreter") + logger.warn("--pyright only works on files, not code snippets or at the interpreter (use --mypy instead)") if paths: try: from pyright import main @@ -1007,8 +1013,8 @@ def run_type_checking(self, paths=(), code=None): "coconut --pyright requires Pyright", extra="run '{python} -m pip install coconut[pyright]' to fix".format(python=sys.executable), ) - args = ["--project", config_file, "--pythonversion", self.type_checking_version] + list(paths) - main(args) + args = ["--project", pyright_config_file, "--pythonversion", self.type_checking_version] + list(paths) + self.register_exit_code(main(args), errmsg="Pyright error") def run_silent_cmd(self, *args): """Same as run_cmd$(show_output=logger.verbose).""" diff --git a/coconut/command/util.py b/coconut/command/util.py index 6198d3cf3..dec6c7228 100644 --- a/coconut/command/util.py +++ b/coconut/command/util.py @@ -488,10 +488,12 @@ def set_env_var(name, value): os.environ[py_str(name)] = py_str(value) -def set_mypy_path(): +def set_mypy_path(ensure_stubs=True): """Put Coconut stubs in MYPYPATH.""" + if ensure_stubs: + install_stubs() # mypy complains about the path if we don't use / over \ - install_dir = install_stubs().replace(os.sep, "/") + install_dir = installed_stub_dir.replace(os.sep, "/") original = os.getenv(mypy_path_env_var) if original is None: new_mypy_path = install_dir diff --git a/coconut/tests/__main__.py b/coconut/tests/__main__.py index 649ac82ed..ea35cb527 100644 --- a/coconut/tests/__main__.py +++ b/coconut/tests/__main__.py @@ -44,12 +44,13 @@ def main(args=None): # compile everything print("Compiling Coconut test suite with args %r and agnostic_target=%r." % (args, agnostic_target)) + type_checking = "--mypy" in args or "--pyright" in args comp_all( args, agnostic_target=agnostic_target, - expect_retcode=0 if "--mypy" not in args else None, + expect_retcode=0 if not type_checking else None, check_errors="--verbose" not in args, - ignore_output=WINDOWS and "--mypy" not in args, + ignore_output=WINDOWS and not type_checking, ) diff --git a/coconut/tests/main_test.py b/coconut/tests/main_test.py index f1451e1e3..07b3a04c2 100644 --- a/coconut/tests/main_test.py +++ b/coconut/tests/main_test.py @@ -154,6 +154,8 @@ def pexpect(p, out): ignore_error_lines_with = ( # ignore SyntaxWarnings containing assert_raises or raise "raise", + # ignore Pyright errors + " - error: ", ) mypy_snip = "a: str = count()[0]" diff --git a/coconut/tests/src/cocotest/agnostic/primary_2.coco b/coconut/tests/src/cocotest/agnostic/primary_2.coco index 902ff5f0b..e95fa4c61 100644 --- a/coconut/tests/src/cocotest/agnostic/primary_2.coco +++ b/coconut/tests/src/cocotest/agnostic/primary_2.coco @@ -465,7 +465,7 @@ def primary_test_2() -> bool: "{x}" """ == '\n"2"\n' assert f"\{1}" == "\\1" - assert f''' '{1}' ''' == " 1 " + assert f''' '{1}' ''' == " '1' " with process_map.multiple_sequential_calls(): # type: ignore assert map((+), range(3), range(4)$[:-1], strict=True) |> list == [0, 2, 4] == process_map((+), range(3), range(4)$[:-1], strict=True) |> list # type: ignore