gh-103492: Clarify SyntaxWarning with literal comparison (#103493)

This commit is contained in:
Shantanu 2023-04-24 15:42:57 -06:00 committed by GitHub
parent 79ae019164
commit ae25855045
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 34 additions and 16 deletions

View file

@ -277,7 +277,7 @@ class CodeopTests(unittest.TestCase):
def test_warning(self):
# Test that the warning is only returned once.
with warnings_helper.check_warnings(
('"is" with a literal', SyntaxWarning),
('"is" with \'str\' literal', SyntaxWarning),
("invalid escape sequence", SyntaxWarning),
) as w:
compile_command(r"'\e' is 0")

View file

@ -236,12 +236,9 @@ class TokenTests(unittest.TestCase):
check(f"[{num}for x in ()]")
check(f"{num}spam", error=True)
with self.assertWarnsRegex(SyntaxWarning, r'invalid \w+ literal'):
compile(f"{num}is x", "<testcase>", "eval")
with warnings.catch_warnings():
warnings.filterwarnings('ignore', '"is" with a literal',
SyntaxWarning)
with self.assertWarnsRegex(SyntaxWarning,
r'invalid \w+ literal'):
compile(f"{num}is x", "<testcase>", "eval")
warnings.simplefilter('error', SyntaxWarning)
with self.assertRaisesRegex(SyntaxError,
r'invalid \w+ literal'):
@ -1467,14 +1464,22 @@ class GrammarTests(unittest.TestCase):
if 1 < 1 > 1 == 1 >= 1 <= 1 != 1 in 1 not in x is x is not x: pass
def test_comparison_is_literal(self):
def check(test, msg='"is" with a literal'):
def check(test, msg):
self.check_syntax_warning(test, msg)
check('x is 1')
check('x is "thing"')
check('1 is x')
check('x is y is 1')
check('x is not 1', '"is not" with a literal')
check('x is 1', '"is" with \'int\' literal')
check('x is "thing"', '"is" with \'str\' literal')
check('1 is x', '"is" with \'int\' literal')
check('x is y is 1', '"is" with \'int\' literal')
check('x is not 1', '"is not" with \'int\' literal')
check('x is not (1, 2)', '"is not" with \'tuple\' literal')
check('(1, 2) is not x', '"is not" with \'tuple\' literal')
check('None is 1', '"is" with \'int\' literal')
check('1 is None', '"is" with \'int\' literal')
check('x == 3 is y', '"is" with \'int\' literal')
check('x == "thing" is y', '"is" with \'str\' literal')
with warnings.catch_warnings():
warnings.simplefilter('error', SyntaxWarning)
@ -1482,6 +1487,10 @@ class GrammarTests(unittest.TestCase):
compile('x is False', '<testcase>', 'exec')
compile('x is True', '<testcase>', 'exec')
compile('x is ...', '<testcase>', 'exec')
compile('None is x', '<testcase>', 'exec')
compile('False is x', '<testcase>', 'exec')
compile('True is x', '<testcase>', 'exec')
compile('... is x', '<testcase>', 'exec')
def test_warn_missed_comma(self):
def check(test):

View file

@ -0,0 +1 @@
Clarify :exc:`SyntaxWarning` with literal ``is`` comparison by specifying which literal is problematic, since comparisons using ``is`` with e.g. None and bool literals are idiomatic.

View file

@ -2269,6 +2269,8 @@ check_is_arg(expr_ty e)
|| value == Py_Ellipsis);
}
static PyTypeObject * infer_type(expr_ty e);
/* Check operands of identity checks ("is" and "is not").
Emit a warning if any operand is a constant except named singletons.
*/
@ -2277,19 +2279,25 @@ check_compare(struct compiler *c, expr_ty e)
{
Py_ssize_t i, n;
bool left = check_is_arg(e->v.Compare.left);
expr_ty left_expr = e->v.Compare.left;
n = asdl_seq_LEN(e->v.Compare.ops);
for (i = 0; i < n; i++) {
cmpop_ty op = (cmpop_ty)asdl_seq_GET(e->v.Compare.ops, i);
bool right = check_is_arg((expr_ty)asdl_seq_GET(e->v.Compare.comparators, i));
expr_ty right_expr = (expr_ty)asdl_seq_GET(e->v.Compare.comparators, i);
bool right = check_is_arg(right_expr);
if (op == Is || op == IsNot) {
if (!right || !left) {
const char *msg = (op == Is)
? "\"is\" with a literal. Did you mean \"==\"?"
: "\"is not\" with a literal. Did you mean \"!=\"?";
return compiler_warn(c, LOC(e), msg);
? "\"is\" with '%.200s' literal. Did you mean \"==\"?"
: "\"is not\" with '%.200s' literal. Did you mean \"!=\"?";
expr_ty literal = !left ? left_expr : right_expr;
return compiler_warn(
c, LOC(e), msg, infer_type(literal)->tp_name
);
}
}
left = right;
left_expr = right_expr;
}
return SUCCESS;
}