Skip to content

Commit b54bfb4

Browse files
authored
Merge branch 'main' into call_list_append
2 parents 1570b41 + 27bd082 commit b54bfb4

File tree

15 files changed

+463
-29
lines changed

15 files changed

+463
-29
lines changed

Include/internal/pycore_opcode_metadata.h

Lines changed: 0 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/_pydatetime.py

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,7 @@ def _parse_isoformat_time(tstr):
467467
hour, minute, second, microsecond = time_comps
468468
became_next_day = False
469469
error_from_components = False
470+
error_from_tz = None
470471
if (hour == 24):
471472
if all(time_comp == 0 for time_comp in time_comps[1:]):
472473
hour = 0
@@ -500,14 +501,22 @@ def _parse_isoformat_time(tstr):
500501
else:
501502
tzsign = -1 if tstr[tz_pos - 1] == '-' else 1
502503

503-
td = timedelta(hours=tz_comps[0], minutes=tz_comps[1],
504-
seconds=tz_comps[2], microseconds=tz_comps[3])
505-
506-
tzi = timezone(tzsign * td)
504+
try:
505+
# This function is intended to validate datetimes, but because
506+
# we restrict time zones to ±24h, it serves here as well.
507+
_check_time_fields(hour=tz_comps[0], minute=tz_comps[1],
508+
second=tz_comps[2], microsecond=tz_comps[3],
509+
fold=0)
510+
except ValueError as e:
511+
error_from_tz = e
512+
else:
513+
td = timedelta(hours=tz_comps[0], minutes=tz_comps[1],
514+
seconds=tz_comps[2], microseconds=tz_comps[3])
515+
tzi = timezone(tzsign * td)
507516

508517
time_comps.append(tzi)
509518

510-
return time_comps, became_next_day, error_from_components
519+
return time_comps, became_next_day, error_from_components, error_from_tz
511520

512521
# tuple[int, int, int] -> tuple[int, int, int] version of date.fromisocalendar
513522
def _isoweek_to_gregorian(year, week, day):
@@ -1633,9 +1642,21 @@ def fromisoformat(cls, time_string):
16331642
time_string = time_string.removeprefix('T')
16341643

16351644
try:
1636-
return cls(*_parse_isoformat_time(time_string)[0])
1637-
except Exception:
1638-
raise ValueError(f'Invalid isoformat string: {time_string!r}')
1645+
time_components, _, error_from_components, error_from_tz = (
1646+
_parse_isoformat_time(time_string)
1647+
)
1648+
except ValueError:
1649+
raise ValueError(
1650+
f'Invalid isoformat string: {time_string!r}') from None
1651+
else:
1652+
if error_from_tz:
1653+
raise error_from_tz
1654+
if error_from_components:
1655+
raise ValueError(
1656+
"Minute, second, and microsecond must be 0 when hour is 24"
1657+
)
1658+
1659+
return cls(*time_components)
16391660

16401661
def strftime(self, format):
16411662
"""Format using strftime(). The date part of the timestamp passed
@@ -1947,11 +1968,16 @@ def fromisoformat(cls, date_string):
19471968

19481969
if tstr:
19491970
try:
1950-
time_components, became_next_day, error_from_components = _parse_isoformat_time(tstr)
1971+
(time_components,
1972+
became_next_day,
1973+
error_from_components,
1974+
error_from_tz) = _parse_isoformat_time(tstr)
19511975
except ValueError:
19521976
raise ValueError(
19531977
f'Invalid isoformat string: {date_string!r}') from None
19541978
else:
1979+
if error_from_tz:
1980+
raise error_from_tz
19551981
if error_from_components:
19561982
raise ValueError("minute, second, and microsecond must be 0 when hour is 24")
19571983

Lib/test/datetimetester.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3571,6 +3571,10 @@ def test_fromisoformat_fails_datetime(self):
35713571
'2009-04-19T12:30:45.400 +02:30', # Space between ms and timezone (gh-130959)
35723572
'2009-04-19T12:30:45.400 ', # Trailing space (gh-130959)
35733573
'2009-04-19T12:30:45. 400', # Space before fraction (gh-130959)
3574+
'2009-04-19T12:30:45+00:90:00', # Time zone field out from range
3575+
'2009-04-19T12:30:45+00:00:90', # Time zone field out from range
3576+
'2009-04-19T12:30:45-00:90:00', # Time zone field out from range
3577+
'2009-04-19T12:30:45-00:00:90', # Time zone field out from range
35743578
]
35753579

35763580
for bad_str in bad_strs:
@@ -4795,6 +4799,11 @@ def test_fromisoformat_fails(self):
47954799
'12:30:45.400 +02:30', # Space between ms and timezone (gh-130959)
47964800
'12:30:45.400 ', # Trailing space (gh-130959)
47974801
'12:30:45. 400', # Space before fraction (gh-130959)
4802+
'24:00:00.000001', # Has non-zero microseconds on 24:00
4803+
'24:00:01.000000', # Has non-zero seconds on 24:00
4804+
'24:01:00.000000', # Has non-zero minutes on 24:00
4805+
'12:30:45+00:90:00', # Time zone field out from range
4806+
'12:30:45+00:00:90', # Time zone field out from range
47984807
]
47994808

48004809
for bad_str in bad_strs:

Lib/test/test_capi/test_opt.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1974,6 +1974,122 @@ def testfunc(n):
19741974
self.assertIn("_GUARD_NOS_LIST", uops)
19751975
self.assertIn("_GUARD_CALLABLE_LIST_APPEND", uops)
19761976

1977+
def test_call_isinstance_is_true(self):
1978+
def testfunc(n):
1979+
x = 0
1980+
for _ in range(n):
1981+
y = isinstance(42, int)
1982+
if y:
1983+
x += 1
1984+
return x
1985+
1986+
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
1987+
self.assertEqual(res, TIER2_THRESHOLD)
1988+
self.assertIsNotNone(ex)
1989+
uops = get_opnames(ex)
1990+
self.assertIn("_CALL_ISINSTANCE", uops)
1991+
self.assertNotIn("_TO_BOOL_BOOL", uops)
1992+
self.assertNotIn("_GUARD_IS_TRUE_POP", uops)
1993+
1994+
def test_call_isinstance_is_false(self):
1995+
def testfunc(n):
1996+
x = 0
1997+
for _ in range(n):
1998+
y = isinstance(42, str)
1999+
if not y:
2000+
x += 1
2001+
return x
2002+
2003+
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
2004+
self.assertEqual(res, TIER2_THRESHOLD)
2005+
self.assertIsNotNone(ex)
2006+
uops = get_opnames(ex)
2007+
self.assertIn("_CALL_ISINSTANCE", uops)
2008+
self.assertNotIn("_TO_BOOL_BOOL", uops)
2009+
self.assertNotIn("_GUARD_IS_FALSE_POP", uops)
2010+
2011+
def test_call_isinstance_subclass(self):
2012+
def testfunc(n):
2013+
x = 0
2014+
for _ in range(n):
2015+
y = isinstance(True, int)
2016+
if y:
2017+
x += 1
2018+
return x
2019+
2020+
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
2021+
self.assertEqual(res, TIER2_THRESHOLD)
2022+
self.assertIsNotNone(ex)
2023+
uops = get_opnames(ex)
2024+
self.assertIn("_CALL_ISINSTANCE", uops)
2025+
self.assertNotIn("_TO_BOOL_BOOL", uops)
2026+
self.assertNotIn("_GUARD_IS_TRUE_POP", uops)
2027+
2028+
def test_call_isinstance_unknown_object(self):
2029+
def testfunc(n):
2030+
x = 0
2031+
for _ in range(n):
2032+
# The optimizer doesn't know the return type here:
2033+
bar = eval("42")
2034+
# This will only narrow to bool:
2035+
y = isinstance(bar, int)
2036+
if y:
2037+
x += 1
2038+
return x
2039+
2040+
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
2041+
self.assertEqual(res, TIER2_THRESHOLD)
2042+
self.assertIsNotNone(ex)
2043+
uops = get_opnames(ex)
2044+
self.assertIn("_CALL_ISINSTANCE", uops)
2045+
self.assertNotIn("_TO_BOOL_BOOL", uops)
2046+
self.assertIn("_GUARD_IS_TRUE_POP", uops)
2047+
2048+
def test_call_isinstance_tuple_of_classes(self):
2049+
def testfunc(n):
2050+
x = 0
2051+
for _ in range(n):
2052+
# A tuple of classes is currently not optimized,
2053+
# so this is only narrowed to bool:
2054+
y = isinstance(42, (int, str))
2055+
if y:
2056+
x += 1
2057+
return x
2058+
2059+
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
2060+
self.assertEqual(res, TIER2_THRESHOLD)
2061+
self.assertIsNotNone(ex)
2062+
uops = get_opnames(ex)
2063+
self.assertIn("_CALL_ISINSTANCE", uops)
2064+
self.assertNotIn("_TO_BOOL_BOOL", uops)
2065+
self.assertIn("_GUARD_IS_TRUE_POP", uops)
2066+
2067+
def test_call_isinstance_metaclass(self):
2068+
class EvenNumberMeta(type):
2069+
def __instancecheck__(self, number):
2070+
return number % 2 == 0
2071+
2072+
class EvenNumber(metaclass=EvenNumberMeta):
2073+
pass
2074+
2075+
def testfunc(n):
2076+
x = 0
2077+
for _ in range(n):
2078+
# Only narrowed to bool
2079+
y = isinstance(42, EvenNumber)
2080+
if y:
2081+
x += 1
2082+
return x
2083+
2084+
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
2085+
self.assertEqual(res, TIER2_THRESHOLD)
2086+
self.assertIsNotNone(ex)
2087+
uops = get_opnames(ex)
2088+
self.assertIn("_CALL_ISINSTANCE", uops)
2089+
self.assertNotIn("_TO_BOOL_BOOL", uops)
2090+
self.assertIn("_GUARD_IS_TRUE_POP", uops)
2091+
2092+
19772093
def global_identity(x):
19782094
return x
19792095

0 commit comments

Comments
 (0)