summaryrefslogtreecommitdiffstats
path: root/Tools/scripts/fixdiv.py
diff options
context:
space:
mode:
Diffstat (limited to 'Tools/scripts/fixdiv.py')
-rwxr-xr-xTools/scripts/fixdiv.py378
1 files changed, 0 insertions, 378 deletions
diff --git a/Tools/scripts/fixdiv.py b/Tools/scripts/fixdiv.py
deleted file mode 100755
index df7c481..0000000
--- a/Tools/scripts/fixdiv.py
+++ /dev/null
@@ -1,378 +0,0 @@
-#! /usr/bin/env python3
-
-"""fixdiv - tool to fix division operators.
-
-To use this tool, first run `python -Qwarnall yourscript.py 2>warnings'.
-This runs the script `yourscript.py' while writing warning messages
-about all uses of the classic division operator to the file
-`warnings'. The warnings look like this:
-
- <file>:<line>: DeprecationWarning: classic <type> division
-
-The warnings are written to stderr, so you must use `2>' for the I/O
-redirect. I know of no way to redirect stderr on Windows in a DOS
-box, so you will have to modify the script to set sys.stderr to some
-kind of log file if you want to do this on Windows.
-
-The warnings are not limited to the script; modules imported by the
-script may also trigger warnings. In fact a useful technique is to
-write a test script specifically intended to exercise all code in a
-particular module or set of modules.
-
-Then run `python fixdiv.py warnings'. This first reads the warnings,
-looking for classic division warnings, and sorts them by file name and
-line number. Then, for each file that received at least one warning,
-it parses the file and tries to match the warnings up to the division
-operators found in the source code. If it is successful, it writes
-its findings to stdout, preceded by a line of dashes and a line of the
-form:
-
- Index: <file>
-
-If the only findings found are suggestions to change a / operator into
-a // operator, the output is acceptable input for the Unix 'patch'
-program.
-
-Here are the possible messages on stdout (N stands for a line number):
-
-- A plain-diff-style change ('NcN', a line marked by '<', a line
- containing '---', and a line marked by '>'):
-
- A / operator was found that should be changed to //. This is the
- recommendation when only int and/or long arguments were seen.
-
-- 'True division / operator at line N' and a line marked by '=':
-
- A / operator was found that can remain unchanged. This is the
- recommendation when only float and/or complex arguments were seen.
-
-- 'Ambiguous / operator (..., ...) at line N', line marked by '?':
-
- A / operator was found for which int or long as well as float or
- complex arguments were seen. This is highly unlikely; if it occurs,
- you may have to restructure the code to keep the classic semantics,
- or maybe you don't care about the classic semantics.
-
-- 'No conclusive evidence on line N', line marked by '*':
-
- A / operator was found for which no warnings were seen. This could
- be code that was never executed, or code that was only executed
- with user-defined objects as arguments. You will have to
- investigate further. Note that // can be overloaded separately from
- /, using __floordiv__. True division can also be separately
- overloaded, using __truediv__. Classic division should be the same
- as either of those. (XXX should I add a warning for division on
- user-defined objects, to disambiguate this case from code that was
- never executed?)
-
-- 'Phantom ... warnings for line N', line marked by '*':
-
- A warning was seen for a line not containing a / operator. The most
- likely cause is a warning about code executed by 'exec' or eval()
- (see note below), or an indirect invocation of the / operator, for
- example via the div() function in the operator module. It could
- also be caused by a change to the file between the time the test
- script was run to collect warnings and the time fixdiv was run.
-
-- 'More than one / operator in line N'; or
- 'More than one / operator per statement in lines N-N':
-
- The scanner found more than one / operator on a single line, or in a
- statement split across multiple lines. Because the warnings
- framework doesn't (and can't) show the offset within the line, and
- the code generator doesn't always give the correct line number for
- operations in a multi-line statement, we can't be sure whether all
- operators in the statement were executed. To be on the safe side,
- by default a warning is issued about this case. In practice, these
- cases are usually safe, and the -m option suppresses these warning.
-
-- 'Can't find the / operator in line N', line marked by '*':
-
- This really shouldn't happen. It means that the tokenize module
- reported a '/' operator but the line it returns didn't contain a '/'
- character at the indicated position.
-
-- 'Bad warning for line N: XYZ', line marked by '*':
-
- This really shouldn't happen. It means that a 'classic XYZ
- division' warning was read with XYZ being something other than
- 'int', 'long', 'float', or 'complex'.
-
-Notes:
-
-- The augmented assignment operator /= is handled the same way as the
- / operator.
-
-- This tool never looks at the // operator; no warnings are ever
- generated for use of this operator.
-
-- This tool never looks at the / operator when a future division
- statement is in effect; no warnings are generated in this case, and
- because the tool only looks at files for which at least one classic
- division warning was seen, it will never look at files containing a
- future division statement.
-
-- Warnings may be issued for code not read from a file, but executed
- using the exec() or eval() functions. These may have
- <string> in the filename position, in which case the fixdiv script
- will attempt and fail to open a file named '<string>' and issue a
- warning about this failure; or these may be reported as 'Phantom'
- warnings (see above). You're on your own to deal with these. You
- could make all recommended changes and add a future division
- statement to all affected files, and then re-run the test script; it
- should not issue any warnings. If there are any, and you have a
- hard time tracking down where they are generated, you can use the
- -Werror option to force an error instead of a first warning,
- generating a traceback.
-
-- The tool should be run from the same directory as that from which
- the original script was run, otherwise it won't be able to open
- files given by relative pathnames.
-"""
-
-import sys
-import getopt
-import re
-import tokenize
-
-multi_ok = 0
-
-def main():
- try:
- opts, args = getopt.getopt(sys.argv[1:], "hm")
- except getopt.error as msg:
- usage(msg)
- return 2
- for o, a in opts:
- if o == "-h":
- print(__doc__)
- return
- if o == "-m":
- global multi_ok
- multi_ok = 1
- if not args:
- usage("at least one file argument is required")
- return 2
- if args[1:]:
- sys.stderr.write("%s: extra file arguments ignored\n", sys.argv[0])
- warnings = readwarnings(args[0])
- if warnings is None:
- return 1
- files = list(warnings.keys())
- if not files:
- print("No classic division warnings read from", args[0])
- return
- files.sort()
- exit = None
- for filename in files:
- x = process(filename, warnings[filename])
- exit = exit or x
- return exit
-
-def usage(msg):
- sys.stderr.write("%s: %s\n" % (sys.argv[0], msg))
- sys.stderr.write("Usage: %s [-m] warnings\n" % sys.argv[0])
- sys.stderr.write("Try `%s -h' for more information.\n" % sys.argv[0])
-
-PATTERN = (r"^(.+?):(\d+): DeprecationWarning: "
- r"classic (int|long|float|complex) division$")
-
-def readwarnings(warningsfile):
- prog = re.compile(PATTERN)
- warnings = {}
- try:
- f = open(warningsfile)
- except IOError as msg:
- sys.stderr.write("can't open: %s\n" % msg)
- return
- with f:
- while 1:
- line = f.readline()
- if not line:
- break
- m = prog.match(line)
- if not m:
- if line.find("division") >= 0:
- sys.stderr.write("Warning: ignored input " + line)
- continue
- filename, lineno, what = m.groups()
- list = warnings.get(filename)
- if list is None:
- warnings[filename] = list = []
- list.append((int(lineno), sys.intern(what)))
- return warnings
-
-def process(filename, list):
- print("-"*70)
- assert list # if this fails, readwarnings() is broken
- try:
- fp = open(filename)
- except IOError as msg:
- sys.stderr.write("can't open: %s\n" % msg)
- return 1
- with fp:
- print("Index:", filename)
- f = FileContext(fp)
- list.sort()
- index = 0 # list[:index] has been processed, list[index:] is still to do
- g = tokenize.generate_tokens(f.readline)
- while 1:
- startlineno, endlineno, slashes = lineinfo = scanline(g)
- if startlineno is None:
- break
- assert startlineno <= endlineno is not None
- orphans = []
- while index < len(list) and list[index][0] < startlineno:
- orphans.append(list[index])
- index += 1
- if orphans:
- reportphantomwarnings(orphans, f)
- warnings = []
- while index < len(list) and list[index][0] <= endlineno:
- warnings.append(list[index])
- index += 1
- if not slashes and not warnings:
- pass
- elif slashes and not warnings:
- report(slashes, "No conclusive evidence")
- elif warnings and not slashes:
- reportphantomwarnings(warnings, f)
- else:
- if len(slashes) > 1:
- if not multi_ok:
- rows = []
- lastrow = None
- for (row, col), line in slashes:
- if row == lastrow:
- continue
- rows.append(row)
- lastrow = row
- assert rows
- if len(rows) == 1:
- print("*** More than one / operator in line", rows[0])
- else:
- print("*** More than one / operator per statement", end=' ')
- print("in lines %d-%d" % (rows[0], rows[-1]))
- intlong = []
- floatcomplex = []
- bad = []
- for lineno, what in warnings:
- if what in ("int", "long"):
- intlong.append(what)
- elif what in ("float", "complex"):
- floatcomplex.append(what)
- else:
- bad.append(what)
- lastrow = None
- for (row, col), line in slashes:
- if row == lastrow:
- continue
- lastrow = row
- line = chop(line)
- if line[col:col+1] != "/":
- print("*** Can't find the / operator in line %d:" % row)
- print("*", line)
- continue
- if bad:
- print("*** Bad warning for line %d:" % row, bad)
- print("*", line)
- elif intlong and not floatcomplex:
- print("%dc%d" % (row, row))
- print("<", line)
- print("---")
- print(">", line[:col] + "/" + line[col:])
- elif floatcomplex and not intlong:
- print("True division / operator at line %d:" % row)
- print("=", line)
- elif intlong and floatcomplex:
- print("*** Ambiguous / operator (%s, %s) at line %d:" %
- ("|".join(intlong), "|".join(floatcomplex), row))
- print("?", line)
-
-def reportphantomwarnings(warnings, f):
- blocks = []
- lastrow = None
- lastblock = None
- for row, what in warnings:
- if row != lastrow:
- lastblock = [row]
- blocks.append(lastblock)
- lastblock.append(what)
- for block in blocks:
- row = block[0]
- whats = "/".join(block[1:])
- print("*** Phantom %s warnings for line %d:" % (whats, row))
- f.report(row, mark="*")
-
-def report(slashes, message):
- lastrow = None
- for (row, col), line in slashes:
- if row != lastrow:
- print("*** %s on line %d:" % (message, row))
- print("*", chop(line))
- lastrow = row
-
-class FileContext:
- def __init__(self, fp, window=5, lineno=1):
- self.fp = fp
- self.window = 5
- self.lineno = 1
- self.eoflookahead = 0
- self.lookahead = []
- self.buffer = []
- def fill(self):
- while len(self.lookahead) < self.window and not self.eoflookahead:
- line = self.fp.readline()
- if not line:
- self.eoflookahead = 1
- break
- self.lookahead.append(line)
- def readline(self):
- self.fill()
- if not self.lookahead:
- return ""
- line = self.lookahead.pop(0)
- self.buffer.append(line)
- self.lineno += 1
- return line
- def __getitem__(self, index):
- self.fill()
- bufstart = self.lineno - len(self.buffer)
- lookend = self.lineno + len(self.lookahead)
- if bufstart <= index < self.lineno:
- return self.buffer[index - bufstart]
- if self.lineno <= index < lookend:
- return self.lookahead[index - self.lineno]
- raise KeyError
- def report(self, first, last=None, mark="*"):
- if last is None:
- last = first
- for i in range(first, last+1):
- try:
- line = self[first]
- except KeyError:
- line = "<missing line>"
- print(mark, chop(line))
-
-def scanline(g):
- slashes = []
- startlineno = None
- endlineno = None
- for type, token, start, end, line in g:
- endlineno = end[0]
- if startlineno is None:
- startlineno = endlineno
- if token in ("/", "/="):
- slashes.append((start, line))
- if type == tokenize.NEWLINE:
- break
- return startlineno, endlineno, slashes
-
-def chop(line):
- if line.endswith("\n"):
- return line[:-1]
- else:
- return line
-
-if __name__ == "__main__":
- sys.exit(main())