Compare commits
41 Commits
alex/submo
...
micha/has_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39b5c696e2 | ||
|
|
b0bc990cbf | ||
|
|
ad3de4e488 | ||
|
|
2214a46139 | ||
|
|
c02bd11b93 | ||
|
|
eeaaa8e9fe | ||
|
|
7f7485d608 | ||
|
|
d755f3b522 | ||
|
|
83168a1bb1 | ||
|
|
0f373603eb | ||
|
|
cc23af944f | ||
|
|
0589700ca1 | ||
|
|
43d983ecae | ||
|
|
5c69bb564c | ||
|
|
89fed85a8d | ||
|
|
051f6896ac | ||
|
|
5b1d3ac9b9 | ||
|
|
b2b0ad38ea | ||
|
|
01c0a3e960 | ||
|
|
5c942119f8 | ||
|
|
2acf1cc0fd | ||
|
|
4fdbe26445 | ||
|
|
682d29c256 | ||
|
|
8e13765b57 | ||
|
|
7d3b7c5754 | ||
|
|
d6a5bbd91c | ||
|
|
1df6544ad8 | ||
|
|
4e1cf5747a | ||
|
|
cbfecfaf41 | ||
|
|
8f530a7ab0 | ||
|
|
5372bb3440 | ||
|
|
d08e414179 | ||
|
|
0b918ae4d5 | ||
|
|
9838f81baf | ||
|
|
ba47349c2e | ||
|
|
04f9949711 | ||
|
|
8bc753b842 | ||
|
|
c7eea1f2e3 | ||
|
|
be8eb92946 | ||
|
|
a544c59186 | ||
|
|
bb464ed924 |
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -60,7 +60,7 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -123,7 +123,7 @@ jobs:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json
|
||||
steps:
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -174,7 +174,7 @@ jobs:
|
||||
outputs:
|
||||
val: ${{ steps.host.outputs.manifest }}
|
||||
steps:
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -250,7 +250,7 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -4390,6 +4390,7 @@ dependencies = [
|
||||
"ruff_python_trivia",
|
||||
"salsa",
|
||||
"tempfile",
|
||||
"tikv-jemallocator",
|
||||
"toml",
|
||||
"tracing",
|
||||
"tracing-flame",
|
||||
|
||||
@@ -5,7 +5,7 @@ resolver = "2"
|
||||
[workspace.package]
|
||||
# Please update rustfmt.toml when bumping the Rust edition
|
||||
edition = "2024"
|
||||
rust-version = "1.89"
|
||||
rust-version = "1.90"
|
||||
homepage = "https://docs.astral.sh/ruff"
|
||||
documentation = "https://docs.astral.sh/ruff"
|
||||
repository = "https://github.com/astral-sh/ruff"
|
||||
|
||||
@@ -57,8 +57,11 @@ Ruff is extremely actively developed and used in major open-source projects like
|
||||
|
||||
...and [many more](#whos-using-ruff).
|
||||
|
||||
Ruff is backed by [Astral](https://astral.sh). Read the [launch post](https://astral.sh/blog/announcing-astral-the-company-behind-ruff),
|
||||
or the original [project announcement](https://notes.crmarsh.com/python-tooling-could-be-much-much-faster).
|
||||
Ruff is backed by [Astral](https://astral.sh), the creators of
|
||||
[uv](https://github.com/astral-sh/uv) and [ty](https://github.com/astral-sh/ty).
|
||||
|
||||
Read the [launch post](https://astral.sh/blog/announcing-astral-the-company-behind-ruff), or the
|
||||
original [project announcement](https://notes.crmarsh.com/python-tooling-could-be-much-much-faster).
|
||||
|
||||
## Testimonials
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ use anyhow::bail;
|
||||
use clap::builder::Styles;
|
||||
use clap::builder::styling::{AnsiColor, Effects};
|
||||
use clap::builder::{TypedValueParser, ValueParserFactory};
|
||||
use clap::{Parser, Subcommand, command};
|
||||
use clap::{Parser, Subcommand};
|
||||
use colored::Colorize;
|
||||
use itertools::Itertools;
|
||||
use path_absolutize::path_dedot;
|
||||
|
||||
@@ -9,7 +9,7 @@ use std::sync::mpsc::channel;
|
||||
use anyhow::Result;
|
||||
use clap::CommandFactory;
|
||||
use colored::Colorize;
|
||||
use log::{error, warn};
|
||||
use log::error;
|
||||
use notify::{RecursiveMode, Watcher, recommended_watcher};
|
||||
|
||||
use args::{GlobalConfigArgs, ServerCommand};
|
||||
|
||||
@@ -194,7 +194,7 @@ static SYMPY: Benchmark = Benchmark::new(
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY312,
|
||||
},
|
||||
13030,
|
||||
13100,
|
||||
);
|
||||
|
||||
static TANJUN: Benchmark = Benchmark::new(
|
||||
@@ -223,7 +223,7 @@ static STATIC_FRAME: Benchmark = Benchmark::new(
|
||||
max_dep_date: "2025-08-09",
|
||||
python_version: PythonVersion::PY311,
|
||||
},
|
||||
950,
|
||||
1100,
|
||||
);
|
||||
|
||||
#[track_caller]
|
||||
|
||||
@@ -144,8 +144,8 @@ fn emit_field(output: &mut String, name: &str, field: &OptionField, parents: &[S
|
||||
output.push('\n');
|
||||
|
||||
if let Some(deprecated) = &field.deprecated {
|
||||
output.push_str("> [!WARN] \"Deprecated\"\n");
|
||||
output.push_str("> This option has been deprecated");
|
||||
output.push_str("!!! warning \"Deprecated\"\n");
|
||||
output.push_str(" This option has been deprecated");
|
||||
|
||||
if let Some(since) = deprecated.since {
|
||||
write!(output, " in {since}").unwrap();
|
||||
@@ -166,8 +166,9 @@ fn emit_field(output: &mut String, name: &str, field: &OptionField, parents: &[S
|
||||
output.push('\n');
|
||||
let _ = writeln!(output, "**Type**: `{}`", field.value_type);
|
||||
output.push('\n');
|
||||
output.push_str("**Example usage** (`pyproject.toml`):\n\n");
|
||||
output.push_str("**Example usage**:\n\n");
|
||||
output.push_str(&format_example(
|
||||
"pyproject.toml",
|
||||
&format_header(
|
||||
field.scope,
|
||||
field.example,
|
||||
@@ -179,11 +180,11 @@ fn emit_field(output: &mut String, name: &str, field: &OptionField, parents: &[S
|
||||
output.push('\n');
|
||||
}
|
||||
|
||||
fn format_example(header: &str, content: &str) -> String {
|
||||
fn format_example(title: &str, header: &str, content: &str) -> String {
|
||||
if header.is_empty() {
|
||||
format!("```toml\n{content}\n```\n",)
|
||||
format!("```toml title=\"{title}\"\n{content}\n```\n",)
|
||||
} else {
|
||||
format!("```toml\n{header}\n{content}\n```\n",)
|
||||
format!("```toml title=\"{title}\"\n{header}\n{content}\n```\n",)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ impl Edit {
|
||||
|
||||
/// Creates an edit that replaces the content in `range` with `content`.
|
||||
pub fn range_replacement(content: String, range: TextRange) -> Self {
|
||||
debug_assert!(!content.is_empty(), "Prefer `Fix::deletion`");
|
||||
debug_assert!(!content.is_empty(), "Prefer `Edit::deletion`");
|
||||
|
||||
Self {
|
||||
content: Some(Box::from(content)),
|
||||
|
||||
@@ -337,7 +337,7 @@ macro_rules! best_fitting {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::prelude::*;
|
||||
use crate::{FormatState, SimpleFormatOptions, VecBuffer, write};
|
||||
use crate::{FormatState, SimpleFormatOptions, VecBuffer};
|
||||
|
||||
struct TestFormat;
|
||||
|
||||
@@ -385,8 +385,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn best_fitting_variants_print_as_lists() {
|
||||
use crate::Formatted;
|
||||
use crate::prelude::*;
|
||||
use crate::{Formatted, format, format_args};
|
||||
|
||||
// The second variant below should be selected when printing at a width of 30
|
||||
let formatted_best_fitting = format!(
|
||||
|
||||
@@ -132,7 +132,6 @@ async def c():
|
||||
# Non-errors
|
||||
###
|
||||
|
||||
# False-negative: RustPython doesn't parse the `\N{snowman}`.
|
||||
"\N{snowman} {}".format(a)
|
||||
|
||||
"{".format(a)
|
||||
@@ -276,3 +275,6 @@ if __name__ == "__main__":
|
||||
number = 0
|
||||
string = "{}".format(number := number + 1)
|
||||
print(string)
|
||||
|
||||
# Unicode escape
|
||||
"\N{angle}AOB = {angle}°".format(angle=180)
|
||||
|
||||
@@ -286,12 +286,7 @@ pub(crate) fn add_argument(argument: &str, arguments: &Arguments, tokens: &Token
|
||||
|
||||
/// Generic function to add a (regular) parameter to a function definition.
|
||||
pub(crate) fn add_parameter(parameter: &str, parameters: &Parameters, source: &str) -> Edit {
|
||||
if let Some(last) = parameters
|
||||
.args
|
||||
.iter()
|
||||
.filter(|arg| arg.default.is_none())
|
||||
.next_back()
|
||||
{
|
||||
if let Some(last) = parameters.args.iter().rfind(|arg| arg.default.is_none()) {
|
||||
// Case 1: at least one regular parameter, so append after the last one.
|
||||
Edit::insertion(format!(", {parameter}"), last.end())
|
||||
} else if !parameters.args.is_empty() {
|
||||
|
||||
@@ -146,7 +146,7 @@ fn reverse_comparison(expr: &Expr, locator: &Locator, stylist: &Stylist) -> Resu
|
||||
let left = (*comparison.left).clone();
|
||||
|
||||
// Copy the right side to the left side.
|
||||
comparison.left = Box::new(comparison.comparisons[0].comparator.clone());
|
||||
*comparison.left = comparison.comparisons[0].comparator.clone();
|
||||
|
||||
// Copy the left side to the right side.
|
||||
comparison.comparisons[0].comparator = left;
|
||||
|
||||
@@ -902,56 +902,76 @@ help: Convert to f-string
|
||||
132 | # Non-errors
|
||||
|
||||
UP032 [*] Use f-string instead of `format` call
|
||||
--> UP032_0.py:160:1
|
||||
--> UP032_0.py:135:1
|
||||
|
|
||||
158 | r'"\N{snowman} {}".format(a)'
|
||||
159 |
|
||||
160 | / "123456789 {}".format(
|
||||
161 | | 11111111111111111111111111111111111111111111111111111111111111111111111111,
|
||||
162 | | )
|
||||
| |_^
|
||||
163 |
|
||||
164 | """
|
||||
133 | ###
|
||||
134 |
|
||||
135 | "\N{snowman} {}".format(a)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
136 |
|
||||
137 | "{".format(a)
|
||||
|
|
||||
help: Convert to f-string
|
||||
157 |
|
||||
158 | r'"\N{snowman} {}".format(a)'
|
||||
159 |
|
||||
132 | # Non-errors
|
||||
133 | ###
|
||||
134 |
|
||||
- "\N{snowman} {}".format(a)
|
||||
135 + f"\N{snowman} {a}"
|
||||
136 |
|
||||
137 | "{".format(a)
|
||||
138 |
|
||||
|
||||
UP032 [*] Use f-string instead of `format` call
|
||||
--> UP032_0.py:159:1
|
||||
|
|
||||
157 | r'"\N{snowman} {}".format(a)'
|
||||
158 |
|
||||
159 | / "123456789 {}".format(
|
||||
160 | | 11111111111111111111111111111111111111111111111111111111111111111111111111,
|
||||
161 | | )
|
||||
| |_^
|
||||
162 |
|
||||
163 | """
|
||||
|
|
||||
help: Convert to f-string
|
||||
156 |
|
||||
157 | r'"\N{snowman} {}".format(a)'
|
||||
158 |
|
||||
- "123456789 {}".format(
|
||||
- 11111111111111111111111111111111111111111111111111111111111111111111111111,
|
||||
- )
|
||||
160 + f"123456789 {11111111111111111111111111111111111111111111111111111111111111111111111111}"
|
||||
161 |
|
||||
162 | """
|
||||
163 | {}
|
||||
159 + f"123456789 {11111111111111111111111111111111111111111111111111111111111111111111111111}"
|
||||
160 |
|
||||
161 | """
|
||||
162 | {}
|
||||
|
||||
UP032 [*] Use f-string instead of `format` call
|
||||
--> UP032_0.py:164:1
|
||||
--> UP032_0.py:163:1
|
||||
|
|
||||
162 | )
|
||||
163 |
|
||||
164 | / """
|
||||
161 | )
|
||||
162 |
|
||||
163 | / """
|
||||
164 | | {}
|
||||
165 | | {}
|
||||
166 | | {}
|
||||
167 | | {}
|
||||
168 | | """.format(
|
||||
169 | | 1,
|
||||
170 | | 2,
|
||||
171 | | 111111111111111111111111111111111111111111111111111111111111111111111111111111111111111,
|
||||
172 | | )
|
||||
167 | | """.format(
|
||||
168 | | 1,
|
||||
169 | | 2,
|
||||
170 | | 111111111111111111111111111111111111111111111111111111111111111111111111111111111111111,
|
||||
171 | | )
|
||||
| |_^
|
||||
173 |
|
||||
174 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = """{}
|
||||
172 |
|
||||
173 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = """{}
|
||||
|
|
||||
help: Convert to f-string
|
||||
161 | 11111111111111111111111111111111111111111111111111111111111111111111111111,
|
||||
162 | )
|
||||
163 |
|
||||
164 + f"""
|
||||
165 + {1}
|
||||
166 + {2}
|
||||
167 + {111111111111111111111111111111111111111111111111111111111111111111111111111111111111111}
|
||||
168 | """
|
||||
160 | 11111111111111111111111111111111111111111111111111111111111111111111111111,
|
||||
161 | )
|
||||
162 |
|
||||
163 + f"""
|
||||
164 + {1}
|
||||
165 + {2}
|
||||
166 + {111111111111111111111111111111111111111111111111111111111111111111111111111111111111111}
|
||||
167 | """
|
||||
- {}
|
||||
- {}
|
||||
- {}
|
||||
@@ -960,392 +980,408 @@ help: Convert to f-string
|
||||
- 2,
|
||||
- 111111111111111111111111111111111111111111111111111111111111111111111111111111111111111,
|
||||
- )
|
||||
169 |
|
||||
170 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = """{}
|
||||
171 | """.format(
|
||||
168 |
|
||||
169 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = """{}
|
||||
170 | """.format(
|
||||
|
||||
UP032 [*] Use f-string instead of `format` call
|
||||
--> UP032_0.py:174:84
|
||||
--> UP032_0.py:173:84
|
||||
|
|
||||
172 | )
|
||||
173 |
|
||||
174 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = """{}
|
||||
171 | )
|
||||
172 |
|
||||
173 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = """{}
|
||||
| ____________________________________________________________________________________^
|
||||
175 | | """.format(
|
||||
176 | | 111111
|
||||
177 | | )
|
||||
174 | | """.format(
|
||||
175 | | 111111
|
||||
176 | | )
|
||||
| |_^
|
||||
178 |
|
||||
179 | "{}".format(
|
||||
177 |
|
||||
178 | "{}".format(
|
||||
|
|
||||
help: Convert to f-string
|
||||
171 | 111111111111111111111111111111111111111111111111111111111111111111111111111111111111111,
|
||||
172 | )
|
||||
173 |
|
||||
170 | 111111111111111111111111111111111111111111111111111111111111111111111111111111111111111,
|
||||
171 | )
|
||||
172 |
|
||||
- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = """{}
|
||||
- """.format(
|
||||
- 111111
|
||||
- )
|
||||
174 + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = f"""{111111}
|
||||
175 + """
|
||||
176 |
|
||||
177 | "{}".format(
|
||||
178 | [
|
||||
173 + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = f"""{111111}
|
||||
174 + """
|
||||
175 |
|
||||
176 | "{}".format(
|
||||
177 | [
|
||||
|
||||
UP032 Use f-string instead of `format` call
|
||||
--> UP032_0.py:202:1
|
||||
--> UP032_0.py:201:1
|
||||
|
|
||||
200 | "{}".format(**c)
|
||||
201 |
|
||||
202 | / "{}".format(
|
||||
203 | | 1 # comment
|
||||
204 | | )
|
||||
199 | "{}".format(**c)
|
||||
200 |
|
||||
201 | / "{}".format(
|
||||
202 | | 1 # comment
|
||||
203 | | )
|
||||
| |_^
|
||||
|
|
||||
help: Convert to f-string
|
||||
|
||||
UP032 [*] Use f-string instead of `format` call
|
||||
--> UP032_0.py:209:1
|
||||
--> UP032_0.py:208:1
|
||||
|
|
||||
207 | # The fixed string will exceed the line length, but it's still smaller than the
|
||||
208 | # existing line length, so it's fine.
|
||||
209 | "<Customer: {}, {}, {}, {}, {}>".format(self.internal_ids, self.external_ids, self.properties, self.tags, self.others)
|
||||
206 | # The fixed string will exceed the line length, but it's still smaller than the
|
||||
207 | # existing line length, so it's fine.
|
||||
208 | "<Customer: {}, {}, {}, {}, {}>".format(self.internal_ids, self.external_ids, self.properties, self.tags, self.others)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
210 |
|
||||
211 | # When fixing, trim the trailing empty string.
|
||||
209 |
|
||||
210 | # When fixing, trim the trailing empty string.
|
||||
|
|
||||
help: Convert to f-string
|
||||
206 |
|
||||
207 | # The fixed string will exceed the line length, but it's still smaller than the
|
||||
208 | # existing line length, so it's fine.
|
||||
205 |
|
||||
206 | # The fixed string will exceed the line length, but it's still smaller than the
|
||||
207 | # existing line length, so it's fine.
|
||||
- "<Customer: {}, {}, {}, {}, {}>".format(self.internal_ids, self.external_ids, self.properties, self.tags, self.others)
|
||||
209 + f"<Customer: {self.internal_ids}, {self.external_ids}, {self.properties}, {self.tags}, {self.others}>"
|
||||
210 |
|
||||
211 | # When fixing, trim the trailing empty string.
|
||||
212 | raise ValueError("Conflicting configuration dicts: {!r} {!r}"
|
||||
208 + f"<Customer: {self.internal_ids}, {self.external_ids}, {self.properties}, {self.tags}, {self.others}>"
|
||||
209 |
|
||||
210 | # When fixing, trim the trailing empty string.
|
||||
211 | raise ValueError("Conflicting configuration dicts: {!r} {!r}"
|
||||
|
||||
UP032 [*] Use f-string instead of `format` call
|
||||
--> UP032_0.py:212:18
|
||||
--> UP032_0.py:211:18
|
||||
|
|
||||
211 | # When fixing, trim the trailing empty string.
|
||||
212 | raise ValueError("Conflicting configuration dicts: {!r} {!r}"
|
||||
210 | # When fixing, trim the trailing empty string.
|
||||
211 | raise ValueError("Conflicting configuration dicts: {!r} {!r}"
|
||||
| __________________^
|
||||
213 | | "".format(new_dict, d))
|
||||
212 | | "".format(new_dict, d))
|
||||
| |_______________________________________^
|
||||
214 |
|
||||
215 | # When fixing, trim the trailing empty string.
|
||||
213 |
|
||||
214 | # When fixing, trim the trailing empty string.
|
||||
|
|
||||
help: Convert to f-string
|
||||
209 | "<Customer: {}, {}, {}, {}, {}>".format(self.internal_ids, self.external_ids, self.properties, self.tags, self.others)
|
||||
210 |
|
||||
211 | # When fixing, trim the trailing empty string.
|
||||
208 | "<Customer: {}, {}, {}, {}, {}>".format(self.internal_ids, self.external_ids, self.properties, self.tags, self.others)
|
||||
209 |
|
||||
210 | # When fixing, trim the trailing empty string.
|
||||
- raise ValueError("Conflicting configuration dicts: {!r} {!r}"
|
||||
- "".format(new_dict, d))
|
||||
212 + raise ValueError(f"Conflicting configuration dicts: {new_dict!r} {d!r}")
|
||||
211 + raise ValueError(f"Conflicting configuration dicts: {new_dict!r} {d!r}")
|
||||
212 |
|
||||
213 | # When fixing, trim the trailing empty string.
|
||||
214 | raise ValueError("Conflicting configuration dicts: {!r} {!r}"
|
||||
|
||||
UP032 [*] Use f-string instead of `format` call
|
||||
--> UP032_0.py:215:18
|
||||
|
|
||||
214 | # When fixing, trim the trailing empty string.
|
||||
215 | raise ValueError("Conflicting configuration dicts: {!r} {!r}"
|
||||
| __________________^
|
||||
216 | | .format(new_dict, d))
|
||||
| |_____________________________________^
|
||||
217 |
|
||||
218 | raise ValueError(
|
||||
|
|
||||
help: Convert to f-string
|
||||
212 | "".format(new_dict, d))
|
||||
213 |
|
||||
214 | # When fixing, trim the trailing empty string.
|
||||
215 | raise ValueError("Conflicting configuration dicts: {!r} {!r}"
|
||||
|
||||
UP032 [*] Use f-string instead of `format` call
|
||||
--> UP032_0.py:216:18
|
||||
|
|
||||
215 | # When fixing, trim the trailing empty string.
|
||||
216 | raise ValueError("Conflicting configuration dicts: {!r} {!r}"
|
||||
| __________________^
|
||||
217 | | .format(new_dict, d))
|
||||
| |_____________________________________^
|
||||
218 |
|
||||
219 | raise ValueError(
|
||||
|
|
||||
help: Convert to f-string
|
||||
213 | "".format(new_dict, d))
|
||||
214 |
|
||||
215 | # When fixing, trim the trailing empty string.
|
||||
- raise ValueError("Conflicting configuration dicts: {!r} {!r}"
|
||||
- .format(new_dict, d))
|
||||
216 + raise ValueError(f"Conflicting configuration dicts: {new_dict!r} {d!r}"
|
||||
217 + )
|
||||
218 |
|
||||
219 | raise ValueError(
|
||||
220 | "Conflicting configuration dicts: {!r} {!r}"
|
||||
215 + raise ValueError(f"Conflicting configuration dicts: {new_dict!r} {d!r}"
|
||||
216 + )
|
||||
217 |
|
||||
218 | raise ValueError(
|
||||
219 | "Conflicting configuration dicts: {!r} {!r}"
|
||||
|
||||
UP032 [*] Use f-string instead of `format` call
|
||||
--> UP032_0.py:220:5
|
||||
--> UP032_0.py:219:5
|
||||
|
|
||||
219 | raise ValueError(
|
||||
220 | / "Conflicting configuration dicts: {!r} {!r}"
|
||||
221 | | "".format(new_dict, d)
|
||||
218 | raise ValueError(
|
||||
219 | / "Conflicting configuration dicts: {!r} {!r}"
|
||||
220 | | "".format(new_dict, d)
|
||||
| |__________________________^
|
||||
222 | )
|
||||
221 | )
|
||||
|
|
||||
help: Convert to f-string
|
||||
217 | .format(new_dict, d))
|
||||
218 |
|
||||
219 | raise ValueError(
|
||||
216 | .format(new_dict, d))
|
||||
217 |
|
||||
218 | raise ValueError(
|
||||
- "Conflicting configuration dicts: {!r} {!r}"
|
||||
- "".format(new_dict, d)
|
||||
220 + f"Conflicting configuration dicts: {new_dict!r} {d!r}"
|
||||
219 + f"Conflicting configuration dicts: {new_dict!r} {d!r}"
|
||||
220 | )
|
||||
221 |
|
||||
222 | raise ValueError(
|
||||
|
||||
UP032 [*] Use f-string instead of `format` call
|
||||
--> UP032_0.py:224:5
|
||||
|
|
||||
223 | raise ValueError(
|
||||
224 | / "Conflicting configuration dicts: {!r} {!r}"
|
||||
225 | | "".format(new_dict, d)
|
||||
| |__________________________^
|
||||
226 |
|
||||
227 | )
|
||||
|
|
||||
help: Convert to f-string
|
||||
221 | )
|
||||
222 |
|
||||
223 | raise ValueError(
|
||||
|
||||
UP032 [*] Use f-string instead of `format` call
|
||||
--> UP032_0.py:225:5
|
||||
|
|
||||
224 | raise ValueError(
|
||||
225 | / "Conflicting configuration dicts: {!r} {!r}"
|
||||
226 | | "".format(new_dict, d)
|
||||
| |__________________________^
|
||||
227 |
|
||||
228 | )
|
||||
|
|
||||
help: Convert to f-string
|
||||
222 | )
|
||||
223 |
|
||||
224 | raise ValueError(
|
||||
- "Conflicting configuration dicts: {!r} {!r}"
|
||||
- "".format(new_dict, d)
|
||||
225 + f"Conflicting configuration dicts: {new_dict!r} {d!r}"
|
||||
226 |
|
||||
227 | )
|
||||
228 |
|
||||
224 + f"Conflicting configuration dicts: {new_dict!r} {d!r}"
|
||||
225 |
|
||||
226 | )
|
||||
227 |
|
||||
|
||||
UP032 [*] Use f-string instead of `format` call
|
||||
--> UP032_0.py:231:1
|
||||
--> UP032_0.py:230:1
|
||||
|
|
||||
230 | # The first string will be converted to an f-string and the curly braces in the second should be converted to be unescaped
|
||||
231 | / (
|
||||
232 | | "{}"
|
||||
233 | | "{{}}"
|
||||
234 | | ).format(a)
|
||||
229 | # The first string will be converted to an f-string and the curly braces in the second should be converted to be unescaped
|
||||
230 | / (
|
||||
231 | | "{}"
|
||||
232 | | "{{}}"
|
||||
233 | | ).format(a)
|
||||
| |___________^
|
||||
235 |
|
||||
236 | ("{}" "{{}}").format(a)
|
||||
234 |
|
||||
235 | ("{}" "{{}}").format(a)
|
||||
|
|
||||
help: Convert to f-string
|
||||
229 |
|
||||
230 | # The first string will be converted to an f-string and the curly braces in the second should be converted to be unescaped
|
||||
231 | (
|
||||
232 + f"{a}"
|
||||
233 | "{}"
|
||||
228 |
|
||||
229 | # The first string will be converted to an f-string and the curly braces in the second should be converted to be unescaped
|
||||
230 | (
|
||||
231 + f"{a}"
|
||||
232 | "{}"
|
||||
- "{{}}"
|
||||
- ).format(a)
|
||||
234 + )
|
||||
235 |
|
||||
236 | ("{}" "{{}}").format(a)
|
||||
237 |
|
||||
233 + )
|
||||
234 |
|
||||
235 | ("{}" "{{}}").format(a)
|
||||
236 |
|
||||
|
||||
UP032 [*] Use f-string instead of `format` call
|
||||
--> UP032_0.py:236:1
|
||||
--> UP032_0.py:235:1
|
||||
|
|
||||
234 | ).format(a)
|
||||
235 |
|
||||
236 | ("{}" "{{}}").format(a)
|
||||
233 | ).format(a)
|
||||
234 |
|
||||
235 | ("{}" "{{}}").format(a)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Convert to f-string
|
||||
233 | "{{}}"
|
||||
234 | ).format(a)
|
||||
235 |
|
||||
232 | "{{}}"
|
||||
233 | ).format(a)
|
||||
234 |
|
||||
- ("{}" "{{}}").format(a)
|
||||
236 + (f"{a}" "{}")
|
||||
235 + (f"{a}" "{}")
|
||||
236 |
|
||||
237 |
|
||||
238 |
|
||||
239 | # Both strings will be converted to an f-string and the curly braces in the second should left escaped
|
||||
238 | # Both strings will be converted to an f-string and the curly braces in the second should left escaped
|
||||
|
||||
UP032 [*] Use f-string instead of `format` call
|
||||
--> UP032_0.py:240:1
|
||||
--> UP032_0.py:239:1
|
||||
|
|
||||
239 | # Both strings will be converted to an f-string and the curly braces in the second should left escaped
|
||||
240 | / (
|
||||
241 | | "{}"
|
||||
242 | | "{{{}}}"
|
||||
243 | | ).format(a, b)
|
||||
238 | # Both strings will be converted to an f-string and the curly braces in the second should left escaped
|
||||
239 | / (
|
||||
240 | | "{}"
|
||||
241 | | "{{{}}}"
|
||||
242 | | ).format(a, b)
|
||||
| |______________^
|
||||
244 |
|
||||
245 | ("{}" "{{{}}}").format(a, b)
|
||||
243 |
|
||||
244 | ("{}" "{{{}}}").format(a, b)
|
||||
|
|
||||
help: Convert to f-string
|
||||
238 |
|
||||
239 | # Both strings will be converted to an f-string and the curly braces in the second should left escaped
|
||||
240 | (
|
||||
237 |
|
||||
238 | # Both strings will be converted to an f-string and the curly braces in the second should left escaped
|
||||
239 | (
|
||||
- "{}"
|
||||
- "{{{}}}"
|
||||
- ).format(a, b)
|
||||
241 + f"{a}"
|
||||
242 + f"{{{b}}}"
|
||||
243 + )
|
||||
244 |
|
||||
245 | ("{}" "{{{}}}").format(a, b)
|
||||
246 |
|
||||
240 + f"{a}"
|
||||
241 + f"{{{b}}}"
|
||||
242 + )
|
||||
243 |
|
||||
244 | ("{}" "{{{}}}").format(a, b)
|
||||
245 |
|
||||
|
||||
UP032 [*] Use f-string instead of `format` call
|
||||
--> UP032_0.py:245:1
|
||||
--> UP032_0.py:244:1
|
||||
|
|
||||
243 | ).format(a, b)
|
||||
244 |
|
||||
245 | ("{}" "{{{}}}").format(a, b)
|
||||
242 | ).format(a, b)
|
||||
243 |
|
||||
244 | ("{}" "{{{}}}").format(a, b)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
246 |
|
||||
247 | # The dictionary should be parenthesized.
|
||||
245 |
|
||||
246 | # The dictionary should be parenthesized.
|
||||
|
|
||||
help: Convert to f-string
|
||||
242 | "{{{}}}"
|
||||
243 | ).format(a, b)
|
||||
244 |
|
||||
241 | "{{{}}}"
|
||||
242 | ).format(a, b)
|
||||
243 |
|
||||
- ("{}" "{{{}}}").format(a, b)
|
||||
245 + (f"{a}" f"{{{b}}}")
|
||||
246 |
|
||||
247 | # The dictionary should be parenthesized.
|
||||
248 | "{}".format({0: 1}[0])
|
||||
244 + (f"{a}" f"{{{b}}}")
|
||||
245 |
|
||||
246 | # The dictionary should be parenthesized.
|
||||
247 | "{}".format({0: 1}[0])
|
||||
|
||||
UP032 [*] Use f-string instead of `format` call
|
||||
--> UP032_0.py:248:1
|
||||
--> UP032_0.py:247:1
|
||||
|
|
||||
247 | # The dictionary should be parenthesized.
|
||||
248 | "{}".format({0: 1}[0])
|
||||
246 | # The dictionary should be parenthesized.
|
||||
247 | "{}".format({0: 1}[0])
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
249 |
|
||||
250 | # The dictionary should be parenthesized.
|
||||
248 |
|
||||
249 | # The dictionary should be parenthesized.
|
||||
|
|
||||
help: Convert to f-string
|
||||
245 | ("{}" "{{{}}}").format(a, b)
|
||||
246 |
|
||||
247 | # The dictionary should be parenthesized.
|
||||
244 | ("{}" "{{{}}}").format(a, b)
|
||||
245 |
|
||||
246 | # The dictionary should be parenthesized.
|
||||
- "{}".format({0: 1}[0])
|
||||
248 + f"{({0: 1}[0])}"
|
||||
249 |
|
||||
250 | # The dictionary should be parenthesized.
|
||||
251 | "{}".format({0: 1}.bar)
|
||||
247 + f"{({0: 1}[0])}"
|
||||
248 |
|
||||
249 | # The dictionary should be parenthesized.
|
||||
250 | "{}".format({0: 1}.bar)
|
||||
|
||||
UP032 [*] Use f-string instead of `format` call
|
||||
--> UP032_0.py:251:1
|
||||
--> UP032_0.py:250:1
|
||||
|
|
||||
250 | # The dictionary should be parenthesized.
|
||||
251 | "{}".format({0: 1}.bar)
|
||||
249 | # The dictionary should be parenthesized.
|
||||
250 | "{}".format({0: 1}.bar)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
252 |
|
||||
253 | # The dictionary should be parenthesized.
|
||||
251 |
|
||||
252 | # The dictionary should be parenthesized.
|
||||
|
|
||||
help: Convert to f-string
|
||||
248 | "{}".format({0: 1}[0])
|
||||
249 |
|
||||
250 | # The dictionary should be parenthesized.
|
||||
247 | "{}".format({0: 1}[0])
|
||||
248 |
|
||||
249 | # The dictionary should be parenthesized.
|
||||
- "{}".format({0: 1}.bar)
|
||||
251 + f"{({0: 1}.bar)}"
|
||||
252 |
|
||||
253 | # The dictionary should be parenthesized.
|
||||
254 | "{}".format({0: 1}())
|
||||
250 + f"{({0: 1}.bar)}"
|
||||
251 |
|
||||
252 | # The dictionary should be parenthesized.
|
||||
253 | "{}".format({0: 1}())
|
||||
|
||||
UP032 [*] Use f-string instead of `format` call
|
||||
--> UP032_0.py:254:1
|
||||
--> UP032_0.py:253:1
|
||||
|
|
||||
253 | # The dictionary should be parenthesized.
|
||||
254 | "{}".format({0: 1}())
|
||||
252 | # The dictionary should be parenthesized.
|
||||
253 | "{}".format({0: 1}())
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
255 |
|
||||
256 | # The string shouldn't be converted, since it would require repeating the function call.
|
||||
254 |
|
||||
255 | # The string shouldn't be converted, since it would require repeating the function call.
|
||||
|
|
||||
help: Convert to f-string
|
||||
251 | "{}".format({0: 1}.bar)
|
||||
252 |
|
||||
253 | # The dictionary should be parenthesized.
|
||||
250 | "{}".format({0: 1}.bar)
|
||||
251 |
|
||||
252 | # The dictionary should be parenthesized.
|
||||
- "{}".format({0: 1}())
|
||||
254 + f"{({0: 1}())}"
|
||||
255 |
|
||||
256 | # The string shouldn't be converted, since it would require repeating the function call.
|
||||
257 | "{x} {x}".format(x=foo())
|
||||
253 + f"{({0: 1}())}"
|
||||
254 |
|
||||
255 | # The string shouldn't be converted, since it would require repeating the function call.
|
||||
256 | "{x} {x}".format(x=foo())
|
||||
|
||||
UP032 [*] Use f-string instead of `format` call
|
||||
--> UP032_0.py:261:1
|
||||
--> UP032_0.py:260:1
|
||||
|
|
||||
260 | # The string _should_ be converted, since the function call is repeated in the arguments.
|
||||
261 | "{0} {1}".format(foo(), foo())
|
||||
259 | # The string _should_ be converted, since the function call is repeated in the arguments.
|
||||
260 | "{0} {1}".format(foo(), foo())
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
262 |
|
||||
263 | # The call should be removed, but the string itself should remain.
|
||||
261 |
|
||||
262 | # The call should be removed, but the string itself should remain.
|
||||
|
|
||||
help: Convert to f-string
|
||||
258 | "{0} {0}".format(foo())
|
||||
259 |
|
||||
260 | # The string _should_ be converted, since the function call is repeated in the arguments.
|
||||
257 | "{0} {0}".format(foo())
|
||||
258 |
|
||||
259 | # The string _should_ be converted, since the function call is repeated in the arguments.
|
||||
- "{0} {1}".format(foo(), foo())
|
||||
261 + f"{foo()} {foo()}"
|
||||
262 |
|
||||
263 | # The call should be removed, but the string itself should remain.
|
||||
264 | ''.format(self.project)
|
||||
260 + f"{foo()} {foo()}"
|
||||
261 |
|
||||
262 | # The call should be removed, but the string itself should remain.
|
||||
263 | ''.format(self.project)
|
||||
|
||||
UP032 [*] Use f-string instead of `format` call
|
||||
--> UP032_0.py:264:1
|
||||
--> UP032_0.py:263:1
|
||||
|
|
||||
263 | # The call should be removed, but the string itself should remain.
|
||||
264 | ''.format(self.project)
|
||||
262 | # The call should be removed, but the string itself should remain.
|
||||
263 | ''.format(self.project)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
265 |
|
||||
266 | # The call should be removed, but the string itself should remain.
|
||||
264 |
|
||||
265 | # The call should be removed, but the string itself should remain.
|
||||
|
|
||||
help: Convert to f-string
|
||||
261 | "{0} {1}".format(foo(), foo())
|
||||
262 |
|
||||
263 | # The call should be removed, but the string itself should remain.
|
||||
260 | "{0} {1}".format(foo(), foo())
|
||||
261 |
|
||||
262 | # The call should be removed, but the string itself should remain.
|
||||
- ''.format(self.project)
|
||||
264 + ''
|
||||
265 |
|
||||
266 | # The call should be removed, but the string itself should remain.
|
||||
267 | "".format(self.project)
|
||||
263 + ''
|
||||
264 |
|
||||
265 | # The call should be removed, but the string itself should remain.
|
||||
266 | "".format(self.project)
|
||||
|
||||
UP032 [*] Use f-string instead of `format` call
|
||||
--> UP032_0.py:267:1
|
||||
--> UP032_0.py:266:1
|
||||
|
|
||||
266 | # The call should be removed, but the string itself should remain.
|
||||
267 | "".format(self.project)
|
||||
265 | # The call should be removed, but the string itself should remain.
|
||||
266 | "".format(self.project)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
268 |
|
||||
269 | # Not a valid type annotation but this test shouldn't result in a panic.
|
||||
267 |
|
||||
268 | # Not a valid type annotation but this test shouldn't result in a panic.
|
||||
|
|
||||
help: Convert to f-string
|
||||
264 | ''.format(self.project)
|
||||
265 |
|
||||
266 | # The call should be removed, but the string itself should remain.
|
||||
263 | ''.format(self.project)
|
||||
264 |
|
||||
265 | # The call should be removed, but the string itself should remain.
|
||||
- "".format(self.project)
|
||||
267 + ""
|
||||
268 |
|
||||
269 | # Not a valid type annotation but this test shouldn't result in a panic.
|
||||
270 | # Refer: https://github.com/astral-sh/ruff/issues/11736
|
||||
266 + ""
|
||||
267 |
|
||||
268 | # Not a valid type annotation but this test shouldn't result in a panic.
|
||||
269 | # Refer: https://github.com/astral-sh/ruff/issues/11736
|
||||
|
||||
UP032 [*] Use f-string instead of `format` call
|
||||
--> UP032_0.py:271:5
|
||||
--> UP032_0.py:270:5
|
||||
|
|
||||
269 | # Not a valid type annotation but this test shouldn't result in a panic.
|
||||
270 | # Refer: https://github.com/astral-sh/ruff/issues/11736
|
||||
271 | x: "'{} + {}'.format(x, y)"
|
||||
268 | # Not a valid type annotation but this test shouldn't result in a panic.
|
||||
269 | # Refer: https://github.com/astral-sh/ruff/issues/11736
|
||||
270 | x: "'{} + {}'.format(x, y)"
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
272 |
|
||||
273 | # Regression https://github.com/astral-sh/ruff/issues/21000
|
||||
271 |
|
||||
272 | # Regression https://github.com/astral-sh/ruff/issues/21000
|
||||
|
|
||||
help: Convert to f-string
|
||||
268 |
|
||||
269 | # Not a valid type annotation but this test shouldn't result in a panic.
|
||||
270 | # Refer: https://github.com/astral-sh/ruff/issues/11736
|
||||
267 |
|
||||
268 | # Not a valid type annotation but this test shouldn't result in a panic.
|
||||
269 | # Refer: https://github.com/astral-sh/ruff/issues/11736
|
||||
- x: "'{} + {}'.format(x, y)"
|
||||
271 + x: "f'{x} + {y}'"
|
||||
272 |
|
||||
273 | # Regression https://github.com/astral-sh/ruff/issues/21000
|
||||
274 | # Fix should parenthesize walrus
|
||||
270 + x: "f'{x} + {y}'"
|
||||
271 |
|
||||
272 | # Regression https://github.com/astral-sh/ruff/issues/21000
|
||||
273 | # Fix should parenthesize walrus
|
||||
|
||||
UP032 [*] Use f-string instead of `format` call
|
||||
--> UP032_0.py:277:14
|
||||
--> UP032_0.py:276:14
|
||||
|
|
||||
275 | if __name__ == "__main__":
|
||||
276 | number = 0
|
||||
277 | string = "{}".format(number := number + 1)
|
||||
274 | if __name__ == "__main__":
|
||||
275 | number = 0
|
||||
276 | string = "{}".format(number := number + 1)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
278 | print(string)
|
||||
277 | print(string)
|
||||
|
|
||||
help: Convert to f-string
|
||||
274 | # Fix should parenthesize walrus
|
||||
275 | if __name__ == "__main__":
|
||||
276 | number = 0
|
||||
273 | # Fix should parenthesize walrus
|
||||
274 | if __name__ == "__main__":
|
||||
275 | number = 0
|
||||
- string = "{}".format(number := number + 1)
|
||||
277 + string = f"{(number := number + 1)}"
|
||||
278 | print(string)
|
||||
276 + string = f"{(number := number + 1)}"
|
||||
277 | print(string)
|
||||
278 |
|
||||
279 | # Unicode escape
|
||||
|
||||
UP032 [*] Use f-string instead of `format` call
|
||||
--> UP032_0.py:280:1
|
||||
|
|
||||
279 | # Unicode escape
|
||||
280 | "\N{angle}AOB = {angle}°".format(angle=180)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Convert to f-string
|
||||
277 | print(string)
|
||||
278 |
|
||||
279 | # Unicode escape
|
||||
- "\N{angle}AOB = {angle}°".format(angle=180)
|
||||
280 + f"\N{angle}AOB = {180}°"
|
||||
|
||||
@@ -1247,6 +1247,7 @@ impl<'a> Generator<'a> {
|
||||
self.p_bytes_repr(&bytes_literal.value, bytes_literal.flags);
|
||||
}
|
||||
}
|
||||
#[expect(clippy::eq_op)]
|
||||
Expr::NumberLiteral(ast::ExprNumberLiteral { value, .. }) => {
|
||||
static INF_STR: &str = "1e309";
|
||||
assert_eq!(f64::MAX_10_EXP, 308);
|
||||
|
||||
1
crates/ruff_python_formatter/resources/test/fixtures/ruff/fluent.options.json
vendored
Normal file
1
crates/ruff_python_formatter/resources/test/fixtures/ruff/fluent.options.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
[{"line_width":8}]
|
||||
35
crates/ruff_python_formatter/resources/test/fixtures/ruff/fluent.py
vendored
Normal file
35
crates/ruff_python_formatter/resources/test/fixtures/ruff/fluent.py
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
# Fixtures for fluent formatting of call chains
|
||||
# Note that `fluent.options.json` sets line width to 8
|
||||
|
||||
|
||||
x = a.b()
|
||||
|
||||
x = a.b().c()
|
||||
|
||||
x = a.b().c().d
|
||||
|
||||
x = a.b.c.d().e()
|
||||
|
||||
x = a.b.c().d.e().f.g()
|
||||
|
||||
# Consecutive calls/subscripts are grouped together
|
||||
# for the purposes of fluent formatting (though, as 2025.12.15,
|
||||
# there may be a break inside of one of these
|
||||
# calls/subscripts, but that is unrelated to the fluent format.)
|
||||
|
||||
x = a()[0]().b().c()
|
||||
|
||||
x = a.b()[0].c.d()[1]().e
|
||||
|
||||
# Parentheses affect both where the root of the call
|
||||
# chain is and how many calls we require before applying
|
||||
# fluent formatting (just 1, in the presence of a parenthesized
|
||||
# root, as of 2025.12.15.)
|
||||
|
||||
x = (a).b()
|
||||
|
||||
x = (a()).b()
|
||||
|
||||
x = (a.b()).d.e()
|
||||
|
||||
x = (a.b().d).e()
|
||||
@@ -216,3 +216,69 @@ max_message_id = (
|
||||
.baz()
|
||||
)
|
||||
|
||||
# Note in preview we split at `pl` which some
|
||||
# folks may dislike. (Similarly with common
|
||||
# `np` and `pd` invocations).
|
||||
#
|
||||
# This is because we cannot reliably predict,
|
||||
# just from syntax, whether a short identifier
|
||||
# is being used as a 'namespace' or as an 'object'.
|
||||
#
|
||||
# As of 2025.12.15, we do not indent methods in
|
||||
# fluent formatting. If we ever decide to do so,
|
||||
# it may make sense to special case call chain roots
|
||||
# that are shorter than the indent-width (like Prettier does).
|
||||
# This would have the benefit of handling these common
|
||||
# two-letter aliases for libraries.
|
||||
|
||||
|
||||
expr = (
|
||||
pl.scan_parquet("/data/pypi-parquet/*.parquet")
|
||||
.filter(
|
||||
[
|
||||
pl.col("path").str.contains(
|
||||
r"\.(asm|c|cc|cpp|cxx|h|hpp|rs|[Ff][0-9]{0,2}(?:or)?|go)$"
|
||||
),
|
||||
~pl.col("path").str.contains(r"(^|/)test(|s|ing)"),
|
||||
~pl.col("path").str.contains("/site-packages/", literal=True),
|
||||
]
|
||||
)
|
||||
.with_columns(
|
||||
month=pl.col("uploaded_on").dt.truncate("1mo"),
|
||||
ext=pl.col("path")
|
||||
.str.extract(pattern=r"\.([a-z0-9]+)$", group_index=1)
|
||||
.str.replace_all(pattern=r"cxx|cpp|cc|c|hpp|h", value="C/C++")
|
||||
.str.replace_all(pattern="^f.*$", value="Fortran")
|
||||
.str.replace("rs", "Rust", literal=True)
|
||||
.str.replace("go", "Go", literal=True)
|
||||
.str.replace("asm", "Assembly", literal=True)
|
||||
.replace({"": None}),
|
||||
)
|
||||
.group_by(["month", "ext"])
|
||||
.agg(project_count=pl.col("project_name").n_unique())
|
||||
.drop_nulls(["ext"])
|
||||
.sort(["month", "project_count"], descending=True)
|
||||
)
|
||||
|
||||
def indentation_matching_for_loop_in_preview():
|
||||
if make_this:
|
||||
if more_nested_because_line_length:
|
||||
identical_hidden_layer_sizes = all(
|
||||
current_hidden_layer_sizes == first_hidden_layer_sizes
|
||||
for current_hidden_layer_sizes in self.component_config[
|
||||
HIDDEN_LAYERS_SIZES
|
||||
].values().attr
|
||||
)
|
||||
|
||||
def indentation_matching_walrus_in_preview():
|
||||
if make_this:
|
||||
if more_nested_because_line_length:
|
||||
with self.read_ctx(book_type) as cursor:
|
||||
if (entry_count := len(names := cursor.execute(
|
||||
'SELECT name FROM address_book WHERE address=?',
|
||||
(address,),
|
||||
).fetchall().some_attr)) == 0 or len(set(names)) > 1:
|
||||
return
|
||||
|
||||
# behavior with parenthesized roots
|
||||
x = (aaaaaaaaaaaaaaaaaaaaaa).bbbbbbbbbbbbbbbbbbb.cccccccccccccccccccccccc().dddddddddddddddddddddddd().eeeeeeeeeeee
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use clap::{Parser, ValueEnum, command};
|
||||
use clap::{Parser, ValueEnum};
|
||||
|
||||
use ruff_formatter::SourceCode;
|
||||
use ruff_python_ast::{PySourceType, PythonVersion};
|
||||
|
||||
@@ -10,6 +10,7 @@ use crate::expression::parentheses::{
|
||||
NeedsParentheses, OptionalParentheses, Parentheses, is_expression_parenthesized,
|
||||
};
|
||||
use crate::prelude::*;
|
||||
use crate::preview::is_fluent_layout_split_first_call_enabled;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatExprAttribute {
|
||||
@@ -47,20 +48,26 @@ impl FormatNodeRule<ExprAttribute> for FormatExprAttribute {
|
||||
)
|
||||
};
|
||||
|
||||
if call_chain_layout == CallChainLayout::Fluent {
|
||||
if call_chain_layout.is_fluent() {
|
||||
if parenthesize_value {
|
||||
// Don't propagate the call chain layout.
|
||||
value.format().with_options(Parentheses::Always).fmt(f)?;
|
||||
} else {
|
||||
match value.as_ref() {
|
||||
Expr::Attribute(expr) => {
|
||||
expr.format().with_options(call_chain_layout).fmt(f)?;
|
||||
expr.format()
|
||||
.with_options(call_chain_layout.transition_after_attribute())
|
||||
.fmt(f)?;
|
||||
}
|
||||
Expr::Call(expr) => {
|
||||
expr.format().with_options(call_chain_layout).fmt(f)?;
|
||||
expr.format()
|
||||
.with_options(call_chain_layout.transition_after_attribute())
|
||||
.fmt(f)?;
|
||||
}
|
||||
Expr::Subscript(expr) => {
|
||||
expr.format().with_options(call_chain_layout).fmt(f)?;
|
||||
expr.format()
|
||||
.with_options(call_chain_layout.transition_after_attribute())
|
||||
.fmt(f)?;
|
||||
}
|
||||
_ => {
|
||||
value.format().with_options(Parentheses::Never).fmt(f)?;
|
||||
@@ -105,8 +112,30 @@ impl FormatNodeRule<ExprAttribute> for FormatExprAttribute {
|
||||
// Allow the `.` on its own line if this is a fluent call chain
|
||||
// and the value either requires parenthesizing or is a call or subscript expression
|
||||
// (it's a fluent chain but not the first element).
|
||||
else if call_chain_layout == CallChainLayout::Fluent {
|
||||
if parenthesize_value || value.is_call_expr() || value.is_subscript_expr() {
|
||||
//
|
||||
// In preview we also break _at_ the first call in the chain.
|
||||
// For example:
|
||||
//
|
||||
// ```diff
|
||||
// # stable formatting vs. preview
|
||||
// x = (
|
||||
// - df.merge()
|
||||
// + df
|
||||
// + .merge()
|
||||
// .groupby()
|
||||
// .agg()
|
||||
// .filter()
|
||||
// )
|
||||
// ```
|
||||
else if call_chain_layout.is_fluent() {
|
||||
if parenthesize_value
|
||||
|| value.is_call_expr()
|
||||
|| value.is_subscript_expr()
|
||||
// Remember to update the doc-comment above when
|
||||
// stabilizing this behavior.
|
||||
|| (is_fluent_layout_split_first_call_enabled(f.context())
|
||||
&& call_chain_layout.is_first_call_like())
|
||||
{
|
||||
soft_line_break().fmt(f)?;
|
||||
}
|
||||
}
|
||||
@@ -148,8 +177,8 @@ impl FormatNodeRule<ExprAttribute> for FormatExprAttribute {
|
||||
)
|
||||
});
|
||||
|
||||
let is_call_chain_root = self.call_chain_layout == CallChainLayout::Default
|
||||
&& call_chain_layout == CallChainLayout::Fluent;
|
||||
let is_call_chain_root =
|
||||
self.call_chain_layout == CallChainLayout::Default && call_chain_layout.is_fluent();
|
||||
if is_call_chain_root {
|
||||
write!(f, [group(&format_inner)])
|
||||
} else {
|
||||
@@ -169,7 +198,8 @@ impl NeedsParentheses for ExprAttribute {
|
||||
self.into(),
|
||||
context.comments().ranges(),
|
||||
context.source(),
|
||||
) == CallChainLayout::Fluent
|
||||
)
|
||||
.is_fluent()
|
||||
{
|
||||
OptionalParentheses::Multiline
|
||||
} else if context.comments().has_dangling(self) {
|
||||
|
||||
@@ -47,7 +47,10 @@ impl FormatNodeRule<ExprCall> for FormatExprCall {
|
||||
func.format().with_options(Parentheses::Always).fmt(f)
|
||||
} else {
|
||||
match func.as_ref() {
|
||||
Expr::Attribute(expr) => expr.format().with_options(call_chain_layout).fmt(f),
|
||||
Expr::Attribute(expr) => expr
|
||||
.format()
|
||||
.with_options(call_chain_layout.decrement_call_like_count())
|
||||
.fmt(f),
|
||||
Expr::Call(expr) => expr.format().with_options(call_chain_layout).fmt(f),
|
||||
Expr::Subscript(expr) => expr.format().with_options(call_chain_layout).fmt(f),
|
||||
_ => func.format().with_options(Parentheses::Never).fmt(f),
|
||||
@@ -67,9 +70,7 @@ impl FormatNodeRule<ExprCall> for FormatExprCall {
|
||||
// queryset.distinct().order_by(field.name).values_list(field_name_flat_long_long=True)
|
||||
// )
|
||||
// ```
|
||||
if call_chain_layout == CallChainLayout::Fluent
|
||||
&& self.call_chain_layout == CallChainLayout::Default
|
||||
{
|
||||
if call_chain_layout.is_fluent() && self.call_chain_layout == CallChainLayout::Default {
|
||||
group(&fmt_func).fmt(f)
|
||||
} else {
|
||||
fmt_func.fmt(f)
|
||||
@@ -87,7 +88,8 @@ impl NeedsParentheses for ExprCall {
|
||||
self.into(),
|
||||
context.comments().ranges(),
|
||||
context.source(),
|
||||
) == CallChainLayout::Fluent
|
||||
)
|
||||
.is_fluent()
|
||||
{
|
||||
OptionalParentheses::Multiline
|
||||
} else if context.comments().has_dangling(self) {
|
||||
|
||||
@@ -397,7 +397,8 @@ impl Format<PyFormatContext<'_>> for FormatBody<'_> {
|
||||
body.into(),
|
||||
comments.ranges(),
|
||||
f.context().source(),
|
||||
) == CallChainLayout::Fluent
|
||||
)
|
||||
.is_fluent()
|
||||
{
|
||||
parenthesize_if_expands(&unparenthesized).fmt(f)
|
||||
} else {
|
||||
|
||||
@@ -51,7 +51,10 @@ impl FormatNodeRule<ExprSubscript> for FormatExprSubscript {
|
||||
value.format().with_options(Parentheses::Always).fmt(f)
|
||||
} else {
|
||||
match value.as_ref() {
|
||||
Expr::Attribute(expr) => expr.format().with_options(call_chain_layout).fmt(f),
|
||||
Expr::Attribute(expr) => expr
|
||||
.format()
|
||||
.with_options(call_chain_layout.decrement_call_like_count())
|
||||
.fmt(f),
|
||||
Expr::Call(expr) => expr.format().with_options(call_chain_layout).fmt(f),
|
||||
Expr::Subscript(expr) => expr.format().with_options(call_chain_layout).fmt(f),
|
||||
_ => value.format().with_options(Parentheses::Never).fmt(f),
|
||||
@@ -71,8 +74,8 @@ impl FormatNodeRule<ExprSubscript> for FormatExprSubscript {
|
||||
.fmt(f)
|
||||
});
|
||||
|
||||
let is_call_chain_root = self.call_chain_layout == CallChainLayout::Default
|
||||
&& call_chain_layout == CallChainLayout::Fluent;
|
||||
let is_call_chain_root =
|
||||
self.call_chain_layout == CallChainLayout::Default && call_chain_layout.is_fluent();
|
||||
if is_call_chain_root {
|
||||
write!(f, [group(&format_inner)])
|
||||
} else {
|
||||
@@ -92,7 +95,8 @@ impl NeedsParentheses for ExprSubscript {
|
||||
self.into(),
|
||||
context.comments().ranges(),
|
||||
context.source(),
|
||||
) == CallChainLayout::Fluent
|
||||
)
|
||||
.is_fluent()
|
||||
{
|
||||
OptionalParentheses::Multiline
|
||||
} else if is_expression_parenthesized(
|
||||
|
||||
@@ -876,6 +876,22 @@ impl<'a> First<'a> {
|
||||
/// )
|
||||
/// ).all()
|
||||
/// ```
|
||||
///
|
||||
/// In [`preview`](crate::preview::is_fluent_layout_split_first_call_enabled), we also track the position of the leftmost call or
|
||||
/// subscript on an attribute in the chain and break just before the dot.
|
||||
///
|
||||
/// So, for example, the right-hand summand in the above expression
|
||||
/// would get formatted as:
|
||||
/// ```python
|
||||
/// Blog.objects
|
||||
/// .filter(
|
||||
/// entry__headline__contains="McCartney",
|
||||
/// )
|
||||
/// .limit_results[:10]
|
||||
/// .filter(
|
||||
/// entry__pub_date__year=2010,
|
||||
/// )
|
||||
/// ```
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub enum CallChainLayout {
|
||||
/// The root of a call chain
|
||||
@@ -883,19 +899,149 @@ pub enum CallChainLayout {
|
||||
Default,
|
||||
|
||||
/// A nested call chain element that uses fluent style.
|
||||
Fluent,
|
||||
Fluent(AttributeState),
|
||||
|
||||
/// A nested call chain element not using fluent style.
|
||||
NonFluent,
|
||||
}
|
||||
|
||||
/// Records information about the current position within
|
||||
/// a call chain.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum AttributeState {
|
||||
/// Stores the number of calls or subscripts
|
||||
/// to the left of the current position in a chain.
|
||||
///
|
||||
/// Consecutive calls/subscripts on a single
|
||||
/// object only count once. For example, if we are at
|
||||
/// `c` in `a.b()[0]()().c()` then this number would be 1.
|
||||
///
|
||||
/// Caveat: If the root of the chain is parenthesized,
|
||||
/// it contributes +1 to this count, even if it is not
|
||||
/// a call or subscript. But the name
|
||||
/// `CallLikeOrParenthesizedRootPreceding`
|
||||
/// is a tad unwieldy, and this also rarely occurs.
|
||||
CallLikePreceding(u32),
|
||||
/// Indicates that we are at the first called or
|
||||
/// subscripted object in the chain
|
||||
///
|
||||
/// For example, if we are at `b` in `a.b()[0]()().c()`
|
||||
FirstCallLike,
|
||||
/// Indicates that we are to the left of the first
|
||||
/// called or subscripted object in the chain, and therefore
|
||||
/// need not break.
|
||||
///
|
||||
/// For example, if we are at `a` in `a.b()[0]()().c()`
|
||||
BeforeFirstCallLike,
|
||||
}
|
||||
|
||||
impl CallChainLayout {
|
||||
/// Returns new state decreasing count of remaining calls/subscripts
|
||||
/// to traverse, or the state `FirstCallOrSubscript`, as appropriate.
|
||||
#[must_use]
|
||||
pub(crate) fn decrement_call_like_count(self) -> Self {
|
||||
match self {
|
||||
Self::Fluent(AttributeState::CallLikePreceding(x)) => {
|
||||
if x > 1 {
|
||||
// Recall that we traverse call chains from right to
|
||||
// left. So after moving from a call/subscript into
|
||||
// an attribute, we _decrease_ the count of
|
||||
// _remaining_ calls or subscripts to the left of our
|
||||
// current position.
|
||||
Self::Fluent(AttributeState::CallLikePreceding(x - 1))
|
||||
} else {
|
||||
Self::Fluent(AttributeState::FirstCallLike)
|
||||
}
|
||||
}
|
||||
_ => self,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns with state change
|
||||
/// `FirstCallOrSubscript` -> `BeforeFirstCallOrSubscript`
|
||||
/// and otherwise returns unchanged.
|
||||
#[must_use]
|
||||
pub(crate) fn transition_after_attribute(self) -> Self {
|
||||
match self {
|
||||
Self::Fluent(AttributeState::FirstCallLike) => {
|
||||
Self::Fluent(AttributeState::BeforeFirstCallLike)
|
||||
}
|
||||
_ => self,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_first_call_like(self) -> bool {
|
||||
matches!(self, Self::Fluent(AttributeState::FirstCallLike))
|
||||
}
|
||||
|
||||
/// Returns either `Fluent` or `NonFluent` depending on a
|
||||
/// heuristic computed for the whole chain.
|
||||
///
|
||||
/// Explicitly, the criterion to return `Fluent` is
|
||||
/// as follows:
|
||||
///
|
||||
/// 1. Beginning from the right (i.e. the `expr` itself),
|
||||
/// traverse inwards past calls, subscripts, and attribute
|
||||
/// expressions until we meet the first expression that is
|
||||
/// either none of these or else is parenthesized. This will
|
||||
/// be the _root_ of the call chain.
|
||||
/// 2. Count the number of _attribute values_ that are _called
|
||||
/// or subscripted_ in the chain (note that this includes the
|
||||
/// root but excludes the rightmost attribute in the chain since
|
||||
/// it is not the _value_ of some attribute).
|
||||
/// 3. If the root is parenthesized, add 1 to that value.
|
||||
/// 4. If the total is at least 2, return `Fluent`. Otherwise
|
||||
/// return `NonFluent`
|
||||
pub(crate) fn from_expression(
|
||||
mut expr: ExprRef,
|
||||
comment_ranges: &CommentRanges,
|
||||
source: &str,
|
||||
) -> Self {
|
||||
let mut attributes_after_parentheses = 0;
|
||||
// TODO(dylan): Once the fluent layout preview style is
|
||||
// stabilized, see if it is possible to simplify some of
|
||||
// the logic around parenthesized roots. (While supporting
|
||||
// both styles it is more difficult to do this.)
|
||||
|
||||
// Count of attribute _values_ which are called or
|
||||
// subscripted, after the leftmost parenthesized
|
||||
// value.
|
||||
//
|
||||
// Examples:
|
||||
// ```
|
||||
// # Count of 3 - notice that .d()
|
||||
// # does not contribute
|
||||
// a().b().c[0]()().d()
|
||||
// # Count of 2 - notice that a()
|
||||
// # does not contribute
|
||||
// (a()).b().c[0].d
|
||||
// ```
|
||||
let mut computed_attribute_values_after_parentheses = 0;
|
||||
|
||||
// Similar to the above, but instead looks at all calls
|
||||
// and subscripts rather than looking only at those on
|
||||
// _attribute values_. So this count can differ from the
|
||||
// above.
|
||||
//
|
||||
// Examples of `computed_attribute_values_after_parentheses` vs
|
||||
// `call_like_count`:
|
||||
//
|
||||
// a().b ---> 1 vs 1
|
||||
// a.b().c --> 1 vs 1
|
||||
// a.b() ---> 0 vs 1
|
||||
let mut call_like_count = 0;
|
||||
|
||||
// Going from right to left, we traverse calls, subscripts,
|
||||
// and attributes until we get to an expression of a different
|
||||
// kind _or_ to a parenthesized expression. This records
|
||||
// the case where we end the traversal at a parenthesized expression.
|
||||
//
|
||||
// In these cases, the inferred semantics of the chain are different.
|
||||
// We interpret this as the user indicating:
|
||||
// "this parenthesized value is the object of interest and we are
|
||||
// doing transformations on it". This increases our confidence that
|
||||
// this should be fluently formatted, and also means we should make
|
||||
// our first break after this value.
|
||||
let mut root_value_parenthesized = false;
|
||||
loop {
|
||||
match expr {
|
||||
ExprRef::Attribute(ast::ExprAttribute { value, .. }) => {
|
||||
@@ -907,10 +1053,10 @@ impl CallChainLayout {
|
||||
// ```
|
||||
if is_expression_parenthesized(value.into(), comment_ranges, source) {
|
||||
// `(a).b`. We preserve these parentheses so don't recurse
|
||||
attributes_after_parentheses += 1;
|
||||
root_value_parenthesized = true;
|
||||
break;
|
||||
} else if matches!(value.as_ref(), Expr::Call(_) | Expr::Subscript(_)) {
|
||||
attributes_after_parentheses += 1;
|
||||
computed_attribute_values_after_parentheses += 1;
|
||||
}
|
||||
|
||||
expr = ExprRef::from(value.as_ref());
|
||||
@@ -925,31 +1071,68 @@ impl CallChainLayout {
|
||||
// ```
|
||||
ExprRef::Call(ast::ExprCall { func: inner, .. })
|
||||
| ExprRef::Subscript(ast::ExprSubscript { value: inner, .. }) => {
|
||||
// We preserve these parentheses so don't recurse
|
||||
// e.g. (a)[0].x().y().z()
|
||||
// ^stop here
|
||||
if is_expression_parenthesized(inner.into(), comment_ranges, source) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Accumulate the `call_like_count`, but we only
|
||||
// want to count things like `a()[0]()()` once.
|
||||
if !inner.is_call_expr() && !inner.is_subscript_expr() {
|
||||
call_like_count += 1;
|
||||
}
|
||||
|
||||
expr = ExprRef::from(inner.as_ref());
|
||||
}
|
||||
_ => {
|
||||
// We to format the following in fluent style:
|
||||
// ```
|
||||
// f2 = (a).w().t(1,)
|
||||
// ^ expr
|
||||
// ```
|
||||
if is_expression_parenthesized(expr, comment_ranges, source) {
|
||||
attributes_after_parentheses += 1;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// We preserve these parentheses so don't recurse
|
||||
if is_expression_parenthesized(expr, comment_ranges, source) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if attributes_after_parentheses < 2 {
|
||||
|
||||
if computed_attribute_values_after_parentheses + u32::from(root_value_parenthesized) < 2 {
|
||||
CallChainLayout::NonFluent
|
||||
} else {
|
||||
CallChainLayout::Fluent
|
||||
CallChainLayout::Fluent(AttributeState::CallLikePreceding(
|
||||
// We count a parenthesized root value as an extra
|
||||
// call for the purposes of tracking state.
|
||||
//
|
||||
// The reason is that, in this case, we want the first
|
||||
// "special" break to happen right after the root, as
|
||||
// opposed to right after the first called/subscripted
|
||||
// attribute.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// ```
|
||||
// (object_of_interest)
|
||||
// .data.filter()
|
||||
// .agg()
|
||||
// .etc()
|
||||
// ```
|
||||
//
|
||||
// instead of (in preview):
|
||||
//
|
||||
// ```
|
||||
// (object_of_interest)
|
||||
// .data
|
||||
// .filter()
|
||||
// .etc()
|
||||
// ```
|
||||
//
|
||||
// For comparison, if we didn't have parentheses around
|
||||
// the root, we want (and get, in preview):
|
||||
//
|
||||
// ```
|
||||
// object_of_interest.data
|
||||
// .filter()
|
||||
// .agg()
|
||||
// .etc()
|
||||
// ```
|
||||
call_like_count + u32::from(root_value_parenthesized),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -972,9 +1155,13 @@ impl CallChainLayout {
|
||||
CallChainLayout::NonFluent
|
||||
}
|
||||
}
|
||||
layout @ (CallChainLayout::Fluent | CallChainLayout::NonFluent) => layout,
|
||||
layout @ (CallChainLayout::Fluent(_) | CallChainLayout::NonFluent) => layout,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_fluent(self) -> bool {
|
||||
matches!(self, CallChainLayout::Fluent(_))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
|
||||
@@ -59,3 +59,10 @@ pub(crate) const fn is_avoid_parens_for_long_as_captures_enabled(
|
||||
pub(crate) const fn is_parenthesize_lambda_bodies_enabled(context: &PyFormatContext) -> bool {
|
||||
context.is_preview()
|
||||
}
|
||||
|
||||
/// Returns `true` if the
|
||||
/// [`fluent_layout_split_first_call`](https://github.com/astral-sh/ruff/pull/21369) preview
|
||||
/// style is enabled.
|
||||
pub(crate) const fn is_fluent_layout_split_first_call_enabled(context: &PyFormatContext) -> bool {
|
||||
context.is_preview()
|
||||
}
|
||||
|
||||
@@ -192,7 +192,7 @@ class Random:
|
||||
}
|
||||
x = {
|
||||
"foobar": (123) + 456,
|
||||
@@ -97,24 +94,20 @@
|
||||
@@ -97,24 +94,21 @@
|
||||
|
||||
|
||||
my_dict = {
|
||||
@@ -221,13 +221,14 @@ class Random:
|
||||
- .second_call()
|
||||
- .third_call(some_args="some value")
|
||||
- )
|
||||
+ "a key in my dict": MyClass.some_attribute.first_call()
|
||||
+ "a key in my dict": MyClass.some_attribute
|
||||
+ .first_call()
|
||||
+ .second_call()
|
||||
+ .third_call(some_args="some value")
|
||||
}
|
||||
|
||||
{
|
||||
@@ -139,17 +132,17 @@
|
||||
@@ -139,17 +133,17 @@
|
||||
|
||||
class Random:
|
||||
def func():
|
||||
@@ -363,7 +364,8 @@ my_dict = {
|
||||
/ 100000.0
|
||||
}
|
||||
my_dict = {
|
||||
"a key in my dict": MyClass.some_attribute.first_call()
|
||||
"a key in my dict": MyClass.some_attribute
|
||||
.first_call()
|
||||
.second_call()
|
||||
.third_call(some_args="some value")
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/await.py
|
||||
snapshot_kind: text
|
||||
---
|
||||
## Input
|
||||
```python
|
||||
@@ -142,3 +141,20 @@ test_data = await (
|
||||
.to_list()
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
## Preview changes
|
||||
```diff
|
||||
--- Stable
|
||||
+++ Preview
|
||||
@@ -65,7 +65,8 @@
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/8644
|
||||
test_data = await (
|
||||
- Stream.from_async(async_data)
|
||||
+ Stream
|
||||
+ .from_async(async_data)
|
||||
.flat_map_async()
|
||||
.map()
|
||||
.filter_async(is_valid_data)
|
||||
```
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/call.py
|
||||
snapshot_kind: text
|
||||
---
|
||||
## Input
|
||||
```python
|
||||
@@ -557,3 +556,20 @@ result = (
|
||||
|
||||
result = (object[complicate_caller])("argument").a["b"].test(argument)
|
||||
```
|
||||
|
||||
|
||||
## Preview changes
|
||||
```diff
|
||||
--- Stable
|
||||
+++ Preview
|
||||
@@ -57,7 +57,8 @@
|
||||
|
||||
# Call chains/fluent interface (https://black.readthedocs.io/en/stable/the_black_code_style/current_style.html#call-chains)
|
||||
result = (
|
||||
- session.query(models.Customer.id)
|
||||
+ session
|
||||
+ .query(models.Customer.id)
|
||||
.filter(
|
||||
models.Customer.account_id == 10000,
|
||||
models.Customer.email == "user@example.org",
|
||||
```
|
||||
|
||||
@@ -2155,7 +2155,7 @@ transform = (
|
||||
),
|
||||
param(
|
||||
lambda left, right: (
|
||||
@@ -471,9 +463,9 @@
|
||||
@@ -471,15 +463,16 @@
|
||||
),
|
||||
param(lambda left, right: ibis.timestamp("2017-04-01").cast(dt.date)),
|
||||
param(
|
||||
@@ -2168,7 +2168,15 @@ transform = (
|
||||
),
|
||||
# This is too long on one line in the lambda body and gets wrapped
|
||||
# inside the body.
|
||||
@@ -507,16 +499,18 @@
|
||||
param(
|
||||
lambda left, right: (
|
||||
- ibis.timestamp("2017-04-01")
|
||||
+ ibis
|
||||
+ .timestamp("2017-04-01")
|
||||
.cast(dt.date)
|
||||
.between(left, right)
|
||||
.between(left, right)
|
||||
@@ -507,16 +500,18 @@
|
||||
]
|
||||
|
||||
# adds parentheses around the body
|
||||
@@ -2190,7 +2198,7 @@ transform = (
|
||||
|
||||
lambda x, y, z: (
|
||||
x + y + z
|
||||
@@ -527,7 +521,7 @@
|
||||
@@ -527,7 +522,7 @@
|
||||
x + y + z # trailing eol body
|
||||
)
|
||||
|
||||
@@ -2199,7 +2207,7 @@ transform = (
|
||||
|
||||
lambda x, y, z: (
|
||||
# leading body
|
||||
@@ -539,21 +533,23 @@
|
||||
@@ -539,21 +534,23 @@
|
||||
)
|
||||
|
||||
(
|
||||
@@ -2233,7 +2241,7 @@ transform = (
|
||||
# dangling header comment
|
||||
source_bucket
|
||||
if name == source_bucket_name
|
||||
@@ -561,8 +557,7 @@
|
||||
@@ -561,8 +558,7 @@
|
||||
)
|
||||
|
||||
(
|
||||
@@ -2243,7 +2251,7 @@ transform = (
|
||||
source_bucket
|
||||
if name == source_bucket_name
|
||||
else storage.Bucket(mock_service, destination_bucket_name)
|
||||
@@ -570,61 +565,70 @@
|
||||
@@ -570,61 +566,71 @@
|
||||
)
|
||||
|
||||
(
|
||||
@@ -2293,7 +2301,8 @@ transform = (
|
||||
- .cast(dt.date)
|
||||
- .between(left, right)
|
||||
+ lambda left, right: (
|
||||
+ ibis.timestamp("2017-04-01") # comment
|
||||
+ ibis
|
||||
+ .timestamp("2017-04-01") # comment
|
||||
+ .cast(dt.date)
|
||||
+ .between(left, right)
|
||||
+ )
|
||||
@@ -2346,7 +2355,7 @@ transform = (
|
||||
)
|
||||
|
||||
(
|
||||
@@ -637,27 +641,31 @@
|
||||
@@ -637,27 +643,31 @@
|
||||
(
|
||||
lambda
|
||||
# comment
|
||||
@@ -2386,7 +2395,7 @@ transform = (
|
||||
)
|
||||
|
||||
(
|
||||
@@ -672,25 +680,28 @@
|
||||
@@ -672,25 +682,28 @@
|
||||
)
|
||||
|
||||
(
|
||||
@@ -2427,7 +2436,7 @@ transform = (
|
||||
)
|
||||
|
||||
(
|
||||
@@ -698,9 +709,9 @@
|
||||
@@ -698,9 +711,9 @@
|
||||
# comment 1
|
||||
*ergs,
|
||||
# comment 2
|
||||
@@ -2440,7 +2449,7 @@ transform = (
|
||||
)
|
||||
|
||||
(
|
||||
@@ -708,19 +719,20 @@
|
||||
@@ -708,19 +721,20 @@
|
||||
# 2
|
||||
left, # 3
|
||||
# 4
|
||||
@@ -2471,7 +2480,7 @@ transform = (
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -738,48 +750,52 @@
|
||||
@@ -738,48 +752,52 @@
|
||||
foo(
|
||||
lambda from_ts, # but still wrap the body if it gets too long
|
||||
to_ts,
|
||||
@@ -2548,7 +2557,7 @@ transform = (
|
||||
)
|
||||
|
||||
(
|
||||
@@ -828,8 +844,7 @@
|
||||
@@ -828,8 +846,7 @@
|
||||
)
|
||||
|
||||
(
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/split_empty_brackets.py
|
||||
snapshot_kind: text
|
||||
---
|
||||
## Input
|
||||
```python
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
---
|
||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/fluent.py
|
||||
---
|
||||
## Input
|
||||
```python
|
||||
# Fixtures for fluent formatting of call chains
|
||||
# Note that `fluent.options.json` sets line width to 8
|
||||
|
||||
|
||||
x = a.b()
|
||||
|
||||
x = a.b().c()
|
||||
|
||||
x = a.b().c().d
|
||||
|
||||
x = a.b.c.d().e()
|
||||
|
||||
x = a.b.c().d.e().f.g()
|
||||
|
||||
# Consecutive calls/subscripts are grouped together
|
||||
# for the purposes of fluent formatting (though, as 2025.12.15,
|
||||
# there may be a break inside of one of these
|
||||
# calls/subscripts, but that is unrelated to the fluent format.)
|
||||
|
||||
x = a()[0]().b().c()
|
||||
|
||||
x = a.b()[0].c.d()[1]().e
|
||||
|
||||
# Parentheses affect both where the root of the call
|
||||
# chain is and how many calls we require before applying
|
||||
# fluent formatting (just 1, in the presence of a parenthesized
|
||||
# root, as of 2025.12.15.)
|
||||
|
||||
x = (a).b()
|
||||
|
||||
x = (a()).b()
|
||||
|
||||
x = (a.b()).d.e()
|
||||
|
||||
x = (a.b().d).e()
|
||||
```
|
||||
|
||||
## Outputs
|
||||
### Output 1
|
||||
```
|
||||
indent-style = space
|
||||
line-width = 8
|
||||
indent-width = 4
|
||||
quote-style = Double
|
||||
line-ending = LineFeed
|
||||
magic-trailing-comma = Respect
|
||||
docstring-code = Disabled
|
||||
docstring-code-line-width = "dynamic"
|
||||
preview = Disabled
|
||||
target_version = 3.10
|
||||
source_type = Python
|
||||
```
|
||||
|
||||
```python
|
||||
# Fixtures for fluent formatting of call chains
|
||||
# Note that `fluent.options.json` sets line width to 8
|
||||
|
||||
|
||||
x = a.b()
|
||||
|
||||
x = a.b().c()
|
||||
|
||||
x = (
|
||||
a.b()
|
||||
.c()
|
||||
.d
|
||||
)
|
||||
|
||||
x = a.b.c.d().e()
|
||||
|
||||
x = (
|
||||
a.b.c()
|
||||
.d.e()
|
||||
.f.g()
|
||||
)
|
||||
|
||||
# Consecutive calls/subscripts are grouped together
|
||||
# for the purposes of fluent formatting (though, as 2025.12.15,
|
||||
# there may be a break inside of one of these
|
||||
# calls/subscripts, but that is unrelated to the fluent format.)
|
||||
|
||||
x = (
|
||||
a()[
|
||||
0
|
||||
]()
|
||||
.b()
|
||||
.c()
|
||||
)
|
||||
|
||||
x = (
|
||||
a.b()[
|
||||
0
|
||||
]
|
||||
.c.d()[
|
||||
1
|
||||
]()
|
||||
.e
|
||||
)
|
||||
|
||||
# Parentheses affect both where the root of the call
|
||||
# chain is and how many calls we require before applying
|
||||
# fluent formatting (just 1, in the presence of a parenthesized
|
||||
# root, as of 2025.12.15.)
|
||||
|
||||
x = (
|
||||
a
|
||||
).b()
|
||||
|
||||
x = (
|
||||
a()
|
||||
).b()
|
||||
|
||||
x = (
|
||||
a.b()
|
||||
).d.e()
|
||||
|
||||
x = (
|
||||
a.b().d
|
||||
).e()
|
||||
```
|
||||
|
||||
|
||||
#### Preview changes
|
||||
```diff
|
||||
--- Stable
|
||||
+++ Preview
|
||||
@@ -7,7 +7,8 @@
|
||||
x = a.b().c()
|
||||
|
||||
x = (
|
||||
- a.b()
|
||||
+ a
|
||||
+ .b()
|
||||
.c()
|
||||
.d
|
||||
)
|
||||
@@ -15,7 +16,8 @@
|
||||
x = a.b.c.d().e()
|
||||
|
||||
x = (
|
||||
- a.b.c()
|
||||
+ a.b
|
||||
+ .c()
|
||||
.d.e()
|
||||
.f.g()
|
||||
)
|
||||
@@ -34,7 +36,8 @@
|
||||
)
|
||||
|
||||
x = (
|
||||
- a.b()[
|
||||
+ a
|
||||
+ .b()[
|
||||
0
|
||||
]
|
||||
.c.d()[
|
||||
```
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/parentheses/call_chains.py
|
||||
snapshot_kind: text
|
||||
---
|
||||
## Input
|
||||
```python
|
||||
@@ -223,6 +222,72 @@ max_message_id = (
|
||||
.baz()
|
||||
)
|
||||
|
||||
# Note in preview we split at `pl` which some
|
||||
# folks may dislike. (Similarly with common
|
||||
# `np` and `pd` invocations).
|
||||
#
|
||||
# This is because we cannot reliably predict,
|
||||
# just from syntax, whether a short identifier
|
||||
# is being used as a 'namespace' or as an 'object'.
|
||||
#
|
||||
# As of 2025.12.15, we do not indent methods in
|
||||
# fluent formatting. If we ever decide to do so,
|
||||
# it may make sense to special case call chain roots
|
||||
# that are shorter than the indent-width (like Prettier does).
|
||||
# This would have the benefit of handling these common
|
||||
# two-letter aliases for libraries.
|
||||
|
||||
|
||||
expr = (
|
||||
pl.scan_parquet("/data/pypi-parquet/*.parquet")
|
||||
.filter(
|
||||
[
|
||||
pl.col("path").str.contains(
|
||||
r"\.(asm|c|cc|cpp|cxx|h|hpp|rs|[Ff][0-9]{0,2}(?:or)?|go)$"
|
||||
),
|
||||
~pl.col("path").str.contains(r"(^|/)test(|s|ing)"),
|
||||
~pl.col("path").str.contains("/site-packages/", literal=True),
|
||||
]
|
||||
)
|
||||
.with_columns(
|
||||
month=pl.col("uploaded_on").dt.truncate("1mo"),
|
||||
ext=pl.col("path")
|
||||
.str.extract(pattern=r"\.([a-z0-9]+)$", group_index=1)
|
||||
.str.replace_all(pattern=r"cxx|cpp|cc|c|hpp|h", value="C/C++")
|
||||
.str.replace_all(pattern="^f.*$", value="Fortran")
|
||||
.str.replace("rs", "Rust", literal=True)
|
||||
.str.replace("go", "Go", literal=True)
|
||||
.str.replace("asm", "Assembly", literal=True)
|
||||
.replace({"": None}),
|
||||
)
|
||||
.group_by(["month", "ext"])
|
||||
.agg(project_count=pl.col("project_name").n_unique())
|
||||
.drop_nulls(["ext"])
|
||||
.sort(["month", "project_count"], descending=True)
|
||||
)
|
||||
|
||||
def indentation_matching_for_loop_in_preview():
|
||||
if make_this:
|
||||
if more_nested_because_line_length:
|
||||
identical_hidden_layer_sizes = all(
|
||||
current_hidden_layer_sizes == first_hidden_layer_sizes
|
||||
for current_hidden_layer_sizes in self.component_config[
|
||||
HIDDEN_LAYERS_SIZES
|
||||
].values().attr
|
||||
)
|
||||
|
||||
def indentation_matching_walrus_in_preview():
|
||||
if make_this:
|
||||
if more_nested_because_line_length:
|
||||
with self.read_ctx(book_type) as cursor:
|
||||
if (entry_count := len(names := cursor.execute(
|
||||
'SELECT name FROM address_book WHERE address=?',
|
||||
(address,),
|
||||
).fetchall().some_attr)) == 0 or len(set(names)) > 1:
|
||||
return
|
||||
|
||||
# behavior with parenthesized roots
|
||||
x = (aaaaaaaaaaaaaaaaaaaaaa).bbbbbbbbbbbbbbbbbbb.cccccccccccccccccccccccc().dddddddddddddddddddddddd().eeeeeeeeeeee
|
||||
```
|
||||
|
||||
## Output
|
||||
@@ -466,4 +531,237 @@ max_message_id = (
|
||||
.sum()
|
||||
.baz()
|
||||
)
|
||||
|
||||
# Note in preview we split at `pl` which some
|
||||
# folks may dislike. (Similarly with common
|
||||
# `np` and `pd` invocations).
|
||||
#
|
||||
# This is because we cannot reliably predict,
|
||||
# just from syntax, whether a short identifier
|
||||
# is being used as a 'namespace' or as an 'object'.
|
||||
#
|
||||
# As of 2025.12.15, we do not indent methods in
|
||||
# fluent formatting. If we ever decide to do so,
|
||||
# it may make sense to special case call chain roots
|
||||
# that are shorter than the indent-width (like Prettier does).
|
||||
# This would have the benefit of handling these common
|
||||
# two-letter aliases for libraries.
|
||||
|
||||
|
||||
expr = (
|
||||
pl.scan_parquet("/data/pypi-parquet/*.parquet")
|
||||
.filter(
|
||||
[
|
||||
pl.col("path").str.contains(
|
||||
r"\.(asm|c|cc|cpp|cxx|h|hpp|rs|[Ff][0-9]{0,2}(?:or)?|go)$"
|
||||
),
|
||||
~pl.col("path").str.contains(r"(^|/)test(|s|ing)"),
|
||||
~pl.col("path").str.contains("/site-packages/", literal=True),
|
||||
]
|
||||
)
|
||||
.with_columns(
|
||||
month=pl.col("uploaded_on").dt.truncate("1mo"),
|
||||
ext=pl.col("path")
|
||||
.str.extract(pattern=r"\.([a-z0-9]+)$", group_index=1)
|
||||
.str.replace_all(pattern=r"cxx|cpp|cc|c|hpp|h", value="C/C++")
|
||||
.str.replace_all(pattern="^f.*$", value="Fortran")
|
||||
.str.replace("rs", "Rust", literal=True)
|
||||
.str.replace("go", "Go", literal=True)
|
||||
.str.replace("asm", "Assembly", literal=True)
|
||||
.replace({"": None}),
|
||||
)
|
||||
.group_by(["month", "ext"])
|
||||
.agg(project_count=pl.col("project_name").n_unique())
|
||||
.drop_nulls(["ext"])
|
||||
.sort(["month", "project_count"], descending=True)
|
||||
)
|
||||
|
||||
|
||||
def indentation_matching_for_loop_in_preview():
|
||||
if make_this:
|
||||
if more_nested_because_line_length:
|
||||
identical_hidden_layer_sizes = all(
|
||||
current_hidden_layer_sizes == first_hidden_layer_sizes
|
||||
for current_hidden_layer_sizes in self.component_config[
|
||||
HIDDEN_LAYERS_SIZES
|
||||
]
|
||||
.values()
|
||||
.attr
|
||||
)
|
||||
|
||||
|
||||
def indentation_matching_walrus_in_preview():
|
||||
if make_this:
|
||||
if more_nested_because_line_length:
|
||||
with self.read_ctx(book_type) as cursor:
|
||||
if (
|
||||
entry_count := len(
|
||||
names := cursor.execute(
|
||||
"SELECT name FROM address_book WHERE address=?",
|
||||
(address,),
|
||||
)
|
||||
.fetchall()
|
||||
.some_attr
|
||||
)
|
||||
) == 0 or len(set(names)) > 1:
|
||||
return
|
||||
|
||||
|
||||
# behavior with parenthesized roots
|
||||
x = (
|
||||
(aaaaaaaaaaaaaaaaaaaaaa)
|
||||
.bbbbbbbbbbbbbbbbbbb.cccccccccccccccccccccccc()
|
||||
.dddddddddddddddddddddddd()
|
||||
.eeeeeeeeeeee
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
## Preview changes
|
||||
```diff
|
||||
--- Stable
|
||||
+++ Preview
|
||||
@@ -21,7 +21,8 @@
|
||||
)
|
||||
|
||||
raise OsError("") from (
|
||||
- Blog.objects.filter(
|
||||
+ Blog.objects
|
||||
+ .filter(
|
||||
entry__headline__contains="Lennon",
|
||||
)
|
||||
.filter(
|
||||
@@ -33,7 +34,8 @@
|
||||
)
|
||||
|
||||
raise OsError("sökdjffffsldkfjlhsakfjhalsökafhsöfdahsödfjösaaksjdllllllllllllll") from (
|
||||
- Blog.objects.filter(
|
||||
+ Blog.objects
|
||||
+ .filter(
|
||||
entry__headline__contains="Lennon",
|
||||
)
|
||||
.filter(
|
||||
@@ -46,7 +48,8 @@
|
||||
|
||||
# Break only after calls and indexing
|
||||
b1 = (
|
||||
- session.query(models.Customer.id)
|
||||
+ session
|
||||
+ .query(models.Customer.id)
|
||||
.filter(
|
||||
models.Customer.account_id == account_id, models.Customer.email == email_address
|
||||
)
|
||||
@@ -54,7 +57,8 @@
|
||||
)
|
||||
|
||||
b2 = (
|
||||
- Blog.objects.filter(
|
||||
+ Blog.objects
|
||||
+ .filter(
|
||||
entry__headline__contains="Lennon",
|
||||
)
|
||||
.limit_results[:10]
|
||||
@@ -70,7 +74,8 @@
|
||||
).filter(
|
||||
entry__pub_date__year=2008,
|
||||
)
|
||||
- + Blog.objects.filter(
|
||||
+ + Blog.objects
|
||||
+ .filter(
|
||||
entry__headline__contains="McCartney",
|
||||
)
|
||||
.limit_results[:10]
|
||||
@@ -89,7 +94,8 @@
|
||||
d11 = x.e().e().e() #
|
||||
d12 = x.e().e().e() #
|
||||
d13 = (
|
||||
- x.e() #
|
||||
+ x
|
||||
+ .e() #
|
||||
.e()
|
||||
.e()
|
||||
)
|
||||
@@ -101,7 +107,8 @@
|
||||
|
||||
# Doesn't fit, fluent style
|
||||
d3 = (
|
||||
- x.e() #
|
||||
+ x
|
||||
+ .e() #
|
||||
.esadjkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk()
|
||||
.esadjkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk()
|
||||
)
|
||||
@@ -218,7 +225,8 @@
|
||||
|
||||
(
|
||||
(
|
||||
- df1_aaaaaaaaaaaa.merge()
|
||||
+ df1_aaaaaaaaaaaa
|
||||
+ .merge()
|
||||
.groupby(
|
||||
1,
|
||||
)
|
||||
@@ -228,7 +236,8 @@
|
||||
|
||||
(
|
||||
(
|
||||
- df1_aaaaaaaaaaaa.merge()
|
||||
+ df1_aaaaaaaaaaaa
|
||||
+ .merge()
|
||||
.groupby(
|
||||
1,
|
||||
)
|
||||
@@ -255,19 +264,19 @@
|
||||
|
||||
|
||||
expr = (
|
||||
- pl.scan_parquet("/data/pypi-parquet/*.parquet")
|
||||
- .filter(
|
||||
- [
|
||||
- pl.col("path").str.contains(
|
||||
- r"\.(asm|c|cc|cpp|cxx|h|hpp|rs|[Ff][0-9]{0,2}(?:or)?|go)$"
|
||||
- ),
|
||||
- ~pl.col("path").str.contains(r"(^|/)test(|s|ing)"),
|
||||
- ~pl.col("path").str.contains("/site-packages/", literal=True),
|
||||
- ]
|
||||
- )
|
||||
+ pl
|
||||
+ .scan_parquet("/data/pypi-parquet/*.parquet")
|
||||
+ .filter([
|
||||
+ pl.col("path").str.contains(
|
||||
+ r"\.(asm|c|cc|cpp|cxx|h|hpp|rs|[Ff][0-9]{0,2}(?:or)?|go)$"
|
||||
+ ),
|
||||
+ ~pl.col("path").str.contains(r"(^|/)test(|s|ing)"),
|
||||
+ ~pl.col("path").str.contains("/site-packages/", literal=True),
|
||||
+ ])
|
||||
.with_columns(
|
||||
month=pl.col("uploaded_on").dt.truncate("1mo"),
|
||||
- ext=pl.col("path")
|
||||
+ ext=pl
|
||||
+ .col("path")
|
||||
.str.extract(pattern=r"\.([a-z0-9]+)$", group_index=1)
|
||||
.str.replace_all(pattern=r"cxx|cpp|cc|c|hpp|h", value="C/C++")
|
||||
.str.replace_all(pattern="^f.*$", value="Fortran")
|
||||
@@ -288,9 +297,8 @@
|
||||
if more_nested_because_line_length:
|
||||
identical_hidden_layer_sizes = all(
|
||||
current_hidden_layer_sizes == first_hidden_layer_sizes
|
||||
- for current_hidden_layer_sizes in self.component_config[
|
||||
- HIDDEN_LAYERS_SIZES
|
||||
- ]
|
||||
+ for current_hidden_layer_sizes in self
|
||||
+ .component_config[HIDDEN_LAYERS_SIZES]
|
||||
.values()
|
||||
.attr
|
||||
)
|
||||
@@ -302,7 +310,8 @@
|
||||
with self.read_ctx(book_type) as cursor:
|
||||
if (
|
||||
entry_count := len(
|
||||
- names := cursor.execute(
|
||||
+ names := cursor
|
||||
+ .execute(
|
||||
"SELECT name FROM address_book WHERE address=?",
|
||||
(address,),
|
||||
)
|
||||
```
|
||||
|
||||
@@ -592,11 +592,23 @@ impl FormatString {
|
||||
fn parse_literal(text: &str) -> Result<(FormatPart, &str), FormatParseError> {
|
||||
let mut cur_text = text;
|
||||
let mut result_string = String::new();
|
||||
let mut pending_escape = false;
|
||||
while !cur_text.is_empty() {
|
||||
if pending_escape
|
||||
&& let Some((unicode_string, remaining)) =
|
||||
FormatString::parse_escaped_unicode_string(cur_text)
|
||||
{
|
||||
result_string.push_str(unicode_string);
|
||||
cur_text = remaining;
|
||||
pending_escape = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
match FormatString::parse_literal_single(cur_text) {
|
||||
Ok((next_char, remaining)) => {
|
||||
result_string.push(next_char);
|
||||
cur_text = remaining;
|
||||
pending_escape = next_char == '\\' && !pending_escape;
|
||||
}
|
||||
Err(err) => {
|
||||
return if result_string.is_empty() {
|
||||
@@ -678,6 +690,13 @@ impl FormatString {
|
||||
}
|
||||
Err(FormatParseError::UnmatchedBracket)
|
||||
}
|
||||
|
||||
fn parse_escaped_unicode_string(text: &str) -> Option<(&str, &str)> {
|
||||
text.strip_prefix("N{")?.find('}').map(|idx| {
|
||||
let end_idx = idx + 3; // 3 for "N{"
|
||||
(&text[..end_idx], &text[end_idx..])
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub trait FromTemplate<'a>: Sized {
|
||||
@@ -1020,4 +1039,48 @@ mod tests {
|
||||
Err(FormatParseError::InvalidCharacterAfterRightBracket)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_unicode_escape() {
|
||||
let expected = Ok(FormatString {
|
||||
format_parts: vec![FormatPart::Literal("I am a \\N{snowman}".to_owned())],
|
||||
});
|
||||
|
||||
assert_eq!(FormatString::from_str("I am a \\N{snowman}"), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_unicode_escape_with_field() {
|
||||
let expected = Ok(FormatString {
|
||||
format_parts: vec![
|
||||
FormatPart::Literal("I am a \\N{snowman}".to_owned()),
|
||||
FormatPart::Field {
|
||||
field_name: "snowman".to_owned(),
|
||||
conversion_spec: None,
|
||||
format_spec: String::new(),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
FormatString::from_str("I am a \\N{snowman}{snowman}"),
|
||||
expected
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_multiple_escape_with_field() {
|
||||
let expected = Ok(FormatString {
|
||||
format_parts: vec![
|
||||
FormatPart::Literal("I am a \\\\N".to_owned()),
|
||||
FormatPart::Field {
|
||||
field_name: "snowman".to_owned(),
|
||||
conversion_spec: None,
|
||||
format_spec: String::new(),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
assert_eq!(FormatString::from_str("I am a \\\\N{snowman}"), expected);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,5 +51,11 @@ regex = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
[target.'cfg(all(not(target_os = "macos"), not(target_os = "windows"), not(target_os = "openbsd"), not(target_os = "aix"), not(target_os = "android"), any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64", target_arch = "riscv64")))'.dependencies]
|
||||
tikv-jemallocator = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
84
crates/ty/docs/configuration.md
generated
84
crates/ty/docs/configuration.md
generated
@@ -18,9 +18,9 @@ Valid severities are:
|
||||
|
||||
**Type**: `dict[RuleName, "ignore" | "warn" | "error"]`
|
||||
|
||||
**Example usage** (`pyproject.toml`):
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
```toml title="pyproject.toml"
|
||||
[tool.ty.rules]
|
||||
possibly-unresolved-reference = "warn"
|
||||
division-by-zero = "ignore"
|
||||
@@ -45,9 +45,9 @@ configuration setting.
|
||||
|
||||
**Type**: `list[str]`
|
||||
|
||||
**Example usage** (`pyproject.toml`):
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
```toml title="pyproject.toml"
|
||||
[tool.ty.environment]
|
||||
extra-paths = ["./shared/my-search-path"]
|
||||
```
|
||||
@@ -76,9 +76,9 @@ This option can be used to point to virtual or system Python environments.
|
||||
|
||||
**Type**: `str`
|
||||
|
||||
**Example usage** (`pyproject.toml`):
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
```toml title="pyproject.toml"
|
||||
[tool.ty.environment]
|
||||
python = "./custom-venv-location/.venv"
|
||||
```
|
||||
@@ -103,9 +103,9 @@ If no platform is specified, ty will use the current platform:
|
||||
|
||||
**Type**: `"win32" | "darwin" | "android" | "ios" | "linux" | "all" | str`
|
||||
|
||||
**Example usage** (`pyproject.toml`):
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
```toml title="pyproject.toml"
|
||||
[tool.ty.environment]
|
||||
# Tailor type stubs and conditionalized type definitions to windows.
|
||||
python-platform = "win32"
|
||||
@@ -137,9 +137,9 @@ to reflect the differing contents of the standard library across Python versions
|
||||
|
||||
**Type**: `"3.7" | "3.8" | "3.9" | "3.10" | "3.11" | "3.12" | "3.13" | "3.14" | <major>.<minor>`
|
||||
|
||||
**Example usage** (`pyproject.toml`):
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
```toml title="pyproject.toml"
|
||||
[tool.ty.environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
@@ -165,9 +165,9 @@ it will also be included in the first party search path.
|
||||
|
||||
**Type**: `list[str]`
|
||||
|
||||
**Example usage** (`pyproject.toml`):
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
```toml title="pyproject.toml"
|
||||
[tool.ty.environment]
|
||||
# Multiple directories (priority order)
|
||||
root = ["./src", "./lib", "./vendor"]
|
||||
@@ -185,9 +185,9 @@ bundled as a zip file in the binary
|
||||
|
||||
**Type**: `str`
|
||||
|
||||
**Example usage** (`pyproject.toml`):
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
```toml title="pyproject.toml"
|
||||
[tool.ty.environment]
|
||||
typeshed = "/path/to/custom/typeshed"
|
||||
```
|
||||
@@ -200,24 +200,22 @@ Configuration override that applies to specific files based on glob patterns.
|
||||
|
||||
An override allows you to apply different rule configurations to specific
|
||||
files or directories. Multiple overrides can match the same file, with
|
||||
later overrides take precedence.
|
||||
later overrides take precedence. Override rules take precedence over global
|
||||
rules for matching files.
|
||||
|
||||
### Precedence
|
||||
|
||||
- Later overrides in the array take precedence over earlier ones
|
||||
- Override rules take precedence over global rules for matching files
|
||||
|
||||
### Examples
|
||||
For example, to relax enforcement of rules in test files:
|
||||
|
||||
```toml
|
||||
# Relax rules for test files
|
||||
[[tool.ty.overrides]]
|
||||
include = ["tests/**", "**/test_*.py"]
|
||||
|
||||
[tool.ty.overrides.rules]
|
||||
possibly-unresolved-reference = "warn"
|
||||
```
|
||||
|
||||
# Ignore generated files but still check important ones
|
||||
Or, to ignore a rule in generated files but retain enforcement in an important file:
|
||||
|
||||
```toml
|
||||
[[tool.ty.overrides]]
|
||||
include = ["generated/**"]
|
||||
exclude = ["generated/important.py"]
|
||||
@@ -240,9 +238,9 @@ If not specified, defaults to `[]` (excludes no files).
|
||||
|
||||
**Type**: `list[str]`
|
||||
|
||||
**Example usage** (`pyproject.toml`):
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
```toml title="pyproject.toml"
|
||||
[[tool.ty.overrides]]
|
||||
exclude = [
|
||||
"generated",
|
||||
@@ -268,9 +266,9 @@ If not specified, defaults to `["**"]` (matches all files).
|
||||
|
||||
**Type**: `list[str]`
|
||||
|
||||
**Example usage** (`pyproject.toml`):
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
```toml title="pyproject.toml"
|
||||
[[tool.ty.overrides]]
|
||||
include = [
|
||||
"src",
|
||||
@@ -292,9 +290,9 @@ severity levels or disable them entirely.
|
||||
|
||||
**Type**: `dict[RuleName, "ignore" | "warn" | "error"]`
|
||||
|
||||
**Example usage** (`pyproject.toml`):
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
```toml title="pyproject.toml"
|
||||
[[tool.ty.overrides]]
|
||||
include = ["src"]
|
||||
|
||||
@@ -358,9 +356,9 @@ to re-include `dist` use `exclude = ["!dist"]`
|
||||
|
||||
**Type**: `list[str]`
|
||||
|
||||
**Example usage** (`pyproject.toml`):
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
```toml title="pyproject.toml"
|
||||
[tool.ty.src]
|
||||
exclude = [
|
||||
"generated",
|
||||
@@ -399,9 +397,9 @@ matches `<project_root>/src` and not `<project_root>/test/src`).
|
||||
|
||||
**Type**: `list[str]`
|
||||
|
||||
**Example usage** (`pyproject.toml`):
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
```toml title="pyproject.toml"
|
||||
[tool.ty.src]
|
||||
include = [
|
||||
"src",
|
||||
@@ -421,9 +419,9 @@ Enabled by default.
|
||||
|
||||
**Type**: `bool`
|
||||
|
||||
**Example usage** (`pyproject.toml`):
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
```toml title="pyproject.toml"
|
||||
[tool.ty.src]
|
||||
respect-ignore-files = false
|
||||
```
|
||||
@@ -432,8 +430,8 @@ respect-ignore-files = false
|
||||
|
||||
### `root`
|
||||
|
||||
> [!WARN] "Deprecated"
|
||||
> This option has been deprecated. Use `environment.root` instead.
|
||||
!!! warning "Deprecated"
|
||||
This option has been deprecated. Use `environment.root` instead.
|
||||
|
||||
The root of the project, used for finding first-party modules.
|
||||
|
||||
@@ -450,9 +448,9 @@ it will also be included in the first party search path.
|
||||
|
||||
**Type**: `str`
|
||||
|
||||
**Example usage** (`pyproject.toml`):
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
```toml title="pyproject.toml"
|
||||
[tool.ty.src]
|
||||
root = "./app"
|
||||
```
|
||||
@@ -471,9 +469,9 @@ Defaults to `false`.
|
||||
|
||||
**Type**: `bool`
|
||||
|
||||
**Example usage** (`pyproject.toml`):
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
```toml title="pyproject.toml"
|
||||
[tool.ty.terminal]
|
||||
# Error if ty emits any warning-level diagnostics.
|
||||
error-on-warning = true
|
||||
@@ -491,9 +489,9 @@ Defaults to `full`.
|
||||
|
||||
**Type**: `full | concise`
|
||||
|
||||
**Example usage** (`pyproject.toml`):
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
```toml title="pyproject.toml"
|
||||
[tool.ty.terminal]
|
||||
output-format = "concise"
|
||||
```
|
||||
|
||||
@@ -2,6 +2,15 @@
|
||||
|
||||
ty defines and respects the following environment variables:
|
||||
|
||||
### `TY_CONFIG_FILE`
|
||||
|
||||
Path to a `ty.toml` configuration file to use.
|
||||
|
||||
When set, ty will use this file for configuration instead of
|
||||
discovering configuration files automatically.
|
||||
|
||||
Equivalent to the `--config-file` command-line argument.
|
||||
|
||||
### `TY_LOG`
|
||||
|
||||
If set, ty will use this value as the log level for its `--verbose` output.
|
||||
|
||||
201
crates/ty/docs/rules.md
generated
201
crates/ty/docs/rules.md
generated
@@ -39,7 +39,7 @@ def test(): -> "int":
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-non-callable" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L134" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L135" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ Calling a non-callable object will raise a `TypeError` at runtime.
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-argument-forms" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L178" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L179" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ f(int) # error
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-declarations" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L204" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L205" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -126,7 +126,7 @@ a = 1
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-metaclass" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L229" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L230" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@ class C(A, B): ...
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-class-definition" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L255" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L256" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -190,7 +190,7 @@ class B(A): ...
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/1.0.0">1.0.0</a>) ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-type-alias-definition" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L281" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L282" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -218,7 +218,7 @@ type B = A
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-base" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L342" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L343" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -245,7 +245,7 @@ class B(A, A): ...
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.12">0.0.1-alpha.12</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-kw-only" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L363" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L364" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -357,7 +357,7 @@ def test(): -> "Literal[5]":
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20inconsistent-mro" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L589" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L590" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -387,7 +387,7 @@ class C(A, B): ...
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20index-out-of-bounds" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L613" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L614" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -413,7 +413,7 @@ t[3] # IndexError: tuple index out of range
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.12">0.0.1-alpha.12</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20instance-layout-conflict" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L395" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L396" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -502,7 +502,7 @@ an atypical memory layout.
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-argument-type" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L667" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L668" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -529,7 +529,7 @@ func("foo") # error: [invalid-argument-type]
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-assignment" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L707" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L708" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -557,7 +557,7 @@ a: int = ''
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-attribute-access" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1997" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2003" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -591,7 +591,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.19">0.0.1-alpha.19</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-await" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L729" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L730" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -627,7 +627,7 @@ asyncio.run(main())
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-base" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L759" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L760" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -651,7 +651,7 @@ class A(42): ... # error: [invalid-base]
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-context-manager" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L810" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L811" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -678,7 +678,7 @@ with 1:
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-declaration" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L831" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L832" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -707,7 +707,7 @@ a: str
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-exception-caught" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L854" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L855" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -751,7 +751,7 @@ except ZeroDivisionError:
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.28">0.0.1-alpha.28</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-explicit-override" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1667" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1673" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -787,13 +787,57 @@ class D(A):
|
||||
def foo(self): ... # fine: overrides `A.foo`
|
||||
```
|
||||
|
||||
## `invalid-frozen-dataclass-subclass`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.35">0.0.1-alpha.35</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-frozen-dataclass-subclass" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2229" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
**What it does**
|
||||
|
||||
Checks for dataclasses with invalid frozen inheritance:
|
||||
- A frozen dataclass cannot inherit from a non-frozen dataclass.
|
||||
- A non-frozen dataclass cannot inherit from a frozen dataclass.
|
||||
|
||||
**Why is this bad?**
|
||||
|
||||
Python raises a `TypeError` at runtime when either of these inheritance
|
||||
patterns occurs.
|
||||
|
||||
**Example**
|
||||
|
||||
|
||||
```python
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass
|
||||
class Base:
|
||||
x: int
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Child(Base): # Error raised here
|
||||
y: int
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class FrozenBase:
|
||||
x: int
|
||||
|
||||
@dataclass
|
||||
class NonFrozenChild(FrozenBase): # Error raised here
|
||||
y: int
|
||||
```
|
||||
|
||||
## `invalid-generic-class`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-class" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L890" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L891" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -804,16 +848,21 @@ Checks for the creation of invalid generic classes
|
||||
**Why is this bad?**
|
||||
|
||||
There are several requirements that you must follow when defining a generic class.
|
||||
Many of these result in `TypeError` being raised at runtime if they are violated.
|
||||
|
||||
**Examples**
|
||||
|
||||
```python
|
||||
from typing import Generic, TypeVar
|
||||
from typing_extensions import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T") # okay
|
||||
T = TypeVar("T")
|
||||
U = TypeVar("U", default=int)
|
||||
|
||||
# error: class uses both PEP-695 syntax and legacy syntax
|
||||
class C[U](Generic[T]): ...
|
||||
|
||||
# error: type parameter with default comes before type parameter without default
|
||||
class D(Generic[U, T]): ...
|
||||
```
|
||||
|
||||
**References**
|
||||
@@ -826,7 +875,7 @@ class C[U](Generic[T]): ...
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.17">0.0.1-alpha.17</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-key" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L634" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L635" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -865,7 +914,7 @@ carol = Person(name="Carol", age=25) # typo!
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-legacy-type-variable" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L916" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L922" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -900,7 +949,7 @@ def f(t: TypeVar("U")): ...
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-metaclass" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1013" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1019" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -934,7 +983,7 @@ class B(metaclass=f): ...
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.20">0.0.1-alpha.20</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-method-override" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2125" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2131" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1041,7 +1090,7 @@ Correct use of `@override` is enforced by ty's `invalid-explicit-override` rule.
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.19">0.0.1-alpha.19</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-named-tuple" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L541" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L542" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1095,7 +1144,7 @@ AttributeError: Cannot overwrite NamedTuple attribute _asdict
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/1.0.0">1.0.0</a>) ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-newtype" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L989" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L995" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1125,7 +1174,7 @@ Baz = NewType("Baz", int | str) # error: invalid base for `typing.NewType`
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-overload" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1040" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1046" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1175,7 +1224,7 @@ def foo(x: int) -> int: ...
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-parameter-default" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1139" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1145" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1201,7 +1250,7 @@ def f(a: int = ''): ...
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-paramspec" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L944" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L950" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1232,7 +1281,7 @@ P2 = ParamSpec("S2") # error: ParamSpec name must match the variable it's assig
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-protocol" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L477" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L478" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1266,7 +1315,7 @@ TypeError: Protocols can only inherit from other protocols, got <class 'int'>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-raise" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1159" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1165" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1315,7 +1364,7 @@ def g():
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-return-type" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L688" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L689" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1340,7 +1389,7 @@ def func() -> int:
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-super-argument" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1202" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1208" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1398,7 +1447,7 @@ TODO #14889
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.6">0.0.1-alpha.6</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-alias-type" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L968" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L974" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1425,7 +1474,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.29">0.0.1-alpha.29</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-arguments" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1434" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1440" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1472,7 +1521,7 @@ Bar[int] # error: too few arguments
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-checking-constant" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1241" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1247" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1502,7 +1551,7 @@ TYPE_CHECKING = ''
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-form" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1265" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1271" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1532,7 +1581,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.11">0.0.1-alpha.11</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-call" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1317" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1323" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1566,7 +1615,7 @@ f(10) # Error
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.11">0.0.1-alpha.11</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-definition" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1289" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1295" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1600,7 +1649,7 @@ class C:
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-variable-constraints" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1345" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1351" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1635,7 +1684,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-argument" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1374" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1380" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1660,7 +1709,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x'
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.20">0.0.1-alpha.20</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-typed-dict-key" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2098" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2104" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1693,7 +1742,7 @@ alice["age"] # KeyError
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20no-matching-overload" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1393" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1399" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1722,7 +1771,7 @@ func("string") # error: [no-matching-overload]
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20non-subscriptable" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1416" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1422" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1746,7 +1795,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-iterable" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1475" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1481" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1772,7 +1821,7 @@ for i in 34: # TypeError: 'int' object is not iterable
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.29">0.0.1-alpha.29</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20override-of-final-method" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1640" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1646" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1805,7 +1854,7 @@ class B(A):
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20parameter-already-assigned" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1526" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1532" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1832,7 +1881,7 @@ f(1, x=2) # Error raised here
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20positional-only-parameter-as-kwarg" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1851" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1857" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1890,7 +1939,7 @@ def test(): -> "int":
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20static-assert-error" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1973" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1979" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1920,7 +1969,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20subclass-of-final-class" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1617" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1623" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1949,7 +1998,7 @@ class B(A): ... # Error raised here
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.30">0.0.1-alpha.30</a>) ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20super-call-in-named-tuple-method" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1785" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1791" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1983,7 +2032,7 @@ class F(NamedTuple):
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20too-many-positional-arguments" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1725" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1731" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2010,7 +2059,7 @@ f("foo") # Error raised here
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20type-assertion-failure" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1703" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1709" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2038,7 +2087,7 @@ def _(x: int):
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unavailable-implicit-super-arguments" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1746" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1752" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2084,7 +2133,7 @@ class A:
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unknown-argument" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1830" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1836" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2111,7 +2160,7 @@ f(x=1, y=2) # Error raised here
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-attribute" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1872" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1878" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2139,7 +2188,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo'
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-import" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1894" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1900" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2164,7 +2213,7 @@ import foo # ModuleNotFoundError: No module named 'foo'
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-reference" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1913" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1919" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2189,7 +2238,7 @@ print(x) # NameError: name 'x' is not defined
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-bool-conversion" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1495" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1501" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2226,7 +2275,7 @@ b1 < b2 < b1 # exception raised here
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-operator" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1932" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1938" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2254,7 +2303,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A'
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20zero-stepsize-in-slice" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1954" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1960" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2279,7 +2328,7 @@ l[1:10:0] # ValueError: slice step cannot be zero
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.20">0.0.1-alpha.20</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20ambiguous-protocol-member" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L506" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L507" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2320,7 +2369,7 @@ class SubProto(BaseProto, Protocol):
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.16">0.0.1-alpha.16</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20deprecated" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L321" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L322" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2408,7 +2457,7 @@ a = 20 / 0 # type: ignore
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-missing-attribute" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1547" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1553" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2436,7 +2485,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c'
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-missing-implicit-call" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L152" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L153" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2468,7 +2517,7 @@ A()[0] # TypeError: 'A' object is not subscriptable
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-missing-import" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1569" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1575" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2500,7 +2549,7 @@ from module import a # ImportError: cannot import name 'a' from 'module'
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20redundant-cast" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2025" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2031" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2527,7 +2576,7 @@ cast(int, f()) # Redundant
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20undefined-reveal" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1812" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1818" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2551,7 +2600,7 @@ reveal_type(1) # NameError: name 'reveal_type' is not defined
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.15">0.0.1-alpha.15</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-global" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2046" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2052" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2609,7 +2658,7 @@ def g():
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.7">0.0.1-alpha.7</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-base" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L777" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L778" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2648,7 +2697,7 @@ class D(C): ... # error: [unsupported-base]
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20useless-overload-body" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1083" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1089" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2711,7 +2760,7 @@ def foo(x: int | str) -> int | str:
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'ignore'."><code>ignore</code></a> ·
|
||||
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a>) ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20division-by-zero" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L303" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L304" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2735,7 +2784,7 @@ Dividing by zero raises a `ZeroDivisionError` at runtime.
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'ignore'."><code>ignore</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unresolved-reference" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1595" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1601" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ use ty_combine::Combine;
|
||||
use ty_project::metadata::options::{EnvironmentOptions, Options, SrcOptions, TerminalOptions};
|
||||
use ty_project::metadata::value::{RangedValue, RelativeGlobPattern, RelativePathBuf, ValueSource};
|
||||
use ty_python_semantic::lint;
|
||||
use ty_static::EnvVars;
|
||||
|
||||
// Configures Clap v3-style help menu colors
|
||||
const STYLES: Styles = Styles::styled()
|
||||
@@ -121,7 +122,7 @@ pub(crate) struct CheckCommand {
|
||||
/// The path to a `ty.toml` file to use for configuration.
|
||||
///
|
||||
/// While ty configuration can be included in a `pyproject.toml` file, it is not allowed in this context.
|
||||
#[arg(long, env = "TY_CONFIG_FILE", value_name = "PATH")]
|
||||
#[arg(long, env = EnvVars::TY_CONFIG_FILE, value_name = "PATH")]
|
||||
pub(crate) config_file: Option<SystemPathBuf>,
|
||||
|
||||
/// The format to use for printing diagnostic messages.
|
||||
|
||||
@@ -2,6 +2,22 @@ use colored::Colorize;
|
||||
use std::io;
|
||||
use ty::{ExitStatus, run};
|
||||
|
||||
#[cfg(all(
|
||||
not(target_os = "macos"),
|
||||
not(target_os = "windows"),
|
||||
not(target_os = "openbsd"),
|
||||
not(target_os = "aix"),
|
||||
not(target_os = "android"),
|
||||
any(
|
||||
target_arch = "x86_64",
|
||||
target_arch = "aarch64",
|
||||
target_arch = "powerpc64",
|
||||
target_arch = "riscv64"
|
||||
)
|
||||
))]
|
||||
#[global_allocator]
|
||||
static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
|
||||
|
||||
pub fn main() -> ExitStatus {
|
||||
run().unwrap_or_else(|error| {
|
||||
use io::Write;
|
||||
|
||||
@@ -29,12 +29,11 @@ pub fn code_actions(
|
||||
|
||||
let mut actions = Vec::new();
|
||||
|
||||
// Suggest imports for unresolved references (often ideal)
|
||||
// TODO: suggest qualifying with an already imported symbol
|
||||
// Suggest imports/qualifications for unresolved references (often ideal)
|
||||
let is_unresolved_reference =
|
||||
lint_id == LintId::of(&UNRESOLVED_REFERENCE) || lint_id == LintId::of(&UNDEFINED_REVEAL);
|
||||
if is_unresolved_reference
|
||||
&& let Some(import_quick_fix) = create_import_symbol_quick_fix(db, file, diagnostic_range)
|
||||
&& let Some(import_quick_fix) = unresolved_fixes(db, file, diagnostic_range)
|
||||
{
|
||||
actions.extend(import_quick_fix);
|
||||
}
|
||||
@@ -49,7 +48,7 @@ pub fn code_actions(
|
||||
actions
|
||||
}
|
||||
|
||||
fn create_import_symbol_quick_fix(
|
||||
fn unresolved_fixes(
|
||||
db: &dyn Db,
|
||||
file: File,
|
||||
diagnostic_range: TextRange,
|
||||
@@ -59,7 +58,7 @@ fn create_import_symbol_quick_fix(
|
||||
let symbol = &node.expr_name()?.id;
|
||||
|
||||
Some(
|
||||
completion::missing_imports(db, file, &parsed, symbol, node)
|
||||
completion::unresolved_fixes(db, file, &parsed, symbol, node)
|
||||
.into_iter()
|
||||
.map(|import| QuickFix {
|
||||
title: import.label,
|
||||
@@ -84,6 +83,7 @@ mod tests {
|
||||
system::{DbWithWritableSystem, SystemPathBuf},
|
||||
};
|
||||
use ruff_diagnostics::Fix;
|
||||
use ruff_python_trivia::textwrap::dedent;
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
use ty_project::ProjectMetadata;
|
||||
use ty_python_semantic::{
|
||||
@@ -149,15 +149,14 @@ mod tests {
|
||||
|
||||
assert_snapshot!(test.code_actions(&UNRESOLVED_REFERENCE), @r"
|
||||
info[code-action]: Ignore 'unresolved-reference' for this line
|
||||
--> main.py:2:17
|
||||
--> main.py:2:5
|
||||
|
|
||||
2 | b = a / 0 # ty:ignore[division-by-zero]
|
||||
| ^
|
||||
2 | b = a / 0 # ty:ignore[division-by-zero]
|
||||
| ^
|
||||
|
|
||||
1 |
|
||||
- b = a / 0 # ty:ignore[division-by-zero]
|
||||
2 + b = a / 0 # ty:ignore[division-by-zero, unresolved-reference]
|
||||
3 |
|
||||
- b = a / 0 # ty:ignore[division-by-zero]
|
||||
2 + b = a / 0 # ty:ignore[division-by-zero, unresolved-reference]
|
||||
");
|
||||
}
|
||||
|
||||
@@ -171,15 +170,14 @@ mod tests {
|
||||
|
||||
assert_snapshot!(test.code_actions(&UNRESOLVED_REFERENCE), @r"
|
||||
info[code-action]: Ignore 'unresolved-reference' for this line
|
||||
--> main.py:2:17
|
||||
--> main.py:2:5
|
||||
|
|
||||
2 | b = a / 0 # ty:ignore[division-by-zero,]
|
||||
| ^
|
||||
2 | b = a / 0 # ty:ignore[division-by-zero,]
|
||||
| ^
|
||||
|
|
||||
1 |
|
||||
- b = a / 0 # ty:ignore[division-by-zero,]
|
||||
2 + b = a / 0 # ty:ignore[division-by-zero, unresolved-reference]
|
||||
3 |
|
||||
- b = a / 0 # ty:ignore[division-by-zero,]
|
||||
2 + b = a / 0 # ty:ignore[division-by-zero, unresolved-reference]
|
||||
");
|
||||
}
|
||||
|
||||
@@ -193,15 +191,14 @@ mod tests {
|
||||
|
||||
assert_snapshot!(test.code_actions(&UNRESOLVED_REFERENCE), @r"
|
||||
info[code-action]: Ignore 'unresolved-reference' for this line
|
||||
--> main.py:2:17
|
||||
--> main.py:2:5
|
||||
|
|
||||
2 | b = a / 0 # ty:ignore[division-by-zero ]
|
||||
| ^
|
||||
2 | b = a / 0 # ty:ignore[division-by-zero ]
|
||||
| ^
|
||||
|
|
||||
1 |
|
||||
- b = a / 0 # ty:ignore[division-by-zero ]
|
||||
2 + b = a / 0 # ty:ignore[division-by-zero, unresolved-reference ]
|
||||
3 |
|
||||
- b = a / 0 # ty:ignore[division-by-zero ]
|
||||
2 + b = a / 0 # ty:ignore[division-by-zero, unresolved-reference ]
|
||||
");
|
||||
}
|
||||
|
||||
@@ -215,15 +212,14 @@ mod tests {
|
||||
|
||||
assert_snapshot!(test.code_actions(&UNRESOLVED_REFERENCE), @r"
|
||||
info[code-action]: Ignore 'unresolved-reference' for this line
|
||||
--> main.py:2:17
|
||||
--> main.py:2:5
|
||||
|
|
||||
2 | b = a / 0 # ty:ignore[division-by-zero] some explanation
|
||||
| ^
|
||||
2 | b = a / 0 # ty:ignore[division-by-zero] some explanation
|
||||
| ^
|
||||
|
|
||||
1 |
|
||||
- b = a / 0 # ty:ignore[division-by-zero] some explanation
|
||||
2 + b = a / 0 # ty:ignore[division-by-zero] some explanation # ty:ignore[unresolved-reference]
|
||||
3 |
|
||||
- b = a / 0 # ty:ignore[division-by-zero] some explanation
|
||||
2 + b = a / 0 # ty:ignore[division-by-zero] some explanation # ty:ignore[unresolved-reference]
|
||||
");
|
||||
}
|
||||
|
||||
@@ -241,22 +237,22 @@ mod tests {
|
||||
|
||||
assert_snapshot!(test.code_actions(&UNRESOLVED_REFERENCE), @r"
|
||||
info[code-action]: Ignore 'unresolved-reference' for this line
|
||||
--> main.py:3:21
|
||||
--> main.py:3:9
|
||||
|
|
||||
2 | b = (
|
||||
3 | / a # ty:ignore[division-by-zero]
|
||||
4 | | /
|
||||
5 | | 0
|
||||
| |_____________________^
|
||||
6 | )
|
||||
2 | b = (
|
||||
3 | / a # ty:ignore[division-by-zero]
|
||||
4 | | /
|
||||
5 | | 0
|
||||
| |_________^
|
||||
6 | )
|
||||
|
|
||||
1 |
|
||||
2 | b = (
|
||||
- a # ty:ignore[division-by-zero]
|
||||
3 + a # ty:ignore[division-by-zero, unresolved-reference]
|
||||
4 | /
|
||||
5 | 0
|
||||
6 | )
|
||||
2 | b = (
|
||||
- a # ty:ignore[division-by-zero]
|
||||
3 + a # ty:ignore[division-by-zero, unresolved-reference]
|
||||
4 | /
|
||||
5 | 0
|
||||
6 | )
|
||||
");
|
||||
}
|
||||
|
||||
@@ -274,22 +270,21 @@ mod tests {
|
||||
|
||||
assert_snapshot!(test.code_actions(&UNRESOLVED_REFERENCE), @r"
|
||||
info[code-action]: Ignore 'unresolved-reference' for this line
|
||||
--> main.py:3:21
|
||||
--> main.py:3:9
|
||||
|
|
||||
2 | b = (
|
||||
3 | / a
|
||||
4 | | /
|
||||
5 | | 0 # ty:ignore[division-by-zero]
|
||||
| |_____________________^
|
||||
6 | )
|
||||
2 | b = (
|
||||
3 | / a
|
||||
4 | | /
|
||||
5 | | 0 # ty:ignore[division-by-zero]
|
||||
| |_________^
|
||||
6 | )
|
||||
|
|
||||
2 | b = (
|
||||
3 | a
|
||||
4 | /
|
||||
- 0 # ty:ignore[division-by-zero]
|
||||
5 + 0 # ty:ignore[division-by-zero, unresolved-reference]
|
||||
6 | )
|
||||
7 |
|
||||
2 | b = (
|
||||
3 | a
|
||||
4 | /
|
||||
- 0 # ty:ignore[division-by-zero]
|
||||
5 + 0 # ty:ignore[division-by-zero, unresolved-reference]
|
||||
6 | )
|
||||
");
|
||||
}
|
||||
|
||||
@@ -307,22 +302,22 @@ mod tests {
|
||||
|
||||
assert_snapshot!(test.code_actions(&UNRESOLVED_REFERENCE), @r"
|
||||
info[code-action]: Ignore 'unresolved-reference' for this line
|
||||
--> main.py:3:21
|
||||
--> main.py:3:9
|
||||
|
|
||||
2 | b = (
|
||||
3 | / a # ty:ignore[division-by-zero]
|
||||
4 | | /
|
||||
5 | | 0 # ty:ignore[division-by-zero]
|
||||
| |_____________________^
|
||||
6 | )
|
||||
2 | b = (
|
||||
3 | / a # ty:ignore[division-by-zero]
|
||||
4 | | /
|
||||
5 | | 0 # ty:ignore[division-by-zero]
|
||||
| |_________^
|
||||
6 | )
|
||||
|
|
||||
1 |
|
||||
2 | b = (
|
||||
- a # ty:ignore[division-by-zero]
|
||||
3 + a # ty:ignore[division-by-zero, unresolved-reference]
|
||||
4 | /
|
||||
5 | 0 # ty:ignore[division-by-zero]
|
||||
6 | )
|
||||
2 | b = (
|
||||
- a # ty:ignore[division-by-zero]
|
||||
3 + a # ty:ignore[division-by-zero, unresolved-reference]
|
||||
4 | /
|
||||
5 | 0 # ty:ignore[division-by-zero]
|
||||
6 | )
|
||||
");
|
||||
}
|
||||
|
||||
@@ -339,20 +334,19 @@ mod tests {
|
||||
|
||||
assert_snapshot!(test.code_actions(&UNRESOLVED_REFERENCE), @r#"
|
||||
info[code-action]: Ignore 'unresolved-reference' for this line
|
||||
--> main.py:3:18
|
||||
--> main.py:3:6
|
||||
|
|
||||
2 | b = f"""
|
||||
3 | {a}
|
||||
| ^
|
||||
4 | more text
|
||||
5 | """
|
||||
2 | b = f"""
|
||||
3 | {a}
|
||||
| ^
|
||||
4 | more text
|
||||
5 | """
|
||||
|
|
||||
2 | b = f"""
|
||||
3 | {a}
|
||||
4 | more text
|
||||
- """
|
||||
5 + """ # ty:ignore[unresolved-reference]
|
||||
6 |
|
||||
2 | b = f"""
|
||||
3 | {a}
|
||||
4 | more text
|
||||
- """
|
||||
5 + """ # ty:ignore[unresolved-reference]
|
||||
"#);
|
||||
}
|
||||
|
||||
@@ -371,23 +365,23 @@ mod tests {
|
||||
|
||||
assert_snapshot!(test.code_actions(&UNRESOLVED_REFERENCE), @r#"
|
||||
info[code-action]: Ignore 'unresolved-reference' for this line
|
||||
--> main.py:4:17
|
||||
--> main.py:4:5
|
||||
|
|
||||
2 | b = f"""
|
||||
3 | {
|
||||
4 | a
|
||||
| ^
|
||||
5 | }
|
||||
6 | more text
|
||||
2 | b = f"""
|
||||
3 | {
|
||||
4 | a
|
||||
| ^
|
||||
5 | }
|
||||
6 | more text
|
||||
|
|
||||
1 |
|
||||
2 | b = f"""
|
||||
3 | {
|
||||
- a
|
||||
4 + a # ty:ignore[unresolved-reference]
|
||||
5 | }
|
||||
6 | more text
|
||||
7 | """
|
||||
2 | b = f"""
|
||||
3 | {
|
||||
- a
|
||||
4 + a # ty:ignore[unresolved-reference]
|
||||
5 | }
|
||||
6 | more text
|
||||
7 | """
|
||||
"#);
|
||||
}
|
||||
|
||||
@@ -403,19 +397,18 @@ mod tests {
|
||||
|
||||
assert_snapshot!(test.code_actions(&UNRESOLVED_REFERENCE), @r#"
|
||||
info[code-action]: Ignore 'unresolved-reference' for this line
|
||||
--> main.py:2:17
|
||||
--> main.py:2:5
|
||||
|
|
||||
2 | b = a + """
|
||||
| ^
|
||||
3 | more text
|
||||
4 | """
|
||||
2 | b = a + """
|
||||
| ^
|
||||
3 | more text
|
||||
4 | """
|
||||
|
|
||||
1 |
|
||||
2 | b = a + """
|
||||
3 | more text
|
||||
- """
|
||||
4 + """ # ty:ignore[unresolved-reference]
|
||||
5 |
|
||||
2 | b = a + """
|
||||
3 | more text
|
||||
- """
|
||||
4 + """ # ty:ignore[unresolved-reference]
|
||||
"#);
|
||||
}
|
||||
|
||||
@@ -430,17 +423,16 @@ mod tests {
|
||||
|
||||
assert_snapshot!(test.code_actions(&UNRESOLVED_REFERENCE), @r#"
|
||||
info[code-action]: Ignore 'unresolved-reference' for this line
|
||||
--> main.py:2:17
|
||||
--> main.py:2:5
|
||||
|
|
||||
2 | b = a \
|
||||
| ^
|
||||
3 | + "test"
|
||||
2 | b = a \
|
||||
| ^
|
||||
3 | + "test"
|
||||
|
|
||||
1 |
|
||||
2 | b = a \
|
||||
- + "test"
|
||||
3 + + "test" # ty:ignore[unresolved-reference]
|
||||
4 |
|
||||
2 | b = a \
|
||||
- + "test"
|
||||
3 + + "test" # ty:ignore[unresolved-reference]
|
||||
"#);
|
||||
}
|
||||
|
||||
@@ -454,27 +446,249 @@ mod tests {
|
||||
|
||||
assert_snapshot!(test.code_actions(&UNDEFINED_REVEAL), @r"
|
||||
info[code-action]: import typing.reveal_type
|
||||
--> main.py:2:13
|
||||
--> main.py:2:1
|
||||
|
|
||||
2 | reveal_type(1)
|
||||
| ^^^^^^^^^^^
|
||||
2 | reveal_type(1)
|
||||
| ^^^^^^^^^^^
|
||||
|
|
||||
help: This is a preferred code action
|
||||
1 + from typing import reveal_type
|
||||
2 |
|
||||
3 | reveal_type(1)
|
||||
4 |
|
||||
3 | reveal_type(1)
|
||||
|
||||
info[code-action]: Ignore 'undefined-reveal' for this line
|
||||
--> main.py:2:13
|
||||
--> main.py:2:1
|
||||
|
|
||||
2 | reveal_type(1)
|
||||
| ^^^^^^^^^^^
|
||||
2 | reveal_type(1)
|
||||
| ^^^^^^^^^^^
|
||||
|
|
||||
1 |
|
||||
- reveal_type(1)
|
||||
2 + reveal_type(1) # ty:ignore[undefined-reveal]
|
||||
- reveal_type(1)
|
||||
2 + reveal_type(1) # ty:ignore[undefined-reveal]
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unresolved_deprecated() {
|
||||
let test = CodeActionTest::with_source(
|
||||
r#"
|
||||
@<START>deprecated<END>("do not use")
|
||||
def my_func(): ...
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.code_actions(&UNRESOLVED_REFERENCE), @r#"
|
||||
info[code-action]: import warnings.deprecated
|
||||
--> main.py:2:2
|
||||
|
|
||||
2 | @deprecated("do not use")
|
||||
| ^^^^^^^^^^
|
||||
3 | def my_func(): ...
|
||||
|
|
||||
help: This is a preferred code action
|
||||
1 + from warnings import deprecated
|
||||
2 |
|
||||
3 | @deprecated("do not use")
|
||||
4 | def my_func(): ...
|
||||
|
||||
info[code-action]: Ignore 'unresolved-reference' for this line
|
||||
--> main.py:2:2
|
||||
|
|
||||
2 | @deprecated("do not use")
|
||||
| ^^^^^^^^^^
|
||||
3 | def my_func(): ...
|
||||
|
|
||||
1 |
|
||||
- @deprecated("do not use")
|
||||
2 + @deprecated("do not use") # ty:ignore[unresolved-reference]
|
||||
3 | def my_func(): ...
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unresolved_deprecated_warnings_imported() {
|
||||
let test = CodeActionTest::with_source(
|
||||
r#"
|
||||
import warnings
|
||||
|
||||
@<START>deprecated<END>("do not use")
|
||||
def my_func(): ...
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.code_actions(&UNRESOLVED_REFERENCE), @r#"
|
||||
info[code-action]: import warnings.deprecated
|
||||
--> main.py:4:2
|
||||
|
|
||||
2 | import warnings
|
||||
3 |
|
||||
4 | @deprecated("do not use")
|
||||
| ^^^^^^^^^^
|
||||
5 | def my_func(): ...
|
||||
|
|
||||
help: This is a preferred code action
|
||||
1 + from warnings import deprecated
|
||||
2 |
|
||||
3 | import warnings
|
||||
4 |
|
||||
|
||||
info[code-action]: qualify warnings.deprecated
|
||||
--> main.py:4:2
|
||||
|
|
||||
2 | import warnings
|
||||
3 |
|
||||
4 | @deprecated("do not use")
|
||||
| ^^^^^^^^^^
|
||||
5 | def my_func(): ...
|
||||
|
|
||||
help: This is a preferred code action
|
||||
1 |
|
||||
2 | import warnings
|
||||
3 |
|
||||
- @deprecated("do not use")
|
||||
4 + @warnings.deprecated("do not use")
|
||||
5 | def my_func(): ...
|
||||
|
||||
info[code-action]: Ignore 'unresolved-reference' for this line
|
||||
--> main.py:4:2
|
||||
|
|
||||
2 | import warnings
|
||||
3 |
|
||||
4 | @deprecated("do not use")
|
||||
| ^^^^^^^^^^
|
||||
5 | def my_func(): ...
|
||||
|
|
||||
1 |
|
||||
2 | import warnings
|
||||
3 |
|
||||
- @deprecated("do not use")
|
||||
4 + @deprecated("do not use") # ty:ignore[unresolved-reference]
|
||||
5 | def my_func(): ...
|
||||
"#);
|
||||
}
|
||||
|
||||
// using `importlib.abc.ExecutionLoader` when no imports are in scope
|
||||
#[test]
|
||||
fn unresolved_loader() {
|
||||
let test = CodeActionTest::with_source(
|
||||
r#"
|
||||
<START>ExecutionLoader<END>
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.code_actions(&UNRESOLVED_REFERENCE), @r"
|
||||
info[code-action]: import importlib.abc.ExecutionLoader
|
||||
--> main.py:2:1
|
||||
|
|
||||
2 | ExecutionLoader
|
||||
| ^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: This is a preferred code action
|
||||
1 + from importlib.abc import ExecutionLoader
|
||||
2 |
|
||||
3 | ExecutionLoader
|
||||
|
||||
info[code-action]: Ignore 'unresolved-reference' for this line
|
||||
--> main.py:2:1
|
||||
|
|
||||
2 | ExecutionLoader
|
||||
| ^^^^^^^^^^^^^^^
|
||||
|
|
||||
1 |
|
||||
- ExecutionLoader
|
||||
2 + ExecutionLoader # ty:ignore[unresolved-reference]
|
||||
");
|
||||
}
|
||||
|
||||
// using `importlib.abc.ExecutionLoader` when `import importlib` is in scope
|
||||
//
|
||||
// TODO: `importlib.abc` is available whenever `importlib` is, so qualifying
|
||||
// `importlib.abc.ExecutionLoader` without adding imports is actually legal here!
|
||||
#[test]
|
||||
fn unresolved_loader_importlib_imported() {
|
||||
let test = CodeActionTest::with_source(
|
||||
r#"
|
||||
import importlib
|
||||
<START>ExecutionLoader<END>
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.code_actions(&UNRESOLVED_REFERENCE), @r"
|
||||
info[code-action]: import importlib.abc.ExecutionLoader
|
||||
--> main.py:3:1
|
||||
|
|
||||
2 | import importlib
|
||||
3 | ExecutionLoader
|
||||
| ^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: This is a preferred code action
|
||||
1 + from importlib.abc import ExecutionLoader
|
||||
2 |
|
||||
3 | import importlib
|
||||
4 | ExecutionLoader
|
||||
|
||||
info[code-action]: Ignore 'unresolved-reference' for this line
|
||||
--> main.py:3:1
|
||||
|
|
||||
2 | import importlib
|
||||
3 | ExecutionLoader
|
||||
| ^^^^^^^^^^^^^^^
|
||||
|
|
||||
1 |
|
||||
2 | import importlib
|
||||
- ExecutionLoader
|
||||
3 + ExecutionLoader # ty:ignore[unresolved-reference]
|
||||
");
|
||||
}
|
||||
|
||||
// Using `importlib.abc.ExecutionLoader` when `import importlib.abc` is in scope
|
||||
#[test]
|
||||
fn unresolved_loader_abc_imported() {
|
||||
let test = CodeActionTest::with_source(
|
||||
r#"
|
||||
import importlib.abc
|
||||
<START>ExecutionLoader<END>
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.code_actions(&UNRESOLVED_REFERENCE), @r"
|
||||
info[code-action]: import importlib.abc.ExecutionLoader
|
||||
--> main.py:3:1
|
||||
|
|
||||
2 | import importlib.abc
|
||||
3 | ExecutionLoader
|
||||
| ^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: This is a preferred code action
|
||||
1 + from importlib.abc import ExecutionLoader
|
||||
2 |
|
||||
3 | import importlib.abc
|
||||
4 | ExecutionLoader
|
||||
|
||||
info[code-action]: qualify importlib.abc.ExecutionLoader
|
||||
--> main.py:3:1
|
||||
|
|
||||
2 | import importlib.abc
|
||||
3 | ExecutionLoader
|
||||
| ^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: This is a preferred code action
|
||||
1 |
|
||||
2 | import importlib.abc
|
||||
- ExecutionLoader
|
||||
3 + importlib.abc.ExecutionLoader
|
||||
|
||||
info[code-action]: Ignore 'unresolved-reference' for this line
|
||||
--> main.py:3:1
|
||||
|
|
||||
2 | import importlib.abc
|
||||
3 | ExecutionLoader
|
||||
| ^^^^^^^^^^^^^^^
|
||||
|
|
||||
1 |
|
||||
2 | import importlib.abc
|
||||
- ExecutionLoader
|
||||
3 + ExecutionLoader # ty:ignore[unresolved-reference]
|
||||
");
|
||||
}
|
||||
|
||||
@@ -493,7 +707,7 @@ mod tests {
|
||||
|
||||
db.init_program().unwrap();
|
||||
|
||||
let mut cleansed = source.to_string();
|
||||
let mut cleansed = dedent(source).to_string();
|
||||
|
||||
let start = cleansed
|
||||
.find("<START>")
|
||||
|
||||
@@ -67,6 +67,7 @@ impl<'db> Completions<'db> {
|
||||
self.items
|
||||
}
|
||||
|
||||
// Convert this collection into a list of "import..." fixes
|
||||
fn into_imports(mut self) -> Vec<ImportEdit> {
|
||||
self.items.sort_by(compare_suggestions);
|
||||
self.items
|
||||
@@ -82,6 +83,28 @@ impl<'db> Completions<'db> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
// Convert this collection into a list of "qualify..." fixes
|
||||
fn into_qualifications(mut self, range: TextRange) -> Vec<ImportEdit> {
|
||||
self.items.sort_by(compare_suggestions);
|
||||
self.items
|
||||
.dedup_by(|c1, c2| (&c1.name, c1.module_name) == (&c2.name, c2.module_name));
|
||||
self.items
|
||||
.into_iter()
|
||||
.filter_map(|item| {
|
||||
// If we would have to actually import something, don't suggest the qualification
|
||||
// (we could, maybe we should, but for now, we don't)
|
||||
if item.import.is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(ImportEdit {
|
||||
label: format!("qualify {}", item.insert.as_ref()?),
|
||||
edit: Edit::replacement(item.insert?.into_string(), range.start(), range.end()),
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Attempts to adds the given completion to this collection.
|
||||
///
|
||||
/// When added, `true` is returned.
|
||||
@@ -467,6 +490,17 @@ pub fn completion<'db>(
|
||||
!ty.is_notimplemented(db)
|
||||
});
|
||||
}
|
||||
if is_specifying_for_statement_iterable(&parsed, offset, typed.as_deref()) {
|
||||
// Remove all keywords that doesn't make sense given the context,
|
||||
// even if they are syntatically valid, e.g. `None`.
|
||||
completions.retain(|item| {
|
||||
let Some(kind) = item.kind else { return true };
|
||||
if kind != CompletionKind::Keyword {
|
||||
return true;
|
||||
}
|
||||
matches!(item.name.as_str(), "await" | "lambda" | "yield")
|
||||
});
|
||||
}
|
||||
completions.into_completions()
|
||||
}
|
||||
|
||||
@@ -481,6 +515,18 @@ fn detect_function_arg_completions<'db>(
|
||||
parsed: &ParsedModuleRef,
|
||||
offset: TextSize,
|
||||
) -> Option<Vec<Completion<'db>>> {
|
||||
// But be careful: this isn't as simple as just finding a call
|
||||
// expression. We also have to make sure we are in the "arguments"
|
||||
// portion of the call. Otherwise we risk incorrectly returning
|
||||
// something for `(<CURSOR>)(arg1, arg2)`-style expressions.
|
||||
if !covering_node(parsed.syntax().into(), TextRange::empty(offset))
|
||||
.ancestors()
|
||||
.take_while(|node| !node.is_statement())
|
||||
.any(|node| node.is_arguments())
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let sig_help = signature_help(db, file, offset)?;
|
||||
let set_function_args = detect_set_function_args(parsed, offset);
|
||||
|
||||
@@ -555,15 +601,19 @@ pub(crate) struct ImportEdit {
|
||||
pub edit: Edit,
|
||||
}
|
||||
|
||||
pub(crate) fn missing_imports(
|
||||
/// Get fixes that would resolve an unresolved reference
|
||||
pub(crate) fn unresolved_fixes(
|
||||
db: &dyn Db,
|
||||
file: File,
|
||||
parsed: &ParsedModuleRef,
|
||||
symbol: &str,
|
||||
node: AnyNodeRef,
|
||||
) -> Vec<ImportEdit> {
|
||||
let mut completions = Completions::exactly(db, symbol);
|
||||
let mut results = Vec::new();
|
||||
let scoped = ScopedTarget { node };
|
||||
|
||||
// Request imports we could add to put the symbol in scope
|
||||
let mut completions = Completions::exactly(db, symbol);
|
||||
add_unimported_completions(
|
||||
db,
|
||||
file,
|
||||
@@ -574,8 +624,23 @@ pub(crate) fn missing_imports(
|
||||
},
|
||||
&mut completions,
|
||||
);
|
||||
results.extend(completions.into_imports());
|
||||
|
||||
completions.into_imports()
|
||||
// Request qualifications we could apply to the symbol to make it resolve
|
||||
let mut completions = Completions::exactly(db, symbol);
|
||||
add_unimported_completions(
|
||||
db,
|
||||
file,
|
||||
parsed,
|
||||
scoped,
|
||||
|module_name: &ModuleName, symbol: &str| {
|
||||
ImportRequest::import(module_name.as_str(), symbol).force()
|
||||
},
|
||||
&mut completions,
|
||||
);
|
||||
results.extend(completions.into_qualifications(node.range()));
|
||||
|
||||
results
|
||||
}
|
||||
|
||||
/// Adds completions derived from keywords.
|
||||
@@ -1565,12 +1630,7 @@ fn is_in_definition_place(
|
||||
/// Returns true when the cursor sits on a binding statement.
|
||||
/// E.g. naming a parameter, type parameter, or `for` <name>).
|
||||
fn is_in_variable_binding(parsed: &ParsedModuleRef, offset: TextSize, typed: Option<&str>) -> bool {
|
||||
let range = if let Some(typed) = typed {
|
||||
let start = offset.saturating_sub(typed.text_len());
|
||||
TextRange::new(start, offset)
|
||||
} else {
|
||||
TextRange::empty(offset)
|
||||
};
|
||||
let range = typed_text_range(typed, offset);
|
||||
|
||||
let covering = covering_node(parsed.syntax().into(), range);
|
||||
covering.ancestors().any(|node| match node {
|
||||
@@ -1625,6 +1685,36 @@ fn is_raising_exception(tokens: &[Token]) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Returns true when the cursor is after the `in` keyword in a
|
||||
/// `for x in <CURSOR>` statement.
|
||||
fn is_specifying_for_statement_iterable(
|
||||
parsed: &ParsedModuleRef,
|
||||
offset: TextSize,
|
||||
typed: Option<&str>,
|
||||
) -> bool {
|
||||
let range = typed_text_range(typed, offset);
|
||||
|
||||
let covering = covering_node(parsed.syntax().into(), range);
|
||||
covering.parent().is_some_and(|node| {
|
||||
matches!(
|
||||
node, ast::AnyNodeRef::StmtFor(stmt_for) if stmt_for.iter.range().contains_range(range)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the `TextRange` of the `typed` text.
|
||||
///
|
||||
/// `typed` should be the text immediately before the
|
||||
/// provided cursor `offset`.
|
||||
fn typed_text_range(typed: Option<&str>, offset: TextSize) -> TextRange {
|
||||
if let Some(typed) = typed {
|
||||
let start = offset.saturating_sub(typed.text_len());
|
||||
TextRange::new(start, offset)
|
||||
} else {
|
||||
TextRange::empty(offset)
|
||||
}
|
||||
}
|
||||
|
||||
/// Order completions according to the following rules:
|
||||
///
|
||||
/// 1) Names with no underscore prefix
|
||||
@@ -2516,9 +2606,7 @@ def frob(): ...
|
||||
|
||||
assert_snapshot!(
|
||||
builder.skip_keywords().skip_builtins().build().snapshot(),
|
||||
@r"
|
||||
foo
|
||||
",
|
||||
@"<No completions found after filtering out completions>",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2532,9 +2620,7 @@ def frob(): ...
|
||||
|
||||
assert_snapshot!(
|
||||
builder.skip_keywords().skip_builtins().build().snapshot(),
|
||||
@r"
|
||||
foo
|
||||
",
|
||||
@"<No completions found after filtering out completions>",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3126,7 +3212,7 @@ quux.<CURSOR>
|
||||
assert_snapshot!(
|
||||
builder.skip_keywords().skip_builtins().type_signatures().build().snapshot(), @r"
|
||||
count :: bound method Quux.count(value: Any, /) -> int
|
||||
index :: bound method Quux.index(value: Any, start: SupportsIndex = Literal[0], stop: SupportsIndex = int, /) -> int
|
||||
index :: bound method Quux.index(value: Any, start: SupportsIndex = 0, stop: SupportsIndex = ..., /) -> int
|
||||
x :: int
|
||||
y :: str
|
||||
__add__ :: Overload[(value: tuple[int | str, ...], /) -> tuple[int | str, ...], (value: tuple[_T@__add__, ...], /) -> tuple[int | str | _T@__add__, ...]]
|
||||
@@ -3191,7 +3277,7 @@ bar(o<CURSOR>
|
||||
builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
|
||||
@r"
|
||||
foo
|
||||
okay
|
||||
okay=
|
||||
"
|
||||
);
|
||||
}
|
||||
@@ -3212,7 +3298,7 @@ bar(o<CURSOR>
|
||||
builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
|
||||
@r"
|
||||
foo
|
||||
okay
|
||||
okay=
|
||||
"
|
||||
);
|
||||
}
|
||||
@@ -3230,9 +3316,9 @@ foo(b<CURSOR>
|
||||
assert_snapshot!(
|
||||
builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
|
||||
@r"
|
||||
bar
|
||||
barbaz
|
||||
baz
|
||||
bar=
|
||||
barbaz=
|
||||
baz=
|
||||
"
|
||||
);
|
||||
}
|
||||
@@ -3249,9 +3335,7 @@ foo(bar=1, b<CURSOR>
|
||||
|
||||
assert_snapshot!(
|
||||
builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
|
||||
@r"
|
||||
baz
|
||||
"
|
||||
@"baz="
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3269,9 +3353,7 @@ abc(o<CURSOR>
|
||||
|
||||
assert_snapshot!(
|
||||
builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
|
||||
@r"
|
||||
okay
|
||||
"
|
||||
@"okay="
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3287,9 +3369,7 @@ abc(okay=1, ba<CURSOR> baz=5
|
||||
|
||||
assert_snapshot!(
|
||||
builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
|
||||
@r"
|
||||
bar
|
||||
"
|
||||
@"bar="
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3333,9 +3413,9 @@ bar(o<CURSOR>
|
||||
builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
|
||||
@r"
|
||||
foo
|
||||
okay
|
||||
okay_abc
|
||||
okay_okay
|
||||
okay=
|
||||
okay_abc=
|
||||
okay_okay=
|
||||
"
|
||||
);
|
||||
}
|
||||
@@ -3355,7 +3435,7 @@ bar(<CURSOR>
|
||||
assert_snapshot!(builder.skip_keywords().skip_builtins().build().snapshot(), @r"
|
||||
bar
|
||||
foo
|
||||
okay
|
||||
okay=
|
||||
");
|
||||
}
|
||||
|
||||
@@ -4711,8 +4791,7 @@ from os.<CURSOR>
|
||||
let last_nonunderscore = test
|
||||
.completions()
|
||||
.iter()
|
||||
.filter(|c| !c.name.starts_with('_'))
|
||||
.next_back()
|
||||
.rfind(|c| !c.name.starts_with('_'))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(&last_nonunderscore.name, "type_check_only");
|
||||
@@ -5824,6 +5903,62 @@ def foo(param: s<CURSOR>)
|
||||
.contains("str");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_statement_keywords_in_for_statement_simple1() {
|
||||
completion_test_builder(
|
||||
"\
|
||||
for x in a<CURSOR>
|
||||
",
|
||||
)
|
||||
.build()
|
||||
.contains("lambda")
|
||||
.contains("await")
|
||||
.not_contains("raise")
|
||||
.not_contains("False");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_statement_keywords_in_for_statement_simple2() {
|
||||
completion_test_builder(
|
||||
"\
|
||||
for x, y, _ in a<CURSOR>
|
||||
",
|
||||
)
|
||||
.build()
|
||||
.contains("lambda")
|
||||
.contains("await")
|
||||
.not_contains("raise")
|
||||
.not_contains("False");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_statement_keywords_in_for_statement_simple3() {
|
||||
completion_test_builder(
|
||||
"\
|
||||
for i, (x, y, z) in a<CURSOR>
|
||||
",
|
||||
)
|
||||
.build()
|
||||
.contains("lambda")
|
||||
.contains("await")
|
||||
.not_contains("raise")
|
||||
.not_contains("False");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_statement_keywords_in_for_statement_complex() {
|
||||
completion_test_builder(
|
||||
"\
|
||||
for i, (obj.x, (a[0], b['k']), _), *rest in a<CURSOR>
|
||||
",
|
||||
)
|
||||
.build()
|
||||
.contains("lambda")
|
||||
.contains("await")
|
||||
.not_contains("raise")
|
||||
.not_contains("False");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn favour_symbols_currently_imported() {
|
||||
let snapshot = CursorTest::builder()
|
||||
@@ -6582,6 +6717,27 @@ def f(zqzqzq: str):
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_import_prioritizes_reusing_import_from_statements() {
|
||||
let builder = completion_test_builder(
|
||||
"\
|
||||
import typing
|
||||
from typing import Callable
|
||||
TypedDi<CURSOR>
|
||||
",
|
||||
);
|
||||
assert_snapshot!(
|
||||
builder.imports().build().snapshot(),
|
||||
@r"
|
||||
TypedDict :: , TypedDict
|
||||
is_typeddict :: , is_typeddict
|
||||
_FilterConfigurationTypedDict :: from logging.config import _FilterConfigurationTypedDict
|
||||
|
||||
_FormatterConfigurationTypedDict :: from logging.config import _FormatterConfigurationTypedDict
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
/// A way to create a simple single-file (named `main.py`) completion test
|
||||
/// builder.
|
||||
///
|
||||
@@ -6607,6 +6763,7 @@ def f(zqzqzq: str):
|
||||
skip_builtins: bool,
|
||||
skip_keywords: bool,
|
||||
type_signatures: bool,
|
||||
imports: bool,
|
||||
module_names: bool,
|
||||
// This doesn't seem like a "very complex" type to me... ---AG
|
||||
#[allow(clippy::type_complexity)]
|
||||
@@ -6639,6 +6796,7 @@ def f(zqzqzq: str):
|
||||
original,
|
||||
filtered,
|
||||
type_signatures: self.type_signatures,
|
||||
imports: self.imports,
|
||||
module_names: self.module_names,
|
||||
}
|
||||
}
|
||||
@@ -6699,6 +6857,15 @@ def f(zqzqzq: str):
|
||||
self
|
||||
}
|
||||
|
||||
/// When set, include the import associated with the
|
||||
/// completion.
|
||||
///
|
||||
/// Not enabled by default.
|
||||
fn imports(mut self) -> CompletionTestBuilder {
|
||||
self.imports = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// When set, the module name for each symbol is included
|
||||
/// in the snapshot (if available).
|
||||
fn module_names(mut self) -> CompletionTestBuilder {
|
||||
@@ -6731,6 +6898,9 @@ def f(zqzqzq: str):
|
||||
/// Whether type signatures should be included in the snapshot
|
||||
/// generated by `CompletionTest::snapshot`.
|
||||
type_signatures: bool,
|
||||
/// Whether to show the import that will be inserted when this
|
||||
/// completion is selected.
|
||||
imports: bool,
|
||||
/// Whether module names should be included in the snapshot
|
||||
/// generated by `CompletionTest::snapshot`.
|
||||
module_names: bool,
|
||||
@@ -6752,7 +6922,7 @@ def f(zqzqzq: str):
|
||||
self.filtered
|
||||
.iter()
|
||||
.map(|c| {
|
||||
let mut snapshot = c.name.as_str().to_string();
|
||||
let mut snapshot = c.insert.as_deref().unwrap_or(c.name.as_str()).to_string();
|
||||
if self.type_signatures {
|
||||
let ty =
|
||||
c.ty.map(|ty| ty.display(self.db).to_string())
|
||||
@@ -6766,6 +6936,17 @@ def f(zqzqzq: str):
|
||||
.unwrap_or("<no import required>");
|
||||
snapshot = format!("{snapshot} :: {module_name}");
|
||||
}
|
||||
if self.imports {
|
||||
if let Some(ref edit) = c.import {
|
||||
if let Some(import) = edit.content() {
|
||||
snapshot = format!("{snapshot} :: {import}");
|
||||
} else {
|
||||
snapshot = format!("{snapshot} :: <import deletion>");
|
||||
}
|
||||
} else {
|
||||
snapshot = format!("{snapshot} :: <no import edit>");
|
||||
}
|
||||
}
|
||||
snapshot
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
@@ -6814,6 +6995,7 @@ def f(zqzqzq: str):
|
||||
skip_builtins: false,
|
||||
skip_keywords: false,
|
||||
type_signatures: false,
|
||||
imports: false,
|
||||
module_names: false,
|
||||
predicate: None,
|
||||
}
|
||||
|
||||
@@ -151,14 +151,19 @@ impl fmt::Display for DisplayHoverContent<'_, '_> {
|
||||
Some(TypeVarVariance::Bivariant) => " (bivariant)",
|
||||
None => "",
|
||||
};
|
||||
|
||||
// Special types like `<special-form of whatever 'blahblah' with 'florps'>`
|
||||
// render poorly with python syntax-highlighting but well as xml
|
||||
let ty_string = ty
|
||||
.display_with(self.db, DisplaySettings::default().multiline())
|
||||
.to_string();
|
||||
let syntax = if ty_string.starts_with('<') {
|
||||
"xml"
|
||||
} else {
|
||||
"python"
|
||||
};
|
||||
self.kind
|
||||
.fenced_code_block(
|
||||
format!(
|
||||
"{}{variance}",
|
||||
ty.display_with(self.db, DisplaySettings::default().multiline())
|
||||
),
|
||||
"python",
|
||||
)
|
||||
.fenced_code_block(format!("{ty_string}{variance}"), syntax)
|
||||
.fmt(f)
|
||||
}
|
||||
HoverContent::Docstring(docstring) => docstring.render(self.kind).fmt(f),
|
||||
@@ -358,7 +363,7 @@ mod tests {
|
||||
Everyone loves my class!!
|
||||
|
||||
---------------------------------------------
|
||||
```python
|
||||
```xml
|
||||
<class 'MyClass'>
|
||||
```
|
||||
---
|
||||
@@ -420,7 +425,7 @@ mod tests {
|
||||
Everyone loves my class!!
|
||||
|
||||
---------------------------------------------
|
||||
```python
|
||||
```xml
|
||||
<class 'MyClass'>
|
||||
```
|
||||
---
|
||||
@@ -480,7 +485,7 @@ mod tests {
|
||||
initializes MyClass (perfectly)
|
||||
|
||||
---------------------------------------------
|
||||
```python
|
||||
```xml
|
||||
<class 'MyClass'>
|
||||
```
|
||||
---
|
||||
@@ -536,7 +541,7 @@ mod tests {
|
||||
initializes MyClass (perfectly)
|
||||
|
||||
---------------------------------------------
|
||||
```python
|
||||
```xml
|
||||
<class 'MyClass'>
|
||||
```
|
||||
---
|
||||
@@ -595,7 +600,7 @@ mod tests {
|
||||
Everyone loves my class!!
|
||||
|
||||
---------------------------------------------
|
||||
```python
|
||||
```xml
|
||||
<class 'MyClass'>
|
||||
```
|
||||
---
|
||||
@@ -1680,7 +1685,7 @@ def ab(a: int, *, c: int):
|
||||
Wow this module rocks.
|
||||
|
||||
---------------------------------------------
|
||||
```python
|
||||
```xml
|
||||
<module 'lib'>
|
||||
```
|
||||
---
|
||||
@@ -2029,7 +2034,7 @@ def function():
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
<class 'Click'>
|
||||
---------------------------------------------
|
||||
```python
|
||||
```xml
|
||||
<class 'Click'>
|
||||
```
|
||||
---------------------------------------------
|
||||
@@ -2234,7 +2239,7 @@ def function():
|
||||
Wow this module rocks.
|
||||
|
||||
---------------------------------------------
|
||||
```python
|
||||
```xml
|
||||
<module 'lib'>
|
||||
```
|
||||
---
|
||||
@@ -3057,10 +3062,10 @@ def function():
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
typing.TypeVar
|
||||
TypeVar
|
||||
---------------------------------------------
|
||||
```python
|
||||
typing.TypeVar
|
||||
TypeVar
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
@@ -3120,10 +3125,10 @@ def function():
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
typing.TypeVar
|
||||
TypeVar
|
||||
---------------------------------------------
|
||||
```python
|
||||
typing.TypeVar
|
||||
TypeVar
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
@@ -3343,7 +3348,7 @@ def function():
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
<module 'mypackage.subpkg'>
|
||||
---------------------------------------------
|
||||
```python
|
||||
```xml
|
||||
<module 'mypackage.subpkg'>
|
||||
```
|
||||
---------------------------------------------
|
||||
@@ -3385,7 +3390,7 @@ def function():
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
<module 'mypackage.subpkg'>
|
||||
---------------------------------------------
|
||||
```python
|
||||
```xml
|
||||
<module 'mypackage.subpkg'>
|
||||
```
|
||||
---------------------------------------------
|
||||
@@ -3469,7 +3474,7 @@ def function():
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
<module 'mypackage.subpkg.submod'>
|
||||
---------------------------------------------
|
||||
```python
|
||||
```xml
|
||||
<module 'mypackage.subpkg.submod'>
|
||||
```
|
||||
---------------------------------------------
|
||||
@@ -3510,7 +3515,7 @@ def function():
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
<module 'mypackage.subpkg'>
|
||||
---------------------------------------------
|
||||
```python
|
||||
```xml
|
||||
<module 'mypackage.subpkg'>
|
||||
```
|
||||
---------------------------------------------
|
||||
|
||||
@@ -745,8 +745,17 @@ impl ImportResponseKind<'_> {
|
||||
fn priority(&self) -> usize {
|
||||
match *self {
|
||||
ImportResponseKind::Unqualified { .. } => 0,
|
||||
ImportResponseKind::Qualified { .. } => 1,
|
||||
ImportResponseKind::Partial(_) => 2,
|
||||
ImportResponseKind::Partial(_) => 1,
|
||||
// N.B. When given the choice between adding a
|
||||
// name to an existing `from ... import ...`
|
||||
// statement and using an existing `import ...`
|
||||
// in a qualified manner, we currently choose
|
||||
// the former. Originally we preferred qualification,
|
||||
// but there is some evidence that this violates
|
||||
// expectations.
|
||||
//
|
||||
// Ref: https://github.com/astral-sh/ty/issues/1274#issuecomment-3352233790
|
||||
ImportResponseKind::Qualified { .. } => 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1332,9 +1341,9 @@ import collections
|
||||
);
|
||||
assert_snapshot!(
|
||||
test.import("collections", "defaultdict"), @r"
|
||||
from collections import OrderedDict
|
||||
from collections import OrderedDict, defaultdict
|
||||
import collections
|
||||
collections.defaultdict
|
||||
defaultdict
|
||||
");
|
||||
}
|
||||
|
||||
|
||||
@@ -6216,7 +6216,7 @@ mod tests {
|
||||
assert_snapshot!(test.inlay_hints(), @r#"
|
||||
from typing import Literal
|
||||
|
||||
a[: <special form 'Literal["a", "b", "c"]'>] = Literal['a', 'b', 'c']
|
||||
a[: <special-form 'Literal["a", "b", "c"]'>] = Literal['a', 'b', 'c']
|
||||
---------------------------------------------
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/typing.pyi:351:1
|
||||
@@ -6232,7 +6232,7 @@ mod tests {
|
||||
|
|
||||
2 | from typing import Literal
|
||||
3 |
|
||||
4 | a[: <special form 'Literal["a", "b", "c"]'>] = Literal['a', 'b', 'c']
|
||||
4 | a[: <special-form 'Literal["a", "b", "c"]'>] = Literal['a', 'b', 'c']
|
||||
| ^^^^^^^
|
||||
|
|
||||
|
||||
@@ -6250,7 +6250,7 @@ mod tests {
|
||||
|
|
||||
2 | from typing import Literal
|
||||
3 |
|
||||
4 | a[: <special form 'Literal["a", "b", "c"]'>] = Literal['a', 'b', 'c']
|
||||
4 | a[: <special-form 'Literal["a", "b", "c"]'>] = Literal['a', 'b', 'c']
|
||||
| ^^^
|
||||
|
|
||||
|
||||
@@ -6268,7 +6268,7 @@ mod tests {
|
||||
|
|
||||
2 | from typing import Literal
|
||||
3 |
|
||||
4 | a[: <special form 'Literal["a", "b", "c"]'>] = Literal['a', 'b', 'c']
|
||||
4 | a[: <special-form 'Literal["a", "b", "c"]'>] = Literal['a', 'b', 'c']
|
||||
| ^^^
|
||||
|
|
||||
|
||||
@@ -6286,7 +6286,7 @@ mod tests {
|
||||
|
|
||||
2 | from typing import Literal
|
||||
3 |
|
||||
4 | a[: <special form 'Literal["a", "b", "c"]'>] = Literal['a', 'b', 'c']
|
||||
4 | a[: <special-form 'Literal["a", "b", "c"]'>] = Literal['a', 'b', 'c']
|
||||
| ^^^
|
||||
|
|
||||
"#);
|
||||
@@ -6635,26 +6635,9 @@ mod tests {
|
||||
|
||||
assert_snapshot!(test.inlay_hints(), @r"
|
||||
from typing import Protocol, TypeVar
|
||||
T[: typing.TypeVar] = TypeVar([name=]'T')
|
||||
Strange[: <special form 'typing.Protocol[T]'>] = Protocol[T]
|
||||
T = TypeVar([name=]'T')
|
||||
Strange[: <special-form 'typing.Protocol[T]'>] = Protocol[T]
|
||||
---------------------------------------------
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> main.py:3:1
|
||||
|
|
||||
2 | from typing import Protocol, TypeVar
|
||||
3 | T = TypeVar('T')
|
||||
| ^
|
||||
4 | Strange = Protocol[T]
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:3:5
|
||||
|
|
||||
2 | from typing import Protocol, TypeVar
|
||||
3 | T[: typing.TypeVar] = TypeVar([name=]'T')
|
||||
| ^^^^^^^^^^^^^^
|
||||
4 | Strange[: <special form 'typing.Protocol[T]'>] = Protocol[T]
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/typing.pyi:276:13
|
||||
|
|
||||
@@ -6666,12 +6649,12 @@ mod tests {
|
||||
278 | bound: Any | None = None, # AnnotationForm
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:3:32
|
||||
--> main2.py:3:14
|
||||
|
|
||||
2 | from typing import Protocol, TypeVar
|
||||
3 | T[: typing.TypeVar] = TypeVar([name=]'T')
|
||||
| ^^^^
|
||||
4 | Strange[: <special form 'typing.Protocol[T]'>] = Protocol[T]
|
||||
3 | T = TypeVar([name=]'T')
|
||||
| ^^^^
|
||||
4 | Strange[: <special-form 'typing.Protocol[T]'>] = Protocol[T]
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
@@ -6687,8 +6670,8 @@ mod tests {
|
||||
--> main2.py:4:26
|
||||
|
|
||||
2 | from typing import Protocol, TypeVar
|
||||
3 | T[: typing.TypeVar] = TypeVar([name=]'T')
|
||||
4 | Strange[: <special form 'typing.Protocol[T]'>] = Protocol[T]
|
||||
3 | T = TypeVar([name=]'T')
|
||||
4 | Strange[: <special-form 'typing.Protocol[T]'>] = Protocol[T]
|
||||
| ^^^^^^^^^^^^^^^
|
||||
|
|
||||
|
||||
@@ -6704,18 +6687,124 @@ mod tests {
|
||||
--> main2.py:4:42
|
||||
|
|
||||
2 | from typing import Protocol, TypeVar
|
||||
3 | T[: typing.TypeVar] = TypeVar([name=]'T')
|
||||
4 | Strange[: <special form 'typing.Protocol[T]'>] = Protocol[T]
|
||||
3 | T = TypeVar([name=]'T')
|
||||
4 | Strange[: <special-form 'typing.Protocol[T]'>] = Protocol[T]
|
||||
| ^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_paramspec_creation_inlay_hint() {
|
||||
let mut test = inlay_hint_test(
|
||||
"
|
||||
from typing import ParamSpec
|
||||
P = ParamSpec('P')",
|
||||
);
|
||||
|
||||
assert_snapshot!(test.inlay_hints(), @r"
|
||||
from typing import ParamSpec
|
||||
P = ParamSpec([name=]'P')
|
||||
---------------------------------------------
|
||||
info[inlay-hint-edit]: File after edits
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/typing.pyi:552:17
|
||||
|
|
||||
550 | def __new__(
|
||||
551 | cls,
|
||||
552 | name: str,
|
||||
| ^^^^
|
||||
553 | *,
|
||||
554 | bound: Any | None = None, # AnnotationForm
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:3:16
|
||||
|
|
||||
2 | from typing import ParamSpec
|
||||
3 | P = ParamSpec([name=]'P')
|
||||
| ^^^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
from typing import Protocol, TypeVar
|
||||
T: typing.TypeVar = TypeVar('T')
|
||||
Strange = Protocol[T]
|
||||
#[test]
|
||||
fn test_typealiastype_creation_inlay_hint() {
|
||||
let mut test = inlay_hint_test(
|
||||
"
|
||||
from typing_extensions import TypeAliasType
|
||||
A = TypeAliasType('A', str)",
|
||||
);
|
||||
|
||||
assert_snapshot!(test.inlay_hints(), @r#"
|
||||
from typing_extensions import TypeAliasType
|
||||
A = TypeAliasType([name=]'A', [value=]str)
|
||||
---------------------------------------------
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/typing.pyi:2032:26
|
||||
|
|
||||
2030 | """
|
||||
2031 |
|
||||
2032 | def __new__(cls, name: str, value: Any, *, type_params: tuple[_TypeParameter, ...] = ()) -> Self: ...
|
||||
| ^^^^
|
||||
2033 | @property
|
||||
2034 | def __value__(self) -> Any: ... # AnnotationForm
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:3:20
|
||||
|
|
||||
2 | from typing_extensions import TypeAliasType
|
||||
3 | A = TypeAliasType([name=]'A', [value=]str)
|
||||
| ^^^^
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/typing.pyi:2032:37
|
||||
|
|
||||
2030 | """
|
||||
2031 |
|
||||
2032 | def __new__(cls, name: str, value: Any, *, type_params: tuple[_TypeParameter, ...] = ()) -> Self: ...
|
||||
| ^^^^^
|
||||
2033 | @property
|
||||
2034 | def __value__(self) -> Any: ... # AnnotationForm
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:3:32
|
||||
|
|
||||
2 | from typing_extensions import TypeAliasType
|
||||
3 | A = TypeAliasType([name=]'A', [value=]str)
|
||||
| ^^^^^
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_typevartuple_creation_inlay_hint() {
|
||||
let mut test = inlay_hint_test(
|
||||
"
|
||||
from typing_extensions import TypeVarTuple
|
||||
Ts = TypeVarTuple('Ts')",
|
||||
);
|
||||
|
||||
assert_snapshot!(test.inlay_hints(), @r"
|
||||
from typing_extensions import TypeVarTuple
|
||||
Ts = TypeVarTuple([name=]'Ts')
|
||||
---------------------------------------------
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/typing.pyi:412:30
|
||||
|
|
||||
410 | def has_default(self) -> bool: ...
|
||||
411 | if sys.version_info >= (3, 13):
|
||||
412 | def __new__(cls, name: str, *, default: Any = ...) -> Self: ... # AnnotationForm
|
||||
| ^^^^
|
||||
413 | elif sys.version_info >= (3, 12):
|
||||
414 | def __new__(cls, name: str) -> Self: ...
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:3:20
|
||||
|
|
||||
2 | from typing_extensions import TypeVarTuple
|
||||
3 | Ts = TypeVarTuple([name=]'Ts')
|
||||
| ^^^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
|
||||
@@ -254,7 +254,9 @@ impl<'db> SemanticTokenVisitor<'db> {
|
||||
}
|
||||
|
||||
fn is_constant_name(name: &str) -> bool {
|
||||
name.chars().all(|c| c.is_uppercase() || c == '_') && name.len() > 1
|
||||
name.chars()
|
||||
.all(|c| c.is_uppercase() || c == '_' || c.is_numeric())
|
||||
&& name.len() > 1
|
||||
}
|
||||
|
||||
fn classify_name(&self, name: &ast::ExprName) -> (SemanticTokenType, SemanticTokenModifier) {
|
||||
@@ -2230,6 +2232,49 @@ class MyClass:
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_constant_variations() {
|
||||
let test = SemanticTokenTest::new(
|
||||
r#"
|
||||
A = 1
|
||||
AB = 1
|
||||
ABC = 1
|
||||
A1 = 1
|
||||
AB1 = 1
|
||||
ABC1 = 1
|
||||
A_B = 1
|
||||
A1_B = 1
|
||||
A_B1 = 1
|
||||
A_1 = 1
|
||||
"#,
|
||||
);
|
||||
|
||||
let tokens = test.highlight_file();
|
||||
|
||||
assert_snapshot!(test.to_snapshot(&tokens), @r#"
|
||||
"A" @ 1..2: Variable [definition]
|
||||
"1" @ 5..6: Number
|
||||
"AB" @ 7..9: Variable [definition, readonly]
|
||||
"1" @ 12..13: Number
|
||||
"ABC" @ 14..17: Variable [definition, readonly]
|
||||
"1" @ 20..21: Number
|
||||
"A1" @ 22..24: Variable [definition, readonly]
|
||||
"1" @ 27..28: Number
|
||||
"AB1" @ 29..32: Variable [definition, readonly]
|
||||
"1" @ 35..36: Number
|
||||
"ABC1" @ 37..41: Variable [definition, readonly]
|
||||
"1" @ 44..45: Number
|
||||
"A_B" @ 46..49: Variable [definition, readonly]
|
||||
"1" @ 52..53: Number
|
||||
"A1_B" @ 54..58: Variable [definition, readonly]
|
||||
"1" @ 61..62: Number
|
||||
"A_B1" @ 63..67: Variable [definition, readonly]
|
||||
"1" @ 70..71: Number
|
||||
"A_1" @ 72..75: Variable [definition, readonly]
|
||||
"1" @ 78..79: Number
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_implicitly_concatenated_strings() {
|
||||
let test = SemanticTokenTest::new(
|
||||
|
||||
@@ -124,6 +124,11 @@ fn get_call_expr(
|
||||
})?;
|
||||
|
||||
// Find the covering node at the given position that is a function call.
|
||||
// Note that we are okay with the range being anywhere within a call
|
||||
// expression, even if it's not in the arguments portion of the call
|
||||
// expression. This is because, e.g., a user can request signature
|
||||
// information at a call site, and this should ideally work anywhere
|
||||
// within the call site, even at the function name.
|
||||
let call = covering_node(root_node, token.range())
|
||||
.find_first(|node| {
|
||||
if !node.is_expr_call() {
|
||||
|
||||
@@ -27,7 +27,6 @@ use std::iter::FusedIterator;
|
||||
use std::panic::{AssertUnwindSafe, UnwindSafe};
|
||||
use std::sync::Arc;
|
||||
use thiserror::Error;
|
||||
use tracing::error;
|
||||
use ty_python_semantic::add_inferred_python_version_hint_to_diagnostic;
|
||||
use ty_python_semantic::lint::RuleSelection;
|
||||
use ty_python_semantic::types::check_types;
|
||||
|
||||
@@ -1225,24 +1225,22 @@ pub struct TerminalOptions {
|
||||
///
|
||||
/// An override allows you to apply different rule configurations to specific
|
||||
/// files or directories. Multiple overrides can match the same file, with
|
||||
/// later overrides take precedence.
|
||||
/// later overrides take precedence. Override rules take precedence over global
|
||||
/// rules for matching files.
|
||||
///
|
||||
/// ### Precedence
|
||||
///
|
||||
/// - Later overrides in the array take precedence over earlier ones
|
||||
/// - Override rules take precedence over global rules for matching files
|
||||
///
|
||||
/// ### Examples
|
||||
/// For example, to relax enforcement of rules in test files:
|
||||
///
|
||||
/// ```toml
|
||||
/// # Relax rules for test files
|
||||
/// [[tool.ty.overrides]]
|
||||
/// include = ["tests/**", "**/test_*.py"]
|
||||
///
|
||||
/// [tool.ty.overrides.rules]
|
||||
/// possibly-unresolved-reference = "warn"
|
||||
/// ```
|
||||
///
|
||||
/// # Ignore generated files but still check important ones
|
||||
/// Or, to ignore a rule in generated files but retain enforcement in an important file:
|
||||
///
|
||||
/// ```toml
|
||||
/// [[tool.ty.overrides]]
|
||||
/// include = ["generated/**"]
|
||||
/// exclude = ["generated/important.py"]
|
||||
|
||||
@@ -169,13 +169,13 @@ def f(x: Any[int]):
|
||||
`Any` cannot be called (this leads to a `TypeError` at runtime):
|
||||
|
||||
```py
|
||||
Any() # error: [call-non-callable] "Object of type `<special form 'typing.Any'>` is not callable"
|
||||
Any() # error: [call-non-callable] "Object of type `<special-form 'typing.Any'>` is not callable"
|
||||
```
|
||||
|
||||
`Any` also cannot be used as a metaclass (under the hood, this leads to an implicit call to `Any`):
|
||||
|
||||
```py
|
||||
class F(metaclass=Any): ... # error: [invalid-metaclass] "Metaclass type `<special form 'typing.Any'>` is not callable"
|
||||
class F(metaclass=Any): ... # error: [invalid-metaclass] "Metaclass type `<special-form 'typing.Any'>` is not callable"
|
||||
```
|
||||
|
||||
And `Any` cannot be used in `isinstance()` checks:
|
||||
|
||||
@@ -407,4 +407,22 @@ def f_okay(c: Callable[[], None]):
|
||||
c.__qualname__ = "my_callable" # okay
|
||||
```
|
||||
|
||||
## From a class
|
||||
|
||||
### Subclasses should return themselves, not superclass
|
||||
|
||||
```py
|
||||
from ty_extensions import into_callable
|
||||
|
||||
class Base:
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
class A(Base):
|
||||
pass
|
||||
|
||||
# revealed: () -> A
|
||||
reveal_type(into_callable(A))
|
||||
```
|
||||
|
||||
[gradual form]: https://typing.python.org/en/latest/spec/glossary.html#term-gradual-form
|
||||
|
||||
@@ -59,7 +59,7 @@ python-version = "3.11"
|
||||
```py
|
||||
from typing import Never
|
||||
|
||||
reveal_type(Never) # revealed: <special form 'typing.Never'>
|
||||
reveal_type(Never) # revealed: <special-form 'typing.Never'>
|
||||
```
|
||||
|
||||
### Python 3.10
|
||||
|
||||
@@ -194,7 +194,7 @@ reveal_type(B().name_does_not_matter()) # revealed: B
|
||||
reveal_type(B().positional_only(1)) # revealed: B
|
||||
reveal_type(B().keyword_only(x=1)) # revealed: B
|
||||
# TODO: This should deally be `B`
|
||||
reveal_type(B().decorated_method()) # revealed: Unknown
|
||||
reveal_type(B().decorated_method()) # revealed: Self@decorated_method
|
||||
|
||||
reveal_type(B().a_property) # revealed: B
|
||||
|
||||
|
||||
@@ -152,6 +152,20 @@ The expressions in these string annotations aren't valid expressions in this con
|
||||
shouldn't panic.
|
||||
|
||||
```py
|
||||
# Regression test for https://github.com/astral-sh/ty/issues/1865
|
||||
# error: [fstring-type-annotation]
|
||||
stringified_fstring_with_conditional: "f'{1 if 1 else 1}'"
|
||||
# error: [fstring-type-annotation]
|
||||
stringified_fstring_with_boolean_expression: "f'{1 or 2}'"
|
||||
# error: [fstring-type-annotation]
|
||||
stringified_fstring_with_generator_expression: "f'{(i for i in range(5))}'"
|
||||
# error: [fstring-type-annotation]
|
||||
stringified_fstring_with_list_comprehension: "f'{[i for i in range(5)]}'"
|
||||
# error: [fstring-type-annotation]
|
||||
stringified_fstring_with_dict_comprehension: "f'{ {i: i for i in range(5)} }'"
|
||||
# error: [fstring-type-annotation]
|
||||
stringified_fstring_with_set_comprehension: "f'{ {i for i in range(5)} }'"
|
||||
|
||||
a: "1 or 2"
|
||||
b: "(x := 1)"
|
||||
# error: [invalid-type-form]
|
||||
|
||||
@@ -43,9 +43,7 @@ async def main():
|
||||
loop = asyncio.get_event_loop()
|
||||
with concurrent.futures.ThreadPoolExecutor() as pool:
|
||||
result = await loop.run_in_executor(pool, blocking_function)
|
||||
|
||||
# TODO: should be `int`
|
||||
reveal_type(result) # revealed: Unknown
|
||||
reveal_type(result) # revealed: int
|
||||
```
|
||||
|
||||
### `asyncio.Task`
|
||||
|
||||
@@ -13,7 +13,7 @@ python-version = "3.10"
|
||||
class A: ...
|
||||
class B: ...
|
||||
|
||||
reveal_type(A | B) # revealed: <types.UnionType special form 'A | B'>
|
||||
reveal_type(A | B) # revealed: <types.UnionType special-form 'A | B'>
|
||||
```
|
||||
|
||||
## Union of two classes (prior to 3.10)
|
||||
@@ -43,14 +43,14 @@ class A: ...
|
||||
class B: ...
|
||||
|
||||
def _(sub_a: type[A], sub_b: type[B]):
|
||||
reveal_type(A | sub_b) # revealed: <types.UnionType special form>
|
||||
reveal_type(sub_a | B) # revealed: <types.UnionType special form>
|
||||
reveal_type(sub_a | sub_b) # revealed: <types.UnionType special form>
|
||||
reveal_type(A | sub_b) # revealed: <types.UnionType special-form>
|
||||
reveal_type(sub_a | B) # revealed: <types.UnionType special-form>
|
||||
reveal_type(sub_a | sub_b) # revealed: <types.UnionType special-form>
|
||||
|
||||
class C[T]: ...
|
||||
class D[T]: ...
|
||||
|
||||
reveal_type(C | D) # revealed: <types.UnionType special form 'C[Unknown] | D[Unknown]'>
|
||||
reveal_type(C | D) # revealed: <types.UnionType special-form 'C[Unknown] | D[Unknown]'>
|
||||
|
||||
reveal_type(C[int] | D[str]) # revealed: <types.UnionType special form 'C[int] | D[str]'>
|
||||
reveal_type(C[int] | D[str]) # revealed: <types.UnionType special-form 'C[int] | D[str]'>
|
||||
```
|
||||
|
||||
@@ -114,6 +114,7 @@ but fall back to `bool` otherwise.
|
||||
```py
|
||||
from enum import Enum
|
||||
from types import FunctionType
|
||||
from typing import TypeVar
|
||||
|
||||
class Answer(Enum):
|
||||
NO = 0
|
||||
@@ -137,6 +138,7 @@ reveal_type(isinstance("", int)) # revealed: bool
|
||||
|
||||
class A: ...
|
||||
class SubclassOfA(A): ...
|
||||
class OtherSubclassOfA(A): ...
|
||||
class B: ...
|
||||
|
||||
reveal_type(isinstance(A, type)) # revealed: Literal[True]
|
||||
@@ -161,6 +163,29 @@ def _(x: A | B, y: list[int]):
|
||||
else:
|
||||
reveal_type(x) # revealed: B & ~A
|
||||
reveal_type(isinstance(x, B)) # revealed: Literal[True]
|
||||
|
||||
T = TypeVar("T")
|
||||
T_bound_A = TypeVar("T_bound_A", bound=A)
|
||||
T_constrained = TypeVar("T_constrained", SubclassOfA, OtherSubclassOfA)
|
||||
|
||||
def _(
|
||||
x: T,
|
||||
x_bound_a: T_bound_A,
|
||||
x_constrained_sub_a: T_constrained,
|
||||
):
|
||||
reveal_type(isinstance(x, object)) # revealed: Literal[True]
|
||||
reveal_type(isinstance(x, A)) # revealed: bool
|
||||
|
||||
reveal_type(isinstance(x_bound_a, object)) # revealed: Literal[True]
|
||||
reveal_type(isinstance(x_bound_a, A)) # revealed: Literal[True]
|
||||
reveal_type(isinstance(x_bound_a, SubclassOfA)) # revealed: bool
|
||||
reveal_type(isinstance(x_bound_a, B)) # revealed: bool
|
||||
|
||||
reveal_type(isinstance(x_constrained_sub_a, object)) # revealed: Literal[True]
|
||||
reveal_type(isinstance(x_constrained_sub_a, A)) # revealed: Literal[True]
|
||||
reveal_type(isinstance(x_constrained_sub_a, SubclassOfA)) # revealed: bool
|
||||
reveal_type(isinstance(x_constrained_sub_a, OtherSubclassOfA)) # revealed: bool
|
||||
reveal_type(isinstance(x_constrained_sub_a, B)) # revealed: bool
|
||||
```
|
||||
|
||||
Certain special forms in the typing module are not instances of `type`, so are strictly-speaking
|
||||
|
||||
@@ -113,9 +113,7 @@ intention that it shouldn't influence the method's descriptor behavior. For exam
|
||||
```py
|
||||
from typing import Callable
|
||||
|
||||
# TODO: this could use a generic signature, but we don't support
|
||||
# `ParamSpec` and solving of typevars inside `Callable` types yet.
|
||||
def memoize(f: Callable[[C1, int], str]) -> Callable[[C1, int], str]:
|
||||
def memoize[**P, R](f: Callable[P, R]) -> Callable[P, R]:
|
||||
raise NotImplementedError
|
||||
|
||||
class C1:
|
||||
@@ -259,4 +257,37 @@ reveal_type(MyClass().method) # revealed: (...) -> int
|
||||
reveal_type(MyClass().method.__name__) # revealed: str
|
||||
```
|
||||
|
||||
## classmethods passed through Callable-returning decorators
|
||||
|
||||
The behavior described above is also applied to classmethods. If a method is decorated with
|
||||
`@classmethod`, and also with another decorator which returns a Callable type, we make the
|
||||
assumption that the decorator returns a callable which still has the classmethod descriptor
|
||||
behavior.
|
||||
|
||||
```py
|
||||
from typing import Callable
|
||||
|
||||
def callable_identity[**P, R](func: Callable[P, R]) -> Callable[P, R]:
|
||||
return func
|
||||
|
||||
class C:
|
||||
@callable_identity
|
||||
@classmethod
|
||||
def f1(cls, x: int) -> str:
|
||||
return "a"
|
||||
|
||||
@classmethod
|
||||
@callable_identity
|
||||
def f2(cls, x: int) -> str:
|
||||
return "a"
|
||||
|
||||
# error: [too-many-positional-arguments]
|
||||
# error: [invalid-argument-type]
|
||||
C.f1(C, 1)
|
||||
C.f1(1)
|
||||
C().f1(1)
|
||||
C.f2(1)
|
||||
C().f2(1)
|
||||
```
|
||||
|
||||
[`tensorbase`]: https://github.com/pytorch/pytorch/blob/f3913ea641d871f04fa2b6588a77f63efeeb9f10/torch/_tensor.py#L1084-L1092
|
||||
|
||||
@@ -57,7 +57,7 @@ We can access attributes on objects of all kinds:
|
||||
import sys
|
||||
|
||||
reveal_type(inspect.getattr_static(sys, "dont_write_bytecode")) # revealed: bool
|
||||
# revealed: def getattr_static(obj: object, attr: str, default: Any | None = EllipsisType) -> Any
|
||||
# revealed: def getattr_static(obj: object, attr: str, default: Any | None = ...) -> Any
|
||||
reveal_type(inspect.getattr_static(inspect, "getattr_static"))
|
||||
|
||||
reveal_type(inspect.getattr_static(1, "real")) # revealed: property
|
||||
@@ -144,7 +144,7 @@ from typing import Any
|
||||
def _(a: Any, tuple_of_any: tuple[Any]):
|
||||
reveal_type(inspect.getattr_static(a, "x", "default")) # revealed: Any | Literal["default"]
|
||||
|
||||
# revealed: def index(self, value: Any, start: SupportsIndex = Literal[0], stop: SupportsIndex = int, /) -> int
|
||||
# revealed: def index(self, value: Any, start: SupportsIndex = 0, stop: SupportsIndex = ..., /) -> int
|
||||
reveal_type(inspect.getattr_static(tuple_of_any, "index", "default"))
|
||||
```
|
||||
|
||||
|
||||
@@ -201,7 +201,7 @@ python-version = "3.12"
|
||||
```py
|
||||
type IntOrStr = int | str
|
||||
|
||||
reveal_type(IntOrStr.__or__) # revealed: bound method typing.TypeAliasType.__or__(right: Any, /) -> _SpecialForm
|
||||
reveal_type(IntOrStr.__or__) # revealed: bound method TypeAliasType.__or__(right: Any, /) -> _SpecialForm
|
||||
```
|
||||
|
||||
## Method calls on types not disjoint from `None`
|
||||
@@ -444,20 +444,18 @@ When a `@classmethod` is additionally decorated with another decorator, it is st
|
||||
class method:
|
||||
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
def does_nothing[T](f: T) -> T:
|
||||
return f
|
||||
|
||||
class C:
|
||||
@classmethod
|
||||
@does_nothing
|
||||
def f1(cls: type[C], x: int) -> str:
|
||||
def f1(cls, x: int) -> str:
|
||||
return "a"
|
||||
|
||||
@does_nothing
|
||||
@classmethod
|
||||
def f2(cls: type[C], x: int) -> str:
|
||||
def f2(cls, x: int) -> str:
|
||||
return "a"
|
||||
|
||||
reveal_type(C.f1(1)) # revealed: str
|
||||
@@ -600,9 +598,9 @@ from typing_extensions import Self
|
||||
|
||||
reveal_type(object.__new__) # revealed: def __new__(cls) -> Self@__new__
|
||||
reveal_type(object().__new__) # revealed: def __new__(cls) -> Self@__new__
|
||||
# revealed: Overload[(cls, x: str | Buffer | SupportsInt | SupportsIndex | SupportsTrunc = Literal[0], /) -> Self@__new__, (cls, x: str | bytes | bytearray, /, base: SupportsIndex) -> Self@__new__]
|
||||
# revealed: Overload[(cls, x: str | Buffer | SupportsInt | SupportsIndex | SupportsTrunc = 0, /) -> Self@__new__, (cls, x: str | bytes | bytearray, /, base: SupportsIndex) -> Self@__new__]
|
||||
reveal_type(int.__new__)
|
||||
# revealed: Overload[(cls, x: str | Buffer | SupportsInt | SupportsIndex | SupportsTrunc = Literal[0], /) -> Self@__new__, (cls, x: str | bytes | bytearray, /, base: SupportsIndex) -> Self@__new__]
|
||||
# revealed: Overload[(cls, x: str | Buffer | SupportsInt | SupportsIndex | SupportsTrunc = 0, /) -> Self@__new__, (cls, x: str | bytes | bytearray, /, base: SupportsIndex) -> Self@__new__]
|
||||
reveal_type((42).__new__)
|
||||
|
||||
class X:
|
||||
|
||||
@@ -36,7 +36,7 @@ class Point:
|
||||
x: int
|
||||
y: int
|
||||
|
||||
reveal_type(Point.__replace__) # revealed: (self: Point, *, x: int = int, y: int = int) -> Point
|
||||
reveal_type(Point.__replace__) # revealed: (self: Point, *, x: int = ..., y: int = ...) -> Point
|
||||
```
|
||||
|
||||
The `__replace__` method can either be called directly or through the `replace` function:
|
||||
|
||||
@@ -567,7 +567,7 @@ def f(x: int):
|
||||
super(x, x)
|
||||
|
||||
type IntAlias = int
|
||||
# error: [invalid-super-argument] "`typing.TypeAliasType` is not a valid class"
|
||||
# error: [invalid-super-argument] "`TypeAliasType` is not a valid class"
|
||||
super(IntAlias, 0)
|
||||
|
||||
# error: [invalid-super-argument] "`str` is not an instance or subclass of `<class 'int'>` in `super(<class 'int'>, str)` call"
|
||||
@@ -602,13 +602,13 @@ super(object, object()).__class__
|
||||
# Not all objects valid in a class's bases list are valid as the first argument to `super()`.
|
||||
# For example, it's valid to inherit from `typing.ChainMap`, but it's not valid as the first argument to `super()`.
|
||||
#
|
||||
# error: [invalid-super-argument] "`<special form 'typing.ChainMap'>` is not a valid class"
|
||||
# error: [invalid-super-argument] "`<special-form 'typing.ChainMap'>` is not a valid class"
|
||||
reveal_type(super(typing.ChainMap, collections.ChainMap())) # revealed: Unknown
|
||||
|
||||
# Meanwhile, it's not valid to inherit from unsubscripted `typing.Generic`,
|
||||
# but it *is* valid as the first argument to `super()`.
|
||||
#
|
||||
# revealed: <super: <special form 'typing.Generic'>, <class 'SupportsInt'>>
|
||||
# revealed: <super: <special-form 'typing.Generic'>, <class 'SupportsInt'>>
|
||||
reveal_type(super(typing.Generic, typing.SupportsInt))
|
||||
|
||||
def _(x: type[typing.Any], y: typing.Any):
|
||||
|
||||
@@ -87,25 +87,25 @@ class C:
|
||||
def inner_a(positional=self.a):
|
||||
return
|
||||
self.a = inner_a
|
||||
# revealed: def inner_a(positional=Unknown | (def inner_a(positional=Unknown) -> Unknown)) -> Unknown
|
||||
# revealed: def inner_a(positional=...) -> Unknown
|
||||
reveal_type(inner_a)
|
||||
|
||||
def inner_b(*, kw_only=self.b):
|
||||
return
|
||||
self.b = inner_b
|
||||
# revealed: def inner_b(*, kw_only=Unknown | (def inner_b(*, kw_only=Unknown) -> Unknown)) -> Unknown
|
||||
# revealed: def inner_b(*, kw_only=...) -> Unknown
|
||||
reveal_type(inner_b)
|
||||
|
||||
def inner_c(positional_only=self.c, /):
|
||||
return
|
||||
self.c = inner_c
|
||||
# revealed: def inner_c(positional_only=Unknown | (def inner_c(positional_only=Unknown, /) -> Unknown), /) -> Unknown
|
||||
# revealed: def inner_c(positional_only=..., /) -> Unknown
|
||||
reveal_type(inner_c)
|
||||
|
||||
def inner_d(*, kw_only=self.d):
|
||||
return
|
||||
self.d = inner_d
|
||||
# revealed: def inner_d(*, kw_only=Unknown | (def inner_d(*, kw_only=Unknown) -> Unknown)) -> Unknown
|
||||
# revealed: def inner_d(*, kw_only=...) -> Unknown
|
||||
reveal_type(inner_d)
|
||||
```
|
||||
|
||||
@@ -114,7 +114,7 @@ We do, however, still check assignability of the default value to the parameter
|
||||
```py
|
||||
class D:
|
||||
def f(self: "D"):
|
||||
# error: [invalid-parameter-default] "Default value of type `Unknown | (def inner_a(a: int = Unknown | (def inner_a(a: int = Unknown) -> Unknown)) -> Unknown)` is not assignable to annotated parameter type `int`"
|
||||
# error: [invalid-parameter-default] "Default value of type `Unknown | (def inner_a(a: int = ...) -> Unknown)` is not assignable to annotated parameter type `int`"
|
||||
def inner_a(a: int = self.a): ...
|
||||
self.a = inner_a
|
||||
```
|
||||
@@ -129,16 +129,16 @@ class C:
|
||||
self.c = lambda positional_only=self.c, /: positional_only
|
||||
self.d = lambda *, kw_only=self.d: kw_only
|
||||
|
||||
# revealed: (positional=Unknown | ((positional=Unknown) -> Unknown)) -> Unknown
|
||||
# revealed: (positional=...) -> Unknown
|
||||
reveal_type(self.a)
|
||||
|
||||
# revealed: (*, kw_only=Unknown | ((*, kw_only=Unknown) -> Unknown)) -> Unknown
|
||||
# revealed: (*, kw_only=...) -> Unknown
|
||||
reveal_type(self.b)
|
||||
|
||||
# revealed: (positional_only=Unknown | ((positional_only=Unknown, /) -> Unknown), /) -> Unknown
|
||||
# revealed: (positional_only=..., /) -> Unknown
|
||||
reveal_type(self.c)
|
||||
|
||||
# revealed: (*, kw_only=Unknown | ((*, kw_only=Unknown) -> Unknown)) -> Unknown
|
||||
# revealed: (*, kw_only=...) -> Unknown
|
||||
reveal_type(self.d)
|
||||
```
|
||||
|
||||
|
||||
@@ -749,8 +749,8 @@ class Outer:
|
||||
outer_a: int = outer_field(init=False)
|
||||
outer_b: str = inner_field(init=False)
|
||||
|
||||
reveal_type(Outer.__init__) # revealed: (self: Outer, outer_b: str = Any) -> None
|
||||
reveal_type(Outer.Inner.__init__) # revealed: (self: Inner, inner_b: str = Any) -> None
|
||||
reveal_type(Outer.__init__) # revealed: (self: Outer, outer_b: str = ...) -> None
|
||||
reveal_type(Outer.Inner.__init__) # revealed: (self: Inner, inner_b: str = ...) -> None
|
||||
```
|
||||
|
||||
## Overloaded dataclass-like decorators
|
||||
|
||||
@@ -69,7 +69,7 @@ class D:
|
||||
y: str = "default"
|
||||
z: int | None = 1 + 2
|
||||
|
||||
reveal_type(D.__init__) # revealed: (self: D, x: int, y: str = Literal["default"], z: int | None = Literal[3]) -> None
|
||||
reveal_type(D.__init__) # revealed: (self: D, x: int, y: str = "default", z: int | None = 3) -> None
|
||||
```
|
||||
|
||||
This also works if the declaration and binding are split:
|
||||
@@ -221,7 +221,7 @@ class D:
|
||||
(x): int = 1
|
||||
|
||||
# TODO: should ideally not include a `x` parameter
|
||||
reveal_type(D.__init__) # revealed: (self: D, x: int = Literal[1]) -> None
|
||||
reveal_type(D.__init__) # revealed:(self: D, x: int = 1) -> None
|
||||
```
|
||||
|
||||
## `@dataclass` calls with arguments
|
||||
@@ -521,6 +521,73 @@ frozen = MyFrozenChildClass()
|
||||
del frozen.x # TODO this should emit an [invalid-assignment]
|
||||
```
|
||||
|
||||
### frozen/non-frozen inheritance
|
||||
|
||||
If a non-frozen dataclass inherits from a frozen dataclass, an exception is raised at runtime. We
|
||||
catch this error:
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
`a.py`:
|
||||
|
||||
```py
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class FrozenBase:
|
||||
x: int
|
||||
|
||||
@dataclass
|
||||
# error: [invalid-frozen-dataclass-subclass] "Non-frozen dataclass `Child` cannot inherit from frozen dataclass `FrozenBase`"
|
||||
class Child(FrozenBase):
|
||||
y: int
|
||||
```
|
||||
|
||||
Frozen dataclasses inheriting from non-frozen dataclasses are also illegal:
|
||||
|
||||
`b.py`:
|
||||
|
||||
```py
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass
|
||||
class Base:
|
||||
x: int
|
||||
|
||||
@dataclass(frozen=True)
|
||||
# error: [invalid-frozen-dataclass-subclass] "Frozen dataclass `FrozenChild` cannot inherit from non-frozen dataclass `Base`"
|
||||
class FrozenChild(Base):
|
||||
y: int
|
||||
```
|
||||
|
||||
Example of diagnostics when there are multiple files involved:
|
||||
|
||||
`module.py`:
|
||||
|
||||
```py
|
||||
import dataclasses
|
||||
|
||||
@dataclasses.dataclass(frozen=False)
|
||||
class NotFrozenBase:
|
||||
x: int
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
from functools import total_ordering
|
||||
from typing import final
|
||||
from dataclasses import dataclass
|
||||
|
||||
from module import NotFrozenBase
|
||||
|
||||
@final
|
||||
@dataclass(frozen=True)
|
||||
@total_ordering
|
||||
class FrozenChild(NotFrozenBase): # error: [invalid-frozen-dataclass-subclass]
|
||||
y: str
|
||||
```
|
||||
|
||||
### `match_args`
|
||||
|
||||
If `match_args` is set to `True` (the default), the `__match_args__` attribute is a tuple created
|
||||
@@ -603,7 +670,7 @@ class A:
|
||||
a: str = field(kw_only=False)
|
||||
b: int = 0
|
||||
|
||||
reveal_type(A.__init__) # revealed: (self: A, a: str, *, b: int = Literal[0]) -> None
|
||||
reveal_type(A.__init__) # revealed:(self: A, a: str, *, b: int = 0) -> None
|
||||
|
||||
A("hi")
|
||||
```
|
||||
@@ -924,7 +991,7 @@ class C:
|
||||
class_variable1: ClassVar[Final[int]] = 1
|
||||
class_variable2: ClassVar[Final[int]] = 1
|
||||
|
||||
reveal_type(C.__init__) # revealed: (self: C, instance_variable_no_default: int, instance_variable: int = Literal[1]) -> None
|
||||
reveal_type(C.__init__) # revealed:(self: C, instance_variable_no_default: int, instance_variable: int = 1) -> None
|
||||
|
||||
c = C(1)
|
||||
# error: [invalid-assignment] "Cannot assign to final attribute `instance_variable` on type `C`"
|
||||
@@ -1015,7 +1082,7 @@ class C(Base):
|
||||
z: int = 10
|
||||
x: int = 15
|
||||
|
||||
reveal_type(C.__init__) # revealed: (self: C, x: int = Literal[15], y: int = Literal[0], z: int = Literal[10]) -> None
|
||||
reveal_type(C.__init__) # revealed:(self: C, x: int = 15, y: int = 0, z: int = 10) -> None
|
||||
```
|
||||
|
||||
## Conditionally defined fields
|
||||
@@ -1176,7 +1243,7 @@ class UppercaseString:
|
||||
class C:
|
||||
upper: UppercaseString = UppercaseString()
|
||||
|
||||
reveal_type(C.__init__) # revealed: (self: C, upper: str = str) -> None
|
||||
reveal_type(C.__init__) # revealed: (self: C, upper: str = ...) -> None
|
||||
|
||||
c = C("abc")
|
||||
reveal_type(c.upper) # revealed: str
|
||||
@@ -1222,7 +1289,7 @@ class ConvertToLength:
|
||||
class C:
|
||||
converter: ConvertToLength = ConvertToLength()
|
||||
|
||||
reveal_type(C.__init__) # revealed: (self: C, converter: str = Literal[""]) -> None
|
||||
reveal_type(C.__init__) # revealed: (self: C, converter: str = "") -> None
|
||||
|
||||
c = C("abc")
|
||||
reveal_type(c.converter) # revealed: int
|
||||
@@ -1261,7 +1328,7 @@ class AcceptsStrAndInt:
|
||||
class C:
|
||||
field: AcceptsStrAndInt = AcceptsStrAndInt()
|
||||
|
||||
reveal_type(C.__init__) # revealed: (self: C, field: str | int = int) -> None
|
||||
reveal_type(C.__init__) # revealed: (self: C, field: str | int = ...) -> None
|
||||
```
|
||||
|
||||
## `dataclasses.field`
|
||||
|
||||
@@ -11,7 +11,7 @@ class Member:
|
||||
role: str = field(default="user")
|
||||
tag: str | None = field(default=None, init=False)
|
||||
|
||||
# revealed: (self: Member, name: str, role: str = Literal["user"]) -> None
|
||||
# revealed: (self: Member, name: str, role: str = "user") -> None
|
||||
reveal_type(Member.__init__)
|
||||
|
||||
alice = Member(name="Alice", role="admin")
|
||||
@@ -37,7 +37,7 @@ class Data:
|
||||
content: list[int] = field(default_factory=list)
|
||||
timestamp: datetime = field(default_factory=datetime.now, init=False)
|
||||
|
||||
# revealed: (self: Data, content: list[int] = list[int]) -> None
|
||||
# revealed: (self: Data, content: list[int] = ...) -> None
|
||||
reveal_type(Data.__init__)
|
||||
|
||||
data = Data([1, 2, 3])
|
||||
@@ -63,7 +63,7 @@ class Person:
|
||||
age: int | None = field(default=None, kw_only=True)
|
||||
role: str = field(default="user", kw_only=True)
|
||||
|
||||
# revealed: (self: Person, name: str, *, age: int | None = None, role: str = Literal["user"]) -> None
|
||||
# revealed: (self: Person, name: str, *, age: int | None = None, role: str = "user") -> None
|
||||
reveal_type(Person.__init__)
|
||||
|
||||
alice = Person(role="admin", name="Alice")
|
||||
@@ -82,8 +82,7 @@ def get_default() -> str:
|
||||
|
||||
reveal_type(field(default=1)) # revealed: dataclasses.Field[Literal[1]]
|
||||
reveal_type(field(default=None)) # revealed: dataclasses.Field[None]
|
||||
# TODO: this could ideally be `dataclasses.Field[str]` with a better generics solver
|
||||
reveal_type(field(default_factory=get_default)) # revealed: dataclasses.Field[Unknown]
|
||||
reveal_type(field(default_factory=get_default)) # revealed: dataclasses.Field[str]
|
||||
```
|
||||
|
||||
## dataclass_transform field_specifiers
|
||||
|
||||
@@ -144,11 +144,10 @@ from functools import cache
|
||||
def f(x: int) -> int:
|
||||
return x**2
|
||||
|
||||
# TODO: Should be `_lru_cache_wrapper[int]`
|
||||
reveal_type(f) # revealed: _lru_cache_wrapper[Unknown]
|
||||
|
||||
# TODO: Should be `int`
|
||||
reveal_type(f(1)) # revealed: Unknown
|
||||
# revealed: _lru_cache_wrapper[int]
|
||||
reveal_type(f)
|
||||
# revealed: int
|
||||
reveal_type(f(1))
|
||||
```
|
||||
|
||||
## Lambdas as decorators
|
||||
|
||||
@@ -11,9 +11,9 @@ classes. Uses of these items should subsequently produce a warning.
|
||||
from typing_extensions import deprecated
|
||||
|
||||
@deprecated("use OtherClass")
|
||||
def myfunc(): ...
|
||||
def myfunc(x: int): ...
|
||||
|
||||
myfunc() # error: [deprecated] "use OtherClass"
|
||||
myfunc(1) # error: [deprecated] "use OtherClass"
|
||||
```
|
||||
|
||||
```py
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
# Invalid Order of Legacy Type Parameters
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.13"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing import TypeVar, Generic, Protocol
|
||||
|
||||
T1 = TypeVar("T1", default=int)
|
||||
|
||||
T2 = TypeVar("T2")
|
||||
T3 = TypeVar("T3")
|
||||
|
||||
DefaultStrT = TypeVar("DefaultStrT", default=str)
|
||||
|
||||
class SubclassMe(Generic[T1, DefaultStrT]):
|
||||
x: DefaultStrT
|
||||
|
||||
class Baz(SubclassMe[int, DefaultStrT]):
|
||||
pass
|
||||
|
||||
# error: [invalid-generic-class] "Type parameter `T2` without a default cannot follow earlier parameter `T1` with a default"
|
||||
class Foo(Generic[T1, T2]):
|
||||
pass
|
||||
|
||||
class Bar(Generic[T2, T1, T3]): # error: [invalid-generic-class]
|
||||
pass
|
||||
|
||||
class Spam(Generic[T1, T2, DefaultStrT, T3]): # error: [invalid-generic-class]
|
||||
pass
|
||||
|
||||
class Ham(Protocol[T1, T2, DefaultStrT, T3]): # error: [invalid-generic-class]
|
||||
pass
|
||||
|
||||
class VeryBad(
|
||||
Protocol[T1, T2, DefaultStrT, T3], # error: [invalid-generic-class]
|
||||
Generic[T1, T2, DefaultStrT, T3],
|
||||
): ...
|
||||
```
|
||||
@@ -591,9 +591,9 @@ try:
|
||||
reveal_type(x) # revealed: B | D
|
||||
reveal_type(x) # revealed: B | D
|
||||
x = foo
|
||||
reveal_type(x) # revealed: def foo(param=A) -> Unknown
|
||||
reveal_type(x) # revealed: def foo(param=...) -> Unknown
|
||||
except:
|
||||
reveal_type(x) # revealed: Literal[1] | (def foo(param=A) -> Unknown)
|
||||
reveal_type(x) # revealed: Literal[1] | (def foo(param=...) -> Unknown)
|
||||
|
||||
class Bar:
|
||||
x = could_raise_returns_E()
|
||||
@@ -603,9 +603,9 @@ except:
|
||||
reveal_type(x) # revealed: <class 'Bar'>
|
||||
finally:
|
||||
# TODO: should be `Literal[1] | <class 'foo'> | <class 'Bar'>`
|
||||
reveal_type(x) # revealed: (def foo(param=A) -> Unknown) | <class 'Bar'>
|
||||
reveal_type(x) # revealed: (def foo(param=...) -> Unknown) | <class 'Bar'>
|
||||
|
||||
reveal_type(x) # revealed: (def foo(param=A) -> Unknown) | <class 'Bar'>
|
||||
reveal_type(x) # revealed: (def foo(param=...) -> Unknown) | <class 'Bar'>
|
||||
```
|
||||
|
||||
[1]: https://astral-sh.notion.site/Exception-handler-control-flow-11348797e1ca80bb8ce1e9aedbbe439d
|
||||
|
||||
@@ -24,8 +24,8 @@ reveal_type(lambda a, b: a + b) # revealed: (a, b) -> Unknown
|
||||
But, it can have default values:
|
||||
|
||||
```py
|
||||
reveal_type(lambda a=1: a) # revealed: (a=Literal[1]) -> Unknown
|
||||
reveal_type(lambda a, b=2: a) # revealed: (a, b=Literal[2]) -> Unknown
|
||||
reveal_type(lambda a=1: a) # revealed: (a=1) -> Unknown
|
||||
reveal_type(lambda a, b=2: a) # revealed: (a, b=2) -> Unknown
|
||||
```
|
||||
|
||||
And, positional-only parameters:
|
||||
@@ -37,7 +37,7 @@ reveal_type(lambda a, b, /, c: c) # revealed: (a, b, /, c) -> Unknown
|
||||
And, keyword-only parameters:
|
||||
|
||||
```py
|
||||
reveal_type(lambda a, *, b=2, c: b) # revealed: (a, *, b=Literal[2], c) -> Unknown
|
||||
reveal_type(lambda a, *, b=2, c: b) # revealed: (a, *, b=2, c) -> Unknown
|
||||
```
|
||||
|
||||
And, variadic parameter:
|
||||
@@ -55,7 +55,7 @@ reveal_type(lambda **kwargs: kwargs) # revealed: (**kwargs) -> Unknown
|
||||
Mixing all of them together:
|
||||
|
||||
```py
|
||||
# revealed: (a, b, /, c=Literal[True], *args, *, d=Literal["default"], e=Literal[5], **kwargs) -> Unknown
|
||||
# revealed: (a, b, /, c=True, *args, *, d="default", e=5, **kwargs) -> Unknown
|
||||
reveal_type(lambda a, b, /, c=True, *args, d="default", e=5, **kwargs: None)
|
||||
```
|
||||
|
||||
@@ -94,7 +94,7 @@ Here, a `lambda` expression is used as the default value for a parameter in anot
|
||||
expression.
|
||||
|
||||
```py
|
||||
reveal_type(lambda a=lambda x, y: 0: 2) # revealed: (a=(x, y) -> Unknown) -> Unknown
|
||||
reveal_type(lambda a=lambda x, y: 0: 2) # revealed: (a=...) -> Unknown
|
||||
```
|
||||
|
||||
## Assignment
|
||||
|
||||
@@ -38,7 +38,7 @@ class Product(BaseModel):
|
||||
name: str = Field(..., kw_only=False, min_length=1)
|
||||
internal_price_cent: int = Field(..., gt=0, alias="price_cent")
|
||||
|
||||
reveal_type(Product.__init__) # revealed: (self: Product, name: str = Any, *, price_cent: int = Any) -> None
|
||||
reveal_type(Product.__init__) # revealed: (self: Product, name: str = ..., *, price_cent: int = ...) -> None
|
||||
|
||||
product = Product("Laptop", price_cent=999_00)
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ class User:
|
||||
id: int
|
||||
role: str = strawberry.field(default="user")
|
||||
|
||||
reveal_type(User.__init__) # revealed: (self: User, *, id: int, role: str = Any) -> None
|
||||
reveal_type(User.__init__) # revealed: (self: User, *, id: int, role: str = ...) -> None
|
||||
|
||||
user = User(id=1)
|
||||
reveal_type(user.id) # revealed: int
|
||||
|
||||
@@ -80,7 +80,7 @@ class Foo(Protocol):
|
||||
def f[T](self, v: T) -> T: ...
|
||||
|
||||
t = (Protocol, int)
|
||||
reveal_type(t[0]) # revealed: <special form 'typing.Protocol'>
|
||||
reveal_type(t[0]) # revealed: <special-form 'typing.Protocol'>
|
||||
|
||||
class Lorem(t[0]):
|
||||
def f(self) -> int: ...
|
||||
|
||||
@@ -555,8 +555,7 @@ def identity(x: T) -> T:
|
||||
def head(xs: list[T]) -> T:
|
||||
return xs[0]
|
||||
|
||||
# TODO: this should be `Literal[1]`
|
||||
reveal_type(invoke(identity, 1)) # revealed: Unknown
|
||||
reveal_type(invoke(identity, 1)) # revealed: Literal[1]
|
||||
|
||||
# TODO: this should be `Unknown | int`
|
||||
reveal_type(invoke(head, [1, 2, 3])) # revealed: Unknown
|
||||
|
||||
@@ -9,7 +9,7 @@ from typing import ParamSpec
|
||||
|
||||
P = ParamSpec("P")
|
||||
reveal_type(type(P)) # revealed: <class 'ParamSpec'>
|
||||
reveal_type(P) # revealed: typing.ParamSpec
|
||||
reveal_type(P) # revealed: ParamSpec
|
||||
reveal_type(P.__name__) # revealed: Literal["P"]
|
||||
```
|
||||
|
||||
@@ -424,9 +424,8 @@ p3 = ParamSpecWithDefault4[[int], [str]]()
|
||||
reveal_type(p3.attr1) # revealed: (int, /) -> None
|
||||
reveal_type(p3.attr2) # revealed: (str, /) -> None
|
||||
|
||||
# TODO: error
|
||||
# Un-ordered type variables as the default of `PAnother` is `P`
|
||||
class ParamSpecWithDefault5(Generic[PAnother, P]):
|
||||
class ParamSpecWithDefault5(Generic[PAnother, P]): # error: [invalid-generic-class]
|
||||
attr: Callable[PAnother, None]
|
||||
|
||||
# TODO: error
|
||||
|
||||
@@ -22,7 +22,7 @@ from typing import TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
reveal_type(type(T)) # revealed: <class 'TypeVar'>
|
||||
reveal_type(T) # revealed: typing.TypeVar
|
||||
reveal_type(T) # revealed: TypeVar
|
||||
reveal_type(T.__name__) # revealed: Literal["T"]
|
||||
```
|
||||
|
||||
@@ -146,7 +146,7 @@ from typing import TypeVar
|
||||
|
||||
T = TypeVar("T", default=int)
|
||||
reveal_type(type(T)) # revealed: <class 'TypeVar'>
|
||||
reveal_type(T) # revealed: typing.TypeVar
|
||||
reveal_type(T) # revealed: TypeVar
|
||||
reveal_type(T.__default__) # revealed: int
|
||||
reveal_type(T.__bound__) # revealed: None
|
||||
reveal_type(T.__constraints__) # revealed: tuple[()]
|
||||
@@ -187,7 +187,7 @@ from typing import TypeVar
|
||||
|
||||
T = TypeVar("T", bound=int)
|
||||
reveal_type(type(T)) # revealed: <class 'TypeVar'>
|
||||
reveal_type(T) # revealed: typing.TypeVar
|
||||
reveal_type(T) # revealed: TypeVar
|
||||
reveal_type(T.__bound__) # revealed: int
|
||||
reveal_type(T.__constraints__) # revealed: tuple[()]
|
||||
|
||||
@@ -211,7 +211,7 @@ from typing import TypeVar
|
||||
|
||||
T = TypeVar("T", int, str)
|
||||
reveal_type(type(T)) # revealed: <class 'TypeVar'>
|
||||
reveal_type(T) # revealed: typing.TypeVar
|
||||
reveal_type(T) # revealed: TypeVar
|
||||
reveal_type(T.__constraints__) # revealed: tuple[int, str]
|
||||
|
||||
S = TypeVar("S")
|
||||
@@ -518,8 +518,7 @@ V = TypeVar("V", default="V")
|
||||
class D(Generic[V]):
|
||||
x: V
|
||||
|
||||
# TODO: we shouldn't leak a typevar like this in type inference
|
||||
reveal_type(D().x) # revealed: V@D
|
||||
reveal_type(D().x) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Regression
|
||||
|
||||
@@ -800,6 +800,29 @@ def func(x: D): ...
|
||||
func(G()) # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
### Self-referential protocol with different specialization
|
||||
|
||||
This is a minimal reproduction for [ty#1874](https://github.com/astral-sh/ty/issues/1874).
|
||||
|
||||
```py
|
||||
from __future__ import annotations
|
||||
from typing import Protocol
|
||||
from ty_extensions import generic_context
|
||||
|
||||
class A[S, R](Protocol):
|
||||
def get(self, s: S) -> R: ...
|
||||
def set(self, s: S, r: R) -> S: ...
|
||||
def merge[R2](self, other: A[S, R2]) -> A[S, tuple[R, R2]]: ...
|
||||
|
||||
class Impl[S, R](A[S, R]):
|
||||
def foo(self, s: S) -> S:
|
||||
return self.set(s, self.get(s))
|
||||
|
||||
reveal_type(generic_context(A.get)) # revealed: ty_extensions.GenericContext[Self@get]
|
||||
reveal_type(generic_context(A.merge)) # revealed: ty_extensions.GenericContext[Self@merge, R2@merge]
|
||||
reveal_type(generic_context(Impl.foo)) # revealed: ty_extensions.GenericContext[Self@foo]
|
||||
```
|
||||
|
||||
## Tuple as a PEP-695 generic class
|
||||
|
||||
Our special handling for `tuple` does not break if `tuple` is defined as a PEP-695 generic class in
|
||||
|
||||
@@ -493,8 +493,7 @@ def identity[T](x: T) -> T:
|
||||
def head[T](xs: list[T]) -> T:
|
||||
return xs[0]
|
||||
|
||||
# TODO: this should be `Literal[1]`
|
||||
reveal_type(invoke(identity, 1)) # revealed: Unknown
|
||||
reveal_type(invoke(identity, 1)) # revealed: Literal[1]
|
||||
|
||||
# TODO: this should be `Unknown | int`
|
||||
reveal_type(invoke(head, [1, 2, 3])) # revealed: Unknown
|
||||
@@ -736,3 +735,159 @@ def f[T](x: T, y: Not[T]) -> T:
|
||||
y = x # error: [invalid-assignment]
|
||||
return x
|
||||
```
|
||||
|
||||
## `Callable` parameters
|
||||
|
||||
We can recurse into the parameters and return values of `Callable` parameters to infer
|
||||
specializations of a generic function.
|
||||
|
||||
```py
|
||||
from typing import Any, Callable, NoReturn, overload, Self
|
||||
from ty_extensions import generic_context, into_callable
|
||||
|
||||
def accepts_callable[**P, R](callable: Callable[P, R]) -> Callable[P, R]:
|
||||
return callable
|
||||
|
||||
def returns_int() -> int:
|
||||
raise NotImplementedError
|
||||
|
||||
# revealed: () -> int
|
||||
reveal_type(into_callable(returns_int))
|
||||
# revealed: () -> int
|
||||
reveal_type(accepts_callable(returns_int))
|
||||
# revealed: int
|
||||
reveal_type(accepts_callable(returns_int)())
|
||||
|
||||
class ClassWithoutConstructor: ...
|
||||
|
||||
# revealed: () -> ClassWithoutConstructor
|
||||
reveal_type(into_callable(ClassWithoutConstructor))
|
||||
# revealed: () -> ClassWithoutConstructor
|
||||
reveal_type(accepts_callable(ClassWithoutConstructor))
|
||||
# revealed: ClassWithoutConstructor
|
||||
reveal_type(accepts_callable(ClassWithoutConstructor)())
|
||||
|
||||
class ClassWithNew:
|
||||
def __new__(cls, *args, **kwargs) -> Self:
|
||||
raise NotImplementedError
|
||||
|
||||
# revealed: (...) -> ClassWithNew
|
||||
reveal_type(into_callable(ClassWithNew))
|
||||
# revealed: (...) -> ClassWithNew
|
||||
reveal_type(accepts_callable(ClassWithNew))
|
||||
# revealed: ClassWithNew
|
||||
reveal_type(accepts_callable(ClassWithNew)())
|
||||
|
||||
class ClassWithInit:
|
||||
def __init__(self) -> None: ...
|
||||
|
||||
# revealed: () -> ClassWithInit
|
||||
reveal_type(into_callable(ClassWithInit))
|
||||
# revealed: () -> ClassWithInit
|
||||
reveal_type(accepts_callable(ClassWithInit))
|
||||
# revealed: ClassWithInit
|
||||
reveal_type(accepts_callable(ClassWithInit)())
|
||||
|
||||
class ClassWithNewAndInit:
|
||||
def __new__(cls, *args, **kwargs) -> Self:
|
||||
raise NotImplementedError
|
||||
|
||||
def __init__(self, x: int) -> None: ...
|
||||
|
||||
# TODO: We do not currently solve a common behavioral supertype for the two solutions of P.
|
||||
# revealed: ((...) -> ClassWithNewAndInit) | ((x: int) -> ClassWithNewAndInit)
|
||||
reveal_type(into_callable(ClassWithNewAndInit))
|
||||
# TODO: revealed: ((...) -> ClassWithNewAndInit) | ((x: int) -> ClassWithNewAndInit)
|
||||
# revealed: (...) -> ClassWithNewAndInit
|
||||
reveal_type(accepts_callable(ClassWithNewAndInit))
|
||||
# revealed: ClassWithNewAndInit
|
||||
reveal_type(accepts_callable(ClassWithNewAndInit)())
|
||||
|
||||
class Meta(type):
|
||||
def __call__(cls, *args: Any, **kwargs: Any) -> NoReturn:
|
||||
raise NotImplementedError
|
||||
|
||||
class ClassWithNoReturnMetatype(metaclass=Meta):
|
||||
def __new__(cls, *args: Any, **kwargs: Any) -> Self:
|
||||
raise NotImplementedError
|
||||
|
||||
# TODO: The return types here are wrong, because we end up creating a constraint (Never ≤ R), which
|
||||
# we confuse with "R has no lower bound".
|
||||
# revealed: (...) -> Never
|
||||
reveal_type(into_callable(ClassWithNoReturnMetatype))
|
||||
# TODO: revealed: (...) -> Never
|
||||
# revealed: (...) -> Unknown
|
||||
reveal_type(accepts_callable(ClassWithNoReturnMetatype))
|
||||
# TODO: revealed: Never
|
||||
# revealed: Unknown
|
||||
reveal_type(accepts_callable(ClassWithNoReturnMetatype)())
|
||||
|
||||
class Proxy: ...
|
||||
|
||||
class ClassWithIgnoredInit:
|
||||
def __new__(cls) -> Proxy:
|
||||
return Proxy()
|
||||
|
||||
def __init__(self, x: int) -> None: ...
|
||||
|
||||
# revealed: () -> Proxy
|
||||
reveal_type(into_callable(ClassWithIgnoredInit))
|
||||
# revealed: () -> Proxy
|
||||
reveal_type(accepts_callable(ClassWithIgnoredInit))
|
||||
# revealed: Proxy
|
||||
reveal_type(accepts_callable(ClassWithIgnoredInit)())
|
||||
|
||||
class ClassWithOverloadedInit[T]:
|
||||
t: T # invariant
|
||||
|
||||
@overload
|
||||
def __init__(self: "ClassWithOverloadedInit[int]", x: int) -> None: ...
|
||||
@overload
|
||||
def __init__(self: "ClassWithOverloadedInit[str]", x: str) -> None: ...
|
||||
def __init__(self, x: int | str) -> None: ...
|
||||
|
||||
# TODO: The old solver cannot handle this overloaded constructor. The ideal solution is that we
|
||||
# would solve **P once, and map it to the entire overloaded signature of the constructor. This
|
||||
# mapping would have to include the return types, since there are different return types for each
|
||||
# overload. We would then also have to determine that R must be equal to the return type of **P's
|
||||
# solution.
|
||||
|
||||
# revealed: Overload[(x: int) -> ClassWithOverloadedInit[int], (x: str) -> ClassWithOverloadedInit[str]]
|
||||
reveal_type(into_callable(ClassWithOverloadedInit))
|
||||
# TODO: revealed: Overload[(x: int) -> ClassWithOverloadedInit[int], (x: str) -> ClassWithOverloadedInit[str]]
|
||||
# revealed: Overload[(x: int) -> ClassWithOverloadedInit[int] | ClassWithOverloadedInit[str], (x: str) -> ClassWithOverloadedInit[int] | ClassWithOverloadedInit[str]]
|
||||
reveal_type(accepts_callable(ClassWithOverloadedInit))
|
||||
# TODO: revealed: ClassWithOverloadedInit[int]
|
||||
# revealed: ClassWithOverloadedInit[int] | ClassWithOverloadedInit[str]
|
||||
reveal_type(accepts_callable(ClassWithOverloadedInit)(0))
|
||||
# TODO: revealed: ClassWithOverloadedInit[str]
|
||||
# revealed: ClassWithOverloadedInit[int] | ClassWithOverloadedInit[str]
|
||||
reveal_type(accepts_callable(ClassWithOverloadedInit)(""))
|
||||
|
||||
class GenericClass[T]:
|
||||
t: T # invariant
|
||||
|
||||
def __new__(cls, x: list[T], y: list[T]) -> Self:
|
||||
raise NotImplementedError
|
||||
|
||||
def _(x: list[str]):
|
||||
# TODO: This fails because we are not propagating GenericClass's generic context into the
|
||||
# Callable that we create for it.
|
||||
# revealed: (x: list[T@GenericClass], y: list[T@GenericClass]) -> GenericClass[T@GenericClass]
|
||||
reveal_type(into_callable(GenericClass))
|
||||
# revealed: ty_extensions.GenericContext[T@GenericClass]
|
||||
reveal_type(generic_context(into_callable(GenericClass)))
|
||||
|
||||
# revealed: (x: list[T@GenericClass], y: list[T@GenericClass]) -> GenericClass[T@GenericClass]
|
||||
reveal_type(accepts_callable(GenericClass))
|
||||
# TODO: revealed: ty_extensions.GenericContext[T@GenericClass]
|
||||
# revealed: None
|
||||
reveal_type(generic_context(accepts_callable(GenericClass)))
|
||||
|
||||
# TODO: revealed: GenericClass[str]
|
||||
# TODO: no errors
|
||||
# revealed: GenericClass[T@GenericClass]
|
||||
# error: [invalid-argument-type]
|
||||
# error: [invalid-argument-type]
|
||||
reveal_type(accepts_callable(GenericClass)(x, x))
|
||||
```
|
||||
|
||||
@@ -12,7 +12,7 @@ python-version = "3.13"
|
||||
|
||||
```py
|
||||
def foo1[**P]() -> None:
|
||||
reveal_type(P) # revealed: typing.ParamSpec
|
||||
reveal_type(P) # revealed: ParamSpec
|
||||
```
|
||||
|
||||
## Bounds and constraints
|
||||
@@ -45,14 +45,14 @@ The default value for a `ParamSpec` can be either a list of types, `...`, or ano
|
||||
|
||||
```py
|
||||
def foo2[**P = ...]() -> None:
|
||||
reveal_type(P) # revealed: typing.ParamSpec
|
||||
reveal_type(P) # revealed: ParamSpec
|
||||
|
||||
def foo3[**P = [int, str]]() -> None:
|
||||
reveal_type(P) # revealed: typing.ParamSpec
|
||||
reveal_type(P) # revealed: ParamSpec
|
||||
|
||||
def foo4[**P, **Q = P]():
|
||||
reveal_type(P) # revealed: typing.ParamSpec
|
||||
reveal_type(Q) # revealed: typing.ParamSpec
|
||||
reveal_type(P) # revealed: ParamSpec
|
||||
reveal_type(Q) # revealed: ParamSpec
|
||||
```
|
||||
|
||||
Other values are invalid.
|
||||
@@ -477,7 +477,7 @@ def keyword_only_with_default_2(*, y: int = 42) -> int:
|
||||
# parameter list i.e., `()`
|
||||
# TODO: This shouldn't error
|
||||
# error: [invalid-argument-type]
|
||||
# revealed: (*, x: int = Literal[42]) -> bool
|
||||
# revealed: (*, x: int = 42) -> bool
|
||||
reveal_type(multiple(keyword_only_with_default_1, keyword_only_with_default_2))
|
||||
|
||||
def keyword_only1(*, x: int) -> int:
|
||||
@@ -503,7 +503,8 @@ class C[**P]:
|
||||
def __init__(self, f: Callable[P, int]) -> None:
|
||||
self.f = f
|
||||
|
||||
def f(x: int, y: str) -> bool:
|
||||
# Note that the return type must match exactly, since C is invariant on the return type of C.f.
|
||||
def f(x: int, y: str) -> int:
|
||||
return True
|
||||
|
||||
c = C(f)
|
||||
@@ -618,6 +619,22 @@ reveal_type(foo.method) # revealed: bound method Foo[(int, str, /)].method(int,
|
||||
reveal_type(foo.method(1, "a")) # revealed: str
|
||||
```
|
||||
|
||||
### Gradual types propagate through `ParamSpec` inference
|
||||
|
||||
```py
|
||||
from typing import Callable
|
||||
|
||||
def callable_identity[**P, R](func: Callable[P, R]) -> Callable[P, R]:
|
||||
return func
|
||||
|
||||
@callable_identity
|
||||
def f(env: dict) -> None:
|
||||
pass
|
||||
|
||||
# revealed: (env: dict[Unknown, Unknown]) -> None
|
||||
reveal_type(f)
|
||||
```
|
||||
|
||||
### Overloads
|
||||
|
||||
`overloaded.pyi`:
|
||||
@@ -662,7 +679,7 @@ reveal_type(change_return_type(int_int)) # revealed: Overload[(x: int) -> str,
|
||||
reveal_type(change_return_type(int_str)) # revealed: Overload[(x: int) -> str, (x: str) -> str]
|
||||
|
||||
# error: [invalid-argument-type]
|
||||
reveal_type(change_return_type(str_str)) # revealed: Overload[(x: int) -> str, (x: str) -> str]
|
||||
reveal_type(change_return_type(str_str)) # revealed: (...) -> str
|
||||
|
||||
# TODO: Both of these shouldn't raise an error
|
||||
# error: [invalid-argument-type]
|
||||
@@ -670,3 +687,59 @@ reveal_type(with_parameters(int_int, 1)) # revealed: Overload[(x: int) -> str,
|
||||
# error: [invalid-argument-type]
|
||||
reveal_type(with_parameters(int_int, "a")) # revealed: Overload[(x: int) -> str, (x: str) -> str]
|
||||
```
|
||||
|
||||
## ParamSpec attribute assignability
|
||||
|
||||
When comparing signatures with `ParamSpec` attributes (`P.args` and `P.kwargs`), two different
|
||||
inferable `ParamSpec` attributes with the same kind are assignable to each other. This enables
|
||||
method overrides where both methods have their own `ParamSpec`.
|
||||
|
||||
### Same attribute kind, both inferable
|
||||
|
||||
```py
|
||||
from typing import Callable
|
||||
|
||||
class Parent:
|
||||
def method[**P](self, callback: Callable[P, None]) -> Callable[P, None]:
|
||||
return callback
|
||||
|
||||
class Child1(Parent):
|
||||
# This is a valid override: Q.args matches P.args, Q.kwargs matches P.kwargs
|
||||
def method[**Q](self, callback: Callable[Q, None]) -> Callable[Q, None]:
|
||||
return callback
|
||||
|
||||
# Both signatures use ParamSpec, so they should be compatible
|
||||
def outer[**P](f: Callable[P, int]) -> Callable[P, int]:
|
||||
def inner[**Q](g: Callable[Q, int]) -> Callable[Q, int]:
|
||||
return g
|
||||
return inner(f)
|
||||
```
|
||||
|
||||
We can explicitly mark it as an override using the `@override` decorator.
|
||||
|
||||
```py
|
||||
from typing import override
|
||||
|
||||
class Child2(Parent):
|
||||
@override
|
||||
def method[**Q](self, callback: Callable[Q, None]) -> Callable[Q, None]:
|
||||
return callback
|
||||
```
|
||||
|
||||
### One `ParamSpec` not inferable
|
||||
|
||||
Here, `P` is in a non-inferable position while `Q` is inferable. So, they are not considered
|
||||
assignable.
|
||||
|
||||
```py
|
||||
from typing import Callable
|
||||
|
||||
class Container[**P]:
|
||||
def method(self, f: Callable[P, None]) -> Callable[P, None]:
|
||||
return f
|
||||
|
||||
def try_assign[**Q](self, f: Callable[Q, None]) -> Callable[Q, None]:
|
||||
# error: [invalid-return-type] "Return type does not match returned value: expected `(**Q@try_assign) -> None`, found `(**P@Container) -> None`"
|
||||
# error: [invalid-argument-type] "Argument to bound method `method` is incorrect: Expected `(**P@Container) -> None`, found `(**Q@try_assign) -> None`"
|
||||
return self.method(f)
|
||||
```
|
||||
|
||||
@@ -17,7 +17,7 @@ instances of `typing.TypeVar`, just like legacy type variables.
|
||||
```py
|
||||
def f[T]():
|
||||
reveal_type(type(T)) # revealed: <class 'TypeVar'>
|
||||
reveal_type(T) # revealed: typing.TypeVar
|
||||
reveal_type(T) # revealed: TypeVar
|
||||
reveal_type(T.__name__) # revealed: Literal["T"]
|
||||
```
|
||||
|
||||
@@ -33,7 +33,7 @@ python-version = "3.13"
|
||||
```py
|
||||
def f[T = int]():
|
||||
reveal_type(type(T)) # revealed: <class 'TypeVar'>
|
||||
reveal_type(T) # revealed: typing.TypeVar
|
||||
reveal_type(T) # revealed: TypeVar
|
||||
reveal_type(T.__default__) # revealed: int
|
||||
reveal_type(T.__bound__) # revealed: None
|
||||
reveal_type(T.__constraints__) # revealed: tuple[()]
|
||||
@@ -66,7 +66,7 @@ class Invalid[S = T]: ...
|
||||
```py
|
||||
def f[T: int]():
|
||||
reveal_type(type(T)) # revealed: <class 'TypeVar'>
|
||||
reveal_type(T) # revealed: typing.TypeVar
|
||||
reveal_type(T) # revealed: TypeVar
|
||||
reveal_type(T.__bound__) # revealed: int
|
||||
reveal_type(T.__constraints__) # revealed: tuple[()]
|
||||
|
||||
@@ -79,7 +79,7 @@ def g[S]():
|
||||
```py
|
||||
def f[T: (int, str)]():
|
||||
reveal_type(type(T)) # revealed: <class 'TypeVar'>
|
||||
reveal_type(T) # revealed: typing.TypeVar
|
||||
reveal_type(T) # revealed: TypeVar
|
||||
reveal_type(T.__constraints__) # revealed: tuple[int, str]
|
||||
reveal_type(T.__bound__) # revealed: None
|
||||
|
||||
@@ -883,7 +883,7 @@ reveal_type(C[int]().y) # revealed: int
|
||||
class D[T = T]:
|
||||
x: T
|
||||
|
||||
reveal_type(D().x) # revealed: T@D
|
||||
reveal_type(D().x) # revealed: Unknown
|
||||
```
|
||||
|
||||
[pep 695]: https://peps.python.org/pep-0695/
|
||||
|
||||
@@ -426,7 +426,8 @@ from ty_extensions import ConstraintSet, generic_context
|
||||
def mentions[T, U]():
|
||||
# (T@mentions ≤ int) ∧ (U@mentions = list[T@mentions])
|
||||
constraints = ConstraintSet.range(Never, T, int) & ConstraintSet.range(list[T], U, list[T])
|
||||
# revealed: ty_extensions.Specialization[T@mentions = int, U@mentions = list[int]]
|
||||
# TODO: revealed: ty_extensions.Specialization[T@mentions = int, U@mentions = list[int]]
|
||||
# revealed: ty_extensions.Specialization[T@mentions = int, U@mentions = Unknown]
|
||||
reveal_type(generic_context(mentions).specialize_constrained(constraints))
|
||||
```
|
||||
|
||||
|
||||
@@ -77,44 +77,44 @@ IntOrTypeVar = int | T
|
||||
TypeVarOrNone = T | None
|
||||
NoneOrTypeVar = None | T
|
||||
|
||||
reveal_type(IntOrStr) # revealed: <types.UnionType special form 'int | str'>
|
||||
reveal_type(IntOrStrOrBytes1) # revealed: <types.UnionType special form 'int | str | bytes'>
|
||||
reveal_type(IntOrStrOrBytes2) # revealed: <types.UnionType special form 'int | str | bytes'>
|
||||
reveal_type(IntOrStrOrBytes3) # revealed: <types.UnionType special form 'int | str | bytes'>
|
||||
reveal_type(IntOrStrOrBytes4) # revealed: <types.UnionType special form 'int | str | bytes'>
|
||||
reveal_type(IntOrStrOrBytes5) # revealed: <types.UnionType special form 'int | str | bytes'>
|
||||
reveal_type(IntOrStrOrBytes6) # revealed: <types.UnionType special form 'int | str | bytes'>
|
||||
reveal_type(BytesOrIntOrStr) # revealed: <types.UnionType special form 'bytes | int | str'>
|
||||
reveal_type(IntOrNone) # revealed: <types.UnionType special form 'int | None'>
|
||||
reveal_type(NoneOrInt) # revealed: <types.UnionType special form 'None | int'>
|
||||
reveal_type(IntOrStrOrNone) # revealed: <types.UnionType special form 'int | str | None'>
|
||||
reveal_type(NoneOrIntOrStr) # revealed: <types.UnionType special form 'None | int | str'>
|
||||
reveal_type(IntOrAny) # revealed: <types.UnionType special form 'int | Any'>
|
||||
reveal_type(AnyOrInt) # revealed: <types.UnionType special form 'Any | int'>
|
||||
reveal_type(NoneOrAny) # revealed: <types.UnionType special form 'None | Any'>
|
||||
reveal_type(AnyOrNone) # revealed: <types.UnionType special form 'Any | None'>
|
||||
reveal_type(NeverOrAny) # revealed: <types.UnionType special form 'Any'>
|
||||
reveal_type(AnyOrNever) # revealed: <types.UnionType special form 'Any'>
|
||||
reveal_type(UnknownOrInt) # revealed: <types.UnionType special form 'Unknown | int'>
|
||||
reveal_type(IntOrUnknown) # revealed: <types.UnionType special form 'int | Unknown'>
|
||||
reveal_type(StrOrZero) # revealed: <types.UnionType special form 'str | Literal[0]'>
|
||||
reveal_type(ZeroOrStr) # revealed: <types.UnionType special form 'Literal[0] | str'>
|
||||
reveal_type(IntOrLiteralString) # revealed: <types.UnionType special form 'int | LiteralString'>
|
||||
reveal_type(LiteralStringOrInt) # revealed: <types.UnionType special form 'LiteralString | int'>
|
||||
reveal_type(NoneOrTuple) # revealed: <types.UnionType special form 'None | tuple[int, str]'>
|
||||
reveal_type(TupleOrNone) # revealed: <types.UnionType special form 'tuple[int, str] | None'>
|
||||
reveal_type(IntOrAnnotated) # revealed: <types.UnionType special form 'int | str'>
|
||||
reveal_type(AnnotatedOrInt) # revealed: <types.UnionType special form 'str | int'>
|
||||
reveal_type(IntOrOptional) # revealed: <types.UnionType special form 'int | str | None'>
|
||||
reveal_type(OptionalOrInt) # revealed: <types.UnionType special form 'str | None | int'>
|
||||
reveal_type(IntOrTypeOfStr) # revealed: <types.UnionType special form 'int | type[str]'>
|
||||
reveal_type(TypeOfStrOrInt) # revealed: <types.UnionType special form 'type[str] | int'>
|
||||
reveal_type(IntOrCallable) # revealed: <types.UnionType special form 'int | ((str, /) -> bytes)'>
|
||||
reveal_type(CallableOrInt) # revealed: <types.UnionType special form '((str, /) -> bytes) | int'>
|
||||
reveal_type(TypeVarOrInt) # revealed: <types.UnionType special form 'T@TypeVarOrInt | int'>
|
||||
reveal_type(IntOrTypeVar) # revealed: <types.UnionType special form 'int | T@IntOrTypeVar'>
|
||||
reveal_type(TypeVarOrNone) # revealed: <types.UnionType special form 'T@TypeVarOrNone | None'>
|
||||
reveal_type(NoneOrTypeVar) # revealed: <types.UnionType special form 'None | T@NoneOrTypeVar'>
|
||||
reveal_type(IntOrStr) # revealed: <types.UnionType special-form 'int | str'>
|
||||
reveal_type(IntOrStrOrBytes1) # revealed: <types.UnionType special-form 'int | str | bytes'>
|
||||
reveal_type(IntOrStrOrBytes2) # revealed: <types.UnionType special-form 'int | str | bytes'>
|
||||
reveal_type(IntOrStrOrBytes3) # revealed: <types.UnionType special-form 'int | str | bytes'>
|
||||
reveal_type(IntOrStrOrBytes4) # revealed: <types.UnionType special-form 'int | str | bytes'>
|
||||
reveal_type(IntOrStrOrBytes5) # revealed: <types.UnionType special-form 'int | str | bytes'>
|
||||
reveal_type(IntOrStrOrBytes6) # revealed: <types.UnionType special-form 'int | str | bytes'>
|
||||
reveal_type(BytesOrIntOrStr) # revealed: <types.UnionType special-form 'bytes | int | str'>
|
||||
reveal_type(IntOrNone) # revealed: <types.UnionType special-form 'int | None'>
|
||||
reveal_type(NoneOrInt) # revealed: <types.UnionType special-form 'None | int'>
|
||||
reveal_type(IntOrStrOrNone) # revealed: <types.UnionType special-form 'int | str | None'>
|
||||
reveal_type(NoneOrIntOrStr) # revealed: <types.UnionType special-form 'None | int | str'>
|
||||
reveal_type(IntOrAny) # revealed: <types.UnionType special-form 'int | Any'>
|
||||
reveal_type(AnyOrInt) # revealed: <types.UnionType special-form 'Any | int'>
|
||||
reveal_type(NoneOrAny) # revealed: <types.UnionType special-form 'None | Any'>
|
||||
reveal_type(AnyOrNone) # revealed: <types.UnionType special-form 'Any | None'>
|
||||
reveal_type(NeverOrAny) # revealed: <types.UnionType special-form 'Any'>
|
||||
reveal_type(AnyOrNever) # revealed: <types.UnionType special-form 'Any'>
|
||||
reveal_type(UnknownOrInt) # revealed: <types.UnionType special-form 'Unknown | int'>
|
||||
reveal_type(IntOrUnknown) # revealed: <types.UnionType special-form 'int | Unknown'>
|
||||
reveal_type(StrOrZero) # revealed: <types.UnionType special-form 'str | Literal[0]'>
|
||||
reveal_type(ZeroOrStr) # revealed: <types.UnionType special-form 'Literal[0] | str'>
|
||||
reveal_type(IntOrLiteralString) # revealed: <types.UnionType special-form 'int | LiteralString'>
|
||||
reveal_type(LiteralStringOrInt) # revealed: <types.UnionType special-form 'LiteralString | int'>
|
||||
reveal_type(NoneOrTuple) # revealed: <types.UnionType special-form 'None | tuple[int, str]'>
|
||||
reveal_type(TupleOrNone) # revealed: <types.UnionType special-form 'tuple[int, str] | None'>
|
||||
reveal_type(IntOrAnnotated) # revealed: <types.UnionType special-form 'int | str'>
|
||||
reveal_type(AnnotatedOrInt) # revealed: <types.UnionType special-form 'str | int'>
|
||||
reveal_type(IntOrOptional) # revealed: <types.UnionType special-form 'int | str | None'>
|
||||
reveal_type(OptionalOrInt) # revealed: <types.UnionType special-form 'str | None | int'>
|
||||
reveal_type(IntOrTypeOfStr) # revealed: <types.UnionType special-form 'int | type[str]'>
|
||||
reveal_type(TypeOfStrOrInt) # revealed: <types.UnionType special-form 'type[str] | int'>
|
||||
reveal_type(IntOrCallable) # revealed: <types.UnionType special-form 'int | ((str, /) -> bytes)'>
|
||||
reveal_type(CallableOrInt) # revealed: <types.UnionType special-form '((str, /) -> bytes) | int'>
|
||||
reveal_type(TypeVarOrInt) # revealed: <types.UnionType special-form 'T@TypeVarOrInt | int'>
|
||||
reveal_type(IntOrTypeVar) # revealed: <types.UnionType special-form 'int | T@IntOrTypeVar'>
|
||||
reveal_type(TypeVarOrNone) # revealed: <types.UnionType special-form 'T@TypeVarOrNone | None'>
|
||||
reveal_type(NoneOrTypeVar) # revealed: <types.UnionType special-form 'None | T@NoneOrTypeVar'>
|
||||
|
||||
def _(
|
||||
int_or_str: IntOrStr,
|
||||
@@ -295,7 +295,7 @@ X = Foo | Bar
|
||||
# In an ideal world, perhaps we would respect `Meta.__or__` here and reveal `str`?
|
||||
# But we still need to record what the elements are, since (according to the typing spec)
|
||||
# `X` is still a valid type alias
|
||||
reveal_type(X) # revealed: <types.UnionType special form 'Foo | Bar'>
|
||||
reveal_type(X) # revealed: <types.UnionType special-form 'Foo | Bar'>
|
||||
|
||||
def f(obj: X):
|
||||
reveal_type(obj) # revealed: Foo | Bar
|
||||
@@ -391,17 +391,17 @@ MyOptional = T | None
|
||||
|
||||
reveal_type(MyList) # revealed: <class 'list[T@MyList]'>
|
||||
reveal_type(MyDict) # revealed: <class 'dict[T@MyDict, U@MyDict]'>
|
||||
reveal_type(MyType) # revealed: <special form 'type[T@MyType]'>
|
||||
reveal_type(MyType) # revealed: <special-form 'type[T@MyType]'>
|
||||
reveal_type(IntAndType) # revealed: <class 'tuple[int, T@IntAndType]'>
|
||||
reveal_type(Pair) # revealed: <class 'tuple[T@Pair, T@Pair]'>
|
||||
reveal_type(Sum) # revealed: <class 'tuple[T@Sum, U@Sum]'>
|
||||
reveal_type(ListOrTuple) # revealed: <types.UnionType special form 'list[T@ListOrTuple] | tuple[T@ListOrTuple, ...]'>
|
||||
# revealed: <types.UnionType special form 'list[T@ListOrTupleLegacy] | tuple[T@ListOrTupleLegacy, ...]'>
|
||||
reveal_type(ListOrTuple) # revealed: <types.UnionType special-form 'list[T@ListOrTuple] | tuple[T@ListOrTuple, ...]'>
|
||||
# revealed: <types.UnionType special-form 'list[T@ListOrTupleLegacy] | tuple[T@ListOrTupleLegacy, ...]'>
|
||||
reveal_type(ListOrTupleLegacy)
|
||||
reveal_type(MyCallable) # revealed: <typing.Callable special form '(**P@MyCallable) -> T@MyCallable'>
|
||||
reveal_type(AnnotatedType) # revealed: <special form 'typing.Annotated[T@AnnotatedType, <metadata>]'>
|
||||
reveal_type(TransparentAlias) # revealed: typing.TypeVar
|
||||
reveal_type(MyOptional) # revealed: <types.UnionType special form 'T@MyOptional | None'>
|
||||
reveal_type(MyCallable) # revealed: <typing.Callable special-form '(**P@MyCallable) -> T@MyCallable'>
|
||||
reveal_type(AnnotatedType) # revealed: <special-form 'typing.Annotated[T@AnnotatedType, <metadata>]'>
|
||||
reveal_type(TransparentAlias) # revealed: TypeVar
|
||||
reveal_type(MyOptional) # revealed: <types.UnionType special-form 'T@MyOptional | None'>
|
||||
|
||||
def _(
|
||||
list_of_ints: MyList[int],
|
||||
@@ -457,13 +457,13 @@ AnnotatedInt = AnnotatedType[int]
|
||||
SubclassOfInt = MyType[int]
|
||||
CallableIntToStr = MyCallable[[int], str]
|
||||
|
||||
reveal_type(IntsOrNone) # revealed: <types.UnionType special form 'list[int] | None'>
|
||||
reveal_type(IntsOrStrs) # revealed: <types.UnionType special form 'tuple[int, int] | tuple[str, str]'>
|
||||
reveal_type(IntsOrNone) # revealed: <types.UnionType special-form 'list[int] | None'>
|
||||
reveal_type(IntsOrStrs) # revealed: <types.UnionType special-form 'tuple[int, int] | tuple[str, str]'>
|
||||
reveal_type(ListOfPairs) # revealed: <class 'list[tuple[str, str]]'>
|
||||
reveal_type(ListOrTupleOfInts) # revealed: <types.UnionType special form 'list[int] | tuple[int, ...]'>
|
||||
reveal_type(AnnotatedInt) # revealed: <special form 'typing.Annotated[int, <metadata>]'>
|
||||
reveal_type(SubclassOfInt) # revealed: <special form 'type[int]'>
|
||||
reveal_type(CallableIntToStr) # revealed: <typing.Callable special form '(int, /) -> str'>
|
||||
reveal_type(ListOrTupleOfInts) # revealed: <types.UnionType special-form 'list[int] | tuple[int, ...]'>
|
||||
reveal_type(AnnotatedInt) # revealed: <special-form 'typing.Annotated[int, <metadata>]'>
|
||||
reveal_type(SubclassOfInt) # revealed: <special-form 'type[int]'>
|
||||
reveal_type(CallableIntToStr) # revealed: <typing.Callable special-form '(int, /) -> str'>
|
||||
|
||||
def _(
|
||||
ints_or_none: IntsOrNone,
|
||||
@@ -495,8 +495,8 @@ MyOtherType = MyType[T]
|
||||
TypeOrList = MyType[B] | MyList[B]
|
||||
|
||||
reveal_type(MyOtherList) # revealed: <class 'list[T@MyOtherList]'>
|
||||
reveal_type(MyOtherType) # revealed: <special form 'type[T@MyOtherType]'>
|
||||
reveal_type(TypeOrList) # revealed: <types.UnionType special form 'type[B@TypeOrList] | list[B@TypeOrList]'>
|
||||
reveal_type(MyOtherType) # revealed: <special-form 'type[T@MyOtherType]'>
|
||||
reveal_type(TypeOrList) # revealed: <types.UnionType special-form 'type[B@TypeOrList] | list[B@TypeOrList]'>
|
||||
|
||||
def _(
|
||||
list_of_ints: MyOtherList[int],
|
||||
@@ -730,7 +730,7 @@ def _(doubly_specialized: DictInt[int]):
|
||||
|
||||
Union = list[str] | list[int]
|
||||
|
||||
# error: [non-subscriptable] "Cannot subscript non-generic type: `<types.UnionType special form 'list[str] | list[int]'>` is already specialized"
|
||||
# error: [non-subscriptable] "Cannot subscript non-generic type: `<types.UnionType special-form 'list[str] | list[int]'>` is already specialized"
|
||||
def _(doubly_specialized: Union[int]):
|
||||
reveal_type(doubly_specialized) # revealed: Unknown
|
||||
|
||||
@@ -974,7 +974,7 @@ from typing import Optional
|
||||
|
||||
MyOptionalInt = Optional[int]
|
||||
|
||||
reveal_type(MyOptionalInt) # revealed: <types.UnionType special form 'int | None'>
|
||||
reveal_type(MyOptionalInt) # revealed: <types.UnionType special-form 'int | None'>
|
||||
|
||||
def _(optional_int: MyOptionalInt):
|
||||
reveal_type(optional_int) # revealed: int | None
|
||||
@@ -1007,9 +1007,9 @@ MyLiteralString = LiteralString
|
||||
MyNoReturn = NoReturn
|
||||
MyNever = Never
|
||||
|
||||
reveal_type(MyLiteralString) # revealed: <special form 'typing.LiteralString'>
|
||||
reveal_type(MyNoReturn) # revealed: <special form 'typing.NoReturn'>
|
||||
reveal_type(MyNever) # revealed: <special form 'typing.Never'>
|
||||
reveal_type(MyLiteralString) # revealed: <special-form 'typing.LiteralString'>
|
||||
reveal_type(MyNoReturn) # revealed: <special-form 'typing.NoReturn'>
|
||||
reveal_type(MyNever) # revealed: <special-form 'typing.Never'>
|
||||
|
||||
def _(
|
||||
ls: MyLiteralString,
|
||||
@@ -1062,8 +1062,8 @@ from typing import Union
|
||||
IntOrStr = Union[int, str]
|
||||
IntOrStrOrBytes = Union[int, Union[str, bytes]]
|
||||
|
||||
reveal_type(IntOrStr) # revealed: <types.UnionType special form 'int | str'>
|
||||
reveal_type(IntOrStrOrBytes) # revealed: <types.UnionType special form 'int | str | bytes'>
|
||||
reveal_type(IntOrStr) # revealed: <types.UnionType special-form 'int | str'>
|
||||
reveal_type(IntOrStrOrBytes) # revealed: <types.UnionType special-form 'int | str | bytes'>
|
||||
|
||||
def _(
|
||||
int_or_str: IntOrStr,
|
||||
@@ -1091,7 +1091,7 @@ An empty `typing.Union` leads to a `TypeError` at runtime, so we emit an error.
|
||||
# error: [invalid-type-form] "`typing.Union` requires at least one type argument"
|
||||
EmptyUnion = Union[()]
|
||||
|
||||
reveal_type(EmptyUnion) # revealed: <types.UnionType special form 'Never'>
|
||||
reveal_type(EmptyUnion) # revealed: <types.UnionType special-form 'Never'>
|
||||
|
||||
def _(empty: EmptyUnion):
|
||||
reveal_type(empty) # revealed: Never
|
||||
@@ -1136,14 +1136,14 @@ SubclassOfG = type[G]
|
||||
SubclassOfGInt = type[G[int]]
|
||||
SubclassOfP = type[P]
|
||||
|
||||
reveal_type(SubclassOfA) # revealed: <special form 'type[A]'>
|
||||
reveal_type(SubclassOfAny) # revealed: <special form 'type[Any]'>
|
||||
reveal_type(SubclassOfAOrB1) # revealed: <special form 'type[A | B]'>
|
||||
reveal_type(SubclassOfAOrB2) # revealed: <types.UnionType special form 'type[A] | type[B]'>
|
||||
reveal_type(SubclassOfAOrB3) # revealed: <types.UnionType special form 'type[A] | type[B]'>
|
||||
reveal_type(SubclassOfG) # revealed: <special form 'type[G[Unknown]]'>
|
||||
reveal_type(SubclassOfGInt) # revealed: <special form 'type[G[int]]'>
|
||||
reveal_type(SubclassOfP) # revealed: <special form 'type[P]'>
|
||||
reveal_type(SubclassOfA) # revealed: <special-form 'type[A]'>
|
||||
reveal_type(SubclassOfAny) # revealed: <special-form 'type[Any]'>
|
||||
reveal_type(SubclassOfAOrB1) # revealed: <special-form 'type[A | B]'>
|
||||
reveal_type(SubclassOfAOrB2) # revealed: <types.UnionType special-form 'type[A] | type[B]'>
|
||||
reveal_type(SubclassOfAOrB3) # revealed: <types.UnionType special-form 'type[A] | type[B]'>
|
||||
reveal_type(SubclassOfG) # revealed: <special-form 'type[G[Unknown]]'>
|
||||
reveal_type(SubclassOfGInt) # revealed: <special-form 'type[G[int]]'>
|
||||
reveal_type(SubclassOfP) # revealed: <special-form 'type[P]'>
|
||||
|
||||
def _(
|
||||
subclass_of_a: SubclassOfA,
|
||||
@@ -1224,14 +1224,14 @@ SubclassOfG = Type[G]
|
||||
SubclassOfGInt = Type[G[int]]
|
||||
SubclassOfP = Type[P]
|
||||
|
||||
reveal_type(SubclassOfA) # revealed: <special form 'type[A]'>
|
||||
reveal_type(SubclassOfAny) # revealed: <special form 'type[Any]'>
|
||||
reveal_type(SubclassOfAOrB1) # revealed: <special form 'type[A | B]'>
|
||||
reveal_type(SubclassOfAOrB2) # revealed: <types.UnionType special form 'type[A] | type[B]'>
|
||||
reveal_type(SubclassOfAOrB3) # revealed: <types.UnionType special form 'type[A] | type[B]'>
|
||||
reveal_type(SubclassOfG) # revealed: <special form 'type[G[Unknown]]'>
|
||||
reveal_type(SubclassOfGInt) # revealed: <special form 'type[G[int]]'>
|
||||
reveal_type(SubclassOfP) # revealed: <special form 'type[P]'>
|
||||
reveal_type(SubclassOfA) # revealed: <special-form 'type[A]'>
|
||||
reveal_type(SubclassOfAny) # revealed: <special-form 'type[Any]'>
|
||||
reveal_type(SubclassOfAOrB1) # revealed: <special-form 'type[A | B]'>
|
||||
reveal_type(SubclassOfAOrB2) # revealed: <types.UnionType special-form 'type[A] | type[B]'>
|
||||
reveal_type(SubclassOfAOrB3) # revealed: <types.UnionType special-form 'type[A] | type[B]'>
|
||||
reveal_type(SubclassOfG) # revealed: <special-form 'type[G[Unknown]]'>
|
||||
reveal_type(SubclassOfGInt) # revealed: <special-form 'type[G[int]]'>
|
||||
reveal_type(SubclassOfP) # revealed: <special-form 'type[P]'>
|
||||
|
||||
def _(
|
||||
subclass_of_a: SubclassOfA,
|
||||
@@ -1346,25 +1346,25 @@ DefaultDictOrNone = DefaultDict[str, int] | None
|
||||
DequeOrNone = Deque[str] | None
|
||||
OrderedDictOrNone = OrderedDict[str, int] | None
|
||||
|
||||
reveal_type(NoneOrList) # revealed: <types.UnionType special form 'None | list[str]'>
|
||||
reveal_type(NoneOrSet) # revealed: <types.UnionType special form 'None | set[str]'>
|
||||
reveal_type(NoneOrDict) # revealed: <types.UnionType special form 'None | dict[str, int]'>
|
||||
reveal_type(NoneOrFrozenSet) # revealed: <types.UnionType special form 'None | frozenset[str]'>
|
||||
reveal_type(NoneOrChainMap) # revealed: <types.UnionType special form 'None | ChainMap[str, int]'>
|
||||
reveal_type(NoneOrCounter) # revealed: <types.UnionType special form 'None | Counter[str]'>
|
||||
reveal_type(NoneOrDefaultDict) # revealed: <types.UnionType special form 'None | defaultdict[str, int]'>
|
||||
reveal_type(NoneOrDeque) # revealed: <types.UnionType special form 'None | deque[str]'>
|
||||
reveal_type(NoneOrOrderedDict) # revealed: <types.UnionType special form 'None | OrderedDict[str, int]'>
|
||||
reveal_type(NoneOrList) # revealed: <types.UnionType special-form 'None | list[str]'>
|
||||
reveal_type(NoneOrSet) # revealed: <types.UnionType special-form 'None | set[str]'>
|
||||
reveal_type(NoneOrDict) # revealed: <types.UnionType special-form 'None | dict[str, int]'>
|
||||
reveal_type(NoneOrFrozenSet) # revealed: <types.UnionType special-form 'None | frozenset[str]'>
|
||||
reveal_type(NoneOrChainMap) # revealed: <types.UnionType special-form 'None | ChainMap[str, int]'>
|
||||
reveal_type(NoneOrCounter) # revealed: <types.UnionType special-form 'None | Counter[str]'>
|
||||
reveal_type(NoneOrDefaultDict) # revealed: <types.UnionType special-form 'None | defaultdict[str, int]'>
|
||||
reveal_type(NoneOrDeque) # revealed: <types.UnionType special-form 'None | deque[str]'>
|
||||
reveal_type(NoneOrOrderedDict) # revealed: <types.UnionType special-form 'None | OrderedDict[str, int]'>
|
||||
|
||||
reveal_type(ListOrNone) # revealed: <types.UnionType special form 'list[int] | None'>
|
||||
reveal_type(SetOrNone) # revealed: <types.UnionType special form 'set[int] | None'>
|
||||
reveal_type(DictOrNone) # revealed: <types.UnionType special form 'dict[str, int] | None'>
|
||||
reveal_type(FrozenSetOrNone) # revealed: <types.UnionType special form 'frozenset[int] | None'>
|
||||
reveal_type(ChainMapOrNone) # revealed: <types.UnionType special form 'ChainMap[str, int] | None'>
|
||||
reveal_type(CounterOrNone) # revealed: <types.UnionType special form 'Counter[str] | None'>
|
||||
reveal_type(DefaultDictOrNone) # revealed: <types.UnionType special form 'defaultdict[str, int] | None'>
|
||||
reveal_type(DequeOrNone) # revealed: <types.UnionType special form 'deque[str] | None'>
|
||||
reveal_type(OrderedDictOrNone) # revealed: <types.UnionType special form 'OrderedDict[str, int] | None'>
|
||||
reveal_type(ListOrNone) # revealed: <types.UnionType special-form 'list[int] | None'>
|
||||
reveal_type(SetOrNone) # revealed: <types.UnionType special-form 'set[int] | None'>
|
||||
reveal_type(DictOrNone) # revealed: <types.UnionType special-form 'dict[str, int] | None'>
|
||||
reveal_type(FrozenSetOrNone) # revealed: <types.UnionType special-form 'frozenset[int] | None'>
|
||||
reveal_type(ChainMapOrNone) # revealed: <types.UnionType special-form 'ChainMap[str, int] | None'>
|
||||
reveal_type(CounterOrNone) # revealed: <types.UnionType special-form 'Counter[str] | None'>
|
||||
reveal_type(DefaultDictOrNone) # revealed: <types.UnionType special-form 'defaultdict[str, int] | None'>
|
||||
reveal_type(DequeOrNone) # revealed: <types.UnionType special-form 'deque[str] | None'>
|
||||
reveal_type(OrderedDictOrNone) # revealed: <types.UnionType special-form 'OrderedDict[str, int] | None'>
|
||||
|
||||
def _(
|
||||
none_or_list: NoneOrList,
|
||||
@@ -1457,9 +1457,9 @@ CallableNoArgs = Callable[[], None]
|
||||
BasicCallable = Callable[[int, str], bytes]
|
||||
GradualCallable = Callable[..., str]
|
||||
|
||||
reveal_type(CallableNoArgs) # revealed: <typing.Callable special form '() -> None'>
|
||||
reveal_type(BasicCallable) # revealed: <typing.Callable special form '(int, str, /) -> bytes'>
|
||||
reveal_type(GradualCallable) # revealed: <typing.Callable special form '(...) -> str'>
|
||||
reveal_type(CallableNoArgs) # revealed: <typing.Callable special-form '() -> None'>
|
||||
reveal_type(BasicCallable) # revealed: <typing.Callable special-form '(int, str, /) -> bytes'>
|
||||
reveal_type(GradualCallable) # revealed: <typing.Callable special-form '(...) -> str'>
|
||||
|
||||
def _(
|
||||
callable_no_args: CallableNoArgs,
|
||||
@@ -1491,8 +1491,8 @@ InvalidCallable1 = Callable[[int]]
|
||||
# error: [invalid-type-form] "The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...`"
|
||||
InvalidCallable2 = Callable[int, str]
|
||||
|
||||
reveal_type(InvalidCallable1) # revealed: <typing.Callable special form '(...) -> Unknown'>
|
||||
reveal_type(InvalidCallable2) # revealed: <typing.Callable special form '(...) -> Unknown'>
|
||||
reveal_type(InvalidCallable1) # revealed: <typing.Callable special-form '(...) -> Unknown'>
|
||||
reveal_type(InvalidCallable2) # revealed: <typing.Callable special-form '(...) -> Unknown'>
|
||||
|
||||
def _(invalid_callable1: InvalidCallable1, invalid_callable2: InvalidCallable2):
|
||||
reveal_type(invalid_callable1) # revealed: (...) -> Unknown
|
||||
|
||||
@@ -53,8 +53,8 @@ in `import os.path as os.path` the `os.path` is not a valid identifier.
|
||||
```py
|
||||
from b import Any, Literal, foo
|
||||
|
||||
reveal_type(Any) # revealed: <special form 'typing.Any'>
|
||||
reveal_type(Literal) # revealed: <special form 'typing.Literal'>
|
||||
reveal_type(Any) # revealed: <special-form 'typing.Any'>
|
||||
reveal_type(Literal) # revealed: <special-form 'typing.Literal'>
|
||||
reveal_type(foo) # revealed: <module 'foo'>
|
||||
```
|
||||
|
||||
@@ -132,7 +132,7 @@ reveal_type(Any) # revealed: Unknown
|
||||
```pyi
|
||||
from typing import Any
|
||||
|
||||
reveal_type(Any) # revealed: <special form 'typing.Any'>
|
||||
reveal_type(Any) # revealed: <special-form 'typing.Any'>
|
||||
```
|
||||
|
||||
## Nested mixed re-export and not
|
||||
@@ -169,7 +169,7 @@ reveal_type(Any) # revealed: Unknown
|
||||
```pyi
|
||||
from typing import Any
|
||||
|
||||
reveal_type(Any) # revealed: <special form 'typing.Any'>
|
||||
reveal_type(Any) # revealed: <special-form 'typing.Any'>
|
||||
```
|
||||
|
||||
## Exported as different name
|
||||
|
||||
@@ -1437,7 +1437,7 @@ are present due to `*` imports.
|
||||
import collections.abc
|
||||
|
||||
reveal_type(collections.abc.Sequence) # revealed: <class 'Sequence'>
|
||||
reveal_type(collections.abc.Callable) # revealed: <special form 'typing.Callable'>
|
||||
reveal_type(collections.abc.Callable) # revealed: <special-form 'typing.Callable'>
|
||||
reveal_type(collections.abc.Set) # revealed: <class 'AbstractSet'>
|
||||
```
|
||||
|
||||
|
||||
@@ -304,7 +304,7 @@ x11: list[Literal[1] | Literal[2] | Literal[3]] = [1, 2, 3]
|
||||
reveal_type(x11) # revealed: list[Literal[1, 2, 3]]
|
||||
|
||||
x12: Y[Y[Literal[1]]] = [[1]]
|
||||
reveal_type(x12) # revealed: list[Y[Literal[1]]]
|
||||
reveal_type(x12) # revealed: list[list[Literal[1]]]
|
||||
|
||||
x13: list[tuple[Literal[1], Literal[2], Literal[3]]] = [(1, 2, 3)]
|
||||
reveal_type(x13) # revealed: list[tuple[Literal[1], Literal[2], Literal[3]]]
|
||||
|
||||
@@ -301,7 +301,7 @@ class B: ...
|
||||
|
||||
EitherOr = A | B
|
||||
|
||||
# error: [invalid-base] "Invalid class base with type `<types.UnionType special form 'A | B'>`"
|
||||
# error: [invalid-base] "Invalid class base with type `<types.UnionType special-form 'A | B'>`"
|
||||
class Foo(EitherOr): ...
|
||||
```
|
||||
|
||||
|
||||
@@ -156,7 +156,7 @@ from typing import Union
|
||||
|
||||
IntOrStr = Union[int, str]
|
||||
|
||||
reveal_type(IntOrStr) # revealed: <types.UnionType special form 'int | str'>
|
||||
reveal_type(IntOrStr) # revealed: <types.UnionType special-form 'int | str'>
|
||||
|
||||
def _(x: int | str | bytes | memoryview | range):
|
||||
if isinstance(x, IntOrStr):
|
||||
|
||||
@@ -209,7 +209,7 @@ from typing import Union
|
||||
|
||||
IntOrStr = Union[int, str]
|
||||
|
||||
reveal_type(IntOrStr) # revealed: <types.UnionType special form 'int | str'>
|
||||
reveal_type(IntOrStr) # revealed: <types.UnionType special-form 'int | str'>
|
||||
|
||||
def f(x: type[int | str | bytes | range]):
|
||||
if issubclass(x, IntOrStr):
|
||||
|
||||
@@ -418,6 +418,18 @@ Using the `@abstractmethod` decorator requires that the class's metaclass is `AB
|
||||
from it.
|
||||
|
||||
```py
|
||||
from abc import ABCMeta
|
||||
|
||||
class CustomAbstractMetaclass(ABCMeta): ...
|
||||
|
||||
class Fine(metaclass=CustomAbstractMetaclass):
|
||||
@overload
|
||||
@abstractmethod
|
||||
def f(self, x: int) -> int: ...
|
||||
@overload
|
||||
@abstractmethod
|
||||
def f(self, x: str) -> str: ...
|
||||
|
||||
class Foo:
|
||||
@overload
|
||||
@abstractmethod
|
||||
@@ -448,6 +460,52 @@ class PartialFoo(ABC):
|
||||
def f(self, x: str) -> str: ...
|
||||
```
|
||||
|
||||
#### `TYPE_CHECKING` blocks
|
||||
|
||||
As in other areas of ty, we treat `TYPE_CHECKING` blocks the same as "inline stub files", so we
|
||||
permit overloaded functions to exist without an implementation if all overloads are defined inside
|
||||
an `if TYPE_CHECKING` block:
|
||||
|
||||
```py
|
||||
from typing import overload, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@overload
|
||||
def a() -> str: ...
|
||||
@overload
|
||||
def a(x: int) -> int: ...
|
||||
|
||||
class F:
|
||||
@overload
|
||||
def method(self) -> None: ...
|
||||
@overload
|
||||
def method(self, x: int) -> int: ...
|
||||
|
||||
class G:
|
||||
if TYPE_CHECKING:
|
||||
@overload
|
||||
def method(self) -> None: ...
|
||||
@overload
|
||||
def method(self, x: int) -> int: ...
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@overload
|
||||
def b() -> str: ...
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@overload
|
||||
def b(x: int) -> int: ...
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@overload
|
||||
def c() -> None: ...
|
||||
|
||||
# not all overloads are in a `TYPE_CHECKING` block, so this is an error
|
||||
@overload
|
||||
# error: [invalid-overload]
|
||||
def c(x: int) -> int: ...
|
||||
```
|
||||
|
||||
### `@overload`-decorated functions with non-stub bodies
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
@@ -113,7 +113,7 @@ MyList: TypeAlias = list[T]
|
||||
ListOrSet: TypeAlias = list[T] | set[T]
|
||||
|
||||
reveal_type(MyList) # revealed: <class 'list[T]'>
|
||||
reveal_type(ListOrSet) # revealed: <types.UnionType special form 'list[T] | set[T]'>
|
||||
reveal_type(ListOrSet) # revealed: <types.UnionType special-form 'list[T] | set[T]'>
|
||||
|
||||
def _(list_of_int: MyList[int], list_or_set_of_str: ListOrSet[str]):
|
||||
reveal_type(list_of_int) # revealed: list[int]
|
||||
@@ -293,7 +293,7 @@ def _(rec: RecursiveHomogeneousTuple):
|
||||
reveal_type(rec) # revealed: tuple[Divergent, ...]
|
||||
|
||||
ClassInfo: TypeAlias = type | UnionType | tuple["ClassInfo", ...]
|
||||
reveal_type(ClassInfo) # revealed: <types.UnionType special form 'type | UnionType | tuple[Divergent, ...]'>
|
||||
reveal_type(ClassInfo) # revealed: <types.UnionType special-form 'type | UnionType | tuple[Divergent, ...]'>
|
||||
|
||||
def my_isinstance(obj: object, classinfo: ClassInfo) -> bool:
|
||||
# TODO should be `type | UnionType | tuple[ClassInfo, ...]`
|
||||
|
||||
@@ -12,7 +12,7 @@ python-version = "3.12"
|
||||
```py
|
||||
type IntOrStr = int | str
|
||||
|
||||
reveal_type(IntOrStr) # revealed: typing.TypeAliasType
|
||||
reveal_type(IntOrStr) # revealed: TypeAliasType
|
||||
reveal_type(IntOrStr.__name__) # revealed: Literal["IntOrStr"]
|
||||
|
||||
x: IntOrStr = 1
|
||||
@@ -205,7 +205,7 @@ from typing_extensions import TypeAliasType, Union
|
||||
|
||||
IntOrStr = TypeAliasType("IntOrStr", Union[int, str])
|
||||
|
||||
reveal_type(IntOrStr) # revealed: typing.TypeAliasType
|
||||
reveal_type(IntOrStr) # revealed: TypeAliasType
|
||||
|
||||
reveal_type(IntOrStr.__name__) # revealed: Literal["IntOrStr"]
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ diagnostic message for `invalid-exception-caught` expects to construct `typing.P
|
||||
def foo[**P]() -> None:
|
||||
try:
|
||||
pass
|
||||
# error: [invalid-exception-caught] "Invalid object caught in an exception handler: Object has type `typing.ParamSpec`"
|
||||
# error: [invalid-exception-caught] "Invalid object caught in an exception handler: Object has type `ParamSpec`"
|
||||
except P:
|
||||
pass
|
||||
```
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
# Implicit class body attributes
|
||||
|
||||
## Class body implicit attributes
|
||||
|
||||
Python makes certain names available implicitly inside class body scopes. These are `__qualname__`,
|
||||
`__module__`, and `__doc__`, as documented at
|
||||
<https://docs.python.org/3/reference/datamodel.html#creating-the-class-object>.
|
||||
|
||||
```py
|
||||
class Foo:
|
||||
reveal_type(__qualname__) # revealed: str
|
||||
reveal_type(__module__) # revealed: str
|
||||
reveal_type(__doc__) # revealed: str | None
|
||||
```
|
||||
|
||||
## `__firstlineno__` (Python 3.13+)
|
||||
|
||||
Python 3.13 added `__firstlineno__` to the class body namespace.
|
||||
|
||||
### Available in Python 3.13+
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.13"
|
||||
```
|
||||
|
||||
```py
|
||||
class Foo:
|
||||
reveal_type(__firstlineno__) # revealed: int
|
||||
```
|
||||
|
||||
### Not available in Python 3.12 and earlier
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
class Foo:
|
||||
# error: [unresolved-reference]
|
||||
__firstlineno__
|
||||
```
|
||||
|
||||
## Nested classes
|
||||
|
||||
These implicit attributes are also available in nested classes, and refer to the nested class:
|
||||
|
||||
```py
|
||||
class Outer:
|
||||
class Inner:
|
||||
reveal_type(__qualname__) # revealed: str
|
||||
reveal_type(__module__) # revealed: str
|
||||
```
|
||||
|
||||
## Class body implicit attributes have priority over globals
|
||||
|
||||
If a global variable with the same name exists, the class body implicit attribute takes priority
|
||||
within the class body:
|
||||
|
||||
```py
|
||||
__qualname__ = 42
|
||||
__module__ = 42
|
||||
|
||||
class Foo:
|
||||
# Inside the class body, these are the implicit class attributes
|
||||
reveal_type(__qualname__) # revealed: str
|
||||
reveal_type(__module__) # revealed: str
|
||||
|
||||
# Outside the class, the globals are visible
|
||||
reveal_type(__qualname__) # revealed: Literal[42]
|
||||
reveal_type(__module__) # revealed: Literal[42]
|
||||
```
|
||||
|
||||
## `__firstlineno__` has priority over globals (Python 3.13+)
|
||||
|
||||
The same applies to `__firstlineno__` on Python 3.13+:
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.13"
|
||||
```
|
||||
|
||||
```py
|
||||
__firstlineno__ = "not an int"
|
||||
|
||||
class Foo:
|
||||
reveal_type(__firstlineno__) # revealed: int
|
||||
|
||||
reveal_type(__firstlineno__) # revealed: Literal["not an int"]
|
||||
```
|
||||
|
||||
## Class body implicit attributes are not visible in methods
|
||||
|
||||
The implicit class body attributes are only available directly in the class body, not in nested
|
||||
function scopes (methods):
|
||||
|
||||
```py
|
||||
class Foo:
|
||||
# Available directly in the class body
|
||||
x = __qualname__
|
||||
reveal_type(x) # revealed: str
|
||||
|
||||
def method(self):
|
||||
# Not available in methods - falls back to builtins/globals
|
||||
# error: [unresolved-reference]
|
||||
__qualname__
|
||||
```
|
||||
|
||||
## Real-world use case: logging
|
||||
|
||||
A common use case is defining a logger with the class name:
|
||||
|
||||
```py
|
||||
import logging
|
||||
|
||||
class MyClass:
|
||||
logger = logging.getLogger(__qualname__)
|
||||
reveal_type(logger) # revealed: Logger
|
||||
```
|
||||
@@ -97,7 +97,7 @@ inside the module:
|
||||
import typing
|
||||
|
||||
reveal_type(typing.__name__) # revealed: str
|
||||
reveal_type(typing.__init__) # revealed: bound method ModuleType.__init__(name: str, doc: str | None = EllipsisType) -> None
|
||||
reveal_type(typing.__init__) # revealed: bound method ModuleType.__init__(name: str, doc: str | None = ...) -> None
|
||||
|
||||
# For a stub module, we don't know that `__file__` is a string (at runtime it may be entirely
|
||||
# unset, but we follow typeshed here):
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: dataclasses.md - Dataclasses - Other dataclass parameters - frozen/non-frozen inheritance
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/dataclasses/dataclasses.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## a.py
|
||||
|
||||
```
|
||||
1 | from dataclasses import dataclass
|
||||
2 |
|
||||
3 | @dataclass(frozen=True)
|
||||
4 | class FrozenBase:
|
||||
5 | x: int
|
||||
6 |
|
||||
7 | @dataclass
|
||||
8 | # error: [invalid-frozen-dataclass-subclass] "Non-frozen dataclass `Child` cannot inherit from frozen dataclass `FrozenBase`"
|
||||
9 | class Child(FrozenBase):
|
||||
10 | y: int
|
||||
```
|
||||
|
||||
## b.py
|
||||
|
||||
```
|
||||
1 | from dataclasses import dataclass
|
||||
2 |
|
||||
3 | @dataclass
|
||||
4 | class Base:
|
||||
5 | x: int
|
||||
6 |
|
||||
7 | @dataclass(frozen=True)
|
||||
8 | # error: [invalid-frozen-dataclass-subclass] "Frozen dataclass `FrozenChild` cannot inherit from non-frozen dataclass `Base`"
|
||||
9 | class FrozenChild(Base):
|
||||
10 | y: int
|
||||
```
|
||||
|
||||
## module.py
|
||||
|
||||
```
|
||||
1 | import dataclasses
|
||||
2 |
|
||||
3 | @dataclasses.dataclass(frozen=False)
|
||||
4 | class NotFrozenBase:
|
||||
5 | x: int
|
||||
```
|
||||
|
||||
## main.py
|
||||
|
||||
```
|
||||
1 | from functools import total_ordering
|
||||
2 | from typing import final
|
||||
3 | from dataclasses import dataclass
|
||||
4 |
|
||||
5 | from module import NotFrozenBase
|
||||
6 |
|
||||
7 | @final
|
||||
8 | @dataclass(frozen=True)
|
||||
9 | @total_ordering
|
||||
10 | class FrozenChild(NotFrozenBase): # error: [invalid-frozen-dataclass-subclass]
|
||||
11 | y: str
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-frozen-dataclass-subclass]: Non-frozen dataclass cannot inherit from frozen dataclass
|
||||
--> src/a.py:7:1
|
||||
|
|
||||
5 | x: int
|
||||
6 |
|
||||
7 | @dataclass
|
||||
| ---------- `Child` dataclass parameters
|
||||
8 | # error: [invalid-frozen-dataclass-subclass] "Non-frozen dataclass `Child` cannot inherit from frozen dataclass `FrozenBase`"
|
||||
9 | class Child(FrozenBase):
|
||||
| ^^^^^^----------^ Subclass `Child` is not frozen but base class `FrozenBase` is
|
||||
10 | y: int
|
||||
|
|
||||
info: This causes the class creation to fail
|
||||
info: Base class definition
|
||||
--> src/a.py:3:1
|
||||
|
|
||||
1 | from dataclasses import dataclass
|
||||
2 |
|
||||
3 | @dataclass(frozen=True)
|
||||
| ----------------------- `FrozenBase` dataclass parameters
|
||||
4 | class FrozenBase:
|
||||
| ^^^^^^^^^^ `FrozenBase` definition
|
||||
5 | x: int
|
||||
|
|
||||
info: rule `invalid-frozen-dataclass-subclass` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-frozen-dataclass-subclass]: Frozen dataclass cannot inherit from non-frozen dataclass
|
||||
--> src/b.py:7:1
|
||||
|
|
||||
5 | x: int
|
||||
6 |
|
||||
7 | @dataclass(frozen=True)
|
||||
| ----------------------- `FrozenChild` dataclass parameters
|
||||
8 | # error: [invalid-frozen-dataclass-subclass] "Frozen dataclass `FrozenChild` cannot inherit from non-frozen dataclass `Base`"
|
||||
9 | class FrozenChild(Base):
|
||||
| ^^^^^^^^^^^^----^ Subclass `FrozenChild` is frozen but base class `Base` is not
|
||||
10 | y: int
|
||||
|
|
||||
info: This causes the class creation to fail
|
||||
info: Base class definition
|
||||
--> src/b.py:3:1
|
||||
|
|
||||
1 | from dataclasses import dataclass
|
||||
2 |
|
||||
3 | @dataclass
|
||||
| ---------- `Base` dataclass parameters
|
||||
4 | class Base:
|
||||
| ^^^^ `Base` definition
|
||||
5 | x: int
|
||||
|
|
||||
info: rule `invalid-frozen-dataclass-subclass` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-frozen-dataclass-subclass]: Frozen dataclass cannot inherit from non-frozen dataclass
|
||||
--> src/main.py:8:1
|
||||
|
|
||||
7 | @final
|
||||
8 | @dataclass(frozen=True)
|
||||
| ----------------------- `FrozenChild` dataclass parameters
|
||||
9 | @total_ordering
|
||||
10 | class FrozenChild(NotFrozenBase): # error: [invalid-frozen-dataclass-subclass]
|
||||
| ^^^^^^^^^^^^-------------^ Subclass `FrozenChild` is frozen but base class `NotFrozenBase` is not
|
||||
11 | y: str
|
||||
|
|
||||
info: This causes the class creation to fail
|
||||
info: Base class definition
|
||||
--> src/module.py:3:1
|
||||
|
|
||||
1 | import dataclasses
|
||||
2 |
|
||||
3 | @dataclasses.dataclass(frozen=False)
|
||||
| ------------------------------------ `NotFrozenBase` dataclass parameters
|
||||
4 | class NotFrozenBase:
|
||||
| ^^^^^^^^^^^^^ `NotFrozenBase` definition
|
||||
5 | x: int
|
||||
|
|
||||
info: rule `invalid-frozen-dataclass-subclass` is enabled by default
|
||||
|
||||
```
|
||||
@@ -15,9 +15,9 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/deprecated.md
|
||||
1 | from typing_extensions import deprecated
|
||||
2 |
|
||||
3 | @deprecated("use OtherClass")
|
||||
4 | def myfunc(): ...
|
||||
4 | def myfunc(x: int): ...
|
||||
5 |
|
||||
6 | myfunc() # error: [deprecated] "use OtherClass"
|
||||
6 | myfunc(1) # error: [deprecated] "use OtherClass"
|
||||
7 | from typing_extensions import deprecated
|
||||
8 |
|
||||
9 | @deprecated("use BetterClass")
|
||||
@@ -42,9 +42,9 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/deprecated.md
|
||||
warning[deprecated]: The function `myfunc` is deprecated
|
||||
--> src/mdtest_snippet.py:6:1
|
||||
|
|
||||
4 | def myfunc(): ...
|
||||
4 | def myfunc(x: int): ...
|
||||
5 |
|
||||
6 | myfunc() # error: [deprecated] "use OtherClass"
|
||||
6 | myfunc(1) # error: [deprecated] "use OtherClass"
|
||||
| ^^^^^^ use OtherClass
|
||||
7 | from typing_extensions import deprecated
|
||||
|
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: invalid_type_parameter_order.md - Invalid Order of Legacy Type Parameters
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_type_parameter_order.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing import TypeVar, Generic, Protocol
|
||||
2 |
|
||||
3 | T1 = TypeVar("T1", default=int)
|
||||
4 |
|
||||
5 | T2 = TypeVar("T2")
|
||||
6 | T3 = TypeVar("T3")
|
||||
7 |
|
||||
8 | DefaultStrT = TypeVar("DefaultStrT", default=str)
|
||||
9 |
|
||||
10 | class SubclassMe(Generic[T1, DefaultStrT]):
|
||||
11 | x: DefaultStrT
|
||||
12 |
|
||||
13 | class Baz(SubclassMe[int, DefaultStrT]):
|
||||
14 | pass
|
||||
15 |
|
||||
16 | # error: [invalid-generic-class] "Type parameter `T2` without a default cannot follow earlier parameter `T1` with a default"
|
||||
17 | class Foo(Generic[T1, T2]):
|
||||
18 | pass
|
||||
19 |
|
||||
20 | class Bar(Generic[T2, T1, T3]): # error: [invalid-generic-class]
|
||||
21 | pass
|
||||
22 |
|
||||
23 | class Spam(Generic[T1, T2, DefaultStrT, T3]): # error: [invalid-generic-class]
|
||||
24 | pass
|
||||
25 |
|
||||
26 | class Ham(Protocol[T1, T2, DefaultStrT, T3]): # error: [invalid-generic-class]
|
||||
27 | pass
|
||||
28 |
|
||||
29 | class VeryBad(
|
||||
30 | Protocol[T1, T2, DefaultStrT, T3], # error: [invalid-generic-class]
|
||||
31 | Generic[T1, T2, DefaultStrT, T3],
|
||||
32 | ): ...
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-generic-class]: Type parameters without defaults cannot follow type parameters with defaults
|
||||
--> src/mdtest_snippet.py:17:19
|
||||
|
|
||||
16 | # error: [invalid-generic-class] "Type parameter `T2` without a default cannot follow earlier parameter `T1` with a default"
|
||||
17 | class Foo(Generic[T1, T2]):
|
||||
| ^^^^^^
|
||||
| |
|
||||
| Type variable `T2` does not have a default
|
||||
| Earlier TypeVar `T1` does
|
||||
18 | pass
|
||||
|
|
||||
::: src/mdtest_snippet.py:3:1
|
||||
|
|
||||
1 | from typing import TypeVar, Generic, Protocol
|
||||
2 |
|
||||
3 | T1 = TypeVar("T1", default=int)
|
||||
| ------------------------------- `T1` defined here
|
||||
4 |
|
||||
5 | T2 = TypeVar("T2")
|
||||
| ------------------ `T2` defined here
|
||||
6 | T3 = TypeVar("T3")
|
||||
|
|
||||
info: rule `invalid-generic-class` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-generic-class]: Type parameters without defaults cannot follow type parameters with defaults
|
||||
--> src/mdtest_snippet.py:20:19
|
||||
|
|
||||
18 | pass
|
||||
19 |
|
||||
20 | class Bar(Generic[T2, T1, T3]): # error: [invalid-generic-class]
|
||||
| ^^^^^^^^^^
|
||||
| |
|
||||
| Type variable `T3` does not have a default
|
||||
| Earlier TypeVar `T1` does
|
||||
21 | pass
|
||||
|
|
||||
::: src/mdtest_snippet.py:3:1
|
||||
|
|
||||
1 | from typing import TypeVar, Generic, Protocol
|
||||
2 |
|
||||
3 | T1 = TypeVar("T1", default=int)
|
||||
| ------------------------------- `T1` defined here
|
||||
4 |
|
||||
5 | T2 = TypeVar("T2")
|
||||
6 | T3 = TypeVar("T3")
|
||||
| ------------------ `T3` defined here
|
||||
7 |
|
||||
8 | DefaultStrT = TypeVar("DefaultStrT", default=str)
|
||||
|
|
||||
info: rule `invalid-generic-class` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-generic-class]: Type parameters without defaults cannot follow type parameters with defaults
|
||||
--> src/mdtest_snippet.py:23:20
|
||||
|
|
||||
21 | pass
|
||||
22 |
|
||||
23 | class Spam(Generic[T1, T2, DefaultStrT, T3]): # error: [invalid-generic-class]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
| |
|
||||
| Type variables `T2` and `T3` do not have defaults
|
||||
| Earlier TypeVar `T1` does
|
||||
24 | pass
|
||||
|
|
||||
::: src/mdtest_snippet.py:3:1
|
||||
|
|
||||
1 | from typing import TypeVar, Generic, Protocol
|
||||
2 |
|
||||
3 | T1 = TypeVar("T1", default=int)
|
||||
| ------------------------------- `T1` defined here
|
||||
4 |
|
||||
5 | T2 = TypeVar("T2")
|
||||
| ------------------ `T2` defined here
|
||||
6 | T3 = TypeVar("T3")
|
||||
|
|
||||
info: rule `invalid-generic-class` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-generic-class]: Type parameters without defaults cannot follow type parameters with defaults
|
||||
--> src/mdtest_snippet.py:26:20
|
||||
|
|
||||
24 | pass
|
||||
25 |
|
||||
26 | class Ham(Protocol[T1, T2, DefaultStrT, T3]): # error: [invalid-generic-class]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
| |
|
||||
| Type variables `T2` and `T3` do not have defaults
|
||||
| Earlier TypeVar `T1` does
|
||||
27 | pass
|
||||
|
|
||||
::: src/mdtest_snippet.py:3:1
|
||||
|
|
||||
1 | from typing import TypeVar, Generic, Protocol
|
||||
2 |
|
||||
3 | T1 = TypeVar("T1", default=int)
|
||||
| ------------------------------- `T1` defined here
|
||||
4 |
|
||||
5 | T2 = TypeVar("T2")
|
||||
| ------------------ `T2` defined here
|
||||
6 | T3 = TypeVar("T3")
|
||||
|
|
||||
info: rule `invalid-generic-class` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-generic-class]: Type parameters without defaults cannot follow type parameters with defaults
|
||||
--> src/mdtest_snippet.py:30:14
|
||||
|
|
||||
29 | class VeryBad(
|
||||
30 | Protocol[T1, T2, DefaultStrT, T3], # error: [invalid-generic-class]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
| |
|
||||
| Type variables `T2` and `T3` do not have defaults
|
||||
| Earlier TypeVar `T1` does
|
||||
31 | Generic[T1, T2, DefaultStrT, T3],
|
||||
32 | ): ...
|
||||
|
|
||||
::: src/mdtest_snippet.py:3:1
|
||||
|
|
||||
1 | from typing import TypeVar, Generic, Protocol
|
||||
2 |
|
||||
3 | T1 = TypeVar("T1", default=int)
|
||||
| ------------------------------- `T1` defined here
|
||||
4 |
|
||||
5 | T2 = TypeVar("T2")
|
||||
| ------------------ `T2` defined here
|
||||
6 | T3 = TypeVar("T3")
|
||||
|
|
||||
info: rule `invalid-generic-class` is enabled by default
|
||||
|
||||
```
|
||||
@@ -63,7 +63,7 @@ error[invalid-argument-type]: Invalid second argument to `isinstance`
|
||||
10 | # error: [invalid-argument-type]
|
||||
|
|
||||
info: A `UnionType` instance can only be used as the second argument to `isinstance` if all elements are class objects
|
||||
info: Elements `<special form 'Literal[42]'>` and `<class 'list[int]'>` in the union are not class objects
|
||||
info: Elements `<special-form 'Literal[42]'>` and `<class 'list[int]'>` in the union are not class objects
|
||||
info: rule `invalid-argument-type` is enabled by default
|
||||
|
||||
```
|
||||
@@ -82,7 +82,7 @@ error[invalid-argument-type]: Invalid second argument to `isinstance`
|
||||
13 | else:
|
||||
|
|
||||
info: A `UnionType` instance can only be used as the second argument to `isinstance` if all elements are class objects
|
||||
info: Element `<special form 'typing.Any'>` in the union, and 2 more elements, are not class objects
|
||||
info: Element `<special-form 'typing.Any'>` in the union, and 2 more elements, are not class objects
|
||||
info: rule `invalid-argument-type` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
@@ -73,8 +73,8 @@ error[missing-argument]: No argument provided for required parameter `a` of func
|
||||
13 |
|
||||
14 | Foo().method() # error: [missing-argument]
|
||||
|
|
||||
info: Union variant `def f(a, b=Literal[42]) -> Unknown` is incompatible with this call site
|
||||
info: Attempted to call union type `(def f(a, b=Literal[42]) -> Unknown) | (def g(a, b) -> Unknown)`
|
||||
info: Union variant `def f(a, b=42) -> Unknown` is incompatible with this call site
|
||||
info: Attempted to call union type `(def f(a, b=42) -> Unknown) | (def g(a, b) -> Unknown)`
|
||||
info: rule `missing-argument` is enabled by default
|
||||
|
||||
```
|
||||
@@ -91,7 +91,7 @@ error[missing-argument]: No argument provided for required parameter `a` of func
|
||||
14 | Foo().method() # error: [missing-argument]
|
||||
|
|
||||
info: Union variant `def g(a, b) -> Unknown` is incompatible with this call site
|
||||
info: Attempted to call union type `(def f(a, b=Literal[42]) -> Unknown) | (def g(a, b) -> Unknown)`
|
||||
info: Attempted to call union type `(def f(a, b=42) -> Unknown) | (def g(a, b) -> Unknown)`
|
||||
info: rule `missing-argument` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
@@ -24,7 +24,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/mro.md
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[inconsistent-mro]: Cannot create a consistent method resolution order (MRO) for class `Baz` with bases list `[<special form 'typing.Protocol[T]'>, <class 'Foo'>, <class 'Bar[T@Baz]'>]`
|
||||
error[inconsistent-mro]: Cannot create a consistent method resolution order (MRO) for class `Baz` with bases list `[<special-form 'typing.Protocol[T]'>, <class 'Foo'>, <class 'Bar[T@Baz]'>]`
|
||||
--> src/mdtest_snippet.py:7:1
|
||||
|
|
||||
5 | class Foo(Protocol): ...
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user