Skip to content

Commit

Permalink
Drop Python 3.8 (#820)
Browse files Browse the repository at this point in the history
  • Loading branch information
JelleZijlstra authored Oct 11, 2024
1 parent d51234e commit ddd5c23
Show file tree
Hide file tree
Showing 8 changed files with 85 additions and 74 deletions.
3 changes: 0 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@ jobs:
fail-fast: false
matrix:
include:
- name: py38
python: "3.8"
toxenv: py38
- name: py39
python: "3.9"
toxenv: py39
Expand Down
1 change: 1 addition & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Unreleased

- Drop support for Python 3.8 (#820)
- Flag invalid regexes in arguments to functions like
`re.search` (#816)

Expand Down
5 changes: 3 additions & 2 deletions pyanalyze/asynq_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,9 @@ def set_func_name(
# Override current_func_name only if this is the outermost function, so that data access
# within nested functions is attributed to the outer function. However, for async inner
# functions, check batching within the function separately.
with qcore.override(self, "current_async_kind", async_kind), qcore.override(
self, "is_classmethod", is_classmethod
with (
qcore.override(self, "current_async_kind", async_kind),
qcore.override(self, "is_classmethod", is_classmethod),
):
if (
self.current_func_name is None
Expand Down
133 changes: 72 additions & 61 deletions pyanalyze/name_check_visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -1775,9 +1775,11 @@ def _set_current_class(self, current_class: Optional[type]) -> Iterator[None]:
current_enum_members = {}
else:
current_enum_members = None
with qcore.override(self, "current_class", current_class), qcore.override(
self.asynq_checker, "current_class", current_class
), qcore.override(self, "current_enum_members", current_enum_members):
with (
qcore.override(self, "current_class", current_class),
qcore.override(self.asynq_checker, "current_class", current_class),
qcore.override(self, "current_enum_members", current_enum_members),
):
yield

def visit_ClassDef(self, node: ast.ClassDef) -> Value:
Expand Down Expand Up @@ -1845,14 +1847,22 @@ def _visit_class_and_get_value(
# If this is a nested class, we need to run the collecting phase to get data
# about names accessed from the class.
if len(self.scopes.scopes) > 2:
with self.scopes.add_scope(
ScopeType.class_scope, scope_node=node, scope_object=current_class
), self._set_current_class(current_class):
with (
self.scopes.add_scope(
ScopeType.class_scope,
scope_node=node,
scope_object=current_class,
),
self._set_current_class(current_class),
):
self._generic_visit_list(node.body)
else:
with self.scopes.add_scope(
ScopeType.class_scope, scope_node=None, scope_object=current_class
), self._set_current_class(current_class):
with (
self.scopes.add_scope(
ScopeType.class_scope, scope_node=None, scope_object=current_class
),
self._set_current_class(current_class),
):
self._generic_visit_list(node.body)

if current_class is not None:
Expand Down Expand Up @@ -2017,22 +2027,20 @@ def visit_FunctionDef(self, node: FunctionDefNode) -> Value:
expected_return, TypeGuardExtension
)

with self.asynq_checker.set_func_name(
node.name,
async_kind=info.async_kind,
is_classmethod=info.is_classmethod,
), qcore.override(
self, "yield_checker", YieldChecker(self)
), qcore.override(
self, "is_async_def", isinstance(node, ast.AsyncFunctionDef)
), qcore.override(
self, "current_function_name", node.name
), qcore.override(
self, "current_function", potential_function
), qcore.override(
self, "expected_return_value", expected_return
), qcore.override(
self, "current_function_info", info
with (
self.asynq_checker.set_func_name(
node.name,
async_kind=info.async_kind,
is_classmethod=info.is_classmethod,
),
qcore.override(self, "yield_checker", YieldChecker(self)),
qcore.override(
self, "is_async_def", isinstance(node, ast.AsyncFunctionDef)
),
qcore.override(self, "current_function_name", node.name),
qcore.override(self, "current_function", potential_function),
qcore.override(self, "expected_return_value", expected_return),
qcore.override(self, "current_function_info", info),
):
result = self._visit_function_body(info)

Expand Down Expand Up @@ -2284,8 +2292,11 @@ def _visit_function_body(self, function_info: FunctionInfo) -> FunctionResult:
error.node, error.message, error_code=ErrorCode.bad_evaluator
)
if self.annotate:
with self.catch_errors(), self.scopes.add_scope(
ScopeType.function_scope, scope_node=node
with (
self.catch_errors(),
self.scopes.add_scope(
ScopeType.function_scope, scope_node=node
),
):
self._generic_visit_list(node.body)
return FunctionResult(parameters=params)
Expand All @@ -2295,12 +2306,11 @@ def _visit_function_body(self, function_info: FunctionInfo) -> FunctionResult:
# scope propagate into this scope. This means that we'll use the constraints
# of the place where the function is defined, not those of where the function
# is called, which is strictly speaking wrong but should be fine in practice.
with self.scopes.add_scope(
ScopeType.function_scope, scope_node=node
), qcore.override(self, "is_generator", False), qcore.override(
self, "async_kind", function_info.async_kind
), qcore.override(
self, "_name_node_to_statement", {}
with (
self.scopes.add_scope(ScopeType.function_scope, scope_node=node),
qcore.override(self, "is_generator", False),
qcore.override(self, "async_kind", function_info.async_kind),
qcore.override(self, "_name_node_to_statement", {}),
):
scope = self.scopes.current_scope()
assert isinstance(scope, FunctionScope)
Expand All @@ -2321,12 +2331,10 @@ def _visit_function_body(self, function_info: FunctionInfo) -> FunctionResult:
VisitorState.check_names,
)

with qcore.override(
self, "state", VisitorState.collect_names
), qcore.override(
self, "return_values", []
), self.yield_checker.set_function_node(
node
with (
qcore.override(self, "state", VisitorState.collect_names),
qcore.override(self, "return_values", []),
self.yield_checker.set_function_node(node),
):
if isinstance(node, ast.Lambda):
self.visit(node.body)
Expand All @@ -2340,12 +2348,11 @@ def _visit_function_body(self, function_info: FunctionInfo) -> FunctionResult:
# collect state) to evaluate the first one visited during the check state
self.yield_checker.reset_yield_checks()

with qcore.override(self, "current_class", None), qcore.override(
self, "state", VisitorState.check_names
), qcore.override(
self, "return_values", []
), self.yield_checker.set_function_node(
node
with (
qcore.override(self, "current_class", None),
qcore.override(self, "state", VisitorState.check_names),
qcore.override(self, "return_values", []),
self.yield_checker.set_function_node(node),
):
if isinstance(node, ast.Lambda):
return_values = [self.visit(node.body)]
Expand Down Expand Up @@ -2989,14 +2996,16 @@ def _visit_sequence_comp(
# Strictly speaking this is unsafe to do for generator expressions, which may
# be evaluated at a different place in the function than where they are defined,
# but that is unlikely to be an issue in practice.
with self.scopes.add_scope(
ScopeType.function_scope, scope_node=node
), qcore.override(self, "_name_node_to_statement", {}):
with (
self.scopes.add_scope(ScopeType.function_scope, scope_node=node),
qcore.override(self, "_name_node_to_statement", {}),
):
return self._visit_comprehension_inner(node, typ, iterable_type)

with self.scopes.add_scope(
ScopeType.function_scope, scope_node=node
), qcore.override(self, "_name_node_to_statement", {}):
with (
self.scopes.add_scope(ScopeType.function_scope, scope_node=node),
qcore.override(self, "_name_node_to_statement", {}),
):
scope = self.scopes.current_scope()
assert isinstance(scope, FunctionScope)
for state in (VisitorState.collect_names, VisitorState.check_names):
Expand Down Expand Up @@ -4687,9 +4696,10 @@ def visit_Assign(self, node: ast.Assign) -> None:
is_yield = isinstance(node.value, ast.Yield)
value = self.visit(node.value)

with qcore.override(
self, "being_assigned", value
), self.yield_checker.check_yield_result_assignment(is_yield):
with (
qcore.override(self, "being_assigned", value),
self.yield_checker.check_yield_result_assignment(is_yield),
):
# syntax like 'x = y = 0' results in multiple targets
self._generic_visit_list(node.targets)

Expand Down Expand Up @@ -4759,10 +4769,10 @@ def visit_AnnAssign(self, node: ast.AnnAssign) -> None:
is_yield = False
value = None

with qcore.override(
self, "being_assigned", value
), self.yield_checker.check_yield_result_assignment(is_yield), qcore.override(
self, "ann_assign_type", (expected_type, is_final)
with (
qcore.override(self, "being_assigned", value),
self.yield_checker.check_yield_result_assignment(is_yield),
qcore.override(self, "ann_assign_type", (expected_type, is_final)),
):
self.visit(node.target)

Expand All @@ -4779,9 +4789,10 @@ def visit_AugAssign(self, node: ast.AugAssign) -> None:
node.target, lhs, node.op, node.value, rhs, node, is_inplace=True
)

with qcore.override(
self, "being_assigned", value
), self.yield_checker.check_yield_result_assignment(is_yield):
with (
qcore.override(self, "being_assigned", value),
self.yield_checker.check_yield_result_assignment(is_yield),
):
# syntax like 'x = y = 0' results in multiple targets
self.visit(node.target)

Expand Down
7 changes: 4 additions & 3 deletions pyanalyze/type_evaluation.py
Original file line number Diff line number Diff line change
Expand Up @@ -758,9 +758,10 @@ def visit_If(self, node: ast.If) -> EvalReturn:
self.visit_block(node.orelse)
return None
if condition.left_varmap is not None:
with self.ctx.narrow_variables(
condition.left_varmap
), self.add_active_condition(condition.condition):
with (
self.ctx.narrow_variables(condition.left_varmap),
self.add_active_condition(condition.condition),
):
left_result = self.visit_block(node.body)
else:
left_result = None
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[tool.black]
target-version = ['py38']
target-version = ['py39']
include = '\.pyi?$'
skip-magic-trailing-comma = true
preview = true
Expand Down Expand Up @@ -44,7 +44,7 @@ implicit_any = true

[tool.ruff]
line-length = 100
target-version = "py38"
target-version = "py39"

[tool.ruff.lint]
select = [
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,5 @@
# These are useful for unit tests of pyanalyze extensions
# outside the package.
package_data={"pyanalyze": package_data},
python_requires=">=3.8",
python_requires=">=3.9",
)
4 changes: 2 additions & 2 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tox]
envlist =
py38,py39,py310,py311,py312,black,ruff
py39,py310,py311,py312,py313,black,ruff
skip_missing_interpreters = True

[testenv]
Expand All @@ -12,7 +12,7 @@ commands =

[testenv:black]
deps =
black==24.3.0
black==24.10.0

commands =
black --check pyanalyze/
Expand Down

0 comments on commit ddd5c23

Please sign in to comment.