Catching "return" and "return expr" at compile time
Attached is a context diff against the latest version of Python/compile.c that checks at compile time for functions that both return expressions or execute return statements with no expression (or equivalently, fall off the end of the function). I figured I'd post it here to get a little friendly feedback and bug discovery before shooting it off to c.l.py. I modified compile.c instead of some preexisting PyLint script because I don't know what's popular out there. On the other hand, I'm sure most people who would be interested in this sort of thing have access to the C source... The basic idea is that each straight line chunk of code is terminated one of four ways: 1. return with no expression 2. return an expression 3. raise an exception 4. fall off the end of the chunk Falling off the end of the function is obviously treated like return with no expression. (This is, after all, what motivated me to do this. ;-) This information is recorded in a new bit vector added to the struct compiling object that's carried around during the compilation. Compound statements simply aggregate the bit vectors for their various clauses in ways appropriate to their semantics. At the end of a function's compilation, the set of return bits computed up to that point tells you whether or not to spit out a warning. Note that it does nothing to recognize constant expressions. The following function will generate a warning: def f(): i = 0 while 1: i = i + 1 if i > 10: return i even though the only way to return from the function is the return statement. To get the above to shut up the compiler you'd have to do something like class CantGetHere: pass def f(): i = 0 while 1: i = i + 1 if i > 10: return i raise CantGetHere Raise statements are treated as a valid way to "return" from a function. Registering them as separate styles of returns serves effectively to turn off the "no return" bit for a block of code. Raise is compatible with either form of return, though they aren't compatible with each other. The code is run whenever a module is compiled. I didn't bother to add a new flag to the python interpreter to enable/disable warnings during compilation, though a -w switch for Python has been mentioned before. I ran the modified byte code compiler over a silly test module as well as executing ./python Lib/test/regrtest.py ./python Lib/compileall.py It uncovered some interesting programming practices and one item I think is an actual bug. In Lib/gzip.py, GzipFile._read returns EOFError at one point instead of raising it. At other points in the method it raises EOFError. There are no other return statements in the function. (I haven't taken the time/had the nerve to run it against my own Python code yet. ;-) I'm firmly of the opinion that more subtle bugs exist in the way people write functions that return and raise values than in the code that calls those functions, contrary to a few vocal folks on c.l.py who may believe otherwise. Enjoy, Skip Montanaro | http://www.mojam.com/ skip@mojam.com | http://www.musi-cal.com/~skip/ 847-971-7098 | Python: Programming the way Guido indented...
participants (9)
-
Barry A. Warsaw
-
Fred L. Drake, Jr.
-
Greg Stein
-
Greg Ward
-
Guido van Rossum
-
M.-A. Lemburg
-
Mark Hammond
-
Skip Montanaro
-
Tim Peters