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

Add semantic analysis of type aliases and parameters #6109

Merged
merged 19 commits into from
Jul 28, 2023
Merged

Conversation

zanieb
Copy link
Member

@zanieb zanieb commented Jul 26, 2023

Requires astral-sh/RustPython-Parser#42
Related PyCQA/pyflakes#778
PEP-695
Part of #5062

Summary

Adds a scope for type parameters, a type parameter binding kind, and checker visitation of type parameters in type alias statements, function definitions, and class definitions.

A few changes were necessary to ensure correctness following the insertion of a new scope between function and class scopes and their parent.

Test Plan

Undefined name snapshots.

Unused type parameter rule will be added as follow-up.

@zanieb zanieb force-pushed the zanie/695-semantic branch 3 times, most recently from 063126f to fcd5e14 Compare July 26, 2023 22:26
@zanieb
Copy link
Member Author

zanieb commented Jul 26, 2023

If interested, working with examples at https://gist.github.com/zanieb/fe1d6c32db2e9a6cc1bcb3be6feaaf05

@charliermarsh
Copy link
Member

This is looking good to me!

@github-actions
Copy link
Contributor

github-actions bot commented Jul 26, 2023

PR Check Results

Ecosystem

✅ ecosystem check detected no changes.

Benchmark

Linux

group                                      main                                    pr
-----                                      ----                                    --
formatter/large/dataset.py                 1.19     12.9±0.38ms     3.1 MB/sec     1.00     10.9±0.47ms     3.7 MB/sec
formatter/numpy/ctypeslib.py               1.19      2.5±0.12ms     6.7 MB/sec     1.00      2.1±0.11ms     8.0 MB/sec
formatter/numpy/globals.py                 1.05   262.1±11.92µs    11.3 MB/sec     1.00   249.6±23.05µs    11.8 MB/sec
formatter/pydantic/types.py                1.13      5.3±0.29ms     4.8 MB/sec     1.00      4.7±0.26ms     5.4 MB/sec
linter/all-rules/large/dataset.py          1.00     14.2±0.68ms     2.9 MB/sec     1.00     14.3±0.53ms     2.9 MB/sec
linter/all-rules/numpy/ctypeslib.py        1.00      3.5±0.09ms     4.8 MB/sec     1.06      3.7±0.11ms     4.6 MB/sec
linter/all-rules/numpy/globals.py          1.00   493.1±19.60µs     6.0 MB/sec     1.02   502.4±17.31µs     5.9 MB/sec
linter/all-rules/pydantic/types.py         1.00      6.3±0.22ms     4.1 MB/sec     1.03      6.5±0.29ms     3.9 MB/sec
linter/default-rules/large/dataset.py      1.00      7.5±0.26ms     5.4 MB/sec     1.07      8.1±0.28ms     5.0 MB/sec
linter/default-rules/numpy/ctypeslib.py    1.07  1731.7±213.19µs     9.6 MB/sec    1.00  1612.3±81.91µs    10.3 MB/sec
linter/default-rules/numpy/globals.py      1.00    177.2±6.23µs    16.7 MB/sec     1.10   194.9±17.64µs    15.1 MB/sec
linter/default-rules/pydantic/types.py     1.00      3.3±0.14ms     7.6 MB/sec     1.04      3.5±0.14ms     7.3 MB/sec

Windows

group                                      main                                    pr
-----                                      ----                                    --
formatter/large/dataset.py                 1.00     13.7±0.71ms     3.0 MB/sec     1.01     13.9±0.65ms     2.9 MB/sec
formatter/numpy/ctypeslib.py               1.00      2.7±0.17ms     6.3 MB/sec     1.00      2.6±0.18ms     6.3 MB/sec
formatter/numpy/globals.py                 1.02   294.9±33.43µs    10.0 MB/sec     1.00   289.6±21.05µs    10.2 MB/sec
formatter/pydantic/types.py                1.01      5.8±0.42ms     4.4 MB/sec     1.00      5.7±0.27ms     4.5 MB/sec
linter/all-rules/large/dataset.py          1.00     17.9±0.71ms     2.3 MB/sec     1.00     17.9±0.73ms     2.3 MB/sec
linter/all-rules/numpy/ctypeslib.py        1.00      4.6±0.31ms     3.6 MB/sec     1.02      4.7±0.32ms     3.5 MB/sec
linter/all-rules/numpy/globals.py          1.00   545.8±38.41µs     5.4 MB/sec     1.01   549.6±34.32µs     5.4 MB/sec
linter/all-rules/pydantic/types.py         1.00      7.9±0.36ms     3.2 MB/sec     1.01      8.0±0.48ms     3.2 MB/sec
linter/default-rules/large/dataset.py      1.00      9.7±0.41ms     4.2 MB/sec     1.02      9.9±0.54ms     4.1 MB/sec
linter/default-rules/numpy/ctypeslib.py    1.00  1957.4±102.10µs     8.5 MB/sec    1.01  1983.9±145.44µs     8.4 MB/sec
linter/default-rules/numpy/globals.py      1.01   215.0±18.54µs    13.7 MB/sec     1.00   212.6±13.41µs    13.9 MB/sec
linter/default-rules/pydantic/types.py     1.00      4.3±0.32ms     5.9 MB/sec     1.03      4.5±0.41ms     5.7 MB/sec

@zanieb zanieb marked this pull request as ready for review July 27, 2023 21:46
@zanieb
Copy link
Member Author

zanieb commented Jul 27, 2023

Huh my latest commit is pushed but missing. Silly GitHub.

# Types used in aliased assignment must exist

type Foo = DoesNotExist # F821: Undefined name `DoesNotExist`
type Foo = list[DoesNotExist] # F821: Undefined name `DoesNotExist`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Were you able to test these against the updated version of Pyflakes?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't! I'll do that and confirm they match. I tested for correctness with Pyright and Python runtime errors.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The results are identical except that pyflakes emits two incorrect warnings

10:21: undefined name 'Ts'
11:21: undefined name 'P'

They appear to handle binding of type var tuples and param specs incorrectly.

}

/// Returns the first parent of the given scope that is not a [`ScopeKind::Type`] scope, if any.
pub fn scope_parent_skip_types(&self, scope: &Scope) -> Option<&Scope<'a>> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, maybe non_type_scope_parent, so that it describes the data returned rather than the action taken? But not a strong opinion.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not attached to my name but I don't like that one either haha maybe scope_non_type_parent? eek. I also wanted to try scope_first_parent_not(kind: ScopeKind) or something but making it generic felt weird too.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe what you have here is just clearest... Or scope_parent_skip_type_scopes???

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe first_non_type_parent_scope or non_type_parent_scope?

Or we name it first_value_scope. The way I think about it is that variables can either bind a value or a type.

Unrelated: Is this a python thing that types have their own scope? In JS, they all end up in the same scope, but each binding is either a value or a type.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, surprisingly they added an entirely new lexical scope to support type parameters specifically in PEP 695: https://peps.python.org/pep-0695/#type-parameter-scopes. It's required due to some of the nuances of when these things are evaluated.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Perhaps we should consider a more specific name for that scope kind though.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kind of like TypeParam scope more than Type but I didn't want to deviate in this first pass

Copy link
Member

@charliermarsh charliermarsh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great! I love the thoroughness of the test suite. I admittedly didn't review the actual test results for semantic correctness but am happy to take a closer look on any of the finer points if useful.

def can_access_inside[T](t: T) -> T: # OK
print(T) # OK
return t # OK
class Forward: ... # OK
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Self: Need to move Forward definition to end of file or change names so they do not collide across test cases.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another benefit of isolating fixtures >.<

}

/// Returns the first parent of the given scope that is not a [`ScopeKind::Type`] scope, if any.
pub fn scope_parent_skip_types(&self, scope: &Scope) -> Option<&Scope<'a>> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe first_non_type_parent_scope or non_type_parent_scope?

Or we name it first_value_scope. The way I think about it is that variables can either bind a value or a type.

Unrelated: Is this a python thing that types have their own scope? In JS, they all end up in the same scope, but each binding is either a value or a type.

crates/ruff_python_semantic/src/model.rs Outdated Show resolved Hide resolved
crates/ruff_python_semantic/src/model.rs Show resolved Hide resolved
@zanieb zanieb merged commit 047c211 into main Jul 28, 2023
@zanieb zanieb deleted the zanie/695-semantic branch July 28, 2023 22:06
@zanieb zanieb mentioned this pull request Jul 31, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants