Skip to content

Commit

Permalink
gh-110309: Prune empty constant in format specs (#110320)
Browse files Browse the repository at this point in the history
Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com>
  • Loading branch information
sunmy2019 and blurb-it[bot] committed Oct 5, 2023
1 parent cc389ef commit 2cb62c6
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 10 deletions.
48 changes: 48 additions & 0 deletions Lib/test/test_fstring.py
Expand Up @@ -514,6 +514,54 @@ def test_ast_fstring_empty_format_spec(self):
self.assertEqual(type(format_spec), ast.JoinedStr)
self.assertEqual(len(format_spec.values), 0)

def test_ast_fstring_format_spec(self):
expr = "f'{1:{name}}'"

mod = ast.parse(expr)
self.assertEqual(type(mod), ast.Module)
self.assertEqual(len(mod.body), 1)

fstring = mod.body[0].value
self.assertEqual(type(fstring), ast.JoinedStr)
self.assertEqual(len(fstring.values), 1)

fv = fstring.values[0]
self.assertEqual(type(fv), ast.FormattedValue)

format_spec = fv.format_spec
self.assertEqual(type(format_spec), ast.JoinedStr)
self.assertEqual(len(format_spec.values), 1)

format_spec_value = format_spec.values[0]
self.assertEqual(type(format_spec_value), ast.FormattedValue)
self.assertEqual(format_spec_value.value.id, 'name')

expr = "f'{1:{name1}{name2}}'"

mod = ast.parse(expr)
self.assertEqual(type(mod), ast.Module)
self.assertEqual(len(mod.body), 1)

fstring = mod.body[0].value
self.assertEqual(type(fstring), ast.JoinedStr)
self.assertEqual(len(fstring.values), 1)

fv = fstring.values[0]
self.assertEqual(type(fv), ast.FormattedValue)

format_spec = fv.format_spec
self.assertEqual(type(format_spec), ast.JoinedStr)
self.assertEqual(len(format_spec.values), 2)

format_spec_value = format_spec.values[0]
self.assertEqual(type(format_spec_value), ast.FormattedValue)
self.assertEqual(format_spec_value.value.id, 'name1')

format_spec_value = format_spec.values[1]
self.assertEqual(type(format_spec_value), ast.FormattedValue)
self.assertEqual(format_spec_value.value.id, 'name2')


def test_docstring(self):
def f():
f'''Not a docstring'''
Expand Down
@@ -0,0 +1 @@
Remove unnecessary empty constant nodes in the ast of f-string specs.
40 changes: 30 additions & 10 deletions Parser/action_helpers.c
Expand Up @@ -998,18 +998,38 @@ _PyPegen_setup_full_format_spec(Parser *p, Token *colon, asdl_expr_seq *spec, in
return NULL;
}

// This is needed to keep compatibility with 3.11, where an empty format spec is parsed
// as an *empty* JoinedStr node, instead of having an empty constant in it.
if (asdl_seq_LEN(spec) == 1) {
expr_ty e = asdl_seq_GET(spec, 0);
if (e->kind == Constant_kind
&& PyUnicode_Check(e->v.Constant.value)
&& PyUnicode_GetLength(e->v.Constant.value) == 0) {
spec = _Py_asdl_expr_seq_new(0, arena);
// This is needed to keep compatibility with 3.11, where an empty format
// spec is parsed as an *empty* JoinedStr node, instead of having an empty
// constant in it.
Py_ssize_t n_items = asdl_seq_LEN(spec);
Py_ssize_t non_empty_count = 0;
for (Py_ssize_t i = 0; i < n_items; i++) {
expr_ty item = asdl_seq_GET(spec, i);
non_empty_count += !(item->kind == Constant_kind &&
PyUnicode_CheckExact(item->v.Constant.value) &&
PyUnicode_GET_LENGTH(item->v.Constant.value) == 0);
}
if (non_empty_count != n_items) {
asdl_expr_seq *resized_spec =
_Py_asdl_expr_seq_new(non_empty_count, p->arena);
if (resized_spec == NULL) {
return NULL;
}
Py_ssize_t j = 0;
for (Py_ssize_t i = 0; i < n_items; i++) {
expr_ty item = asdl_seq_GET(spec, i);
if (item->kind == Constant_kind &&
PyUnicode_CheckExact(item->v.Constant.value) &&
PyUnicode_GET_LENGTH(item->v.Constant.value) == 0) {
continue;
}
asdl_seq_SET(resized_spec, j++, item);
}
assert(j == non_empty_count);
spec = resized_spec;
}

expr_ty res = _PyAST_JoinedStr(spec, lineno, col_offset, end_lineno, end_col_offset, p->arena);
expr_ty res = _PyAST_JoinedStr(spec, lineno, col_offset, end_lineno,
end_col_offset, p->arena);
if (!res) {
return NULL;
}
Expand Down

0 comments on commit 2cb62c6

Please sign in to comment.