Skip to content

Commit a355529

Browse files
authored
Merge pull request #529 from python/main
Sync Fork from Upstream Repo
2 parents be4c122 + 8ce3008 commit a355529

File tree

14 files changed

+375
-357
lines changed

14 files changed

+375
-357
lines changed

Doc/library/traceback.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,14 @@ capture data for later printing in a lightweight fashion.
353353
.. versionchanged:: 3.6
354354
Long sequences of repeated frames are now abbreviated.
355355

356+
.. method:: format_frame(frame)
357+
358+
Returns a string for printing one of the frames involved in the stack.
359+
This method gets called for each frame object to be printed in the
360+
:class:`StackSummary`.
361+
362+
.. versionadded:: 3.11
363+
356364

357365
:class:`FrameSummary` Objects
358366
-----------------------------

Grammar/python.gram

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -666,7 +666,6 @@ await_primary[expr_ty] (memo):
666666
| AWAIT a=primary { CHECK_VERSION(expr_ty, 5, "Await expressions are", _PyAST_Await(a, EXTRA)) }
667667
| primary
668668
primary[expr_ty]:
669-
| invalid_primary # must be before 'primay genexp' because of invalid_genexp
670669
| a=primary '.' b=NAME { _PyAST_Attribute(a, b->v.Name.id, Load, EXTRA) }
671670
| a=primary b=genexp { _PyAST_Call(a, CHECK(asdl_expr_seq*, (asdl_expr_seq*)_PyPegen_singleton_seq(p, b)), NULL, EXTRA) }
672671
| a=primary '(' b=[arguments] ')' {
@@ -893,8 +892,6 @@ invalid_del_stmt:
893892
RAISE_SYNTAX_ERROR_INVALID_TARGET(DEL_TARGETS, a) }
894893
invalid_block:
895894
| NEWLINE !INDENT { RAISE_INDENTATION_ERROR("expected an indented block") }
896-
invalid_primary:
897-
| primary a='{' { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "invalid syntax") }
898895
invalid_comprehension:
899896
| ('[' | '(' | '{') a=starred_expression for_if_clauses {
900897
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "iterable unpacking cannot be used in comprehension") }

Lib/test/test_exceptions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ def testSyntaxErrorOffset(self):
209209
check(b'Python = "\xcf\xb3\xf2\xee\xed" +', 1, 18)
210210
check('x = "a', 1, 5)
211211
check('lambda x: x = 2', 1, 1)
212-
check('f{a + b + c}', 1, 2)
212+
check('f{a + b + c}', 1, 1)
213213
check('[file for str(file) in []\n])', 2, 2)
214214
check('a = « hello » « world »', 1, 5)
215215
check('[\nfile\nfor str(file)\nin\n[]\n]', 3, 5)

Lib/test/test_syntax.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1305,7 +1305,7 @@ def test_expression_with_assignment(self):
13051305
)
13061306

13071307
def test_curly_brace_after_primary_raises_immediately(self):
1308-
self._check_error("f{", "invalid syntax", mode="single")
1308+
self._check_error("f{}", "invalid syntax", mode="single")
13091309

13101310
def test_assign_call(self):
13111311
self._check_error("f() = 1", "assign")

Lib/test/test_threading.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1604,6 +1604,31 @@ def test_interrupt_main_invalid_signal(self):
16041604
self.assertRaises(ValueError, _thread.interrupt_main, signal.NSIG)
16051605
self.assertRaises(ValueError, _thread.interrupt_main, 1000000)
16061606

1607+
@threading_helper.reap_threads
1608+
def test_can_interrupt_tight_loops(self):
1609+
cont = True
1610+
started = False
1611+
iterations = 100_000_000
1612+
1613+
def worker():
1614+
nonlocal iterations
1615+
nonlocal started
1616+
started = True
1617+
while cont:
1618+
if iterations:
1619+
iterations -= 1
1620+
else:
1621+
return
1622+
pass
1623+
1624+
t = threading.Thread(target=worker)
1625+
t.start()
1626+
while not started:
1627+
pass
1628+
cont = False
1629+
t.join()
1630+
self.assertNotEqual(iterations, 0)
1631+
16071632

16081633
class AtexitTests(unittest.TestCase):
16091634

Lib/test/test_traceback.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1429,6 +1429,21 @@ def some_inner(k, v):
14291429
' v = 4\n' % (__file__, some_inner.__code__.co_firstlineno + 3)
14301430
], s.format())
14311431

1432+
def test_custom_format_frame(self):
1433+
class CustomStackSummary(traceback.StackSummary):
1434+
def format_frame(self, frame):
1435+
return f'{frame.filename}:{frame.lineno}'
1436+
1437+
def some_inner():
1438+
return CustomStackSummary.extract(
1439+
traceback.walk_stack(None), limit=1)
1440+
1441+
s = some_inner()
1442+
self.assertEqual(
1443+
s.format(),
1444+
[f'{__file__}:{some_inner.__code__.co_firstlineno + 1}'])
1445+
1446+
14321447
class TestTracebackException(unittest.TestCase):
14331448

14341449
def test_smoke(self):

Lib/test/test_types.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -623,7 +623,7 @@ def test_or_types_operator(self):
623623
self.assertEqual(None | typing.List[int], typing.Union[None, typing.List[int]])
624624
self.assertEqual(str | float | int | complex | int, (int | str) | (float | complex))
625625
self.assertEqual(typing.Union[str, int, typing.List[int]], str | int | typing.List[int])
626-
self.assertEqual(int | int, int)
626+
self.assertIs(int | int, int)
627627
self.assertEqual(
628628
BaseException |
629629
bool |
@@ -651,6 +651,8 @@ def test_or_types_operator(self):
651651
3 | int
652652
with self.assertRaises(TypeError):
653653
Example() | int
654+
x = int | str
655+
self.assertNotEqual(x, {})
654656
with self.assertRaises(TypeError):
655657
(int | str) < typing.Union[str, int]
656658
with self.assertRaises(TypeError):
@@ -663,6 +665,10 @@ def test_or_types_operator(self):
663665
x.__args__ = [str, int]
664666
(int | str ) == x
665667

668+
def test_hash(self):
669+
self.assertEqual(hash(int | str), hash(str | int))
670+
self.assertEqual(hash(int | str), hash(typing.Union[int, str]))
671+
666672
def test_instancecheck(self):
667673
x = int | str
668674
self.assertIsInstance(1, x)
@@ -700,6 +706,8 @@ def test_or_type_operator_with_TypeVar(self):
700706
TV = typing.TypeVar('T')
701707
assert TV | str == typing.Union[TV, str]
702708
assert str | TV == typing.Union[str, TV]
709+
self.assertIs((int | TV)[int], int)
710+
self.assertIs((TV | int)[int], int)
703711

704712
def test_union_args(self):
705713
self.assertEqual((int | str).__args__, (int, str))
@@ -717,6 +725,7 @@ def test_union_parameter_chaining(self):
717725
self.assertEqual(list[int | list[T]][str], list[int | list[str]])
718726
self.assertEqual((list[T] | list[S]).__parameters__, (T, S))
719727
self.assertEqual((list[T] | list[S])[int, T], list[int] | list[T])
728+
self.assertEqual((list[T] | list[S])[int, int], list[int])
720729

721730
def test_or_type_operator_with_forward(self):
722731
T = typing.TypeVar('T')

Lib/traceback.py

Lines changed: 44 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,48 @@ def from_list(klass, a_list):
449449
result.append(FrameSummary(filename, lineno, name, line=line))
450450
return result
451451

452+
def format_frame(self, frame):
453+
"""Format the lines for a single frame.
454+
455+
Returns a string representing one frame involved in the stack. This
456+
gets called for every frame to be printed in the stack summary.
457+
"""
458+
row = []
459+
row.append(' File "{}", line {}, in {}\n'.format(
460+
frame.filename, frame.lineno, frame.name))
461+
if frame.line:
462+
row.append(' {}\n'.format(frame.line.strip()))
463+
464+
stripped_characters = len(frame._original_line) - len(frame.line.lstrip())
465+
if frame.end_lineno == frame.lineno and frame.end_colno != 0:
466+
colno = _byte_offset_to_character_offset(frame._original_line, frame.colno)
467+
end_colno = _byte_offset_to_character_offset(frame._original_line, frame.end_colno)
468+
469+
try:
470+
anchors = _extract_caret_anchors_from_line_segment(
471+
frame._original_line[colno - 1:end_colno - 1]
472+
)
473+
except Exception:
474+
anchors = None
475+
476+
row.append(' ')
477+
row.append(' ' * (colno - stripped_characters))
478+
479+
if anchors:
480+
row.append(anchors.primary_char * (anchors.left_end_offset))
481+
row.append(anchors.secondary_char * (anchors.right_start_offset - anchors.left_end_offset))
482+
row.append(anchors.primary_char * (end_colno - colno - anchors.right_start_offset))
483+
else:
484+
row.append('^' * (end_colno - colno))
485+
486+
row.append('\n')
487+
488+
if frame.locals:
489+
for name, value in sorted(frame.locals.items()):
490+
row.append(' {name} = {value}\n'.format(name=name, value=value))
491+
492+
return ''.join(row)
493+
452494
def format(self):
453495
"""Format the stack ready for printing.
454496
@@ -483,40 +525,8 @@ def format(self):
483525
count += 1
484526
if count > _RECURSIVE_CUTOFF:
485527
continue
486-
row = []
487-
row.append(' File "{}", line {}, in {}\n'.format(
488-
frame.filename, frame.lineno, frame.name))
489-
if frame.line:
490-
row.append(' {}\n'.format(frame.line.strip()))
491-
492-
stripped_characters = len(frame._original_line) - len(frame.line.lstrip())
493-
if frame.end_lineno == frame.lineno and frame.end_colno != 0:
494-
colno = _byte_offset_to_character_offset(frame._original_line, frame.colno)
495-
end_colno = _byte_offset_to_character_offset(frame._original_line, frame.end_colno)
496-
497-
try:
498-
anchors = _extract_caret_anchors_from_line_segment(
499-
frame._original_line[colno - 1:end_colno - 1]
500-
)
501-
except Exception:
502-
anchors = None
503-
504-
row.append(' ')
505-
row.append(' ' * (colno - stripped_characters))
506-
507-
if anchors:
508-
row.append(anchors.primary_char * (anchors.left_end_offset))
509-
row.append(anchors.secondary_char * (anchors.right_start_offset - anchors.left_end_offset))
510-
row.append(anchors.primary_char * (end_colno - colno - anchors.right_start_offset))
511-
else:
512-
row.append('^' * (end_colno - colno))
513-
514-
row.append('\n')
515-
516-
if frame.locals:
517-
for name, value in sorted(frame.locals.items()):
518-
row.append(' {name} = {value}\n'.format(name=name, value=value))
519-
result.append(''.join(row))
528+
result.append(self.format_frame(frame))
529+
520530
if count > _RECURSIVE_CUTOFF:
521531
count -= _RECURSIVE_CUTOFF
522532
result.append(
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Collapse union of equal types. E.g. the result of ``int | int`` is now ``int``. Fix comparison of the union type with non-hashable objects. E.g. ``int | str == {}`` no longer raises a TypeError.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix the hash of the union type: it no longer depends on the order of
2+
arguments.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Added the :func:`StackSummary.format_frame` function in :mod:`traceback`.
2+
This allows users to customize the way individual lines are formatted in
3+
tracebacks without re-implementing logic to handle recursive tracebacks.

Objects/unionobject.c

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,13 @@ static Py_hash_t
3636
union_hash(PyObject *self)
3737
{
3838
unionobject *alias = (unionobject *)self;
39-
Py_hash_t h1 = PyObject_Hash(alias->args);
40-
if (h1 == -1) {
41-
return -1;
39+
PyObject *args = PyFrozenSet_New(alias->args);
40+
if (args == NULL) {
41+
return (Py_hash_t)-1;
4242
}
43-
return h1;
43+
Py_hash_t hash = PyObject_Hash(args);
44+
Py_DECREF(args);
45+
return hash;
4446
}
4547

4648
static int
@@ -185,9 +187,9 @@ union_richcompare(PyObject *a, PyObject *b, int op)
185187
}
186188
}
187189
} else {
188-
if (PySet_Add(b_set, b) == -1) {
189-
goto exit;
190-
}
190+
Py_DECREF(a_set);
191+
Py_DECREF(b_set);
192+
Py_RETURN_NOTIMPLEMENTED;
191193
}
192194
result = PyObject_RichCompare(a_set, b_set, op);
193195
exit:
@@ -549,17 +551,25 @@ _Py_Union(PyObject *args)
549551
}
550552
}
551553

554+
args = dedup_and_flatten_args(args);
555+
if (args == NULL) {
556+
return NULL;
557+
}
558+
if (PyTuple_GET_SIZE(args) == 1) {
559+
PyObject *result1 = PyTuple_GET_ITEM(args, 0);
560+
Py_INCREF(result1);
561+
Py_DECREF(args);
562+
return result1;
563+
}
564+
552565
result = PyObject_GC_New(unionobject, &_Py_UnionType);
553566
if (result == NULL) {
567+
Py_DECREF(args);
554568
return NULL;
555569
}
556570

557571
result->parameters = NULL;
558-
result->args = dedup_and_flatten_args(args);
572+
result->args = args;
559573
_PyObject_GC_TRACK(result);
560-
if (result->args == NULL) {
561-
Py_DECREF(result);
562-
return NULL;
563-
}
564574
return (PyObject*)result;
565575
}

0 commit comments

Comments
 (0)