Skip to content

Commit 128e50f

Browse files
authored
Fix end_lineno on PyPy 3.8 (#1454)
* Fix end_lineno on PyPy 3.8 * Add CI job for PyPy 3.8
1 parent 29dcc35 commit 128e50f

File tree

7 files changed

+49
-16
lines changed

7 files changed

+49
-16
lines changed

.github/workflows/ci.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ jobs:
336336
timeout-minutes: 10
337337
strategy:
338338
matrix:
339-
python-version: ["pypy-3.6", "pypy-3.7"]
339+
python-version: ["pypy-3.6", "pypy-3.7", "pypy-3.8"]
340340
outputs:
341341
python-key: ${{ steps.generate-python-key.outputs.key }}
342342
steps:
@@ -381,7 +381,7 @@ jobs:
381381
strategy:
382382
fail-fast: false
383383
matrix:
384-
python-version: ["pypy-3.6", "pypy-3.7"]
384+
python-version: ["pypy-3.6", "pypy-3.7", "pypy-3.8"]
385385
steps:
386386
- name: Check out code from GitHub
387387
uses: actions/[email protected]

ChangeLog

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ Release date: TBA
1818

1919
Closes #1410
2020

21+
* Set ``end_lineno`` and ``end_col_offset`` attributes to ``None`` for all nodes
22+
with PyPy 3.8. PyPy 3.8 assigns these attributes inconsistently which could lead
23+
to unexpected errors. Overwriting them with ``None`` will cause a fallback
24+
to the already supported way of PyPy 3.7.
25+
2126

2227
What's New in astroid 2.10.1?
2328
=============================

astroid/nodes/scoped_nodes/scoped_nodes.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
from astroid import bases
5858
from astroid import decorators as decorators_mod
5959
from astroid import mixins, util
60-
from astroid.const import PY38_PLUS, PY39_PLUS
60+
from astroid.const import IMPLEMENTATION_PYPY, PY38, PY38_PLUS, PY39_PLUS
6161
from astroid.context import (
6262
CallContext,
6363
InferenceContext,
@@ -2399,7 +2399,7 @@ def _newstyle_impl(self, context=None):
23992399
@cached_property
24002400
def fromlineno(self) -> Optional[int]:
24012401
"""The first line that this node appears on in the source code."""
2402-
if not PY38_PLUS:
2402+
if not PY38_PLUS or PY38 and IMPLEMENTATION_PYPY:
24032403
# For Python < 3.8 the lineno is the line number of the first decorator.
24042404
# We want the class statement lineno. Similar to 'FunctionDef.fromlineno'
24052405
lineno = self.lineno

astroid/rebuilder.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454

5555
from astroid import nodes
5656
from astroid._ast import ParserModule, get_parser_module, parse_function_type_comment
57-
from astroid.const import IMPLEMENTATION_PYPY, PY36, PY38, PY38_PLUS, Context
57+
from astroid.const import IMPLEMENTATION_PYPY, PY36, PY38, PY38_PLUS, PY39_PLUS, Context
5858
from astroid.manager import AstroidManager
5959
from astroid.nodes import NodeNG
6060
from astroid.nodes.utils import Position
@@ -224,7 +224,7 @@ def _fix_doc_node_position(self, node: NodesWithDocsType) -> None:
224224

225225
lineno = node.lineno or 1 # lineno of modules is 0
226226
end_range: Optional[int] = node.doc_node.lineno
227-
if IMPLEMENTATION_PYPY:
227+
if IMPLEMENTATION_PYPY and not PY39_PLUS:
228228
end_range = None
229229
# pylint: disable-next=unsubscriptable-object
230230
data = "\n".join(self._data[lineno - 1 : end_range])
@@ -271,6 +271,26 @@ def _fix_doc_node_position(self, node: NodesWithDocsType) -> None:
271271
node.doc_node.end_lineno = lineno + t.end[0] - 1
272272
node.doc_node.end_col_offset = t.end[1]
273273

274+
def _reset_end_lineno(self, newnode: nodes.NodeNG) -> None:
275+
"""Reset end_lineno and end_col_offset attributes for PyPy 3.8.
276+
277+
For some nodes, these are either set to -1 or only partially assigned.
278+
To keep consistency across astroid and pylint, reset all.
279+
280+
This has been fixed in PyPy 3.9.
281+
For reference, an (incomplete) list of nodes with issues:
282+
- ClassDef - For
283+
- FunctionDef - While
284+
- Call - If
285+
- Decorators - TryExcept
286+
- With - TryFinally
287+
- Assign
288+
"""
289+
newnode.end_lineno = None
290+
newnode.end_col_offset = None
291+
for child_node in newnode.get_children():
292+
self._reset_end_lineno(child_node)
293+
274294
def visit_module(
275295
self, node: "ast.Module", modname: str, modpath: str, package: bool
276296
) -> nodes.Module:
@@ -292,6 +312,8 @@ def visit_module(
292312
doc_node=self.visit(doc_ast_node, newnode),
293313
)
294314
self._fix_doc_node_position(newnode)
315+
if IMPLEMENTATION_PYPY and PY38:
316+
self._reset_end_lineno(newnode)
295317
return newnode
296318

297319
if sys.version_info >= (3, 10):

tests/unittest_builder.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,12 @@
3838
import tempfile
3939
import textwrap
4040
import unittest
41+
import unittest.mock
4142

4243
import pytest
4344

4445
from astroid import Instance, builder, nodes, test_utils, util
45-
from astroid.const import PY38_PLUS
46+
from astroid.const import IMPLEMENTATION_PYPY, PY38, PY38_PLUS, PY39_PLUS
4647
from astroid.exceptions import (
4748
AstroidBuildingError,
4849
AstroidSyntaxError,
@@ -78,9 +79,12 @@ def test_callfunc_lineno(self) -> None:
7879
self.assertEqual(name.tolineno, 4)
7980
strarg = callfunc.args[0]
8081
self.assertIsInstance(strarg, nodes.Const)
81-
if hasattr(sys, "pypy_version_info"):
82+
if IMPLEMENTATION_PYPY:
8283
self.assertEqual(strarg.fromlineno, 4)
83-
self.assertEqual(strarg.tolineno, 4)
84+
if not PY39_PLUS:
85+
self.assertEqual(strarg.tolineno, 4)
86+
else:
87+
self.assertEqual(strarg.tolineno, 5)
8488
else:
8589
if not PY38_PLUS:
8690
self.assertEqual(strarg.fromlineno, 5)
@@ -183,8 +187,8 @@ class C:
183187

184188
c = ast_module.body[2]
185189
assert isinstance(c, nodes.ClassDef)
186-
if not PY38_PLUS:
187-
# Not perfect, but best we can do for Python 3.7
190+
if not PY38_PLUS or PY38 and IMPLEMENTATION_PYPY:
191+
# Not perfect, but best we can do for Python 3.7 and PyPy 3.8
188192
# Can't detect closing bracket on new line.
189193
assert c.fromlineno == 12
190194
else:

tests/unittest_nodes_lineno.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@
44

55
import astroid
66
from astroid import builder, nodes
7-
from astroid.const import PY38_PLUS, PY39_PLUS, PY310_PLUS
7+
from astroid.const import IMPLEMENTATION_PYPY, PY38, PY38_PLUS, PY39_PLUS, PY310_PLUS
88

99

1010
@pytest.mark.skipif(
11-
PY38_PLUS, reason="end_lineno and end_col_offset were added in PY38"
11+
PY38_PLUS and not (PY38 and IMPLEMENTATION_PYPY),
12+
reason="end_lineno and end_col_offset were added in PY38",
1213
)
1314
class TestEndLinenoNotSet:
1415
"""Test 'end_lineno' and 'end_col_offset' are initialized as 'None' for Python < 3.8."""
@@ -36,7 +37,8 @@ def test_end_lineno_not_set() -> None:
3637

3738

3839
@pytest.mark.skipif(
39-
not PY38_PLUS, reason="end_lineno and end_col_offset were added in PY38"
40+
not PY38_PLUS or PY38 and IMPLEMENTATION_PYPY,
41+
reason="end_lineno and end_col_offset were added in PY38",
4042
)
4143
class TestLinenoColOffset:
4244
"""Test 'lineno', 'col_offset', 'end_lineno', and 'end_col_offset' for all nodes."""

tests/unittest_scoped_nodes.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
util,
5959
)
6060
from astroid.bases import BoundMethod, Generator, Instance, UnboundMethod
61-
from astroid.const import PY38_PLUS, PY310_PLUS, WIN32
61+
from astroid.const import IMPLEMENTATION_PYPY, PY38, PY38_PLUS, PY310_PLUS, WIN32
6262
from astroid.exceptions import (
6363
AttributeInferenceError,
6464
DuplicateBasesError,
@@ -1291,7 +1291,7 @@ def g2():
12911291
astroid = builder.parse(data)
12921292
self.assertEqual(astroid["g1"].fromlineno, 4)
12931293
self.assertEqual(astroid["g1"].tolineno, 5)
1294-
if not PY38_PLUS:
1294+
if not PY38_PLUS or PY38 and IMPLEMENTATION_PYPY:
12951295
self.assertEqual(astroid["g2"].fromlineno, 9)
12961296
else:
12971297
self.assertEqual(astroid["g2"].fromlineno, 10)

0 commit comments

Comments
 (0)