blob: f66ea6b544502719fab1e39c0d39b42a3e58fed9 [file] [log] [blame]
#!/usr/bin/env python3
# Usage: find_type.py FILENAME START_LINE START_COL END_LINE END_COL MYPY_AND_ARGS
# Prints out the type of the expression in the given location if the mypy run
# succeeds cleanly. Otherwise, prints out the errors encountered.
# Note: this only works on expressions, and not assignment targets.
# Note: MYPY_AND_ARGS is should be the remainder of argv, not a single
# spaces-included argument.
# NOTE: Line numbers are 1-based; column numbers are 0-based.
#
#
# Example vim usage:
# function RevealType()
# " Set this to the command you use to run mypy on your project. Include the mypy invocation.
# let mypycmd = 'python3 -m mypy mypy --incremental'
# let [startline, startcol] = getpos("'<")[1:2]
# let [endline, endcol] = getpos("'>")[1:2]
# " Convert to 0-based column offsets
# let startcol = startcol - 1
# " Change this line to point to the find_type.py script.
# execute '!python3 /path/to/mypy/scripts/find_type.py % ' . startline . ' ' . startcol . ' ' . endline . ' ' . endcol . ' ' . mypycmd
# endfunction
# vnoremap <Leader>t :call RevealType()<CR>
#
# For an Emacs example, see misc/macs.el.
from typing import List, Tuple, Optional
import subprocess
import sys
import tempfile
import os.path
import re
REVEAL_TYPE_START = 'reveal_type('
REVEAL_TYPE_END = ')'
def update_line(line: str, s: str, pos: int) -> str:
return line[:pos] + s + line[pos:]
def run_mypy(mypy_and_args: List[str], filename: str, tmp_name: str) -> str:
proc = subprocess.run(mypy_and_args + ['--shadow-file', filename, tmp_name], stdout=subprocess.PIPE)
assert(isinstance(proc.stdout, bytes)) # Guaranteed to be true because we called run with universal_newlines=False
return proc.stdout.decode(encoding="utf-8")
def get_revealed_type(line: str, relevant_file: str, relevant_line: int) -> Optional[str]:
m = re.match(r"(.+?):(\d+): note: Revealed type is '(.*)'$", line)
if (m and
int(m.group(2)) == relevant_line and
os.path.samefile(relevant_file, m.group(1))):
return m.group(3)
else:
return None
def process_output(output: str, filename: str, start_line: int) -> Tuple[Optional[str], bool]:
error_found = False
for line in output.splitlines():
t = get_revealed_type(line, filename, start_line)
if t:
return t, error_found
elif 'error:' in line:
error_found = True
return None, True # finding no reveal_type is an error
def main():
filename, start_line_str, start_col_str, end_line_str, end_col_str, *mypy_and_args = sys.argv[1:]
start_line = int(start_line_str)
start_col = int(start_col_str)
end_line = int(end_line_str)
end_col = int(end_col_str)
with open(filename, 'r') as f:
lines = f.readlines()
lines[end_line - 1] = update_line(lines[end_line - 1], REVEAL_TYPE_END, end_col) # insert after end_col
lines[start_line - 1] = update_line(lines[start_line - 1], REVEAL_TYPE_START, start_col)
with tempfile.NamedTemporaryFile(mode='w', prefix='mypy') as tmp_f:
tmp_f.writelines(lines)
tmp_f.flush()
output = run_mypy(mypy_and_args, filename, tmp_f.name)
revealed_type, error = process_output(output, filename, start_line)
if revealed_type:
print(revealed_type)
if error:
print(output)
exit(int(error))
if __name__ == "__main__":
main()