Commit graph

201 commits

Author SHA1 Message Date
InSync
3b27d5dbad
[red-knot] More precise inference for chained boolean expressions (#15089)
## Summary

Resolves #13632.

## Test Plan

Markdown tests.
2024-12-22 10:02:28 -08:00
David Peter
000948ad3b
[red-knot] Statically known branches (#15019)
## Summary

This changeset adds support for precise type-inference and
boundness-handling of definitions inside control-flow branches with
statically-known conditions, i.e. test-expressions whose truthiness we
can unambiguously infer as *always false* or *always true*.

This branch also includes:
- `sys.platform` support
- statically-known branches handling for Boolean expressions and while
  loops
- new `target-version` requirements in some Markdown tests which were
  now required due to the understanding of `sys.version_info` branches.

closes #12700 
closes #15034 

## Performance

### `tomllib`, -7%, needs to resolve one additional module (sys)

| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|:---|---:|---:|---:|---:|
| `./red_knot_main --project /home/shark/tomllib` | 22.2 ± 1.3 | 19.1 |
25.6 | 1.00 |
| `./red_knot_feature --project /home/shark/tomllib` | 23.8 ± 1.6 | 20.8
| 28.6 | 1.07 ± 0.09 |

### `black`, -6%

| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|:---|---:|---:|---:|---:|
| `./red_knot_main --project /home/shark/black` | 129.3 ± 5.1 | 119.0 |
137.8 | 1.00 |
| `./red_knot_feature --project /home/shark/black` | 136.5 ± 6.8 | 123.8
| 147.5 | 1.06 ± 0.07 |

## Test Plan

- New Markdown tests for the main feature in
  `statically-known-branches.md`
- New Markdown tests for `sys.platform`
- Adapted tests for `EllipsisType`, `Never`, etc
2024-12-21 11:33:10 +01:00
Dhruv Manilawala
d47fba1e4a
[red-knot] Add support for unpacking union types (#15052)
## Summary

Refer:
https://github.com/astral-sh/ruff/issues/13773#issuecomment-2548020368

This PR adds support for unpacking union types. 

Unpacking a union type requires us to first distribute the types for all
the targets that are involved in an unpacking. For example, if there are
two targets and a union type that needs to be unpacked, each target will
get a type from each element in the union type.

For example, if the type is `tuple[int, int] | tuple[int, str]` and the
target has two elements `(a, b)`, then
* The type of `a` will be a union of `int` and `int` which are at index
0 in the first and second tuple respectively which resolves to an `int`.
* Similarly, the type of `b` will be a union of `int` and `str` which
are at index 1 in the first and second tuple respectively which will be
`int | str`.

### Refactors

There are couple of refactors that are added in this PR:
* Add a `debug_assertion` to validate that the unpack target is a list
or a tuple
* Add a separate method to handle starred expression

## Test Plan

Update `unpacking.md` with additional test cases that uses union types.
This is done using parameter type hints style.
2024-12-20 16:31:15 +05:30
Micha Reiser
913bce3cd5
Basic support for type: ignore comments (#15046)
## Summary

This PR adds initial support for `type: ignore`. It doesn't do anything
fancy yet like:

* Detecting invalid type ignore comments
* Detecting type ignore comments that are part of another suppression
comment: `# fmt: skip # type: ignore`
* Suppressing specific lints `type: ignore [code]`
* Detecting unsused type ignore comments
* ...

The goal is to add this functionality in separate PRs.

## Test Plan

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2024-12-20 10:35:09 +01:00
Alex Waygood
3aed14935d
[red-knot] Add support for @final classes (#15070)
Co-authored-by: Carl Meyer <carl@astral.sh>
2024-12-19 21:02:14 +00:00
Alex Waygood
bb43085939
[red-knot] Reduce TODOs in Type::member() (#15066) 2024-12-19 15:54:01 +00:00
Alex Waygood
40cba5dc8a
[red-knot] Cleanup various todo_type!() messages (#15063)
Co-authored-by: Micha Reiser <micha@reiser.io>
2024-12-19 13:03:41 +00:00
Douglas Creager
2802cbde29
Don't special-case class instances in unary expression inference (#15045)
We have a handy `to_meta_type` that does the right thing for class
instances, and also works for all of the other types that are “instances
of” something. Unless I'm missing something, this should let us get rid
of the catch-all clause in one fell swoop.

cf #14548
2024-12-18 14:37:17 -05:00
InSync
ed2bce6ebb
[red-knot] Report invalid exceptions (#15042)
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2024-12-18 18:31:24 +00:00
Douglas Creager
e8e461da6a
Prioritize attribute in from/import statement (#15041)
This tweaks the new semantics from #15026 a bit when a symbol could be
interpreted both as an attribute and a submodule of a package. For
`from...import`, we should actually prioritize the attribute, because of
how the statement itself is implemented [1].

> 1. check if the imported module has an attribute by that name
> 2. if not, attempt to import a submodule with that name and then check
the imported module again for that attribute

[1] https://docs.python.org/3/reference/simple_stmts.html#the-import-statement
2024-12-17 16:58:23 -05:00
Douglas Creager
91c9168dd7
Handle nested imports correctly in from ... import (#15026)
#14946 fixed our handling of nested imports with the `import` statement,
but didn't touch `from...import` statements.

cf
https://github.com/astral-sh/ruff/issues/14826#issuecomment-2525344515
2024-12-17 14:23:34 -05:00
cake-monotone
f463fa7b7c
[red-knot] Narrowing For Truthiness Checks (if x or if not x) (#14687)
## Summary

Fixes #14550.

Add `AlwaysTruthy` and `AlwaysFalsy` types, representing the set of objects whose `__bool__` method can only ever return `True` or `False`, respectively, and narrow `if x` and `if not x` accordingly.


## Test Plan

- New Markdown test for truthiness narrowing `narrow/truthiness.md`
- unit tests in `types.rs` and `builders.rs` (`cargo test --package
red_knot_python_semantic --lib -- types`)
2024-12-17 08:37:07 -08:00
Alex Waygood
463046ae07
[red-knot] Explicitly test diagnostics are emitted for unresolvable submodule imports (#15035) 2024-12-17 12:55:50 +00:00
InSync
7c2e7cf25e
[red-knot] Basic support for other legacy typing aliases (#14998)
## Summary

Resolves #14997.

## Test Plan

Markdown tests.
2024-12-17 09:33:15 +00:00
Dhruv Manilawala
dcdc6e7c64
[red-knot] Avoid undeclared path when raising conflicting declarations (#14958)
## Summary

This PR updates the logic when raising conflicting declarations
diagnostic to avoid the undeclared path if present.

The conflicting declaration diagnostics is added when there are two or
more declarations in the control flow path of a definition whose type
isn't equivalent to each other. This can be seen in the following
example:

```py
if flag:
	x: int
x = 1  # conflicting-declarations: Unknown, int
```

After this PR, we'd avoid considering "Unknown" as part of the
conflicting declarations. This means we'd still flag it for the
following case:

```py
if flag:
	x: int
else:
	x: str
x = 1  # conflicting-declarations: int, str
```

A solution that's local to the exception control flow was also explored
which required updating the logic for merging the flow snapshot to avoid
considering declarations using a flag. This is preserved here:
https://github.com/astral-sh/ruff/compare/dhruv/control-flow-no-declarations?expand=1.

The main motivation to avoid that is we don't really understand what the
user experience is w.r.t. the Unknown type and the
conflicting-declaration diagnostics. This makes us unsure on what the
right semantics are as to whether that diagnostics should be raised or
not and when to raise them. For now, we've decided to move forward with
this PR and could decide to adopt another solution or remove the
conflicting-declaration diagnostics in the future.

Closes: #13966 

## Test Plan

Update the existing mdtest case. Add an additional case specific to
exception control flow to verify that the diagnostic is not being raised
now.
2024-12-17 09:49:39 +05:30
Douglas Creager
4ddf9228f6
Bind top-most parent when importing nested module (#14946)
When importing a nested module, we were correctly creating a binding for
the top-most parent, but we were binding that to the nested module, not
to that parent module. Moreover, we weren't treating those submodules as
members of their containing parents. This PR addresses both issues, so
that nested imports work as expected.

As discussed in ~Slack~ whatever chat app I find myself in these days
😄, this requires keeping track of which modules have been imported
within the current file, so that when we resolve member access on a
module reference, we can see if that member has been imported as a
submodule. If so, we return the submodule reference immediately, instead
of checking whether the parent module's definition defines the symbol.

This is currently done in a flow insensitive manner. The `SemanticIndex`
now tracks all of the modules that are imported (via `import`, not via
`from...import`). The member access logic mentioned above currently only
considers module imports in the file containing the attribute
expression.

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
2024-12-16 16:15:40 -05:00
Alex Waygood
1389cb8e59
[red-knot] Emit an error if a bare Annotated or Literal is used in a type expression (#14973) 2024-12-15 02:00:52 +00:00
Alex Waygood
fa46ba2306
[red-knot] Fix bugs relating to assignability of dynamic type[] types (#14972) 2024-12-15 01:15:10 +00:00
github-actions[bot]
53c7ef8bfe
Sync vendored typeshed stubs (#14977)
Co-authored-by: typeshedbot <>
Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
2024-12-15 01:02:41 +00:00
Alex Waygood
90a5439791
[red-knot] Use type[Unknown] rather than Unknown as the fallback metaclass for invalid classes (#14961) 2024-12-13 19:48:51 +00:00
InSync
aa1938f6ba
[red-knot] Understand Annotated (#14950)
## Summary

Resolves #14922.

## Test Plan

Markdown tests.

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Co-authored-by: Carl Meyer <carl@astral.sh>
2024-12-13 09:41:37 -08:00
David Peter
c3a64b44b7
[red-knot] mdtest: python version requirements (#14954)
## Summary

This is not strictly required yet, but makes these tests future-proof.
They need a `python-version` requirement as they rely on language
features that are not available in 3.9.
2024-12-13 10:40:38 +01:00
David Peter
e96b13c027
[red-knot] Support typing.TYPE_CHECKING (#14952)
## Summary

Add support for `typing.TYPE_CHECKING` and
`typing_extensions.TYPE_CHECKING`.

relates to: https://github.com/astral-sh/ruff/issues/14170

## Test Plan

New Markdown-based tests
2024-12-13 09:24:48 +00:00
David Peter
2ccc9b19a7
[red-knot] Improve match mdtests (#14951)
## Summary

Minor improvement for the `match` tests to make sure we can't infer
statically whether or not a certain `case` applies.
2024-12-13 09:50:17 +01:00
Micha Reiser
c1837e4189
Rename custom-typeshed-dir, target-version and current-directory CLI options (#14930)
## Summary

This PR renames the `--custom-typeshed-dir`, `target-version`, and
`--current-directory` cli options to `--typeshed`,
`--python-version`, and `--project` as discussed in the CLI proposal
document.
I added aliases for `--target-version` (for Ruff compat) and
`--custom-typeshed-dir` (for Alex)

## Test Plan

Long help

```
An extremely fast Python type checker.

Usage: red_knot [OPTIONS] [COMMAND]

Commands:
  server  Start the language server
  help    Print this message or the help of the given subcommand(s)

Options:
      --project <PROJECT>
          Run the command within the given project directory.
          
          All `pyproject.toml` files will be discovered by walking up the directory tree from the project root, as will the project's virtual environment (`.venv`).
          
          Other command-line arguments (such as relative paths) will be resolved relative to the current working directory."#,

      --venv-path <PATH>
          Path to the virtual environment the project uses.
          
          If provided, red-knot will use the `site-packages` directory of this virtual environment to resolve type information for the project's third-party dependencies.

      --typeshed-path <PATH>
          Custom directory to use for stdlib typeshed stubs

      --extra-search-path <PATH>
          Additional path to use as a module-resolution source (can be passed multiple times)

      --python-version <VERSION>
          Python version to assume when resolving types
          
          [possible values: 3.7, 3.8, 3.9, 3.10, 3.11, 3.12, 3.13]

  -v, --verbose...
          Use verbose output (or `-vv` and `-vvv` for more verbose output)

  -W, --watch
          Run in watch mode by re-running whenever files change

  -h, --help
          Print help (see a summary with '-h')

  -V, --version
          Print version
```

Short help 

```
An extremely fast Python type checker.

Usage: red_knot [OPTIONS] [COMMAND]

Commands:
  server  Start the language server
  help    Print this message or the help of the given subcommand(s)

Options:
      --project <PROJECT>         Run the command within the given project directory
      --venv-path <PATH>          Path to the virtual environment the project uses
      --typeshed-path <PATH>      Custom directory to use for stdlib typeshed stubs
      --extra-search-path <PATH>  Additional path to use as a module-resolution source (can be passed multiple times)
      --python-version <VERSION>  Python version to assume when resolving types [possible values: 3.7, 3.8, 3.9, 3.10, 3.11, 3.12, 3.13]
  -v, --verbose...                Use verbose output (or `-vv` and `-vvv` for more verbose output)
  -W, --watch                     Run in watch mode by re-running whenever files change
  -h, --help                      Print help (see more with '--help')
  -V, --version                   Print version

```

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2024-12-13 08:21:52 +00:00
David Peter
d7ce548893
[red-knot] Add narrowing for 'while' loops (#14947)
## Summary

Add type narrowing for `while` loops and corresponding `else` branches.

closes #14861 

## Test Plan

New Markdown tests.
2024-12-13 07:40:14 +01:00
David Peter
657d26ff20
[red-knot] Tests for 'while' loop boundness (#14944)
## Summary

Regression test(s) for something that broken while implementing #14759.
We have similar tests for other control flow elements, but feel free to
let me know if this seems superfluous.

## Test Plan

New mdtests
2024-12-12 21:06:56 +01:00
Alex Waygood
dbc191d2d6
[red-knot] Fixes to Type::to_meta_type (#14942) 2024-12-12 19:55:11 +00:00
Alex Waygood
71239f248e
[red-knot] Add explicit TODO branches for many typing special forms and qualifiers (#14936) 2024-12-12 17:57:26 +00:00
Alex Waygood
45b565cbb5
[red-knot] Any cannot be parameterized (#14933) 2024-12-12 11:50:34 +00:00
InSync
e4885a2fb2
[red-knot] Understand typing.Tuple (#14927)
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2024-12-12 00:58:06 +00:00
David Peter
a7e5e42b88
[red-knot] Make attributes.md test future-proof (#14923)
## Summary

Using `typing.LiteralString` breaks as soon as we understand
`sys.version_info` branches, as it's only available in 3.11 and later.

## Test Plan

Made sure it didn't fail on my #14759 branch anymore.
2024-12-11 20:46:24 +01:00
Alex Waygood
c361cf66ad
[red-knot] Precise inference for __class__ attributes on objects of all types (#14921) 2024-12-11 17:30:34 +00:00
Alex Waygood
a54353392f
[red-knot] Add failing test for use of type[] as a base class (#14913)
We support using `typing.Type[]` as a base class (and we have tests for
it), but not yet `builtins.type[]`. At some point we should fix that,
but I don't think it';s worth spending much time on now (and it might be
easier once we've implemented generics?). This PR just adds a failing
test with a TODO.
2024-12-11 17:08:00 +00:00
InSync
f30227c436
[red-knot] Understand typing.Type (#14904)
Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
2024-12-11 11:01:38 +00:00
Douglas Creager
d4126f6049
Handle type[Any] correctly (#14876)
This adds support for `type[Any]`, which represents an unknown type (not
an instance of an unknown type), and `type`, which we are choosing to
interpret as `type[object]`.

Closes #14546
2024-12-10 16:12:37 -05:00
Micha Reiser
5fc8e5d80e
[red-knot] Add infrastructure to declare lints (#14873)
## Summary

This is the second PR out of three that adds support for
enabling/disabling lint rules in Red Knot. You may want to take a look
at the [first PR](https://github.com/astral-sh/ruff/pull/14869) in this
stack to familiarize yourself with the used terminology.

This PR adds a new syntax to define a lint: 

```rust
declare_lint! {
    /// ## What it does
    /// Checks for references to names that are not defined.
    ///
    /// ## Why is this bad?
    /// Using an undefined variable will raise a `NameError` at runtime.
    ///
    /// ## Example
    ///
    /// ```python
    /// print(x)  # NameError: name 'x' is not defined
    /// ```
    pub(crate) static UNRESOLVED_REFERENCE = {
        summary: "detects references to names that are not defined",
        status: LintStatus::preview("1.0.0"),
        default_level: Level::Warn,
    }
}
```

A lint has a name and metadata about its status (preview, stable,
removed, deprecated), the default diagnostic level (unless the
configuration changes), and documentation. I use a macro here to derive
the kebab-case name and extract the documentation automatically.

This PR doesn't yet add any mechanism to discover all known lints. This
will be added in the next and last PR in this stack.


## Documentation
I documented some rules but then decided that it's probably not my best
use of time if I document all of them now (it also means that I play
catch-up with all of you forever). That's why I left some rules
undocumented (marked with TODO)

## Where is the best place to define all lints?

I'm not sure. I think what I have in this PR is fine but I also don't
love it because most lints are in a single place but not all of them. If
you have ideas, let me know.


## Why is the message not part of the lint, unlike Ruff's `Violation`

I understand that the main motivation for defining `message` on
`Violation` in Ruff is to remove the need to repeat the same message
over and over again. I'm not sure if this is an actual problem. Most
rules only emit a diagnostic in a single place and they commonly use
different messages if they emit diagnostics in different code paths,
requiring extra fields on the `Violation` struct.

That's why I'm not convinced that there's an actual need for it and
there are alternatives that can reduce the repetition when creating a
diagnostic:

* Create a helper function. We already do this in red knot with the
`add_xy` methods
* Create a custom `Diagnostic` implementation that tailors the entire
diagnostic and pre-codes e.g. the message

Avoiding an extra field on the `Violation` also removes the need to
allocate intermediate strings as it is commonly the place in Ruff.
Instead, Red Knot can use a borrowed string with `format_args`

## Test Plan

`cargo test`
2024-12-10 16:14:44 +00:00
InSync
15fe540251
Improve mdtests style (#14884)
Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
2024-12-10 13:05:51 +00:00
Alex Waygood
ab26d9cf9a
[red-knot] Improve type inference for except handlers (#14838) 2024-12-09 22:49:58 +00:00
InSync
3865fb6641
[red-knot] Understanding type[Union[A, B]] (#14858)
Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
2024-12-09 12:47:14 +00:00
Dimitri Papadopoulos Orfanos
59145098d6
Fix typos found by codespell (#14863)
## Summary

Just fix typos.

## Test Plan

CI tests.

---------

Co-authored-by: Micha Reiser <micha@reiser.io>
2024-12-09 09:32:12 +00:00
Shaygan Hooshyari
269e47be96
Understand type[A | B] special form in annotations (#14830)
resolves https://github.com/astral-sh/ruff/issues/14703

I decided to use recursion to get the type, so if anything is added to
the single element inference it will be applied for the union.
Also added this
[change](https://github.com/astral-sh/ruff/issues/14703#issuecomment-2510286217)
in this PR since it was easy.

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
2024-12-07 17:34:50 +00:00
Douglas Creager
8fdd88013d
Support type[a.X] with qualified class names (#14825)
This adds support for `type[a.X]`, where the `type` special form is
applied to a qualified name that resolves to a class literal. This works
for both nested classes and classes imported from another module.

Closes #14545
2024-12-06 17:14:51 -05:00
Carl Meyer
3017b3b687
[red-knot] function parameter types (#14802)
## Summary

Inferred and declared types for function parameters, in the function
body scope.

Fixes #13693.

## Test Plan

Added mdtests.

---------

Co-authored-by: Micha Reiser <micha@reiser.io>
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2024-12-06 12:55:56 -08:00
David Peter
6b9f3d7d7c
[red-knot] Import LiteralString/Never from typing_extensions (#14817)
## Summary

`typing.Never` and `typing.LiteralString` are only conditionally
exported from `typing` for Python versions 3.11 and later. We run the
Markdown tests with the default Python version of 3.9, so here we change
the import to `typing_extensions` instead, and add a new test to make
sure we'll continue to understand the `typing`-version of these symbols
for newer versions.

This didn't cause problems so far, as we don't understand
`sys.version_info` branches yet.

## Test Plan

New Markdown tests to make sure this will continue to work in the
future.
2024-12-06 13:57:51 +01:00
Douglas Creager
918358aaa6
Migrate some inference tests to mdtests (#14795)
As part of #13696, this PR ports a smallish number of inference tests
over to the mdtest framework.
2024-12-06 11:19:22 +01:00
David Peter
b01a651e69
[red-knot] Support for TOML configs in Markdown tests (#14785)
## Summary

This adds support for specifying the target Python version from a
Markdown test. It is a somewhat limited ad-hoc solution, but designed to
be future-compatible. TOML blocks can be added to arbitrary sections in
the Markdown block. They have the following format:

````markdown
```toml
[tool.knot.environment]
target-version = "3.13"
```
````

So far, there is nothing else that can be configured, but it should be
straightforward to extend this to things like a custom typeshed path.

This is in preparation for the statically-known branches feature where
we are going to have to specify the target version for lots of tests.

## Test Plan

- New Markdown test that fails without the explicitly specified
`target-version`.
- Manually tested various error paths when specifying a wrong
`target-version` field.
- Made sure that running tests is as fast as before.
2024-12-06 10:22:08 +01:00
Dhruv Manilawala
40b0b67dd9
[red-knot] Separate invalid syntax code snippets (#14803)
Ref: https://github.com/astral-sh/ruff/pull/14788#discussion_r1872242283

This PR:
* Separates code snippets as individual tests for the invalid syntax
cases
* Adds a general comment explaining why the parser could emit more
syntax errors than expected
2024-12-06 02:41:33 +00:00
Dhruv Manilawala
e9941cd714
[red-knot] Move standalone expr inference to for non-name target (#14788)
## Summary

Ref: https://github.com/astral-sh/ruff/pull/14754#discussion_r1871040646

## Test Plan

Remove the TODO comment and update the mdtest.
2024-12-05 18:06:20 +05:30
Dhruv Manilawala
43bf1a8907
Add tests for "keyword as identifier" syntax errors (#14754)
## Summary

This is related to #13778, more specifically
https://github.com/astral-sh/ruff/issues/13778#issuecomment-2513556004.

This PR adds various test cases where a keyword is being where an
identifier is expected. The tests are to make sure that red knot doesn't
panic, raises the syntax error and the identifier is added to the symbol
table. The final part allows editor related features like renaming the
symbol.
2024-12-05 17:32:48 +05:30