Skip to content

Commit

Permalink
pythongh-103492: Clarify SyntaxWarning with literal comparison (pytho…
Browse files Browse the repository at this point in the history
  • Loading branch information
hauntsaninja committed Apr 24, 2023
1 parent 79ae019 commit ae25855
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 16 deletions.
2 changes: 1 addition & 1 deletion Lib/test/test_codeop.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ def test_filename(self):
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")
Expand Down
31 changes: 20 additions & 11 deletions Lib/test/test_grammar.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,12 +236,9 @@ def check(test, error=False):
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'):
Expand Down Expand Up @@ -1467,21 +1464,33 @@ def test_comparison(self):
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)
compile('x is None', '<testcase>', 'exec')
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):
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
16 changes: 12 additions & 4 deletions Python/compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand All @@ -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;
}
Expand Down

0 comments on commit ae25855

Please sign in to comment.