Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix line number for slices, clean up old logic #18397

Merged
merged 4 commits into from
Jan 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading