-
Notifications
You must be signed in to change notification settings - Fork 10
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
Support traversal of TypeAlias for Python 3.12 #115
Conversation
Also update tox and GitHub Actions to use Python 3.12 by default.
Pull Request Test Coverage Report for Build 8745231098Warning: This coverage report may be inaccurate.This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.
Details
💛 - Coveralls |
c1c52f5
to
6e765f4
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added a commit with the proposed fixes.
src/ssort/_requirements.py
Outdated
if sys.version_info >= (3, 12): | ||
for type_param in node.type_params: | ||
yield from get_requirements(type_param) | ||
scope.update(type_param.name for type_param in node.type_params) # type: ignore[attr-defined] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added the # type: ignore[attr-defined]
here an in a couple of lines below because mypy complained that "type_param" has no attribute "name"
. But type_param is TypeVar | ParamSpec | TypeVarTuple
and all the latter have a name.
src/ssort/_requirements.py
Outdated
if not requirement.deferred: | ||
requirement = dataclasses.replace(requirement, deferred=True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've copied the two lines above from the similar code in _get_requirements_for_function_def
. I don't know what a referred requirement is or if this code is needed here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is good. Might be worth factoring out a _get_scope_from_type_params
function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A deferred requirement is one that is captured by a statement but not used immediately.
For example here, a
has an immediate requirement on b
:
a = b
and here the requirement is deferred:
def a():
return b
@type_parameter_syntax | ||
def test_type_var_bindings(): | ||
node = _parse("type RecursiveList[T] = T | list[RecursiveList[T]]") | ||
assert list(get_bindings(node)) == ["RecursiveList"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm surprised that this all just worked without any changes to _bindings.py
. Nice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep. “Type aliases can refer to themselves” (as defined in the PEP) works because _get_requirements_for_type_alias does scope.add(node.name.id)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The only new binding introduced here is for TypeAlias
es, which have a Name
and is resolved by the existing _get_bindings_for_name
function. Type parameters, which are TypeVar | ParamSpec | TypeVarTuple
, are treated for TypeAlias | FunctionDef | AsyncFunctionDef | ClassDef
as arguments
are treated for FunctionDef | AsyncFunctionDef | Lambda
: they are added into the current scope for their usages to be resolved.
43c277b
to
d062f04
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the review again. I've added a new commit with the latest suggestion, plus extracted a couple more functions.
src/ssort/_parsing.py
Outdated
if token.string == ")": | ||
depth -= 1 | ||
|
||
def _optional_nested_brackets(open: str, close: str): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've extracted the repeated code here into _optional_nested_brackets.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would you mind hoisting it out of split_class
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm thinking of reverting this refactoring of _optional_nested_brackets and leaving a more complete refactoring of split_class for a latter PR. Agree?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Curious to see it. Go ahead.
@@ -32,6 +33,13 @@ def get_requirements(node: ast.AST) -> Iterable[Requirement]: | |||
yield from get_requirements(child) | |||
|
|||
|
|||
def _get_requirements_from_nodes( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've hopefully made the code a little more simple with a _get_requirements_from_nodes function.
scope = _get_scope_from_type_params(node.type_params) | ||
for requirement in _get_requirements_from_nodes(node.type_params): | ||
if requirement.name not in scope: | ||
yield requirement |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's now using the scope from type params to filter their own requirements. This adds the compliance to:
Type parameters are visible to other type parameters declared elsewhere in the list. This allows type parameters to use other type parameters within their definition.
for the PEP. It's not considering any order for sake of simplicity, leaving the more strict rules to the compiler or type checkers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, had read that as scope being updated from left to right but I think you are right.
Also extract _get_requirements_from_nodes, _get_scope_from_type_params and _optional_nested_brackets functions.
d062f04
to
ac899c9
Compare
|
||
scope = set(CLASS_BUILTINS) | ||
for requirement in _get_requirements_from_nodes(node.bases): | ||
if requirement.name not in scope: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good spot.
I think this is just about done. Should have time this evening to do a full read through then will merge and bake a release. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No more comments from me. Looks good. Hoist or revert _optional_nested_brackets
and will merge when next at the keyboard.
Cool! I've reverted _optional_nested_brackets. The reason I didn't want to hoist it was because I thought it would have a weird signature, as if depends on both token and tokens, and mutates token:
Instead, I think this would be better refactored as a having split_class become a class and _optional_nested_brackets a private method, with token and tokens being instance attributes. I tried a similar effect first by having _optional_nested_brackets as a closure. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you. Merging and releasing.
Also update tox and GitHub Actions to use Python 3.12 by default.