Skip to content

Commit

Permalink
Expand Y015 to cover assignments to negative numbers (#292)
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexWaygood authored Oct 4, 2022
1 parent 396baf8 commit d8511bd
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 22 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Bugfixes:
inside `class` blocks.
* Expand Y035 to cover `__slots__` definitions as well as `__match_args__` and
`__all__` definitions.
* Expand Y015 so that errors are emitted for assignments to negative numbers.

## 22.8.2

Expand Down
60 changes: 40 additions & 20 deletions pyi.py
Original file line number Diff line number Diff line change
Expand Up @@ -900,6 +900,40 @@ def _check_for_typevarlike_assignments(
else:
self.error(node, Y001.format(cls_name))

def _Y015_error(self, node: ast.Assign | ast.AnnAssign) -> None:
old_syntax = unparse(node)
copy_of_node = deepcopy(node)
copy_of_node.value = ast.Constant(value=...)
new_syntax = unparse(copy_of_node)
error_message = Y015.format(old_syntax=old_syntax, new_syntax=new_syntax)
self.error(node, error_message)

@staticmethod
def _Y015_violation_detected(node: ast.Assign | ast.AnnAssign) -> bool:
assignment = node.value

if isinstance(node, ast.AnnAssign):
if assignment and not isinstance(assignment, ast.Ellipsis):
return True
return False

if isinstance(assignment, (ast.Num, ast.Str, ast.Bytes)):
return True
if (
isinstance(assignment, ast.UnaryOp)
and isinstance(assignment.op, ast.USub)
and isinstance(assignment.operand, ast.Num)
):
return True
if (
isinstance(assignment, (ast.Constant, ast.NameConstant))
and not isinstance(assignment, ast.Ellipsis)
and assignment.value is not None
):
return True

return False

def visit_Assign(self, node: ast.Assign) -> None:
if self.in_function.active:
# We error for unexpected things within functions separately.
Expand Down Expand Up @@ -927,6 +961,7 @@ def visit_Assign(self, node: ast.Assign) -> None:
return
assert isinstance(target, ast.Name)
assignment = node.value

if isinstance(assignment, ast.Call):
function = assignment.func
if _is_TypedDict(function):
Expand All @@ -936,15 +971,9 @@ def visit_Assign(self, node: ast.Assign) -> None:
self._check_for_typevarlike_assignments(
node=node, function=function, object_name=target_name
)
return

elif isinstance(assignment, (ast.Num, ast.Str, ast.Bytes)):
return self._Y015_error(node)

if (
isinstance(assignment, (ast.Constant, ast.NameConstant))
and not isinstance(assignment, ast.Ellipsis)
and assignment.value is not None
):
if self._Y015_violation_detected(node):
return self._Y015_error(node)

if not is_special_assignment:
Expand Down Expand Up @@ -1063,24 +1092,23 @@ def visit_AnnAssign(self, node: ast.AnnAssign) -> None:
self.generic_visit(node)
return

node_target, node_value = node.target, node.value
node_target = node.target
if isinstance(node_target, ast.Name):
target_name = node_target.id
if _is_assignment_which_must_have_a_value(
target_name, in_class=self.in_class.active
):
with self.string_literals_allowed.enabled():
self.generic_visit(node)
if node_value is None:
if node.value is None:
self.error(node, Y035.format(var=target_name))
return

if _is_TypeAlias(node_annotation) and isinstance(node_target, ast.Name):
return self._check_typealias(node=node, alias_name=target_name)

self.generic_visit(node)

if node_value and not isinstance(node_value, ast.Ellipsis):
if self._Y015_violation_detected(node):
self._Y015_error(node)

def _check_union_members(self, members: Sequence[ast.expr]) -> None:
Expand Down Expand Up @@ -1713,14 +1741,6 @@ def visit_arguments(self, node: ast.arguments) -> None:
if not isinstance(default, ast.Ellipsis):
self.error(default, (Y014 if arg.annotation is None else Y011))

def _Y015_error(self, node: ast.Assign | ast.AnnAssign) -> None:
old_syntax = unparse(node)
copy_of_node = deepcopy(node)
copy_of_node.value = ast.Constant(value=...)
new_syntax = unparse(copy_of_node)
error_message = Y015.format(old_syntax=old_syntax, new_syntax=new_syntax)
self.error(node, error_message)

def error(self, node: ast.AST, message: str) -> None:
self.errors.append(Error(node.lineno, node.col_offset, message, PyiTreeChecker))

Expand Down
2 changes: 1 addition & 1 deletion requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ isort==5.10.1 # Must match .pre-commit-config.yaml
flake8-bugbear==22.8.23
flake8-noqa==1.2.9
types-pyflakes==2.5.0
ast_decompiler<1.0; python_version < '3.9'
ast_decompiler>=0.7.0,<1.0; python_version < '3.9'
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
install_requires=[
"flake8 >= 3.2.1, < 6.0.0",
"pyflakes >= 2.1.1",
'ast-decompiler <1.0; python_version < "3.9"',
'ast-decompiler >= 0.7.0, < 1.0; python_version < "3.9"',
],
test_suite="tests.test_pyi",
classifiers=[
Expand Down
4 changes: 4 additions & 0 deletions tests/attribute_annotations.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ field5 = 0 # type: int # Y033 Do not use type comments in stubs (e.g. use "x:
field6 = 0 # Y015 Bad default value. Use "field6 = ..." instead of "field6 = 0"
field7 = b"" # Y015 Bad default value. Use "field7 = ..." instead of "field7 = b''"
field8 = False # Y015 Bad default value. Use "field8 = ..." instead of "field8 = False"
field81 = -1 # Y015 Bad default value. Use "field81 = ..." instead of "field81 = -1"
field82: float = -98.43 # Y015 Bad default value. Use "field82: float = ..." instead of "field82: float = -98.43"
field83 = -42j # Y015 Bad default value. Use "field83 = ..." instead of "field83 = -42j"

# We don't want this one to trigger Y015 -- it's valid as a TypeAlias
field9 = None # Y026 Use typing_extensions.TypeAlias for type aliases, e.g. "field9: TypeAlias = None"
Expand All @@ -25,6 +28,7 @@ field14: Final = True
field15: Final = ('a', 'b', 'c')
field16: typing.Final = "foo"
field17: typing_extensions.Final = "foo"
field18: Final = -24j

class Foo:
field1: int
Expand Down

0 comments on commit d8511bd

Please sign in to comment.