Skip to content

Commit

Permalink
Fix line number for slices, clean up old logic (#18397)
Browse files Browse the repository at this point in the history
Fixes #17655

The decorator cleanup moves a type ignore, but so does the bug fix for
decorators in #18392 , so might as well batch into a single release
  • Loading branch information
hauntsaninja authored Jan 6, 2025
1 parent b96a3f1 commit 6181b0f
Show file tree
Hide file tree
Showing 7 changed files with 44 additions and 44 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Next release

### Performance improvements

TODO

### Drop Support for Python 3.8

Mypy no longer supports running with Python 3.8, which has reached end-of-life.
Expand Down Expand Up @@ -40,6 +44,14 @@ Contributed by Christoph Tyralla (PR [18180](https://github.com/python/mypy/pull
(Speaking of partial types, another reminder that mypy plans on enabling `--local-partial-types`
by default in **mypy 2.0**).

### Better line numbers for decorators and slice expressions

Mypy now uses more correct line numbers for decorators and slice expressions. In some cases, this
may necessitate changing the location of a `# type: ignore` comment.

Contributed by Shantanu Jain (PR [18392](https://github.com/python/mypy/pull/18392),
PR [18397](https://github.com/python/mypy/pull/18397)).

## Mypy 1.14

We’ve just uploaded mypy 1.14 to the Python Package Index ([PyPI](https://pypi.org/project/mypy/)).
Expand Down
37 changes: 8 additions & 29 deletions mypy/fastparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -1009,28 +1009,22 @@ def do_func_def(
func_def.is_coroutine = True
if func_type is not None:
func_type.definition = func_def
func_type.line = lineno
func_type.set_line(lineno)

if n.decorator_list:
# Set deco_line to the old pre-3.8 lineno, in order to keep
# existing "# type: ignore" comments working:
deco_line = n.decorator_list[0].lineno

var = Var(func_def.name)
var.is_ready = False
var.set_line(lineno)

func_def.is_decorated = True
func_def.deco_line = deco_line
func_def.set_line(lineno, n.col_offset, end_line, end_column)
self.set_line(func_def, n)

deco = Decorator(func_def, self.translate_expr_list(n.decorator_list), var)
first = n.decorator_list[0]
deco.set_line(first.lineno, first.col_offset, end_line, end_column)
retval: FuncDef | Decorator = deco
else:
# FuncDef overrides set_line -- can't use self.set_line
func_def.set_line(lineno, n.col_offset, end_line, end_column)
self.set_line(func_def, n)
retval = func_def
if self.options.include_docstrings:
func_def.docstring = ast3.get_docstring(n, clean=False)
Expand Down Expand Up @@ -1149,10 +1143,7 @@ def visit_ClassDef(self, n: ast3.ClassDef) -> ClassDef:
type_args=explicit_type_params,
)
cdef.decorators = self.translate_expr_list(n.decorator_list)
# Set lines to match the old mypy 0.700 lines, in order to keep
# existing "# type: ignore" comments working:
cdef.line = n.lineno
cdef.deco_line = n.decorator_list[0].lineno if n.decorator_list else None
self.set_line(cdef, n)

if self.options.include_docstrings:
cdef.docstring = ast3.get_docstring(n, clean=False)
Expand Down Expand Up @@ -1247,8 +1238,7 @@ def visit_AnnAssign(self, n: ast3.AnnAssign) -> AssignmentStmt:
line = n.lineno
if n.value is None: # always allow 'x: int'
rvalue: Expression = TempNode(AnyType(TypeOfAny.special_form), no_rhs=True)
rvalue.line = line
rvalue.column = n.col_offset
self.set_line(rvalue, n)
else:
rvalue = self.visit(n.value)
typ = TypeConverter(self.errors, line=line).visit(n.annotation)
Expand Down Expand Up @@ -1675,19 +1665,7 @@ def visit_Attribute(self, n: Attribute) -> MemberExpr | SuperExpr:
# Subscript(expr value, slice slice, expr_context ctx)
def visit_Subscript(self, n: ast3.Subscript) -> IndexExpr:
e = IndexExpr(self.visit(n.value), self.visit(n.slice))
self.set_line(e, n)
# alias to please mypyc
is_py38_or_earlier = sys.version_info < (3, 9)
if isinstance(n.slice, ast3.Slice) or (
is_py38_or_earlier and isinstance(n.slice, ast3.ExtSlice)
):
# Before Python 3.9, Slice has no line/column in the raw ast. To avoid incompatibility
# visit_Slice doesn't set_line, even in Python 3.9 on.
# ExtSlice also has no line/column info. In Python 3.9 on, line/column is set for
# e.index when visiting n.slice.
e.index.line = e.line
e.index.column = e.column
return e
return self.set_line(e, n)

# Starred(expr value, expr_context ctx)
def visit_Starred(self, n: Starred) -> StarExpr:
Expand Down Expand Up @@ -1718,7 +1696,8 @@ def visit_Tuple(self, n: ast3.Tuple) -> TupleExpr:

# Slice(expr? lower, expr? upper, expr? step)
def visit_Slice(self, n: ast3.Slice) -> SliceExpr:
return SliceExpr(self.visit(n.lower), self.visit(n.upper), self.visit(n.step))
e = SliceExpr(self.visit(n.lower), self.visit(n.upper), self.visit(n.step))
return self.set_line(e, n)

# ExtSlice(slice* dims)
def visit_ExtSlice(self, n: ast3.ExtSlice) -> TupleExpr:
Expand Down
2 changes: 1 addition & 1 deletion mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ def span_from_context(ctx: Context) -> Iterable[int]:
TODO: address this in follow up PR
"""
if isinstance(ctx, (ClassDef, FuncDef)):
return range(ctx.deco_line or ctx.line, ctx.line + 1)
return range(ctx.line, ctx.line + 1)
elif not isinstance(ctx, Expression):
return [ctx.line]
else:
Expand Down
6 changes: 0 additions & 6 deletions mypy/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -768,7 +768,6 @@ class FuncDef(FuncItem, SymbolNode, Statement):
"is_conditional",
"abstract_status",
"original_def",
"deco_line",
"is_trivial_body",
"is_mypy_only",
# Present only when a function is decorated with @typing.dataclass_transform or similar
Expand Down Expand Up @@ -798,8 +797,6 @@ def __init__(
self.is_trivial_body = False
# Original conditional definition
self.original_def: None | FuncDef | Var | Decorator = None
# Used for error reporting (to keep backward compatibility with pre-3.8)
self.deco_line: int | None = None
# Definitions that appear in if TYPE_CHECKING are marked with this flag.
self.is_mypy_only = False
self.dataclass_transform_spec: DataclassTransformSpec | None = None
Expand Down Expand Up @@ -1115,7 +1112,6 @@ class ClassDef(Statement):
"keywords",
"analyzed",
"has_incompatible_baseclass",
"deco_line",
"docstring",
"removed_statements",
)
Expand Down Expand Up @@ -1166,8 +1162,6 @@ def __init__(
self.keywords = dict(keywords) if keywords else {}
self.analyzed = None
self.has_incompatible_baseclass = False
# Used for error reporting (to keep backward compatibility with pre-3.8)
self.deco_line: int | None = None
self.docstring: str | None = None
self.removed_statements = []

Expand Down
1 change: 0 additions & 1 deletion mypy/plugins/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,6 @@ def add_overloaded_method_to_class(
var = Var(func.name, func.type)
var.set_line(func.line)
func.is_decorated = True
func.deco_line = func.line

deco = Decorator(func, [], var)
else:
Expand Down
4 changes: 2 additions & 2 deletions test-data/unit/check-python38.test
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ def f(): ... # E: Function is missing a return type annotation \
# flags: --disallow-untyped-defs --warn-unused-ignores
def d(f): ... # type: ignore
@d
# type: ignore
def f(): ... # type: ignore # E: Unused "type: ignore" comment
# type: ignore # E: Unused "type: ignore" comment
def f(): ... # type: ignore

[case testIgnoreDecoratedFunction2]
# flags: --disallow-untyped-defs
Expand Down
26 changes: 21 additions & 5 deletions test-data/unit/parse.test
Original file line number Diff line number Diff line change
Expand Up @@ -3171,10 +3171,10 @@ MypyFile:1(
IndexExpr:1(
NameExpr(a)
TupleExpr:1(
SliceExpr:-1(
SliceExpr:1(
<empty>
<empty>)
SliceExpr:-1(
SliceExpr:1(
<empty>
<empty>)))))

Expand All @@ -3186,10 +3186,10 @@ MypyFile:1(
IndexExpr:1(
NameExpr(a)
TupleExpr:1(
SliceExpr:-1(
SliceExpr:1(
IntExpr(1)
IntExpr(2))
SliceExpr:-1(
SliceExpr:1(
<empty>
<empty>)))))

Expand All @@ -3201,13 +3201,29 @@ MypyFile:1(
IndexExpr:1(
NameExpr(a)
TupleExpr:1(
SliceExpr:-1(
SliceExpr:1(
IntExpr(1)
IntExpr(2)
IntExpr(3))
Ellipsis
IntExpr(1)))))

[case testParseExtendedSlicing4]
m[*index, :]
[out]
main:1: error: invalid syntax
[out version>=3.11]
MypyFile:1(
ExpressionStmt:1(
IndexExpr:1(
NameExpr(m)
TupleExpr:1(
StarExpr:1(
NameExpr(index))
SliceExpr:1(
<empty>
<empty>)))))

[case testParseIfExprInDictExpr]
test = { 'spam': 'eggs' if True else 'bacon' }
[out]
Expand Down

0 comments on commit 6181b0f

Please sign in to comment.