mirror of https://github.com/astral-sh/ruff
Merge remote-tracking branch 'origin/main' into dcreager/callable-return
* origin/main: Fluent formatting of method chains (#21369) [ty] Avoid stack overflow when calculating inferable typevars (#21971) [ty] Add "qualify ..." code fix for undefined references (#21968) [ty] Use jemalloc on linux (#21975) Update MSRV to 1.90 (#21987) [ty] Improve check enforcing that an overloaded function must have an implementation (#21978) Update actions/checkout digest to 8e8c483 (#21982) [ty] Use `ParamSpec` without the attr for inferable check (#21934) [ty] Emit diagnostic when a type variable with a default is followed by one without a default (#21787)
This commit is contained in:
commit
dfcdbcffec
|
|
@ -60,7 +60,7 @@ jobs:
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3
|
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|
@ -123,7 +123,7 @@ jobs:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json
|
BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3
|
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|
@ -174,7 +174,7 @@ jobs:
|
||||||
outputs:
|
outputs:
|
||||||
val: ${{ steps.host.outputs.manifest }}
|
val: ${{ steps.host.outputs.manifest }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3
|
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|
@ -250,7 +250,7 @@ jobs:
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3
|
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|
|
||||||
|
|
@ -4390,6 +4390,7 @@ dependencies = [
|
||||||
"ruff_python_trivia",
|
"ruff_python_trivia",
|
||||||
"salsa",
|
"salsa",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
|
"tikv-jemallocator",
|
||||||
"toml",
|
"toml",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-flame",
|
"tracing-flame",
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ resolver = "2"
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
# Please update rustfmt.toml when bumping the Rust edition
|
# Please update rustfmt.toml when bumping the Rust edition
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
rust-version = "1.89"
|
rust-version = "1.90"
|
||||||
homepage = "https://docs.astral.sh/ruff"
|
homepage = "https://docs.astral.sh/ruff"
|
||||||
documentation = "https://docs.astral.sh/ruff"
|
documentation = "https://docs.astral.sh/ruff"
|
||||||
repository = "https://github.com/astral-sh/ruff"
|
repository = "https://github.com/astral-sh/ruff"
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ use anyhow::bail;
|
||||||
use clap::builder::Styles;
|
use clap::builder::Styles;
|
||||||
use clap::builder::styling::{AnsiColor, Effects};
|
use clap::builder::styling::{AnsiColor, Effects};
|
||||||
use clap::builder::{TypedValueParser, ValueParserFactory};
|
use clap::builder::{TypedValueParser, ValueParserFactory};
|
||||||
use clap::{Parser, Subcommand, command};
|
use clap::{Parser, Subcommand};
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use path_absolutize::path_dedot;
|
use path_absolutize::path_dedot;
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ use std::sync::mpsc::channel;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::CommandFactory;
|
use clap::CommandFactory;
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use log::{error, warn};
|
use log::error;
|
||||||
use notify::{RecursiveMode, Watcher, recommended_watcher};
|
use notify::{RecursiveMode, Watcher, recommended_watcher};
|
||||||
|
|
||||||
use args::{GlobalConfigArgs, ServerCommand};
|
use args::{GlobalConfigArgs, ServerCommand};
|
||||||
|
|
|
||||||
|
|
@ -337,7 +337,7 @@ macro_rules! best_fitting {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::{FormatState, SimpleFormatOptions, VecBuffer, write};
|
use crate::{FormatState, SimpleFormatOptions, VecBuffer};
|
||||||
|
|
||||||
struct TestFormat;
|
struct TestFormat;
|
||||||
|
|
||||||
|
|
@ -385,8 +385,8 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn best_fitting_variants_print_as_lists() {
|
fn best_fitting_variants_print_as_lists() {
|
||||||
|
use crate::Formatted;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::{Formatted, format, format_args};
|
|
||||||
|
|
||||||
// The second variant below should be selected when printing at a width of 30
|
// The second variant below should be selected when printing at a width of 30
|
||||||
let formatted_best_fitting = format!(
|
let formatted_best_fitting = format!(
|
||||||
|
|
|
||||||
|
|
@ -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.
|
/// Generic function to add a (regular) parameter to a function definition.
|
||||||
pub(crate) fn add_parameter(parameter: &str, parameters: &Parameters, source: &str) -> Edit {
|
pub(crate) fn add_parameter(parameter: &str, parameters: &Parameters, source: &str) -> Edit {
|
||||||
if let Some(last) = parameters
|
if let Some(last) = parameters.args.iter().rfind(|arg| arg.default.is_none()) {
|
||||||
.args
|
|
||||||
.iter()
|
|
||||||
.filter(|arg| arg.default.is_none())
|
|
||||||
.next_back()
|
|
||||||
{
|
|
||||||
// Case 1: at least one regular parameter, so append after the last one.
|
// Case 1: at least one regular parameter, so append after the last one.
|
||||||
Edit::insertion(format!(", {parameter}"), last.end())
|
Edit::insertion(format!(", {parameter}"), last.end())
|
||||||
} else if !parameters.args.is_empty() {
|
} 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();
|
let left = (*comparison.left).clone();
|
||||||
|
|
||||||
// Copy the right side to the left side.
|
// 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.
|
// Copy the left side to the right side.
|
||||||
comparison.comparisons[0].comparator = left;
|
comparison.comparisons[0].comparator = left;
|
||||||
|
|
|
||||||
|
|
@ -1247,6 +1247,7 @@ impl<'a> Generator<'a> {
|
||||||
self.p_bytes_repr(&bytes_literal.value, bytes_literal.flags);
|
self.p_bytes_repr(&bytes_literal.value, bytes_literal.flags);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[expect(clippy::eq_op)]
|
||||||
Expr::NumberLiteral(ast::ExprNumberLiteral { value, .. }) => {
|
Expr::NumberLiteral(ast::ExprNumberLiteral { value, .. }) => {
|
||||||
static INF_STR: &str = "1e309";
|
static INF_STR: &str = "1e309";
|
||||||
assert_eq!(f64::MAX_10_EXP, 308);
|
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}]
|
||||||
|
|
@ -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()
|
.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 std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use clap::{Parser, ValueEnum, command};
|
use clap::{Parser, ValueEnum};
|
||||||
|
|
||||||
use ruff_formatter::SourceCode;
|
use ruff_formatter::SourceCode;
|
||||||
use ruff_python_ast::{PySourceType, PythonVersion};
|
use ruff_python_ast::{PySourceType, PythonVersion};
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ use crate::expression::parentheses::{
|
||||||
NeedsParentheses, OptionalParentheses, Parentheses, is_expression_parenthesized,
|
NeedsParentheses, OptionalParentheses, Parentheses, is_expression_parenthesized,
|
||||||
};
|
};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use crate::preview::is_fluent_layout_split_first_call_enabled;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct FormatExprAttribute {
|
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 {
|
if parenthesize_value {
|
||||||
// Don't propagate the call chain layout.
|
// Don't propagate the call chain layout.
|
||||||
value.format().with_options(Parentheses::Always).fmt(f)?;
|
value.format().with_options(Parentheses::Always).fmt(f)?;
|
||||||
} else {
|
} else {
|
||||||
match value.as_ref() {
|
match value.as_ref() {
|
||||||
Expr::Attribute(expr) => {
|
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::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::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)?;
|
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
|
// 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
|
// and the value either requires parenthesizing or is a call or subscript expression
|
||||||
// (it's a fluent chain but not the first element).
|
// (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)?;
|
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
|
let is_call_chain_root =
|
||||||
&& call_chain_layout == CallChainLayout::Fluent;
|
self.call_chain_layout == CallChainLayout::Default && call_chain_layout.is_fluent();
|
||||||
if is_call_chain_root {
|
if is_call_chain_root {
|
||||||
write!(f, [group(&format_inner)])
|
write!(f, [group(&format_inner)])
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -169,7 +198,8 @@ impl NeedsParentheses for ExprAttribute {
|
||||||
self.into(),
|
self.into(),
|
||||||
context.comments().ranges(),
|
context.comments().ranges(),
|
||||||
context.source(),
|
context.source(),
|
||||||
) == CallChainLayout::Fluent
|
)
|
||||||
|
.is_fluent()
|
||||||
{
|
{
|
||||||
OptionalParentheses::Multiline
|
OptionalParentheses::Multiline
|
||||||
} else if context.comments().has_dangling(self) {
|
} else if context.comments().has_dangling(self) {
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,10 @@ impl FormatNodeRule<ExprCall> for FormatExprCall {
|
||||||
func.format().with_options(Parentheses::Always).fmt(f)
|
func.format().with_options(Parentheses::Always).fmt(f)
|
||||||
} else {
|
} else {
|
||||||
match func.as_ref() {
|
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::Call(expr) => expr.format().with_options(call_chain_layout).fmt(f),
|
||||||
Expr::Subscript(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),
|
_ => 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)
|
// queryset.distinct().order_by(field.name).values_list(field_name_flat_long_long=True)
|
||||||
// )
|
// )
|
||||||
// ```
|
// ```
|
||||||
if call_chain_layout == CallChainLayout::Fluent
|
if call_chain_layout.is_fluent() && self.call_chain_layout == CallChainLayout::Default {
|
||||||
&& self.call_chain_layout == CallChainLayout::Default
|
|
||||||
{
|
|
||||||
group(&fmt_func).fmt(f)
|
group(&fmt_func).fmt(f)
|
||||||
} else {
|
} else {
|
||||||
fmt_func.fmt(f)
|
fmt_func.fmt(f)
|
||||||
|
|
@ -87,7 +88,8 @@ impl NeedsParentheses for ExprCall {
|
||||||
self.into(),
|
self.into(),
|
||||||
context.comments().ranges(),
|
context.comments().ranges(),
|
||||||
context.source(),
|
context.source(),
|
||||||
) == CallChainLayout::Fluent
|
)
|
||||||
|
.is_fluent()
|
||||||
{
|
{
|
||||||
OptionalParentheses::Multiline
|
OptionalParentheses::Multiline
|
||||||
} else if context.comments().has_dangling(self) {
|
} else if context.comments().has_dangling(self) {
|
||||||
|
|
|
||||||
|
|
@ -397,7 +397,8 @@ impl Format<PyFormatContext<'_>> for FormatBody<'_> {
|
||||||
body.into(),
|
body.into(),
|
||||||
comments.ranges(),
|
comments.ranges(),
|
||||||
f.context().source(),
|
f.context().source(),
|
||||||
) == CallChainLayout::Fluent
|
)
|
||||||
|
.is_fluent()
|
||||||
{
|
{
|
||||||
parenthesize_if_expands(&unparenthesized).fmt(f)
|
parenthesize_if_expands(&unparenthesized).fmt(f)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,10 @@ impl FormatNodeRule<ExprSubscript> for FormatExprSubscript {
|
||||||
value.format().with_options(Parentheses::Always).fmt(f)
|
value.format().with_options(Parentheses::Always).fmt(f)
|
||||||
} else {
|
} else {
|
||||||
match value.as_ref() {
|
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::Call(expr) => expr.format().with_options(call_chain_layout).fmt(f),
|
||||||
Expr::Subscript(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),
|
_ => value.format().with_options(Parentheses::Never).fmt(f),
|
||||||
|
|
@ -71,8 +74,8 @@ impl FormatNodeRule<ExprSubscript> for FormatExprSubscript {
|
||||||
.fmt(f)
|
.fmt(f)
|
||||||
});
|
});
|
||||||
|
|
||||||
let is_call_chain_root = self.call_chain_layout == CallChainLayout::Default
|
let is_call_chain_root =
|
||||||
&& call_chain_layout == CallChainLayout::Fluent;
|
self.call_chain_layout == CallChainLayout::Default && call_chain_layout.is_fluent();
|
||||||
if is_call_chain_root {
|
if is_call_chain_root {
|
||||||
write!(f, [group(&format_inner)])
|
write!(f, [group(&format_inner)])
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -92,7 +95,8 @@ impl NeedsParentheses for ExprSubscript {
|
||||||
self.into(),
|
self.into(),
|
||||||
context.comments().ranges(),
|
context.comments().ranges(),
|
||||||
context.source(),
|
context.source(),
|
||||||
) == CallChainLayout::Fluent
|
)
|
||||||
|
.is_fluent()
|
||||||
{
|
{
|
||||||
OptionalParentheses::Multiline
|
OptionalParentheses::Multiline
|
||||||
} else if is_expression_parenthesized(
|
} else if is_expression_parenthesized(
|
||||||
|
|
|
||||||
|
|
@ -876,6 +876,22 @@ impl<'a> First<'a> {
|
||||||
/// )
|
/// )
|
||||||
/// ).all()
|
/// ).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)]
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
||||||
pub enum CallChainLayout {
|
pub enum CallChainLayout {
|
||||||
/// The root of a call chain
|
/// The root of a call chain
|
||||||
|
|
@ -883,19 +899,149 @@ pub enum CallChainLayout {
|
||||||
Default,
|
Default,
|
||||||
|
|
||||||
/// A nested call chain element that uses fluent style.
|
/// A nested call chain element that uses fluent style.
|
||||||
Fluent,
|
Fluent(AttributeState),
|
||||||
|
|
||||||
/// A nested call chain element not using fluent style.
|
/// A nested call chain element not using fluent style.
|
||||||
NonFluent,
|
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 {
|
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(
|
pub(crate) fn from_expression(
|
||||||
mut expr: ExprRef,
|
mut expr: ExprRef,
|
||||||
comment_ranges: &CommentRanges,
|
comment_ranges: &CommentRanges,
|
||||||
source: &str,
|
source: &str,
|
||||||
) -> Self {
|
) -> 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 {
|
loop {
|
||||||
match expr {
|
match expr {
|
||||||
ExprRef::Attribute(ast::ExprAttribute { value, .. }) => {
|
ExprRef::Attribute(ast::ExprAttribute { value, .. }) => {
|
||||||
|
|
@ -907,10 +1053,10 @@ impl CallChainLayout {
|
||||||
// ```
|
// ```
|
||||||
if is_expression_parenthesized(value.into(), comment_ranges, source) {
|
if is_expression_parenthesized(value.into(), comment_ranges, source) {
|
||||||
// `(a).b`. We preserve these parentheses so don't recurse
|
// `(a).b`. We preserve these parentheses so don't recurse
|
||||||
attributes_after_parentheses += 1;
|
root_value_parenthesized = true;
|
||||||
break;
|
break;
|
||||||
} else if matches!(value.as_ref(), Expr::Call(_) | Expr::Subscript(_)) {
|
} 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());
|
expr = ExprRef::from(value.as_ref());
|
||||||
|
|
@ -925,31 +1071,68 @@ impl CallChainLayout {
|
||||||
// ```
|
// ```
|
||||||
ExprRef::Call(ast::ExprCall { func: inner, .. })
|
ExprRef::Call(ast::ExprCall { func: inner, .. })
|
||||||
| ExprRef::Subscript(ast::ExprSubscript { value: 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());
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// We preserve these parentheses so don't recurse
|
if computed_attribute_values_after_parentheses + u32::from(root_value_parenthesized) < 2 {
|
||||||
if is_expression_parenthesized(expr, comment_ranges, source) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if attributes_after_parentheses < 2 {
|
|
||||||
CallChainLayout::NonFluent
|
CallChainLayout::NonFluent
|
||||||
} else {
|
} 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
|
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)]
|
#[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 {
|
pub(crate) const fn is_parenthesize_lambda_bodies_enabled(context: &PyFormatContext) -> bool {
|
||||||
context.is_preview()
|
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 = {
|
x = {
|
||||||
"foobar": (123) + 456,
|
"foobar": (123) + 456,
|
||||||
@@ -97,24 +94,20 @@
|
@@ -97,24 +94,21 @@
|
||||||
|
|
||||||
|
|
||||||
my_dict = {
|
my_dict = {
|
||||||
|
|
@ -221,13 +221,14 @@ class Random:
|
||||||
- .second_call()
|
- .second_call()
|
||||||
- .third_call(some_args="some value")
|
- .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()
|
+ .second_call()
|
||||||
+ .third_call(some_args="some value")
|
+ .third_call(some_args="some value")
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -139,17 +132,17 @@
|
@@ -139,17 +133,17 @@
|
||||||
|
|
||||||
class Random:
|
class Random:
|
||||||
def func():
|
def func():
|
||||||
|
|
@ -363,7 +364,8 @@ my_dict = {
|
||||||
/ 100000.0
|
/ 100000.0
|
||||||
}
|
}
|
||||||
my_dict = {
|
my_dict = {
|
||||||
"a key in my dict": MyClass.some_attribute.first_call()
|
"a key in my dict": MyClass.some_attribute
|
||||||
|
.first_call()
|
||||||
.second_call()
|
.second_call()
|
||||||
.third_call(some_args="some value")
|
.third_call(some_args="some value")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
---
|
---
|
||||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/await.py
|
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/await.py
|
||||||
snapshot_kind: text
|
|
||||||
---
|
---
|
||||||
## Input
|
## Input
|
||||||
```python
|
```python
|
||||||
|
|
@ -142,3 +141,20 @@ test_data = await (
|
||||||
.to_list()
|
.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
|
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/call.py
|
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/call.py
|
||||||
snapshot_kind: text
|
|
||||||
---
|
---
|
||||||
## Input
|
## Input
|
||||||
```python
|
```python
|
||||||
|
|
@ -557,3 +556,20 @@ result = (
|
||||||
|
|
||||||
result = (object[complicate_caller])("argument").a["b"].test(argument)
|
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(
|
param(
|
||||||
lambda left, right: (
|
lambda left, right: (
|
||||||
@@ -471,9 +463,9 @@
|
@@ -471,15 +463,16 @@
|
||||||
),
|
),
|
||||||
param(lambda left, right: ibis.timestamp("2017-04-01").cast(dt.date)),
|
param(lambda left, right: ibis.timestamp("2017-04-01").cast(dt.date)),
|
||||||
param(
|
param(
|
||||||
|
|
@ -2168,7 +2168,15 @@ transform = (
|
||||||
),
|
),
|
||||||
# This is too long on one line in the lambda body and gets wrapped
|
# This is too long on one line in the lambda body and gets wrapped
|
||||||
# inside the body.
|
# 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
|
# adds parentheses around the body
|
||||||
|
|
@ -2190,7 +2198,7 @@ transform = (
|
||||||
|
|
||||||
lambda x, y, z: (
|
lambda x, y, z: (
|
||||||
x + y + z
|
x + y + z
|
||||||
@@ -527,7 +521,7 @@
|
@@ -527,7 +522,7 @@
|
||||||
x + y + z # trailing eol body
|
x + y + z # trailing eol body
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -2199,7 +2207,7 @@ transform = (
|
||||||
|
|
||||||
lambda x, y, z: (
|
lambda x, y, z: (
|
||||||
# leading body
|
# leading body
|
||||||
@@ -539,21 +533,23 @@
|
@@ -539,21 +534,23 @@
|
||||||
)
|
)
|
||||||
|
|
||||||
(
|
(
|
||||||
|
|
@ -2233,7 +2241,7 @@ transform = (
|
||||||
# dangling header comment
|
# dangling header comment
|
||||||
source_bucket
|
source_bucket
|
||||||
if name == source_bucket_name
|
if name == source_bucket_name
|
||||||
@@ -561,8 +557,7 @@
|
@@ -561,8 +558,7 @@
|
||||||
)
|
)
|
||||||
|
|
||||||
(
|
(
|
||||||
|
|
@ -2243,7 +2251,7 @@ transform = (
|
||||||
source_bucket
|
source_bucket
|
||||||
if name == source_bucket_name
|
if name == source_bucket_name
|
||||||
else storage.Bucket(mock_service, destination_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)
|
- .cast(dt.date)
|
||||||
- .between(left, right)
|
- .between(left, right)
|
||||||
+ lambda left, right: (
|
+ lambda left, right: (
|
||||||
+ ibis.timestamp("2017-04-01") # comment
|
+ ibis
|
||||||
|
+ .timestamp("2017-04-01") # comment
|
||||||
+ .cast(dt.date)
|
+ .cast(dt.date)
|
||||||
+ .between(left, right)
|
+ .between(left, right)
|
||||||
+ )
|
+ )
|
||||||
|
|
@ -2346,7 +2355,7 @@ transform = (
|
||||||
)
|
)
|
||||||
|
|
||||||
(
|
(
|
||||||
@@ -637,27 +641,31 @@
|
@@ -637,27 +643,31 @@
|
||||||
(
|
(
|
||||||
lambda
|
lambda
|
||||||
# comment
|
# 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
|
# comment 1
|
||||||
*ergs,
|
*ergs,
|
||||||
# comment 2
|
# comment 2
|
||||||
|
|
@ -2440,7 +2449,7 @@ transform = (
|
||||||
)
|
)
|
||||||
|
|
||||||
(
|
(
|
||||||
@@ -708,19 +719,20 @@
|
@@ -708,19 +721,20 @@
|
||||||
# 2
|
# 2
|
||||||
left, # 3
|
left, # 3
|
||||||
# 4
|
# 4
|
||||||
|
|
@ -2471,7 +2480,7 @@ transform = (
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -738,48 +750,52 @@
|
@@ -738,48 +752,52 @@
|
||||||
foo(
|
foo(
|
||||||
lambda from_ts, # but still wrap the body if it gets too long
|
lambda from_ts, # but still wrap the body if it gets too long
|
||||||
to_ts,
|
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
|
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/split_empty_brackets.py
|
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/split_empty_brackets.py
|
||||||
snapshot_kind: text
|
|
||||||
---
|
---
|
||||||
## Input
|
## Input
|
||||||
```python
|
```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
|
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/parentheses/call_chains.py
|
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/parentheses/call_chains.py
|
||||||
snapshot_kind: text
|
|
||||||
---
|
---
|
||||||
## Input
|
## Input
|
||||||
```python
|
```python
|
||||||
|
|
@ -223,6 +222,72 @@ max_message_id = (
|
||||||
.baz()
|
.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
|
## Output
|
||||||
|
|
@ -466,4 +531,237 @@ max_message_id = (
|
||||||
.sum()
|
.sum()
|
||||||
.baz()
|
.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,),
|
||||||
|
)
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -51,5 +51,11 @@ regex = { workspace = true }
|
||||||
tempfile = { workspace = true }
|
tempfile = { workspace = true }
|
||||||
toml = { 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]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
|
||||||
|
|
@ -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> ·
|
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> ·
|
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/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#L1998" 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>
|
</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> ·
|
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> ·
|
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/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#L1668" 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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -793,7 +793,7 @@ class D(A):
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></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.35">0.0.1-alpha.35</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/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#L2224" target="_blank">View source</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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -848,16 +848,21 @@ Checks for the creation of invalid generic classes
|
||||||
**Why is this bad?**
|
**Why is this bad?**
|
||||||
|
|
||||||
There are several requirements that you must follow when defining a generic class.
|
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**
|
**Examples**
|
||||||
|
|
||||||
```python
|
```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
|
# error: class uses both PEP-695 syntax and legacy syntax
|
||||||
class C[U](Generic[T]): ...
|
class C[U](Generic[T]): ...
|
||||||
|
|
||||||
|
# error: type parameter with default comes before type parameter without default
|
||||||
|
class D(Generic[U, T]): ...
|
||||||
```
|
```
|
||||||
|
|
||||||
**References**
|
**References**
|
||||||
|
|
@ -909,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> ·
|
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> ·
|
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/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#L917" 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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -944,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> ·
|
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> ·
|
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/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#L1014" 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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -978,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> ·
|
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> ·
|
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/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#L2126" 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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1139,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> ·
|
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>) ·
|
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/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#L990" 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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1169,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> ·
|
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> ·
|
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/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#L1041" 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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1219,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> ·
|
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> ·
|
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/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#L1140" 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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1245,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> ·
|
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> ·
|
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/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#L945" 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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1310,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> ·
|
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> ·
|
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/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#L1160" 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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1384,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> ·
|
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> ·
|
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/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#L1203" 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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1442,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> ·
|
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> ·
|
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/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#L969" 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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1469,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> ·
|
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> ·
|
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/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#L1435" 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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1516,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> ·
|
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> ·
|
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/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#L1242" 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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1546,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> ·
|
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> ·
|
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/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#L1266" 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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1576,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> ·
|
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> ·
|
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/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#L1318" 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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1610,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> ·
|
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> ·
|
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/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#L1290" 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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1644,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> ·
|
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> ·
|
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/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#L1346" 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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1679,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> ·
|
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> ·
|
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/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#L1375" 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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1704,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> ·
|
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> ·
|
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/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#L2099" 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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1737,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> ·
|
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> ·
|
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/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#L1394" 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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1766,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> ·
|
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> ·
|
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/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#L1417" 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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1790,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> ·
|
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> ·
|
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/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#L1476" 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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1816,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> ·
|
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> ·
|
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/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#L1641" 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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1849,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> ·
|
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> ·
|
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/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#L1527" 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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1876,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> ·
|
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> ·
|
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/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#L1852" 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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1934,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> ·
|
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> ·
|
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/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#L1974" 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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1964,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> ·
|
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> ·
|
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/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#L1618" 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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1993,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> ·
|
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>) ·
|
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/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#L1786" 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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2027,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> ·
|
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> ·
|
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/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#L1726" 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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2054,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> ·
|
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> ·
|
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/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#L1704" 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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2082,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> ·
|
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> ·
|
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/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#L1747" 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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2128,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> ·
|
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> ·
|
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/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#L1831" 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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2155,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> ·
|
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> ·
|
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/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#L1873" 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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2183,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> ·
|
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> ·
|
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/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#L1895" 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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2208,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> ·
|
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> ·
|
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/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#L1914" 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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2233,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> ·
|
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> ·
|
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/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#L1496" 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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2270,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> ·
|
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> ·
|
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/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#L1933" 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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2298,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> ·
|
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> ·
|
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/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#L1955" 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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2452,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> ·
|
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> ·
|
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/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#L1548" 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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2512,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> ·
|
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> ·
|
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/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#L1570" 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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2544,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> ·
|
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> ·
|
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/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#L2026" 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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2571,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> ·
|
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> ·
|
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/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#L1813" 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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2595,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> ·
|
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> ·
|
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/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#L2047" 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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2692,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> ·
|
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> ·
|
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/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#L1084" 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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2779,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> ·
|
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> ·
|
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/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#L1596" 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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,22 @@ use colored::Colorize;
|
||||||
use std::io;
|
use std::io;
|
||||||
use ty::{ExitStatus, run};
|
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 {
|
pub fn main() -> ExitStatus {
|
||||||
run().unwrap_or_else(|error| {
|
run().unwrap_or_else(|error| {
|
||||||
use io::Write;
|
use io::Write;
|
||||||
|
|
|
||||||
|
|
@ -29,12 +29,11 @@ pub fn code_actions(
|
||||||
|
|
||||||
let mut actions = Vec::new();
|
let mut actions = Vec::new();
|
||||||
|
|
||||||
// Suggest imports for unresolved references (often ideal)
|
// Suggest imports/qualifications for unresolved references (often ideal)
|
||||||
// TODO: suggest qualifying with an already imported symbol
|
|
||||||
let is_unresolved_reference =
|
let is_unresolved_reference =
|
||||||
lint_id == LintId::of(&UNRESOLVED_REFERENCE) || lint_id == LintId::of(&UNDEFINED_REVEAL);
|
lint_id == LintId::of(&UNRESOLVED_REFERENCE) || lint_id == LintId::of(&UNDEFINED_REVEAL);
|
||||||
if is_unresolved_reference
|
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);
|
actions.extend(import_quick_fix);
|
||||||
}
|
}
|
||||||
|
|
@ -49,7 +48,7 @@ pub fn code_actions(
|
||||||
actions
|
actions
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_import_symbol_quick_fix(
|
fn unresolved_fixes(
|
||||||
db: &dyn Db,
|
db: &dyn Db,
|
||||||
file: File,
|
file: File,
|
||||||
diagnostic_range: TextRange,
|
diagnostic_range: TextRange,
|
||||||
|
|
@ -59,7 +58,7 @@ fn create_import_symbol_quick_fix(
|
||||||
let symbol = &node.expr_name()?.id;
|
let symbol = &node.expr_name()?.id;
|
||||||
|
|
||||||
Some(
|
Some(
|
||||||
completion::missing_imports(db, file, &parsed, symbol, node)
|
completion::unresolved_fixes(db, file, &parsed, symbol, node)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|import| QuickFix {
|
.map(|import| QuickFix {
|
||||||
title: import.label,
|
title: import.label,
|
||||||
|
|
@ -84,6 +83,7 @@ mod tests {
|
||||||
system::{DbWithWritableSystem, SystemPathBuf},
|
system::{DbWithWritableSystem, SystemPathBuf},
|
||||||
};
|
};
|
||||||
use ruff_diagnostics::Fix;
|
use ruff_diagnostics::Fix;
|
||||||
|
use ruff_python_trivia::textwrap::dedent;
|
||||||
use ruff_text_size::{TextRange, TextSize};
|
use ruff_text_size::{TextRange, TextSize};
|
||||||
use ty_project::ProjectMetadata;
|
use ty_project::ProjectMetadata;
|
||||||
use ty_python_semantic::{
|
use ty_python_semantic::{
|
||||||
|
|
@ -149,7 +149,7 @@ mod tests {
|
||||||
|
|
||||||
assert_snapshot!(test.code_actions(&UNRESOLVED_REFERENCE), @r"
|
assert_snapshot!(test.code_actions(&UNRESOLVED_REFERENCE), @r"
|
||||||
info[code-action]: Ignore 'unresolved-reference' for this line
|
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]
|
||||||
| ^
|
| ^
|
||||||
|
|
@ -157,7 +157,6 @@ mod tests {
|
||||||
1 |
|
1 |
|
||||||
- b = a / 0 # ty:ignore[division-by-zero]
|
- b = a / 0 # ty:ignore[division-by-zero]
|
||||||
2 + b = a / 0 # ty:ignore[division-by-zero, unresolved-reference]
|
2 + b = a / 0 # ty:ignore[division-by-zero, unresolved-reference]
|
||||||
3 |
|
|
||||||
");
|
");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -171,7 +170,7 @@ mod tests {
|
||||||
|
|
||||||
assert_snapshot!(test.code_actions(&UNRESOLVED_REFERENCE), @r"
|
assert_snapshot!(test.code_actions(&UNRESOLVED_REFERENCE), @r"
|
||||||
info[code-action]: Ignore 'unresolved-reference' for this line
|
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,]
|
||||||
| ^
|
| ^
|
||||||
|
|
@ -179,7 +178,6 @@ mod tests {
|
||||||
1 |
|
1 |
|
||||||
- b = a / 0 # ty:ignore[division-by-zero,]
|
- b = a / 0 # ty:ignore[division-by-zero,]
|
||||||
2 + b = a / 0 # ty:ignore[division-by-zero, unresolved-reference]
|
2 + b = a / 0 # ty:ignore[division-by-zero, unresolved-reference]
|
||||||
3 |
|
|
||||||
");
|
");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -193,7 +191,7 @@ mod tests {
|
||||||
|
|
||||||
assert_snapshot!(test.code_actions(&UNRESOLVED_REFERENCE), @r"
|
assert_snapshot!(test.code_actions(&UNRESOLVED_REFERENCE), @r"
|
||||||
info[code-action]: Ignore 'unresolved-reference' for this line
|
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 ]
|
||||||
| ^
|
| ^
|
||||||
|
|
@ -201,7 +199,6 @@ mod tests {
|
||||||
1 |
|
1 |
|
||||||
- b = a / 0 # ty:ignore[division-by-zero ]
|
- b = a / 0 # ty:ignore[division-by-zero ]
|
||||||
2 + b = a / 0 # ty:ignore[division-by-zero, unresolved-reference ]
|
2 + b = a / 0 # ty:ignore[division-by-zero, unresolved-reference ]
|
||||||
3 |
|
|
||||||
");
|
");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -215,7 +212,7 @@ mod tests {
|
||||||
|
|
||||||
assert_snapshot!(test.code_actions(&UNRESOLVED_REFERENCE), @r"
|
assert_snapshot!(test.code_actions(&UNRESOLVED_REFERENCE), @r"
|
||||||
info[code-action]: Ignore 'unresolved-reference' for this line
|
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
|
||||||
| ^
|
| ^
|
||||||
|
|
@ -223,7 +220,6 @@ mod tests {
|
||||||
1 |
|
1 |
|
||||||
- b = a / 0 # ty:ignore[division-by-zero] some explanation
|
- 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]
|
2 + b = a / 0 # ty:ignore[division-by-zero] some explanation # ty:ignore[unresolved-reference]
|
||||||
3 |
|
|
||||||
");
|
");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -241,13 +237,13 @@ mod tests {
|
||||||
|
|
||||||
assert_snapshot!(test.code_actions(&UNRESOLVED_REFERENCE), @r"
|
assert_snapshot!(test.code_actions(&UNRESOLVED_REFERENCE), @r"
|
||||||
info[code-action]: Ignore 'unresolved-reference' for this line
|
info[code-action]: Ignore 'unresolved-reference' for this line
|
||||||
--> main.py:3:21
|
--> main.py:3:9
|
||||||
|
|
|
|
||||||
2 | b = (
|
2 | b = (
|
||||||
3 | / a # ty:ignore[division-by-zero]
|
3 | / a # ty:ignore[division-by-zero]
|
||||||
4 | | /
|
4 | | /
|
||||||
5 | | 0
|
5 | | 0
|
||||||
| |_____________________^
|
| |_________^
|
||||||
6 | )
|
6 | )
|
||||||
|
|
|
|
||||||
1 |
|
1 |
|
||||||
|
|
@ -274,13 +270,13 @@ mod tests {
|
||||||
|
|
||||||
assert_snapshot!(test.code_actions(&UNRESOLVED_REFERENCE), @r"
|
assert_snapshot!(test.code_actions(&UNRESOLVED_REFERENCE), @r"
|
||||||
info[code-action]: Ignore 'unresolved-reference' for this line
|
info[code-action]: Ignore 'unresolved-reference' for this line
|
||||||
--> main.py:3:21
|
--> main.py:3:9
|
||||||
|
|
|
|
||||||
2 | b = (
|
2 | b = (
|
||||||
3 | / a
|
3 | / a
|
||||||
4 | | /
|
4 | | /
|
||||||
5 | | 0 # ty:ignore[division-by-zero]
|
5 | | 0 # ty:ignore[division-by-zero]
|
||||||
| |_____________________^
|
| |_________^
|
||||||
6 | )
|
6 | )
|
||||||
|
|
|
|
||||||
2 | b = (
|
2 | b = (
|
||||||
|
|
@ -289,7 +285,6 @@ mod tests {
|
||||||
- 0 # ty:ignore[division-by-zero]
|
- 0 # ty:ignore[division-by-zero]
|
||||||
5 + 0 # ty:ignore[division-by-zero, unresolved-reference]
|
5 + 0 # ty:ignore[division-by-zero, unresolved-reference]
|
||||||
6 | )
|
6 | )
|
||||||
7 |
|
|
||||||
");
|
");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -307,13 +302,13 @@ mod tests {
|
||||||
|
|
||||||
assert_snapshot!(test.code_actions(&UNRESOLVED_REFERENCE), @r"
|
assert_snapshot!(test.code_actions(&UNRESOLVED_REFERENCE), @r"
|
||||||
info[code-action]: Ignore 'unresolved-reference' for this line
|
info[code-action]: Ignore 'unresolved-reference' for this line
|
||||||
--> main.py:3:21
|
--> main.py:3:9
|
||||||
|
|
|
|
||||||
2 | b = (
|
2 | b = (
|
||||||
3 | / a # ty:ignore[division-by-zero]
|
3 | / a # ty:ignore[division-by-zero]
|
||||||
4 | | /
|
4 | | /
|
||||||
5 | | 0 # ty:ignore[division-by-zero]
|
5 | | 0 # ty:ignore[division-by-zero]
|
||||||
| |_____________________^
|
| |_________^
|
||||||
6 | )
|
6 | )
|
||||||
|
|
|
|
||||||
1 |
|
1 |
|
||||||
|
|
@ -339,7 +334,7 @@ mod tests {
|
||||||
|
|
||||||
assert_snapshot!(test.code_actions(&UNRESOLVED_REFERENCE), @r#"
|
assert_snapshot!(test.code_actions(&UNRESOLVED_REFERENCE), @r#"
|
||||||
info[code-action]: Ignore 'unresolved-reference' for this line
|
info[code-action]: Ignore 'unresolved-reference' for this line
|
||||||
--> main.py:3:18
|
--> main.py:3:6
|
||||||
|
|
|
|
||||||
2 | b = f"""
|
2 | b = f"""
|
||||||
3 | {a}
|
3 | {a}
|
||||||
|
|
@ -352,7 +347,6 @@ mod tests {
|
||||||
4 | more text
|
4 | more text
|
||||||
- """
|
- """
|
||||||
5 + """ # ty:ignore[unresolved-reference]
|
5 + """ # ty:ignore[unresolved-reference]
|
||||||
6 |
|
|
||||||
"#);
|
"#);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -371,7 +365,7 @@ mod tests {
|
||||||
|
|
||||||
assert_snapshot!(test.code_actions(&UNRESOLVED_REFERENCE), @r#"
|
assert_snapshot!(test.code_actions(&UNRESOLVED_REFERENCE), @r#"
|
||||||
info[code-action]: Ignore 'unresolved-reference' for this line
|
info[code-action]: Ignore 'unresolved-reference' for this line
|
||||||
--> main.py:4:17
|
--> main.py:4:5
|
||||||
|
|
|
|
||||||
2 | b = f"""
|
2 | b = f"""
|
||||||
3 | {
|
3 | {
|
||||||
|
|
@ -403,7 +397,7 @@ mod tests {
|
||||||
|
|
||||||
assert_snapshot!(test.code_actions(&UNRESOLVED_REFERENCE), @r#"
|
assert_snapshot!(test.code_actions(&UNRESOLVED_REFERENCE), @r#"
|
||||||
info[code-action]: Ignore 'unresolved-reference' for this line
|
info[code-action]: Ignore 'unresolved-reference' for this line
|
||||||
--> main.py:2:17
|
--> main.py:2:5
|
||||||
|
|
|
|
||||||
2 | b = a + """
|
2 | b = a + """
|
||||||
| ^
|
| ^
|
||||||
|
|
@ -415,7 +409,6 @@ mod tests {
|
||||||
3 | more text
|
3 | more text
|
||||||
- """
|
- """
|
||||||
4 + """ # ty:ignore[unresolved-reference]
|
4 + """ # ty:ignore[unresolved-reference]
|
||||||
5 |
|
|
||||||
"#);
|
"#);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -430,7 +423,7 @@ mod tests {
|
||||||
|
|
||||||
assert_snapshot!(test.code_actions(&UNRESOLVED_REFERENCE), @r#"
|
assert_snapshot!(test.code_actions(&UNRESOLVED_REFERENCE), @r#"
|
||||||
info[code-action]: Ignore 'unresolved-reference' for this line
|
info[code-action]: Ignore 'unresolved-reference' for this line
|
||||||
--> main.py:2:17
|
--> main.py:2:5
|
||||||
|
|
|
|
||||||
2 | b = a \
|
2 | b = a \
|
||||||
| ^
|
| ^
|
||||||
|
|
@ -440,7 +433,6 @@ mod tests {
|
||||||
2 | b = a \
|
2 | b = a \
|
||||||
- + "test"
|
- + "test"
|
||||||
3 + + "test" # ty:ignore[unresolved-reference]
|
3 + + "test" # ty:ignore[unresolved-reference]
|
||||||
4 |
|
|
||||||
"#);
|
"#);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -454,7 +446,7 @@ mod tests {
|
||||||
|
|
||||||
assert_snapshot!(test.code_actions(&UNDEFINED_REVEAL), @r"
|
assert_snapshot!(test.code_actions(&UNDEFINED_REVEAL), @r"
|
||||||
info[code-action]: import typing.reveal_type
|
info[code-action]: import typing.reveal_type
|
||||||
--> main.py:2:13
|
--> main.py:2:1
|
||||||
|
|
|
|
||||||
2 | reveal_type(1)
|
2 | reveal_type(1)
|
||||||
| ^^^^^^^^^^^
|
| ^^^^^^^^^^^
|
||||||
|
|
@ -463,10 +455,9 @@ mod tests {
|
||||||
1 + from typing import reveal_type
|
1 + from typing import reveal_type
|
||||||
2 |
|
2 |
|
||||||
3 | reveal_type(1)
|
3 | reveal_type(1)
|
||||||
4 |
|
|
||||||
|
|
||||||
info[code-action]: Ignore 'undefined-reveal' for this line
|
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)
|
||||||
| ^^^^^^^^^^^
|
| ^^^^^^^^^^^
|
||||||
|
|
@ -474,7 +465,230 @@ mod tests {
|
||||||
1 |
|
1 |
|
||||||
- reveal_type(1)
|
- reveal_type(1)
|
||||||
2 + reveal_type(1) # ty:ignore[undefined-reveal]
|
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 |
|
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();
|
db.init_program().unwrap();
|
||||||
|
|
||||||
let mut cleansed = source.to_string();
|
let mut cleansed = dedent(source).to_string();
|
||||||
|
|
||||||
let start = cleansed
|
let start = cleansed
|
||||||
.find("<START>")
|
.find("<START>")
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,7 @@ impl<'db> Completions<'db> {
|
||||||
self.items
|
self.items
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert this collection into a list of "import..." fixes
|
||||||
fn into_imports(mut self) -> Vec<ImportEdit> {
|
fn into_imports(mut self) -> Vec<ImportEdit> {
|
||||||
self.items.sort_by(compare_suggestions);
|
self.items.sort_by(compare_suggestions);
|
||||||
self.items
|
self.items
|
||||||
|
|
@ -82,6 +83,28 @@ impl<'db> Completions<'db> {
|
||||||
.collect()
|
.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.
|
/// Attempts to adds the given completion to this collection.
|
||||||
///
|
///
|
||||||
/// When added, `true` is returned.
|
/// When added, `true` is returned.
|
||||||
|
|
@ -555,15 +578,19 @@ pub(crate) struct ImportEdit {
|
||||||
pub edit: Edit,
|
pub edit: Edit,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn missing_imports(
|
/// Get fixes that would resolve an unresolved reference
|
||||||
|
pub(crate) fn unresolved_fixes(
|
||||||
db: &dyn Db,
|
db: &dyn Db,
|
||||||
file: File,
|
file: File,
|
||||||
parsed: &ParsedModuleRef,
|
parsed: &ParsedModuleRef,
|
||||||
symbol: &str,
|
symbol: &str,
|
||||||
node: AnyNodeRef,
|
node: AnyNodeRef,
|
||||||
) -> Vec<ImportEdit> {
|
) -> Vec<ImportEdit> {
|
||||||
let mut completions = Completions::exactly(db, symbol);
|
let mut results = Vec::new();
|
||||||
let scoped = ScopedTarget { node };
|
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(
|
add_unimported_completions(
|
||||||
db,
|
db,
|
||||||
file,
|
file,
|
||||||
|
|
@ -574,8 +601,23 @@ pub(crate) fn missing_imports(
|
||||||
},
|
},
|
||||||
&mut completions,
|
&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.
|
/// Adds completions derived from keywords.
|
||||||
|
|
@ -4711,8 +4753,7 @@ from os.<CURSOR>
|
||||||
let last_nonunderscore = test
|
let last_nonunderscore = test
|
||||||
.completions()
|
.completions()
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|c| !c.name.starts_with('_'))
|
.rfind(|c| !c.name.starts_with('_'))
|
||||||
.next_back()
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(&last_nonunderscore.name, "type_check_only");
|
assert_eq!(&last_nonunderscore.name, "type_check_only");
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@ use std::iter::FusedIterator;
|
||||||
use std::panic::{AssertUnwindSafe, UnwindSafe};
|
use std::panic::{AssertUnwindSafe, UnwindSafe};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tracing::error;
|
|
||||||
use ty_python_semantic::add_inferred_python_version_hint_to_diagnostic;
|
use ty_python_semantic::add_inferred_python_version_hint_to_diagnostic;
|
||||||
use ty_python_semantic::lint::RuleSelection;
|
use ty_python_semantic::lint::RuleSelection;
|
||||||
use ty_python_semantic::types::check_types;
|
use ty_python_semantic::types::check_types;
|
||||||
|
|
|
||||||
|
|
@ -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],
|
||||||
|
): ...
|
||||||
|
```
|
||||||
|
|
@ -424,9 +424,8 @@ p3 = ParamSpecWithDefault4[[int], [str]]()
|
||||||
reveal_type(p3.attr1) # revealed: (int, /) -> None
|
reveal_type(p3.attr1) # revealed: (int, /) -> None
|
||||||
reveal_type(p3.attr2) # revealed: (str, /) -> None
|
reveal_type(p3.attr2) # revealed: (str, /) -> None
|
||||||
|
|
||||||
# TODO: error
|
|
||||||
# Un-ordered type variables as the default of `PAnother` is `P`
|
# 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]
|
attr: Callable[PAnother, None]
|
||||||
|
|
||||||
# TODO: error
|
# TODO: error
|
||||||
|
|
|
||||||
|
|
@ -800,6 +800,29 @@ def func(x: D): ...
|
||||||
func(G()) # error: [invalid-argument-type]
|
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
|
## 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
|
Our special handling for `tuple` does not break if `tuple` is defined as a PEP-695 generic class in
|
||||||
|
|
|
||||||
|
|
@ -687,3 +687,59 @@ reveal_type(with_parameters(int_int, 1)) # revealed: Overload[(x: int) -> str,
|
||||||
# error: [invalid-argument-type]
|
# error: [invalid-argument-type]
|
||||||
reveal_type(with_parameters(int_int, "a")) # revealed: Overload[(x: int) -> str, (x: str) -> str]
|
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)
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -418,6 +418,18 @@ Using the `@abstractmethod` decorator requires that the class's metaclass is `AB
|
||||||
from it.
|
from it.
|
||||||
|
|
||||||
```py
|
```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:
|
class Foo:
|
||||||
@overload
|
@overload
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
|
|
@ -448,6 +460,52 @@ class PartialFoo(ABC):
|
||||||
def f(self, x: str) -> str: ...
|
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
|
### `@overload`-decorated functions with non-stub bodies
|
||||||
|
|
||||||
<!-- snapshot-diagnostics -->
|
<!-- snapshot-diagnostics -->
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
```
|
||||||
|
|
@ -42,7 +42,11 @@ error[invalid-overload]: Overloads for function `func` must be followed by a non
|
||||||
9 | class Foo:
|
9 | class Foo:
|
||||||
|
|
|
|
||||||
info: Attempting to call `func` will raise `TypeError` at runtime
|
info: Attempting to call `func` will raise `TypeError` at runtime
|
||||||
info: Overloaded functions without implementations are only permitted in stub files, on protocols, or for abstract methods
|
info: Overloaded functions without implementations are only permitted:
|
||||||
|
info: - in stub files
|
||||||
|
info: - in `if TYPE_CHECKING` blocks
|
||||||
|
info: - as methods on protocol classes
|
||||||
|
info: - or as `@abstractmethod`-decorated methods on abstract classes
|
||||||
info: See https://docs.python.org/3/library/typing.html#typing.overload for more details
|
info: See https://docs.python.org/3/library/typing.html#typing.overload for more details
|
||||||
info: rule `invalid-overload` is enabled by default
|
info: rule `invalid-overload` is enabled by default
|
||||||
|
|
||||||
|
|
@ -58,7 +62,11 @@ error[invalid-overload]: Overloads for function `method` must be followed by a n
|
||||||
| ^^^^^^
|
| ^^^^^^
|
||||||
|
|
|
|
||||||
info: Attempting to call `method` will raise `TypeError` at runtime
|
info: Attempting to call `method` will raise `TypeError` at runtime
|
||||||
info: Overloaded functions without implementations are only permitted in stub files, on protocols, or for abstract methods
|
info: Overloaded functions without implementations are only permitted:
|
||||||
|
info: - in stub files
|
||||||
|
info: - in `if TYPE_CHECKING` blocks
|
||||||
|
info: - as methods on protocol classes
|
||||||
|
info: - or as `@abstractmethod`-decorated methods on abstract classes
|
||||||
info: See https://docs.python.org/3/library/typing.html#typing.overload for more details
|
info: See https://docs.python.org/3/library/typing.html#typing.overload for more details
|
||||||
info: rule `invalid-overload` is enabled by default
|
info: rule `invalid-overload` is enabled by default
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10869,7 +10869,6 @@ pub struct UnionTypeInstance<'db> {
|
||||||
/// `<class 'str'>`. For `Union[int, str]`, this field is `None`, as we infer
|
/// `<class 'str'>`. For `Union[int, str]`, this field is `None`, as we infer
|
||||||
/// the elements as type expressions. Use `value_expression_types` to get the
|
/// the elements as type expressions. Use `value_expression_types` to get the
|
||||||
/// corresponding value expression types.
|
/// corresponding value expression types.
|
||||||
#[expect(clippy::ref_option)]
|
|
||||||
#[returns(ref)]
|
#[returns(ref)]
|
||||||
_value_expr_types: Option<Box<[Type<'db>]>>,
|
_value_expr_types: Option<Box<[Type<'db>]>>,
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1830,13 +1830,6 @@ impl<'db> ClassLiteral<'db> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determine if this is an abstract class.
|
|
||||||
pub(super) fn is_abstract(self, db: &'db dyn Db) -> bool {
|
|
||||||
self.metaclass(db)
|
|
||||||
.as_class_literal()
|
|
||||||
.is_some_and(|metaclass| metaclass.is_known(db, KnownClass::ABCMeta))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the types of the decorators on this class
|
/// Return the types of the decorators on this class
|
||||||
#[salsa::tracked(returns(deref), cycle_initial=decorators_cycle_initial, heap_size=ruff_memory_usage::heap_size)]
|
#[salsa::tracked(returns(deref), cycle_initial=decorators_cycle_initial, heap_size=ruff_memory_usage::heap_size)]
|
||||||
fn decorators(self, db: &'db dyn Db) -> Box<[Type<'db>]> {
|
fn decorators(self, db: &'db dyn Db) -> Box<[Type<'db>]> {
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ use crate::types::{
|
||||||
ProtocolInstanceType, SpecialFormType, SubclassOfInner, Type, TypeContext, binding_type,
|
ProtocolInstanceType, SpecialFormType, SubclassOfInner, Type, TypeContext, binding_type,
|
||||||
protocol_class::ProtocolClass,
|
protocol_class::ProtocolClass,
|
||||||
};
|
};
|
||||||
use crate::types::{DataclassFlags, KnownInstanceType, MemberLookupPolicy};
|
use crate::types::{DataclassFlags, KnownInstanceType, MemberLookupPolicy, TypeVarInstance};
|
||||||
use crate::{Db, DisplaySettings, FxIndexMap, Module, ModuleName, Program, declare_lint};
|
use crate::{Db, DisplaySettings, FxIndexMap, Module, ModuleName, Program, declare_lint};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use ruff_db::{
|
use ruff_db::{
|
||||||
|
|
@ -894,15 +894,20 @@ declare_lint! {
|
||||||
///
|
///
|
||||||
/// ## Why is this bad?
|
/// ## Why is this bad?
|
||||||
/// There are several requirements that you must follow when defining a generic class.
|
/// 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
|
/// ## Examples
|
||||||
/// ```python
|
/// ```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
|
/// # error: class uses both PEP-695 syntax and legacy syntax
|
||||||
/// class C[U](Generic[T]): ...
|
/// class C[U](Generic[T]): ...
|
||||||
|
///
|
||||||
|
/// # error: type parameter with default comes before type parameter without default
|
||||||
|
/// class D(Generic[U, T]): ...
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## References
|
/// ## References
|
||||||
|
|
@ -3695,6 +3700,90 @@ pub(crate) fn report_cannot_pop_required_field_on_typed_dict<'db>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn report_invalid_type_param_order<'db>(
|
||||||
|
context: &InferContext<'db, '_>,
|
||||||
|
class: ClassLiteral<'db>,
|
||||||
|
node: &ast::StmtClassDef,
|
||||||
|
typevar_with_default: TypeVarInstance<'db>,
|
||||||
|
invalid_later_typevars: &[TypeVarInstance<'db>],
|
||||||
|
) {
|
||||||
|
let db = context.db();
|
||||||
|
|
||||||
|
let base_index = class
|
||||||
|
.explicit_bases(db)
|
||||||
|
.iter()
|
||||||
|
.position(|base| {
|
||||||
|
matches!(
|
||||||
|
base,
|
||||||
|
Type::KnownInstance(
|
||||||
|
KnownInstanceType::SubscriptedProtocol(_)
|
||||||
|
| KnownInstanceType::SubscriptedGeneric(_)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.expect(
|
||||||
|
"It should not be possible for a class to have a legacy generic context \
|
||||||
|
if it does not inherit from `Protocol[]` or `Generic[]`",
|
||||||
|
);
|
||||||
|
|
||||||
|
let base_node = &node.bases()[base_index];
|
||||||
|
|
||||||
|
let primary_diagnostic_range = base_node
|
||||||
|
.as_subscript_expr()
|
||||||
|
.map(|subscript| &*subscript.slice)
|
||||||
|
.unwrap_or(base_node)
|
||||||
|
.range();
|
||||||
|
|
||||||
|
let Some(builder) = context.report_lint(&INVALID_GENERIC_CLASS, primary_diagnostic_range)
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut diagnostic = builder.into_diagnostic(
|
||||||
|
"Type parameters without defaults cannot follow type parameters with defaults",
|
||||||
|
);
|
||||||
|
|
||||||
|
diagnostic.set_concise_message(format_args!(
|
||||||
|
"Type parameter `{}` without a default cannot follow earlier parameter `{}` with a default",
|
||||||
|
invalid_later_typevars[0].name(db),
|
||||||
|
typevar_with_default.name(db),
|
||||||
|
));
|
||||||
|
|
||||||
|
if let [single_typevar] = invalid_later_typevars {
|
||||||
|
diagnostic.set_primary_message(format_args!(
|
||||||
|
"Type variable `{}` does not have a default",
|
||||||
|
single_typevar.name(db),
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
let later_typevars =
|
||||||
|
format_enumeration(invalid_later_typevars.iter().map(|tv| tv.name(db)));
|
||||||
|
diagnostic.set_primary_message(format_args!(
|
||||||
|
"Type variables {later_typevars} do not have defaults",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
diagnostic.annotate(
|
||||||
|
Annotation::primary(Span::from(context.file()).with_range(primary_diagnostic_range))
|
||||||
|
.message(format_args!(
|
||||||
|
"Earlier TypeVar `{}` does",
|
||||||
|
typevar_with_default.name(db)
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
|
||||||
|
for tvar in [typevar_with_default, invalid_later_typevars[0]] {
|
||||||
|
let Some(definition) = tvar.definition(db) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let file = definition.file(db);
|
||||||
|
diagnostic.annotate(
|
||||||
|
Annotation::secondary(Span::from(
|
||||||
|
definition.full_range(db, &parsed_module(db, file).load(db)),
|
||||||
|
))
|
||||||
|
.message(format_args!("`{}` defined here", tvar.name(db))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn report_rebound_typevar<'db>(
|
pub(crate) fn report_rebound_typevar<'db>(
|
||||||
context: &InferContext<'db, '_>,
|
context: &InferContext<'db, '_>,
|
||||||
typevar_name: &ast::name::Name,
|
typevar_name: &ast::name::Name,
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ use crate::types::{
|
||||||
IsEquivalentVisitor, KnownClass, KnownInstanceType, MaterializationKind, NormalizedVisitor,
|
IsEquivalentVisitor, KnownClass, KnownInstanceType, MaterializationKind, NormalizedVisitor,
|
||||||
OnlyReorder, Type, TypeContext, TypeMapping, TypeRelation, TypeVarBoundOrConstraints,
|
OnlyReorder, Type, TypeContext, TypeMapping, TypeRelation, TypeVarBoundOrConstraints,
|
||||||
TypeVarIdentity, TypeVarInstance, TypeVarKind, TypeVarVariance, UnionType, declaration_type,
|
TypeVarIdentity, TypeVarInstance, TypeVarKind, TypeVarVariance, UnionType, declaration_type,
|
||||||
walk_bound_type_var_type,
|
walk_type_var_bounds,
|
||||||
};
|
};
|
||||||
use crate::{Db, FxOrderMap, FxOrderSet};
|
use crate::{Db, FxOrderMap, FxOrderSet};
|
||||||
|
|
||||||
|
|
@ -290,6 +290,18 @@ impl<'db> GenericContext<'db> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the typevars that are inferable in this generic context. This set might include
|
||||||
|
/// more typevars than the ones directly bound by the generic context. For instance, consider a
|
||||||
|
/// method of a generic class:
|
||||||
|
///
|
||||||
|
/// ```py
|
||||||
|
/// class C[A]:
|
||||||
|
/// def method[T](self, t: T):
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// In this example, `method`'s generic context binds `Self` and `T`, but its inferable set
|
||||||
|
/// also includes `A@C`. This is needed because at each call site, we need to infer the
|
||||||
|
/// specialized class instance type whose method is being invoked.
|
||||||
pub(crate) fn inferable_typevars(self, db: &'db dyn Db) -> InferableTypeVars<'db, 'db> {
|
pub(crate) fn inferable_typevars(self, db: &'db dyn Db) -> InferableTypeVars<'db, 'db> {
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct CollectTypeVars<'db> {
|
struct CollectTypeVars<'db> {
|
||||||
|
|
@ -299,7 +311,7 @@ impl<'db> GenericContext<'db> {
|
||||||
|
|
||||||
impl<'db> TypeVisitor<'db> for CollectTypeVars<'db> {
|
impl<'db> TypeVisitor<'db> for CollectTypeVars<'db> {
|
||||||
fn should_visit_lazy_type_attributes(&self) -> bool {
|
fn should_visit_lazy_type_attributes(&self) -> bool {
|
||||||
true
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_bound_type_var_type(
|
fn visit_bound_type_var_type(
|
||||||
|
|
@ -310,7 +322,10 @@ impl<'db> GenericContext<'db> {
|
||||||
self.typevars
|
self.typevars
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.insert(bound_typevar.identity(db));
|
.insert(bound_typevar.identity(db));
|
||||||
walk_bound_type_var_type(db, bound_typevar, self);
|
let typevar = bound_typevar.typevar(db);
|
||||||
|
if let Some(bound_or_constraints) = typevar.bound_or_constraints(db) {
|
||||||
|
walk_type_var_bounds(db, bound_or_constraints, self);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_type(&self, db: &'db dyn Db, ty: Type<'db>) {
|
fn visit_type(&self, db: &'db dyn Db, ty: Type<'db>) {
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ use crate::types::diagnostic::{
|
||||||
report_invalid_exception_tuple_caught, report_invalid_generator_function_return_type,
|
report_invalid_exception_tuple_caught, report_invalid_generator_function_return_type,
|
||||||
report_invalid_key_on_typed_dict, report_invalid_or_unsupported_base,
|
report_invalid_key_on_typed_dict, report_invalid_or_unsupported_base,
|
||||||
report_invalid_return_type, report_invalid_type_checking_constant,
|
report_invalid_return_type, report_invalid_type_checking_constant,
|
||||||
report_named_tuple_field_with_leading_underscore,
|
report_invalid_type_param_order, report_named_tuple_field_with_leading_underscore,
|
||||||
report_namedtuple_field_without_default_after_field_with_default, report_non_subscriptable,
|
report_namedtuple_field_without_default_after_field_with_default, report_non_subscriptable,
|
||||||
report_possibly_missing_attribute, report_possibly_unresolved_reference,
|
report_possibly_missing_attribute, report_possibly_unresolved_reference,
|
||||||
report_rebound_typevar, report_slice_step_size_zero, report_unsupported_augmented_assignment,
|
report_rebound_typevar, report_slice_step_size_zero, report_unsupported_augmented_assignment,
|
||||||
|
|
@ -949,10 +949,48 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let scope = class.body_scope(self.db()).scope(self.db());
|
if self.context.is_lint_enabled(&INVALID_GENERIC_CLASS) {
|
||||||
if self.context.is_lint_enabled(&INVALID_GENERIC_CLASS)
|
if !class.has_pep_695_type_params(self.db())
|
||||||
&& let Some(parent) = scope.parent()
|
&& let Some(generic_context) = class.legacy_generic_context(self.db())
|
||||||
{
|
{
|
||||||
|
struct State<'db> {
|
||||||
|
typevar_with_default: TypeVarInstance<'db>,
|
||||||
|
invalid_later_tvars: Vec<TypeVarInstance<'db>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut state: Option<State<'db>> = None;
|
||||||
|
|
||||||
|
for bound_typevar in generic_context.variables(self.db()) {
|
||||||
|
let typevar = bound_typevar.typevar(self.db());
|
||||||
|
let has_default = typevar.default_type(self.db()).is_some();
|
||||||
|
|
||||||
|
if let Some(state) = state.as_mut() {
|
||||||
|
if !has_default {
|
||||||
|
state.invalid_later_tvars.push(typevar);
|
||||||
|
}
|
||||||
|
} else if has_default {
|
||||||
|
state = Some(State {
|
||||||
|
typevar_with_default: typevar,
|
||||||
|
invalid_later_tvars: vec![],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(state) = state
|
||||||
|
&& !state.invalid_later_tvars.is_empty()
|
||||||
|
{
|
||||||
|
report_invalid_type_param_order(
|
||||||
|
&self.context,
|
||||||
|
class,
|
||||||
|
class_node,
|
||||||
|
state.typevar_with_default,
|
||||||
|
&state.invalid_later_tvars,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let scope = class.body_scope(self.db()).scope(self.db());
|
||||||
|
if let Some(parent) = scope.parent() {
|
||||||
for self_typevar in class.typevars_referenced_in_definition(self.db()) {
|
for self_typevar in class.typevars_referenced_in_definition(self.db()) {
|
||||||
let self_typevar_name = self_typevar.typevar(self.db()).name(self.db());
|
let self_typevar_name = self_typevar.typevar(self.db()).name(self.db());
|
||||||
for enclosing in enclosing_generic_contexts(self.db(), self.index, parent) {
|
for enclosing in enclosing_generic_contexts(self.db(), self.index, parent) {
|
||||||
|
|
@ -970,6 +1008,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// (7) Check that a dataclass does not have more than one `KW_ONLY`.
|
// (7) Check that a dataclass does not have more than one `KW_ONLY`.
|
||||||
if let Some(field_policy @ CodeGeneratorKind::DataclassLike(_)) =
|
if let Some(field_policy @ CodeGeneratorKind::DataclassLike(_)) =
|
||||||
|
|
@ -1104,7 +1143,16 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
if implementation.is_none() && !self.in_stub() {
|
if implementation.is_none() && !self.in_stub() {
|
||||||
let mut implementation_required = true;
|
let mut implementation_required = true;
|
||||||
|
|
||||||
if let NodeWithScopeKind::Class(class_node_ref) = scope {
|
if function
|
||||||
|
.iter_overloads_and_implementation(self.db())
|
||||||
|
.all(|f| {
|
||||||
|
f.body_scope(self.db())
|
||||||
|
.scope(self.db())
|
||||||
|
.in_type_checking_block()
|
||||||
|
})
|
||||||
|
{
|
||||||
|
implementation_required = false;
|
||||||
|
} else if let NodeWithScopeKind::Class(class_node_ref) = scope {
|
||||||
let class = binding_type(
|
let class = binding_type(
|
||||||
self.db(),
|
self.db(),
|
||||||
self.index
|
self.index
|
||||||
|
|
@ -1113,7 +1161,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
.expect_class_literal();
|
.expect_class_literal();
|
||||||
|
|
||||||
if class.is_protocol(self.db())
|
if class.is_protocol(self.db())
|
||||||
|| (class.is_abstract(self.db())
|
|| (Type::ClassLiteral(class)
|
||||||
|
.is_subtype_of(self.db(), KnownClass::ABCMeta.to_instance(self.db()))
|
||||||
&& overloads.iter().all(|overload| {
|
&& overloads.iter().all(|overload| {
|
||||||
overload.has_known_decorator(
|
overload.has_known_decorator(
|
||||||
self.db(),
|
self.db(),
|
||||||
|
|
@ -1140,8 +1189,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
&function_node.name
|
&function_node.name
|
||||||
));
|
));
|
||||||
diagnostic.info(
|
diagnostic.info(
|
||||||
"Overloaded functions without implementations are only permitted \
|
"Overloaded functions without implementations are only permitted:",
|
||||||
in stub files, on protocols, or for abstract methods",
|
);
|
||||||
|
diagnostic.info(" - in stub files");
|
||||||
|
diagnostic.info(" - in `if TYPE_CHECKING` blocks");
|
||||||
|
diagnostic.info(" - as methods on protocol classes");
|
||||||
|
diagnostic.info(
|
||||||
|
" - or as `@abstractmethod`-decorated methods on abstract classes",
|
||||||
);
|
);
|
||||||
diagnostic.info(
|
diagnostic.info(
|
||||||
"See https://docs.python.org/3/library/typing.html#typing.overload \
|
"See https://docs.python.org/3/library/typing.html#typing.overload \
|
||||||
|
|
|
||||||
|
|
@ -1209,6 +1209,29 @@ impl<'db> Signature<'db> {
|
||||||
let mut check_types = |type1: Option<Type<'db>>, type2: Option<Type<'db>>| {
|
let mut check_types = |type1: Option<Type<'db>>, type2: Option<Type<'db>>| {
|
||||||
let type1 = type1.unwrap_or(Type::unknown());
|
let type1 = type1.unwrap_or(Type::unknown());
|
||||||
let type2 = type2.unwrap_or(Type::unknown());
|
let type2 = type2.unwrap_or(Type::unknown());
|
||||||
|
|
||||||
|
match (type1, type2) {
|
||||||
|
// This is a special case where the _same_ components of two different `ParamSpec`
|
||||||
|
// type variables are assignable to each other when they're both in an inferable
|
||||||
|
// position.
|
||||||
|
//
|
||||||
|
// `ParamSpec` type variables can only occur in parameter lists so this special case
|
||||||
|
// is present here instead of in `Type::has_relation_to_impl`.
|
||||||
|
(Type::TypeVar(typevar1), Type::TypeVar(typevar2))
|
||||||
|
if typevar1.paramspec_attr(db).is_some()
|
||||||
|
&& typevar1.paramspec_attr(db) == typevar2.paramspec_attr(db)
|
||||||
|
&& typevar1
|
||||||
|
.without_paramspec_attr(db)
|
||||||
|
.is_inferable(db, inferable)
|
||||||
|
&& typevar2
|
||||||
|
.without_paramspec_attr(db)
|
||||||
|
.is_inferable(db, inferable) =>
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
!result
|
!result
|
||||||
.intersect(
|
.intersect(
|
||||||
db,
|
db,
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,51 @@ expression: code_actions
|
||||||
},
|
},
|
||||||
"isPreferred": true
|
"isPreferred": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"title": "qualify warnings.deprecated",
|
||||||
|
"kind": "quickfix",
|
||||||
|
"diagnostics": [
|
||||||
|
{
|
||||||
|
"range": {
|
||||||
|
"start": {
|
||||||
|
"line": 3,
|
||||||
|
"character": 1
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 3,
|
||||||
|
"character": 11
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"severity": 1,
|
||||||
|
"code": "unresolved-reference",
|
||||||
|
"codeDescription": {
|
||||||
|
"href": "https://ty.dev/rules#unresolved-reference"
|
||||||
|
},
|
||||||
|
"source": "ty",
|
||||||
|
"message": "Name `deprecated` used when not defined"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"edit": {
|
||||||
|
"changes": {
|
||||||
|
"file://<temp_dir>/src/foo.py": [
|
||||||
|
{
|
||||||
|
"range": {
|
||||||
|
"start": {
|
||||||
|
"line": 3,
|
||||||
|
"character": 1
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 3,
|
||||||
|
"character": 11
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"newText": "warnings.deprecated"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"isPreferred": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"title": "Ignore 'unresolved-reference' for this line",
|
"title": "Ignore 'unresolved-reference' for this line",
|
||||||
"kind": "quickfix",
|
"kind": "quickfix",
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ install-path = ["$XDG_BIN_HOME/", "$XDG_DATA_HOME/../bin", "~/.local/bin"]
|
||||||
global = "depot-ubuntu-latest-4"
|
global = "depot-ubuntu-latest-4"
|
||||||
|
|
||||||
[dist.github-action-commits]
|
[dist.github-action-commits]
|
||||||
"actions/checkout" = "1af3b93b6815bc44a9784bd300feb67ff0d1eeb3" # v6.0.0
|
"actions/checkout" = "8e8c483db84b4bee98b60c0593521ed34d9990e8" # v6.0.1
|
||||||
"actions/upload-artifact" = "330a01c490aca151604b8cf639adc76d48f6c5d4" # v5.0.0
|
"actions/upload-artifact" = "330a01c490aca151604b8cf639adc76d48f6c5d4" # v5.0.0
|
||||||
"actions/download-artifact" = "018cc2cf5baa6db3ef3c5f8a56943fffe632ef53" # v6.0.0
|
"actions/download-artifact" = "018cc2cf5baa6db3ef3c5f8a56943fffe632ef53" # v6.0.0
|
||||||
"actions/attest-build-provenance" = "c074443f1aee8d4aeeae555aebba3282517141b2" #v2.2.3
|
"actions/attest-build-provenance" = "c074443f1aee8d4aeeae555aebba3282517141b2" #v2.2.3
|
||||||
|
|
|
||||||
|
|
@ -501,6 +501,48 @@ If you want Ruff to split an f-string across multiple lines, ensure there's a li
|
||||||
[self-documenting f-string]: https://realpython.com/python-f-strings/#self-documenting-expressions-for-debugging
|
[self-documenting f-string]: https://realpython.com/python-f-strings/#self-documenting-expressions-for-debugging
|
||||||
[configured quote style]: settings.md/#format_quote-style
|
[configured quote style]: settings.md/#format_quote-style
|
||||||
|
|
||||||
|
#### Fluent layout for method chains
|
||||||
|
|
||||||
|
At times, when developers write long chains of methods on an object, such as
|
||||||
|
|
||||||
|
```python
|
||||||
|
x = df.filter(cond).agg(func).merge(other)
|
||||||
|
```
|
||||||
|
|
||||||
|
the intent is to perform a sequence of transformations or operations
|
||||||
|
on a fixed object of interest - in this example, the object `df`.
|
||||||
|
Assuming the assigned expression exceeds the `line-length`, this preview
|
||||||
|
style will format the above as:
|
||||||
|
|
||||||
|
```python
|
||||||
|
x = (
|
||||||
|
df
|
||||||
|
.filter(cond)
|
||||||
|
.agg(func)
|
||||||
|
.merge(other)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
This deviates from the stable formatting, and also from Black, both
|
||||||
|
of which would produce:
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
x = (
|
||||||
|
df.filter(cond)
|
||||||
|
.agg(func)
|
||||||
|
.merge(other)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Both the stable and preview formatting are variants of something
|
||||||
|
called a **fluent layout**.
|
||||||
|
|
||||||
|
In general, this preview style differs from the stable style
|
||||||
|
only at the first attribute that precedes
|
||||||
|
a call or subscript. The preview formatting breaks _before_ this attribute,
|
||||||
|
while the stable formatting breaks _after_ the call or subscript.
|
||||||
|
|
||||||
## Sorting imports
|
## Sorting imports
|
||||||
|
|
||||||
Currently, the Ruff formatter does not sort imports. In order to both sort imports and format,
|
Currently, the Ruff formatter does not sort imports. In order to both sort imports and format,
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
[toolchain]
|
[toolchain]
|
||||||
channel = "1.91"
|
channel = "1.92"
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@ def main() -> None:
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--ty-path",
|
"--ty-path",
|
||||||
|
action="append",
|
||||||
type=Path,
|
type=Path,
|
||||||
help="Path to the ty binary to benchmark.",
|
help="Path to the ty binary to benchmark.",
|
||||||
)
|
)
|
||||||
|
|
@ -108,7 +109,11 @@ def main() -> None:
|
||||||
for tool_name in args.tool or TOOL_CHOICES:
|
for tool_name in args.tool or TOOL_CHOICES:
|
||||||
match tool_name:
|
match tool_name:
|
||||||
case "ty":
|
case "ty":
|
||||||
suites.append(Ty(path=args.ty_path))
|
if args.ty_path:
|
||||||
|
for path in args.ty_path:
|
||||||
|
suites.append(Ty(path=path))
|
||||||
|
else:
|
||||||
|
suites.append(Ty())
|
||||||
case "pyrefly":
|
case "pyrefly":
|
||||||
suites.append(Pyrefly())
|
suites.append(Pyrefly())
|
||||||
case "pyright":
|
case "pyright":
|
||||||
|
|
|
||||||
|
|
@ -595,7 +595,7 @@
|
||||||
},
|
},
|
||||||
"invalid-generic-class": {
|
"invalid-generic-class": {
|
||||||
"title": "detects invalid generic classes",
|
"title": "detects invalid generic classes",
|
||||||
"description": "## What it does\nChecks for the creation of invalid generic classes\n\n## Why is this bad?\nThere are several requirements that you must follow when defining a generic class.\n\n## Examples\n```python\nfrom typing import Generic, TypeVar\n\nT = TypeVar(\"T\") # okay\n\n# error: class uses both PEP-695 syntax and legacy syntax\nclass C[U](Generic[T]): ...\n```\n\n## References\n- [Typing spec: Generics](https://typing.python.org/en/latest/spec/generics.html#introduction)",
|
"description": "## What it does\nChecks for the creation of invalid generic classes\n\n## Why is this bad?\nThere are several requirements that you must follow when defining a generic class.\nMany of these result in `TypeError` being raised at runtime if they are violated.\n\n## Examples\n```python\nfrom typing_extensions import Generic, TypeVar\n\nT = TypeVar(\"T\")\nU = TypeVar(\"U\", default=int)\n\n# error: class uses both PEP-695 syntax and legacy syntax\nclass C[U](Generic[T]): ...\n\n# error: type parameter with default comes before type parameter without default\nclass D(Generic[U, T]): ...\n```\n\n## References\n- [Typing spec: Generics](https://typing.python.org/en/latest/spec/generics.html#introduction)",
|
||||||
"default": "error",
|
"default": "error",
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue