Compare commits

..

1 Commits

85 changed files with 545 additions and 3660 deletions

View File

@@ -10,12 +10,6 @@ Run all tests (using `nextest` for faster execution):
cargo nextest run
```
For faster test execution, use the `fast-test` profile which enables optimizations while retaining debug info:
```sh
cargo nextest run --cargo-profile fast-test
```
Run tests for a specific crate:
```sh

View File

@@ -335,11 +335,6 @@ strip = false
debug = "full"
lto = false
# Profile for faster iteration: applies minimal optimizations for faster tests.
[profile.fast-test]
inherits = "dev"
opt-level = 1
# The profile that 'cargo dist' will build with.
[profile.dist]
inherits = "release"

View File

@@ -526,7 +526,7 @@ impl VirtualFile {
}
/// Increments the revision of the underlying [`File`].
pub fn sync(&self, db: &mut dyn Db) {
fn sync(&self, db: &mut dyn Db) {
let file = self.0;
tracing::debug!("Updating the revision of `{}`", file.path(db));
let current_revision = file.revision(db);

View File

@@ -36,16 +36,13 @@ use crate::{Fix, FixAvailability, Violation};
/// ```python
/// import logging
///
/// logging.basicConfig(level=logging.INFO)
/// logger = logging.getLogger(__name__)
///
///
/// def sum_less_than_four(a, b):
/// logger.debug("Calling sum_less_than_four")
/// return a + b < 4
///
///
/// if __name__ == "__main__":
/// logging.basicConfig(level=logging.INFO)
/// ```
///
/// ## Fix safety

View File

@@ -5,7 +5,7 @@ use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::fix::edits::add_argument;
use crate::{Fix, FixAvailability, Violation};
use crate::{AlwaysFixableViolation, Applicability, Fix};
/// ## What it does
/// Checks for uses of `subprocess.run` without an explicit `check` argument.
@@ -39,12 +39,9 @@ use crate::{Fix, FixAvailability, Violation};
/// ```
///
/// ## Fix safety
///
/// This rule's fix is marked as display-only because it's not clear whether the
/// potential exception was meant to be ignored by setting `check=False` or if
/// the author simply forgot to include `check=True`. The fix adds
/// `check=False`, making the existing behavior explicit but possibly masking
/// the original intention.
/// This rule's fix is marked as unsafe for function calls that contain
/// `**kwargs`, as adding a `check` keyword argument to such a call may lead
/// to a duplicate keyword argument error.
///
/// ## References
/// - [Python documentation: `subprocess.run`](https://docs.python.org/3/library/subprocess.html#subprocess.run)
@@ -52,18 +49,14 @@ use crate::{Fix, FixAvailability, Violation};
#[violation_metadata(stable_since = "v0.0.285")]
pub(crate) struct SubprocessRunWithoutCheck;
impl Violation for SubprocessRunWithoutCheck {
// The fix is always set on the diagnostic, but display-only fixes aren't
// considered "fixable" in the tests.
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
impl AlwaysFixableViolation for SubprocessRunWithoutCheck {
#[derive_message_formats]
fn message(&self) -> String {
"`subprocess.run` without explicit `check` argument".to_string()
}
fn fix_title(&self) -> Option<String> {
Some("Add explicit `check=False`".to_string())
fn fix_title(&self) -> String {
"Add explicit `check=False`".to_string()
}
}
@@ -81,11 +74,20 @@ pub(crate) fn subprocess_run_without_check(checker: &Checker, call: &ast::ExprCa
if call.arguments.find_keyword("check").is_none() {
let mut diagnostic =
checker.report_diagnostic(SubprocessRunWithoutCheck, call.func.range());
diagnostic.set_fix(Fix::display_only_edit(add_argument(
"check=False",
&call.arguments,
checker.tokens(),
)));
diagnostic.set_fix(Fix::applicable_edit(
add_argument("check=False", &call.arguments, checker.tokens()),
// If the function call contains `**kwargs`, mark the fix as unsafe.
if call
.arguments
.keywords
.iter()
.any(|keyword| keyword.arg.is_none())
{
Applicability::Unsafe
} else {
Applicability::Safe
},
));
}
}
}

View File

@@ -19,7 +19,6 @@ help: Add explicit `check=False`
5 | subprocess.run("ls", shell=True)
6 | subprocess.run(
7 | ["ls"],
note: This is a display-only fix and is likely to be incorrect
PLW1510 [*] `subprocess.run` without explicit `check` argument
--> subprocess_run_without_check.py:5:1
@@ -40,7 +39,6 @@ help: Add explicit `check=False`
6 | subprocess.run(
7 | ["ls"],
8 | shell=False,
note: This is a display-only fix and is likely to be incorrect
PLW1510 [*] `subprocess.run` without explicit `check` argument
--> subprocess_run_without_check.py:6:1
@@ -61,7 +59,6 @@ help: Add explicit `check=False`
9 | )
10 | subprocess.run(["ls"], **kwargs)
11 |
note: This is a display-only fix and is likely to be incorrect
PLW1510 [*] `subprocess.run` without explicit `check` argument
--> subprocess_run_without_check.py:10:1
@@ -82,4 +79,4 @@ help: Add explicit `check=False`
11 |
12 | # Non-errors.
13 | subprocess.run("ls", check=True)
note: This is a display-only fix and is likely to be incorrect
note: This is an unsafe fix and may change runtime behavior

View File

@@ -91,22 +91,20 @@ def example(session):
.all()
# fmt: on
def off_and_on_without_data():
"""Test that comment-only fmt:off/on blocks preserve formatting."""
# fmt: off
#should not be formatted
# fmt: on
"""All comments here are technically on the same prefix.
The comments between will be formatted. This is a known limitation.
"""
# fmt: off
#should not be formatted
# fmt: on
# fmt: off
#should not be formatted
#should not be formatted #also should not be formatted
#hey, that won't work
# fmt: on
pass
def on_and_off_with_comment_only_blocks():
"""Test that fmt:off/on works with multiple directives and comment-only blocks."""
def on_and_off_broken():
"""Another known limitation."""
# fmt: on
# fmt: off
this=should.not_be.formatted()
@@ -115,16 +113,7 @@ def on_and_off_with_comment_only_blocks():
now . considers . multiple . fmt . directives . within . one . prefix
# fmt: on
# fmt: off
#should not be formatted
# fmt: on
# fmt: off
#should not be formatted
# fmt: on
# fmt: off
#should not be formatted
#should not be formatted #also should not be formatted
# ...but comments still get reformatted even though they should not be
# fmt: on
def long_lines():
if True:
@@ -189,50 +178,6 @@ cfg.rule(
# fmt: on
xxxxxxxxxx_xxxxxxxxxxx_xxxxxxx_xxxxxxxxx=5
)
# Test comment-only blocks at file level with various spacing patterns
# fmt: off
#nospace
# twospaces
# fmt: on
# fmt: off
#nospaceatall
#extraspaces
#evenmorespaces
# fmt: on
# fmt: off
# fmt: on
# fmt: off
#SBATCH --job-name=test
#SBATCH --output=test.out
# fmt: on
# fmt: off
#first
#second
# fmt: on
# fmt: off
#!@#$%^&*()
#<=>+-*/
# fmt: on
# fmt: off
#x=1+2
#y = 3
#z = 4
# fmt: on
# fmt: off
yield 'hello'
# No formatting to the end of the file

View File

@@ -112,42 +112,29 @@ def example(session):
def off_and_on_without_data():
"""Test that comment-only fmt:off/on blocks preserve formatting."""
# fmt: off
#should not be formatted
# fmt: on
"""All comments here are technically on the same prefix.
The comments between will be formatted. This is a known limitation.
"""
# fmt: off
#should not be formatted
# hey, that won't work
# fmt: on
# fmt: off
#should not be formatted
#should not be formatted #also should not be formatted
# fmt: on
pass
def on_and_off_with_comment_only_blocks():
"""Test that fmt:off/on works with multiple directives and comment-only blocks."""
def on_and_off_broken():
"""Another known limitation."""
# fmt: on
# fmt: off
this=should.not_be.formatted()
and_=indeed . it is not formatted
because . the . handling . inside . generate_ignored_nodes()
now . considers . multiple . fmt . directives . within . one . prefix
# fmt: off
#should not be formatted
# fmt: on
# fmt: off
#should not be formatted
# fmt: on
# fmt: off
#should not be formatted
#should not be formatted #also should not be formatted
# ...but comments still get reformatted even though they should not be
# fmt: on
@@ -224,50 +211,6 @@ cfg.rule(
# fmt: on
xxxxxxxxxx_xxxxxxxxxxx_xxxxxxx_xxxxxxxxx=5,
)
# Test comment-only blocks at file level with various spacing patterns
# fmt: off
#nospace
# twospaces
# fmt: on
# fmt: off
#nospaceatall
#extraspaces
#evenmorespaces
# fmt: on
# fmt: off
# fmt: on
# fmt: off
#SBATCH --job-name=test
#SBATCH --output=test.out
# fmt: on
# fmt: off
#first
#second
# fmt: on
# fmt: off
#!@#$%^&*()
#<=>+-*/
# fmt: on
# fmt: off
#x=1+2
#y = 3
#z = 4
# fmt: on
# fmt: off
yield 'hello'
# No formatting to the end of the file

View File

@@ -1,21 +1,8 @@
def foo(): return "mock" # fmt: skip
if True: print("yay") # fmt: skip
for i in range(10): print(i) # fmt: skip
if True: print("this"); print("that") # fmt: skip
while True: print("loop"); break # fmt: skip
for x in [1, 2]: print(x); print("done") # fmt: skip
def f(x: int): return x # fmt: skip
j = 1 # fmt: skip
while j < 10: j += 1 # fmt: skip
b = [c for c in "A very long string that would normally generate some kind of collapse, since it is this long"] # fmt: skip
v = (
foo_dict # fmt: skip
.setdefault("a", {})
.setdefault("b", {})
.setdefault("c", {})
.setdefault("d", {})
.setdefault("e", {})
)

View File

@@ -1,21 +1,8 @@
def foo(): return "mock" # fmt: skip
if True: print("yay") # fmt: skip
for i in range(10): print(i) # fmt: skip
if True: print("this"); print("that") # fmt: skip
while True: print("loop"); break # fmt: skip
for x in [1, 2]: print(x); print("done") # fmt: skip
def f(x: int): return x # fmt: skip
j = 1 # fmt: skip
while j < 10: j += 1 # fmt: skip
b = [c for c in "A very long string that would normally generate some kind of collapse, since it is this long"] # fmt: skip
v = (
foo_dict # fmt: skip
.setdefault("a", {})
.setdefault("b", {})
.setdefault("c", {})
.setdefault("d", {})
.setdefault("e", {})
)

View File

@@ -4,84 +4,3 @@ def foo():
# comment 1 # fmt: skip
# comment 2
[
(1, 2),
# # fmt: off
# (3,
# 4),
# # fmt: on
(5, 6),
]
[
(1, 2),
# # fmt: off
# (3,
# 4),
# fmt: on
(5, 6),
]
[
(1, 2),
# fmt: off
# (3,
# 4),
# # fmt: on
(5, 6),
]
[
(1, 2),
# fmt: off
# (3,
# 4),
# fmt: on
(5, 6),
]
[
(1, 2),
# # fmt: off
(3,
4),
# # fmt: on
(5, 6),
]
[
(1, 2),
# # fmt: off
(3,
4),
# fmt: on
(5, 6),
]
[
(1, 2),
# fmt: off
(3,
4),
# # fmt: on
(5, 6),
]
[
(1, 2),
# fmt: off
(3,
4),
# fmt: on
(5, 6),
]
if False:
# fmt: off # some other comment
pass

View File

@@ -4,84 +4,3 @@ def foo():
# comment 1 # fmt: skip
# comment 2
[
(1, 2),
# # fmt: off
# (3,
# 4),
# # fmt: on
(5, 6),
]
[
(1, 2),
# # fmt: off
# (3,
# 4),
# fmt: on
(5, 6),
]
[
(1, 2),
# fmt: off
# (3,
# 4),
# # fmt: on
(5, 6),
]
[
(1, 2),
# fmt: off
# (3,
# 4),
# fmt: on
(5, 6),
]
[
(1, 2),
# # fmt: off
(3,
4),
# # fmt: on
(5, 6),
]
[
(1, 2),
# # fmt: off
(3,
4),
# fmt: on
(5, 6),
]
[
(1, 2),
# fmt: off
(3,
4),
# # fmt: on
(5, 6),
]
[
(1, 2),
# fmt: off
(3,
4),
# fmt: on
(5, 6),
]
if False:
# fmt: off # some other comment
pass

View File

@@ -1 +0,0 @@
{"preview": "enabled"}

View File

@@ -1,8 +0,0 @@
with open("file.txt") as f: content = f.read() # fmt: skip
# Ideally, only the last line would be ignored
# But ignoring only part of the asexpr_test causes a parse error
# Same with ignoring the asexpr_test without also ignoring the entire with_stmt
with open (
"file.txt" ,
) as f: content = f.read() # fmt: skip

View File

@@ -1,8 +0,0 @@
with open("file.txt") as f: content = f.read() # fmt: skip
# Ideally, only the last line would be ignored
# But ignoring only part of the asexpr_test causes a parse error
# Same with ignoring the asexpr_test without also ignoring the entire with_stmt
with open (
"file.txt" ,
) as f: content = f.read() # fmt: skip

View File

@@ -1 +0,0 @@
{"preview": "enabled"}

View File

@@ -1,28 +0,0 @@
t = (
{"foo": "very long string", "bar": "another very long string", "baz": "we should run out of space by now"}, # fmt: skip
{"foo": "bar"},
)
t = (
{
"foo": "very long string",
"bar": "another very long string",
"baz": "we should run out of space by now",
}, # fmt: skip
{"foo": "bar"},
)
t = (
{"foo": "very long string", "bar": "another very long string", "baz": "we should run out of space by now"}, # fmt: skip
{"foo": "bar",},
)
t = (
{
"foo": "very long string",
"bar": "another very long string",
"baz": "we should run out of space by now",
}, # fmt: skip
{"foo": "bar",},
)

View File

@@ -1,32 +0,0 @@
t = (
{"foo": "very long string", "bar": "another very long string", "baz": "we should run out of space by now"}, # fmt: skip
{"foo": "bar"},
)
t = (
{
"foo": "very long string",
"bar": "another very long string",
"baz": "we should run out of space by now",
}, # fmt: skip
{"foo": "bar"},
)
t = (
{"foo": "very long string", "bar": "another very long string", "baz": "we should run out of space by now"}, # fmt: skip
{
"foo": "bar",
},
)
t = (
{
"foo": "very long string",
"bar": "another very long string",
"baz": "we should run out of space by now",
}, # fmt: skip
{
"foo": "bar",
},
)

View File

@@ -1,4 +1,4 @@
a = "this is some code"
b = 5 # fmt:skip
b = 5 #fmt:skip
c = 9 #fmt: skip
d = "thisisasuperlongstringthisisasuperlongstringthisisasuperlongstringthisisasuperlongstring" # fmt:skip
d = "thisisasuperlongstringthisisasuperlongstringthisisasuperlongstringthisisasuperlongstring" #fmt:skip

View File

@@ -1,4 +1,4 @@
a = "this is some code"
b = 5 # fmt:skip
c = 9 #fmt: skip
d = "thisisasuperlongstringthisisasuperlongstringthisisasuperlongstringthisisasuperlongstring" # fmt:skip
b = 5 # fmt:skip
c = 9 # fmt: skip
d = "thisisasuperlongstringthisisasuperlongstringthisisasuperlongstringthisisasuperlongstring" # fmt:skip

View File

@@ -1,19 +0,0 @@
# Multiple fmt: skip in multi-part if-clause
class ClassWithALongName:
Constant1 = 1
Constant2 = 2
Constant3 = 3
def test():
if (
"cond1" == "cond1"
and "cond2" == "cond2"
and 1 in (
ClassWithALongName.Constant1,
ClassWithALongName.Constant2,
ClassWithALongName.Constant3, # fmt: skip
) # fmt: skip
):
return True
return False

View File

@@ -1,19 +0,0 @@
# Multiple fmt: skip in multi-part if-clause
class ClassWithALongName:
Constant1 = 1
Constant2 = 2
Constant3 = 3
def test():
if (
"cond1" == "cond1"
and "cond2" == "cond2"
and 1 in (
ClassWithALongName.Constant1,
ClassWithALongName.Constant2,
ClassWithALongName.Constant3, # fmt: skip
) # fmt: skip
):
return True
return False

View File

@@ -1,35 +0,0 @@
# Multiple fmt: skip on string literals
a = (
"this should " # fmt: skip
"be fine"
)
b = (
"this is " # fmt: skip
"not working" # fmt: skip
)
c = (
"and neither " # fmt: skip
"is this " # fmt: skip
"working"
)
d = (
"nor "
"is this " # fmt: skip
"working" # fmt: skip
)
e = (
"and this " # fmt: skip
"is definitely "
"not working" # fmt: skip
)
# Dictionary entries with fmt: skip (covers issue with long lines)
hotkeys = {
"editor:swap-line-down": [{"key": "ArrowDown", "modifiers": ["Alt", "Mod"]}], # fmt: skip
"editor:swap-line-up": [{"key": "ArrowUp", "modifiers": ["Alt", "Mod"]}], # fmt: skip
"editor:toggle-source": [{"key": "S", "modifiers": ["Alt", "Mod"]}], # fmt: skip
}

View File

@@ -1,35 +0,0 @@
# Multiple fmt: skip on string literals
a = (
"this should " # fmt: skip
"be fine"
)
b = (
"this is " # fmt: skip
"not working" # fmt: skip
)
c = (
"and neither " # fmt: skip
"is this " # fmt: skip
"working"
)
d = (
"nor "
"is this " # fmt: skip
"working" # fmt: skip
)
e = (
"and this " # fmt: skip
"is definitely "
"not working" # fmt: skip
)
# Dictionary entries with fmt: skip (covers issue with long lines)
hotkeys = {
"editor:swap-line-down": [{"key": "ArrowDown", "modifiers": ["Alt", "Mod"]}], # fmt: skip
"editor:swap-line-up": [{"key": "ArrowUp", "modifiers": ["Alt", "Mod"]}], # fmt: skip
"editor:toggle-source": [{"key": "S", "modifiers": ["Alt", "Mod"]}], # fmt: skip
}

View File

@@ -1,24 +0,0 @@
# Test that Jupytext markdown comments are preserved before fmt:off/on blocks
# %% [markdown]
# fmt: off
# fmt: on
# Also test with other comments
# Some comment
# %% [markdown]
# Another comment
# fmt: off
x = 1
# fmt: on
# Test multiple markdown comments
# %% [markdown]
# First markdown
# %% [code]
# Code cell
# fmt: off
y = 2
# fmt: on

View File

@@ -1,24 +0,0 @@
# Test that Jupytext markdown comments are preserved before fmt:off/on blocks
# %% [markdown]
# fmt: off
# fmt: on
# Also test with other comments
# Some comment
# %% [markdown]
# Another comment
# fmt: off
x = 1
# fmt: on
# Test multiple markdown comments
# %% [markdown]
# First markdown
# %% [code]
# Code cell
# fmt: off
y = 2
# fmt: on

View File

@@ -1 +0,0 @@
{"target_version": "3.14"}

View File

@@ -1,40 +0,0 @@
x = t"foo"
x = t'foo {{ {2 + 2}bar {{ baz'
x = t"foo {f'abc'} bar"
x = t"""foo {{ a
foo {2 + 2}bar {{ baz
x = f"foo {{ {
2 + 2 # comment
}bar"
{{ baz
}} buzz
{print("abc" + "def"
)}
abc"""
t'{(abc:=10)}'
t'''This is a really long string, but just make sure that you reflow tstrings {
2+2:d
}'''
t'This is a really long string, but just make sure that you reflow tstrings correctly {2+2:d}'
t"{ 2 + 2 = }"
t'{
X
!r
}'
tr'\{{\}}'
t'''
WITH {f'''
{1}_cte AS ()'''}
'''

View File

@@ -1,40 +0,0 @@
x = t"foo"
x = t"foo {{ {2 + 2}bar {{ baz"
x = t"foo {f'abc'} bar"
x = t"""foo {{ a
foo {2 + 2}bar {{ baz
x = f"foo {{ {
2 + 2 # comment
}bar"
{{ baz
}} buzz
{print("abc" + "def"
)}
abc"""
t"{(abc:=10)}"
t"""This is a really long string, but just make sure that you reflow tstrings {
2+2:d
}"""
t"This is a really long string, but just make sure that you reflow tstrings correctly {2+2:d}"
t"{ 2 + 2 = }"
t"{
X
!r
}"
rt"\{{\}}"
t"""
WITH {f'''
{1}_cte AS ()'''}
"""

View File

@@ -1,19 +0,0 @@
# Regression test for https://github.com/psf/black/issues/3438
import ast
import collections # fmt: skip
import dataclasses
# fmt: off
import os
# fmt: on
import pathlib
import re # fmt: skip
import secrets
# fmt: off
import sys
# fmt: on
import tempfile
import zoneinfo

View File

@@ -1,19 +0,0 @@
# Regression test for https://github.com/psf/black/issues/3438
import ast
import collections # fmt: skip
import dataclasses
# fmt: off
import os
# fmt: on
import pathlib
import re # fmt: skip
import secrets
# fmt: off
import sys
# fmt: on
import tempfile
import zoneinfo

View File

@@ -156,6 +156,24 @@ Please use `--build-option` instead,
`--global-option` is reserved to flags like `--verbose` or `--quiet`.
"""
this_will_become_one_line = (
"a"
"b"
"c"
)
this_will_stay_on_three_lines = (
"a" # comment
"b"
"c"
)
this_will_also_become_one_line = ( # comment
"a"
"b"
"c"
)
assert some_var == expected_result, """
test
"""

View File

@@ -198,6 +198,16 @@ Please use `--build-option` instead,
`--global-option` is reserved to flags like `--verbose` or `--quiet`.
"""
this_will_become_one_line = "abc"
this_will_stay_on_three_lines = (
"a" # comment
"b"
"c"
)
this_will_also_become_one_line = "abc" # comment
assert some_var == expected_result, """
test
"""

View File

@@ -1,10 +0,0 @@
def foo(
a, #type:int
b, #type: str
c, # type: List[int]
d, # type: Dict[int, str]
e, # type: ignore
f, # type : ignore
g, # type : ignore
):
pass

View File

@@ -1,10 +0,0 @@
def foo(
a, # type: int
b, # type: str
c, # type: List[int]
d, # type: Dict[int, str]
e, # type: ignore
f, # type : ignore
g, # type : ignore
):
pass

View File

@@ -1,16 +0,0 @@
# Remove unnecessary parentheses from LHS of assignments
def a():
return [1, 2, 3]
# Single variable with unnecessary parentheses
(b) = a()[0]
# Tuple unpacking with unnecessary parentheses
(c, *_) = a()
# These should not be changed - parentheses are necessary
(d,) = a() # single-element tuple
e = (1 + 2) * 3 # RHS has precedence needs

View File

@@ -1,16 +0,0 @@
# Remove unnecessary parentheses from LHS of assignments
def a():
return [1, 2, 3]
# Single variable with unnecessary parentheses
b = a()[0]
# Tuple unpacking with unnecessary parentheses
c, *_ = a()
# These should not be changed - parentheses are necessary
(d,) = a() # single-element tuple
e = (1 + 2) * 3 # RHS has precedence needs

View File

@@ -1,5 +1,6 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/black/cases/fmtonoff.py
---
## Input
@@ -97,22 +98,20 @@ def example(session):
.all()
# fmt: on
def off_and_on_without_data():
"""Test that comment-only fmt:off/on blocks preserve formatting."""
# fmt: off
#should not be formatted
# fmt: on
"""All comments here are technically on the same prefix.
The comments between will be formatted. This is a known limitation.
"""
# fmt: off
#should not be formatted
# fmt: on
# fmt: off
#should not be formatted
#should not be formatted #also should not be formatted
#hey, that won't work
# fmt: on
pass
def on_and_off_with_comment_only_blocks():
"""Test that fmt:off/on works with multiple directives and comment-only blocks."""
def on_and_off_broken():
"""Another known limitation."""
# fmt: on
# fmt: off
this=should.not_be.formatted()
@@ -121,16 +120,7 @@ def on_and_off_with_comment_only_blocks():
now . considers . multiple . fmt . directives . within . one . prefix
# fmt: on
# fmt: off
#should not be formatted
# fmt: on
# fmt: off
#should not be formatted
# fmt: on
# fmt: off
#should not be formatted
#should not be formatted #also should not be formatted
# ...but comments still get reformatted even though they should not be
# fmt: on
def long_lines():
if True:
@@ -195,50 +185,6 @@ cfg.rule(
# fmt: on
xxxxxxxxxx_xxxxxxxxxxx_xxxxxxx_xxxxxxxxx=5
)
# Test comment-only blocks at file level with various spacing patterns
# fmt: off
#nospace
# twospaces
# fmt: on
# fmt: off
#nospaceatall
#extraspaces
#evenmorespaces
# fmt: on
# fmt: off
# fmt: on
# fmt: off
#SBATCH --job-name=test
#SBATCH --output=test.out
# fmt: on
# fmt: off
#first
#second
# fmt: on
# fmt: off
#!@#$%^&*()
#<=>+-*/
# fmt: on
# fmt: off
#x=1+2
#y = 3
#z = 4
# fmt: on
# fmt: off
yield 'hello'
# No formatting to the end of the file
@@ -279,16 +225,28 @@ d={'a':1,
# fmt: on
goes + here,
andhere,
@@ -136,7 +137,7 @@
and_=indeed . it is not formatted
because . the . handling . inside . generate_ignored_nodes()
now . considers . multiple . fmt . directives . within . one . prefix
-
+ # fmt: on
@@ -118,8 +119,10 @@
"""
# fmt: off
#should not be formatted
- # hey, that won't work
+ #hey, that won't work
+
+
# fmt: on
@@ -187,14 +188,18 @@
pass
@@ -134,7 +137,7 @@
now . considers . multiple . fmt . directives . within . one . prefix
# fmt: on
# fmt: off
- # ...but comments still get reformatted even though they should not be
+ # ...but comments still get reformatted even though they should not be
# fmt: on
@@ -174,14 +177,18 @@
$
""",
# fmt: off
@@ -429,24 +387,22 @@ def example(session):
def off_and_on_without_data():
"""Test that comment-only fmt:off/on blocks preserve formatting."""
# fmt: off
#should not be formatted
# fmt: on
"""All comments here are technically on the same prefix.
The comments between will be formatted. This is a known limitation.
"""
# fmt: off
#should not be formatted
# fmt: on
# fmt: off
#should not be formatted
#should not be formatted #also should not be formatted
#hey, that won't work
# fmt: on
pass
def on_and_off_with_comment_only_blocks():
"""Test that fmt:off/on works with multiple directives and comment-only blocks."""
def on_and_off_broken():
"""Another known limitation."""
# fmt: on
# fmt: off
this=should.not_be.formatted()
@@ -455,16 +411,7 @@ def on_and_off_with_comment_only_blocks():
now . considers . multiple . fmt . directives . within . one . prefix
# fmt: on
# fmt: off
#should not be formatted
# fmt: on
# fmt: off
#should not be formatted
# fmt: on
# fmt: off
#should not be formatted
#should not be formatted #also should not be formatted
# ...but comments still get reformatted even though they should not be
# fmt: on
@@ -545,50 +492,6 @@ cfg.rule(
# fmt: on
xxxxxxxxxx_xxxxxxxxxxx_xxxxxxx_xxxxxxxxx=5,
)
# Test comment-only blocks at file level with various spacing patterns
# fmt: off
#nospace
# twospaces
# fmt: on
# fmt: off
#nospaceatall
#extraspaces
#evenmorespaces
# fmt: on
# fmt: off
# fmt: on
# fmt: off
#SBATCH --job-name=test
#SBATCH --output=test.out
# fmt: on
# fmt: off
#first
#second
# fmt: on
# fmt: off
#!@#$%^&*()
#<=>+-*/
# fmt: on
# fmt: off
#x=1+2
#y = 3
#z = 4
# fmt: on
# fmt: off
yield 'hello'
# No formatting to the end of the file
@@ -714,42 +617,29 @@ def example(session):
def off_and_on_without_data():
"""Test that comment-only fmt:off/on blocks preserve formatting."""
# fmt: off
#should not be formatted
# fmt: on
"""All comments here are technically on the same prefix.
The comments between will be formatted. This is a known limitation.
"""
# fmt: off
#should not be formatted
# hey, that won't work
# fmt: on
# fmt: off
#should not be formatted
#should not be formatted #also should not be formatted
# fmt: on
pass
def on_and_off_with_comment_only_blocks():
"""Test that fmt:off/on works with multiple directives and comment-only blocks."""
def on_and_off_broken():
"""Another known limitation."""
# fmt: on
# fmt: off
this=should.not_be.formatted()
and_=indeed . it is not formatted
because . the . handling . inside . generate_ignored_nodes()
now . considers . multiple . fmt . directives . within . one . prefix
# fmt: off
#should not be formatted
# fmt: on
# fmt: off
#should not be formatted
# fmt: on
# fmt: off
#should not be formatted
#should not be formatted #also should not be formatted
# ...but comments still get reformatted even though they should not be
# fmt: on
@@ -826,50 +716,6 @@ cfg.rule(
# fmt: on
xxxxxxxxxx_xxxxxxxxxxx_xxxxxxx_xxxxxxxxx=5,
)
# Test comment-only blocks at file level with various spacing patterns
# fmt: off
#nospace
# twospaces
# fmt: on
# fmt: off
#nospaceatall
#extraspaces
#evenmorespaces
# fmt: on
# fmt: off
# fmt: on
# fmt: off
#SBATCH --job-name=test
#SBATCH --output=test.out
# fmt: on
# fmt: off
#first
#second
# fmt: on
# fmt: off
#!@#$%^&*()
#<=>+-*/
# fmt: on
# fmt: off
#x=1+2
#y = 3
#z = 4
# fmt: on
# fmt: off
yield 'hello'
# No formatting to the end of the file

View File

@@ -1,5 +1,6 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/black/cases/fmtskip10.py
---
## Input
@@ -7,24 +8,11 @@ source: crates/ruff_python_formatter/tests/fixtures.rs
def foo(): return "mock" # fmt: skip
if True: print("yay") # fmt: skip
for i in range(10): print(i) # fmt: skip
if True: print("this"); print("that") # fmt: skip
while True: print("loop"); break # fmt: skip
for x in [1, 2]: print(x); print("done") # fmt: skip
def f(x: int): return x # fmt: skip
j = 1 # fmt: skip
while j < 10: j += 1 # fmt: skip
b = [c for c in "A very long string that would normally generate some kind of collapse, since it is this long"] # fmt: skip
v = (
foo_dict # fmt: skip
.setdefault("a", {})
.setdefault("b", {})
.setdefault("c", {})
.setdefault("d", {})
.setdefault("e", {})
)
```
## Black Differences
@@ -32,30 +20,19 @@ v = (
```diff
--- Black
+++ Ruff
@@ -1,15 +1,20 @@
@@ -1,8 +1,10 @@
def foo(): return "mock" # fmt: skip
+
+
if True: print("yay") # fmt: skip
for i in range(10): print(i) # fmt: skip
if True: print("this"); print("that") # fmt: skip
while True: print("loop"); break # fmt: skip
for x in [1, 2]: print(x); print("done") # fmt: skip
-def f(x: int): return x # fmt: skip
-j = 1 # fmt: skip
+
+def f(x: int): return x # fmt: skip
+
+
+j = 1 # fmt: skip
while j < 10: j += 1 # fmt: skip
-b = [c for c in "A very long string that would normally generate some kind of collapse, since it is this long"] # fmt: skip
+b = [c for c in "A very long string that would normally generate some kind of collapse, since it is this long"] # fmt: skip
v = (
foo_dict # fmt: skip
```
## Ruff Output
@@ -66,27 +43,11 @@ def foo(): return "mock" # fmt: skip
if True: print("yay") # fmt: skip
for i in range(10): print(i) # fmt: skip
if True: print("this"); print("that") # fmt: skip
while True: print("loop"); break # fmt: skip
for x in [1, 2]: print(x); print("done") # fmt: skip
def f(x: int): return x # fmt: skip
j = 1 # fmt: skip
while j < 10: j += 1 # fmt: skip
b = [c for c in "A very long string that would normally generate some kind of collapse, since it is this long"] # fmt: skip
v = (
foo_dict # fmt: skip
.setdefault("a", {})
.setdefault("b", {})
.setdefault("c", {})
.setdefault("d", {})
.setdefault("e", {})
)
```
## Black Output
@@ -95,22 +56,9 @@ v = (
def foo(): return "mock" # fmt: skip
if True: print("yay") # fmt: skip
for i in range(10): print(i) # fmt: skip
if True: print("this"); print("that") # fmt: skip
while True: print("loop"); break # fmt: skip
for x in [1, 2]: print(x); print("done") # fmt: skip
def f(x: int): return x # fmt: skip
j = 1 # fmt: skip
while j < 10: j += 1 # fmt: skip
b = [c for c in "A very long string that would normally generate some kind of collapse, since it is this long"] # fmt: skip
v = (
foo_dict # fmt: skip
.setdefault("a", {})
.setdefault("b", {})
.setdefault("c", {})
.setdefault("d", {})
.setdefault("e", {})
)
```

View File

@@ -1,321 +0,0 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
---
## Input
```python
def foo():
pass
# comment 1 # fmt: skip
# comment 2
[
(1, 2),
# # fmt: off
# (3,
# 4),
# # fmt: on
(5, 6),
]
[
(1, 2),
# # fmt: off
# (3,
# 4),
# fmt: on
(5, 6),
]
[
(1, 2),
# fmt: off
# (3,
# 4),
# # fmt: on
(5, 6),
]
[
(1, 2),
# fmt: off
# (3,
# 4),
# fmt: on
(5, 6),
]
[
(1, 2),
# # fmt: off
(3,
4),
# # fmt: on
(5, 6),
]
[
(1, 2),
# # fmt: off
(3,
4),
# fmt: on
(5, 6),
]
[
(1, 2),
# fmt: off
(3,
4),
# # fmt: on
(5, 6),
]
[
(1, 2),
# fmt: off
(3,
4),
# fmt: on
(5, 6),
]
if False:
# fmt: off # some other comment
pass
```
## Black Differences
```diff
--- Black
+++ Ruff
@@ -46,8 +46,7 @@
[
(1, 2),
# # fmt: off
- (3,
- 4),
+ (3, 4),
# # fmt: on
(5, 6),
]
@@ -55,8 +54,7 @@
[
(1, 2),
# # fmt: off
- (3,
- 4),
+ (3, 4),
# fmt: on
(5, 6),
]
@@ -65,8 +63,7 @@
[
(1, 2),
# fmt: off
- (3,
- 4),
+ (3, 4),
# # fmt: on
(5, 6),
]
@@ -75,8 +72,7 @@
[
(1, 2),
# fmt: off
- (3,
- 4),
+ (3, 4),
# fmt: on
(5, 6),
]
```
## Ruff Output
```python
def foo():
pass
# comment 1 # fmt: skip
# comment 2
[
(1, 2),
# # fmt: off
# (3,
# 4),
# # fmt: on
(5, 6),
]
[
(1, 2),
# # fmt: off
# (3,
# 4),
# fmt: on
(5, 6),
]
[
(1, 2),
# fmt: off
# (3,
# 4),
# # fmt: on
(5, 6),
]
[
(1, 2),
# fmt: off
# (3,
# 4),
# fmt: on
(5, 6),
]
[
(1, 2),
# # fmt: off
(3, 4),
# # fmt: on
(5, 6),
]
[
(1, 2),
# # fmt: off
(3, 4),
# fmt: on
(5, 6),
]
[
(1, 2),
# fmt: off
(3, 4),
# # fmt: on
(5, 6),
]
[
(1, 2),
# fmt: off
(3, 4),
# fmt: on
(5, 6),
]
if False:
# fmt: off # some other comment
pass
```
## Black Output
```python
def foo():
pass
# comment 1 # fmt: skip
# comment 2
[
(1, 2),
# # fmt: off
# (3,
# 4),
# # fmt: on
(5, 6),
]
[
(1, 2),
# # fmt: off
# (3,
# 4),
# fmt: on
(5, 6),
]
[
(1, 2),
# fmt: off
# (3,
# 4),
# # fmt: on
(5, 6),
]
[
(1, 2),
# fmt: off
# (3,
# 4),
# fmt: on
(5, 6),
]
[
(1, 2),
# # fmt: off
(3,
4),
# # fmt: on
(5, 6),
]
[
(1, 2),
# # fmt: off
(3,
4),
# fmt: on
(5, 6),
]
[
(1, 2),
# fmt: off
(3,
4),
# # fmt: on
(5, 6),
]
[
(1, 2),
# fmt: off
(3,
4),
# fmt: on
(5, 6),
]
if False:
# fmt: off # some other comment
pass
```

View File

@@ -1,59 +0,0 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
---
## Input
```python
with open("file.txt") as f: content = f.read() # fmt: skip
# Ideally, only the last line would be ignored
# But ignoring only part of the asexpr_test causes a parse error
# Same with ignoring the asexpr_test without also ignoring the entire with_stmt
with open (
"file.txt" ,
) as f: content = f.read() # fmt: skip
```
## Black Differences
```diff
--- Black
+++ Ruff
@@ -1,8 +1,8 @@
-with open("file.txt") as f: content = f.read() # fmt: skip
+with open("file.txt") as f: content = f.read() # fmt: skip
# Ideally, only the last line would be ignored
# But ignoring only part of the asexpr_test causes a parse error
# Same with ignoring the asexpr_test without also ignoring the entire with_stmt
with open (
"file.txt" ,
-) as f: content = f.read() # fmt: skip
+) as f: content = f.read() # fmt: skip
```
## Ruff Output
```python
with open("file.txt") as f: content = f.read() # fmt: skip
# Ideally, only the last line would be ignored
# But ignoring only part of the asexpr_test causes a parse error
# Same with ignoring the asexpr_test without also ignoring the entire with_stmt
with open (
"file.txt" ,
) as f: content = f.read() # fmt: skip
```
## Black Output
```python
with open("file.txt") as f: content = f.read() # fmt: skip
# Ideally, only the last line would be ignored
# But ignoring only part of the asexpr_test causes a parse error
# Same with ignoring the asexpr_test without also ignoring the entire with_stmt
with open (
"file.txt" ,
) as f: content = f.read() # fmt: skip
```

View File

@@ -1,149 +0,0 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
---
## Input
```python
t = (
{"foo": "very long string", "bar": "another very long string", "baz": "we should run out of space by now"}, # fmt: skip
{"foo": "bar"},
)
t = (
{
"foo": "very long string",
"bar": "another very long string",
"baz": "we should run out of space by now",
}, # fmt: skip
{"foo": "bar"},
)
t = (
{"foo": "very long string", "bar": "another very long string", "baz": "we should run out of space by now"}, # fmt: skip
{"foo": "bar",},
)
t = (
{
"foo": "very long string",
"bar": "another very long string",
"baz": "we should run out of space by now",
}, # fmt: skip
{"foo": "bar",},
)
```
## Black Differences
```diff
--- Black
+++ Ruff
@@ -1,5 +1,9 @@
t = (
- {"foo": "very long string", "bar": "another very long string", "baz": "we should run out of space by now"}, # fmt: skip
+ {
+ "foo": "very long string",
+ "bar": "another very long string",
+ "baz": "we should run out of space by now",
+ }, # fmt: skip
{"foo": "bar"},
)
@@ -14,8 +18,12 @@
t = (
- {"foo": "very long string", "bar": "another very long string", "baz": "we should run out of space by now"}, # fmt: skip
{
+ "foo": "very long string",
+ "bar": "another very long string",
+ "baz": "we should run out of space by now",
+ }, # fmt: skip
+ {
"foo": "bar",
},
)
```
## Ruff Output
```python
t = (
{
"foo": "very long string",
"bar": "another very long string",
"baz": "we should run out of space by now",
}, # fmt: skip
{"foo": "bar"},
)
t = (
{
"foo": "very long string",
"bar": "another very long string",
"baz": "we should run out of space by now",
}, # fmt: skip
{"foo": "bar"},
)
t = (
{
"foo": "very long string",
"bar": "another very long string",
"baz": "we should run out of space by now",
}, # fmt: skip
{
"foo": "bar",
},
)
t = (
{
"foo": "very long string",
"bar": "another very long string",
"baz": "we should run out of space by now",
}, # fmt: skip
{
"foo": "bar",
},
)
```
## Black Output
```python
t = (
{"foo": "very long string", "bar": "another very long string", "baz": "we should run out of space by now"}, # fmt: skip
{"foo": "bar"},
)
t = (
{
"foo": "very long string",
"bar": "another very long string",
"baz": "we should run out of space by now",
}, # fmt: skip
{"foo": "bar"},
)
t = (
{"foo": "very long string", "bar": "another very long string", "baz": "we should run out of space by now"}, # fmt: skip
{
"foo": "bar",
},
)
t = (
{
"foo": "very long string",
"bar": "another very long string",
"baz": "we should run out of space by now",
}, # fmt: skip
{
"foo": "bar",
},
)
```

View File

@@ -1,43 +0,0 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
---
## Input
```python
a = "this is some code"
b = 5 # fmt:skip
c = 9 #fmt: skip
d = "thisisasuperlongstringthisisasuperlongstringthisisasuperlongstringthisisasuperlongstring" # fmt:skip
```
## Black Differences
```diff
--- Black
+++ Ruff
@@ -1,4 +1,4 @@
a = "this is some code"
-b = 5 # fmt:skip
-c = 9 #fmt: skip
+b = 5 # fmt:skip
+c = 9 # fmt: skip
d = "thisisasuperlongstringthisisasuperlongstringthisisasuperlongstringthisisasuperlongstring" # fmt:skip
```
## Ruff Output
```python
a = "this is some code"
b = 5 # fmt:skip
c = 9 # fmt: skip
d = "thisisasuperlongstringthisisasuperlongstringthisisasuperlongstringthisisasuperlongstring" # fmt:skip
```
## Black Output
```python
a = "this is some code"
b = 5 # fmt:skip
c = 9 #fmt: skip
d = "thisisasuperlongstringthisisasuperlongstringthisisasuperlongstringthisisasuperlongstring" # fmt:skip
```

View File

@@ -1,98 +0,0 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
---
## Input
```python
# Multiple fmt: skip in multi-part if-clause
class ClassWithALongName:
Constant1 = 1
Constant2 = 2
Constant3 = 3
def test():
if (
"cond1" == "cond1"
and "cond2" == "cond2"
and 1 in (
ClassWithALongName.Constant1,
ClassWithALongName.Constant2,
ClassWithALongName.Constant3, # fmt: skip
) # fmt: skip
):
return True
return False
```
## Black Differences
```diff
--- Black
+++ Ruff
@@ -9,11 +9,12 @@
if (
"cond1" == "cond1"
and "cond2" == "cond2"
- and 1 in (
+ and 1
+ in (
ClassWithALongName.Constant1,
ClassWithALongName.Constant2,
- ClassWithALongName.Constant3, # fmt: skip
- ) # fmt: skip
+ ClassWithALongName.Constant3, # fmt: skip
+ ) # fmt: skip
):
return True
return False
```
## Ruff Output
```python
# Multiple fmt: skip in multi-part if-clause
class ClassWithALongName:
Constant1 = 1
Constant2 = 2
Constant3 = 3
def test():
if (
"cond1" == "cond1"
and "cond2" == "cond2"
and 1
in (
ClassWithALongName.Constant1,
ClassWithALongName.Constant2,
ClassWithALongName.Constant3, # fmt: skip
) # fmt: skip
):
return True
return False
```
## Black Output
```python
# Multiple fmt: skip in multi-part if-clause
class ClassWithALongName:
Constant1 = 1
Constant2 = 2
Constant3 = 3
def test():
if (
"cond1" == "cond1"
and "cond2" == "cond2"
and 1 in (
ClassWithALongName.Constant1,
ClassWithALongName.Constant2,
ClassWithALongName.Constant3, # fmt: skip
) # fmt: skip
):
return True
return False
```

View File

@@ -1,148 +0,0 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
---
## Input
```python
# Multiple fmt: skip on string literals
a = (
"this should " # fmt: skip
"be fine"
)
b = (
"this is " # fmt: skip
"not working" # fmt: skip
)
c = (
"and neither " # fmt: skip
"is this " # fmt: skip
"working"
)
d = (
"nor "
"is this " # fmt: skip
"working" # fmt: skip
)
e = (
"and this " # fmt: skip
"is definitely "
"not working" # fmt: skip
)
# Dictionary entries with fmt: skip (covers issue with long lines)
hotkeys = {
"editor:swap-line-down": [{"key": "ArrowDown", "modifiers": ["Alt", "Mod"]}], # fmt: skip
"editor:swap-line-up": [{"key": "ArrowUp", "modifiers": ["Alt", "Mod"]}], # fmt: skip
"editor:toggle-source": [{"key": "S", "modifiers": ["Alt", "Mod"]}], # fmt: skip
}
```
## Black Differences
```diff
--- Black
+++ Ruff
@@ -29,7 +29,11 @@
# Dictionary entries with fmt: skip (covers issue with long lines)
hotkeys = {
- "editor:swap-line-down": [{"key": "ArrowDown", "modifiers": ["Alt", "Mod"]}], # fmt: skip
- "editor:swap-line-up": [{"key": "ArrowUp", "modifiers": ["Alt", "Mod"]}], # fmt: skip
- "editor:toggle-source": [{"key": "S", "modifiers": ["Alt", "Mod"]}], # fmt: skip
+ "editor:swap-line-down": [
+ {"key": "ArrowDown", "modifiers": ["Alt", "Mod"]}
+ ], # fmt: skip
+ "editor:swap-line-up": [
+ {"key": "ArrowUp", "modifiers": ["Alt", "Mod"]}
+ ], # fmt: skip
+ "editor:toggle-source": [{"key": "S", "modifiers": ["Alt", "Mod"]}], # fmt: skip
}
```
## Ruff Output
```python
# Multiple fmt: skip on string literals
a = (
"this should " # fmt: skip
"be fine"
)
b = (
"this is " # fmt: skip
"not working" # fmt: skip
)
c = (
"and neither " # fmt: skip
"is this " # fmt: skip
"working"
)
d = (
"nor "
"is this " # fmt: skip
"working" # fmt: skip
)
e = (
"and this " # fmt: skip
"is definitely "
"not working" # fmt: skip
)
# Dictionary entries with fmt: skip (covers issue with long lines)
hotkeys = {
"editor:swap-line-down": [
{"key": "ArrowDown", "modifiers": ["Alt", "Mod"]}
], # fmt: skip
"editor:swap-line-up": [
{"key": "ArrowUp", "modifiers": ["Alt", "Mod"]}
], # fmt: skip
"editor:toggle-source": [{"key": "S", "modifiers": ["Alt", "Mod"]}], # fmt: skip
}
```
## Black Output
```python
# Multiple fmt: skip on string literals
a = (
"this should " # fmt: skip
"be fine"
)
b = (
"this is " # fmt: skip
"not working" # fmt: skip
)
c = (
"and neither " # fmt: skip
"is this " # fmt: skip
"working"
)
d = (
"nor "
"is this " # fmt: skip
"working" # fmt: skip
)
e = (
"and this " # fmt: skip
"is definitely "
"not working" # fmt: skip
)
# Dictionary entries with fmt: skip (covers issue with long lines)
hotkeys = {
"editor:swap-line-down": [{"key": "ArrowDown", "modifiers": ["Alt", "Mod"]}], # fmt: skip
"editor:swap-line-up": [{"key": "ArrowUp", "modifiers": ["Alt", "Mod"]}], # fmt: skip
"editor:toggle-source": [{"key": "S", "modifiers": ["Alt", "Mod"]}], # fmt: skip
}
```

View File

@@ -1,188 +0,0 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
---
## Input
```python
x = t"foo"
x = t'foo {{ {2 + 2}bar {{ baz'
x = t"foo {f'abc'} bar"
x = t"""foo {{ a
foo {2 + 2}bar {{ baz
x = f"foo {{ {
2 + 2 # comment
}bar"
{{ baz
}} buzz
{print("abc" + "def"
)}
abc"""
t'{(abc:=10)}'
t'''This is a really long string, but just make sure that you reflow tstrings {
2+2:d
}'''
t'This is a really long string, but just make sure that you reflow tstrings correctly {2+2:d}'
t"{ 2 + 2 = }"
t'{
X
!r
}'
tr'\{{\}}'
t'''
WITH {f'''
{1}_cte AS ()'''}
'''
```
## Black Differences
```diff
--- Black
+++ Ruff
@@ -7,34 +7,32 @@
foo {2 + 2}bar {{ baz
x = f"foo {{ {
- 2 + 2 # comment
- }bar"
+ 2 + 2 # comment
+}bar"
{{ baz
}} buzz
- {print("abc" + "def"
-)}
+ {print("abc" + "def")}
abc"""
-t"{(abc:=10)}"
+t"{(abc := 10)}"
t"""This is a really long string, but just make sure that you reflow tstrings {
- 2+2:d
+ 2 + 2:d
}"""
-t"This is a really long string, but just make sure that you reflow tstrings correctly {2+2:d}"
+t"This is a really long string, but just make sure that you reflow tstrings correctly {2 + 2:d}"
t"{ 2 + 2 = }"
-t"{
-X
-!r
-}"
+t"{X!r}"
rt"\{{\}}"
t"""
- WITH {f'''
- {1}_cte AS ()'''}
+ WITH {
+ f'''
+ {1}_cte AS ()'''
+}
"""
```
## Ruff Output
```python
x = t"foo"
x = t"foo {{ {2 + 2}bar {{ baz"
x = t"foo {f'abc'} bar"
x = t"""foo {{ a
foo {2 + 2}bar {{ baz
x = f"foo {{ {
2 + 2 # comment
}bar"
{{ baz
}} buzz
{print("abc" + "def")}
abc"""
t"{(abc := 10)}"
t"""This is a really long string, but just make sure that you reflow tstrings {
2 + 2:d
}"""
t"This is a really long string, but just make sure that you reflow tstrings correctly {2 + 2:d}"
t"{ 2 + 2 = }"
t"{X!r}"
rt"\{{\}}"
t"""
WITH {
f'''
{1}_cte AS ()'''
}
"""
```
## Black Output
```python
x = t"foo"
x = t"foo {{ {2 + 2}bar {{ baz"
x = t"foo {f'abc'} bar"
x = t"""foo {{ a
foo {2 + 2}bar {{ baz
x = f"foo {{ {
2 + 2 # comment
}bar"
{{ baz
}} buzz
{print("abc" + "def"
)}
abc"""
t"{(abc:=10)}"
t"""This is a really long string, but just make sure that you reflow tstrings {
2+2:d
}"""
t"This is a really long string, but just make sure that you reflow tstrings correctly {2+2:d}"
t"{ 2 + 2 = }"
t"{
X
!r
}"
rt"\{{\}}"
t"""
WITH {f'''
{1}_cte AS ()'''}
"""
```

View File

@@ -1,90 +0,0 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
---
## Input
```python
# Regression test for https://github.com/psf/black/issues/3438
import ast
import collections # fmt: skip
import dataclasses
# fmt: off
import os
# fmt: on
import pathlib
import re # fmt: skip
import secrets
# fmt: off
import sys
# fmt: on
import tempfile
import zoneinfo
```
## Black Differences
```diff
--- Black
+++ Ruff
@@ -3,6 +3,7 @@
import ast
import collections # fmt: skip
import dataclasses
+
# fmt: off
import os
# fmt: on
```
## Ruff Output
```python
# Regression test for https://github.com/psf/black/issues/3438
import ast
import collections # fmt: skip
import dataclasses
# fmt: off
import os
# fmt: on
import pathlib
import re # fmt: skip
import secrets
# fmt: off
import sys
# fmt: on
import tempfile
import zoneinfo
```
## Black Output
```python
# Regression test for https://github.com/psf/black/issues/3438
import ast
import collections # fmt: skip
import dataclasses
# fmt: off
import os
# fmt: on
import pathlib
import re # fmt: skip
import secrets
# fmt: off
import sys
# fmt: on
import tempfile
import zoneinfo
```

View File

@@ -1,5 +1,6 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/black/cases/preview_multiline_strings.py
---
## Input
@@ -162,6 +163,24 @@ Please use `--build-option` instead,
`--global-option` is reserved to flags like `--verbose` or `--quiet`.
"""
this_will_become_one_line = (
"a"
"b"
"c"
)
this_will_stay_on_three_lines = (
"a" # comment
"b"
"c"
)
this_will_also_become_one_line = ( # comment
"a"
"b"
"c"
)
assert some_var == expected_result, """
test
"""
@@ -402,7 +421,18 @@ a = b if """
[
"""cow
moos""",
@@ -214,10 +253,8 @@
@@ -206,7 +245,9 @@
"c"
)
-this_will_also_become_one_line = "abc" # comment
+this_will_also_become_one_line = ( # comment
+ "abc"
+)
assert some_var == expected_result, """
test
@@ -224,10 +265,8 @@
"""Sxxxxxxx xxxxxxxx, xxxxxxx xx xxxxxxxxx
xxxxxxxxxxxxx xxxxxxx xxxxxxxxx xxx-xxxxxxxxxx xxxxxx xx xxx-xxxxxx"""
),
@@ -415,7 +445,7 @@ a = b if """
},
}
@@ -236,14 +273,12 @@
@@ -246,14 +285,12 @@
a
a"""
),
@@ -676,6 +706,18 @@ Please use `--build-option` instead,
`--global-option` is reserved to flags like `--verbose` or `--quiet`.
"""
this_will_become_one_line = "abc"
this_will_stay_on_three_lines = (
"a" # comment
"b"
"c"
)
this_will_also_become_one_line = ( # comment
"abc"
)
assert some_var == expected_result, """
test
"""
@@ -986,6 +1028,16 @@ Please use `--build-option` instead,
`--global-option` is reserved to flags like `--verbose` or `--quiet`.
"""
this_will_become_one_line = "abc"
this_will_stay_on_three_lines = (
"a" # comment
"b"
"c"
)
this_will_also_become_one_line = "abc" # comment
assert some_var == expected_result, """
test
"""

View File

@@ -1,67 +0,0 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
---
## Input
```python
def foo(
a, #type:int
b, #type: str
c, # type: List[int]
d, # type: Dict[int, str]
e, # type: ignore
f, # type : ignore
g, # type : ignore
):
pass
```
## Black Differences
```diff
--- Black
+++ Ruff
@@ -1,9 +1,9 @@
def foo(
- a, # type: int
+ a, # type:int
b, # type: str
c, # type: List[int]
- d, # type: Dict[int, str]
- e, # type: ignore
+ d, # type: Dict[int, str]
+ e, # type: ignore
f, # type : ignore
g, # type : ignore
):
```
## Ruff Output
```python
def foo(
a, # type:int
b, # type: str
c, # type: List[int]
d, # type: Dict[int, str]
e, # type: ignore
f, # type : ignore
g, # type : ignore
):
pass
```
## Black Output
```python
def foo(
a, # type: int
b, # type: str
c, # type: List[int]
d, # type: Dict[int, str]
e, # type: ignore
f, # type : ignore
g, # type : ignore
):
pass
```

View File

@@ -1,85 +0,0 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
---
## Input
```python
# Remove unnecessary parentheses from LHS of assignments
def a():
return [1, 2, 3]
# Single variable with unnecessary parentheses
(b) = a()[0]
# Tuple unpacking with unnecessary parentheses
(c, *_) = a()
# These should not be changed - parentheses are necessary
(d,) = a() # single-element tuple
e = (1 + 2) * 3 # RHS has precedence needs
```
## Black Differences
```diff
--- Black
+++ Ruff
@@ -6,10 +6,10 @@
# Single variable with unnecessary parentheses
-b = a()[0]
+(b) = a()[0]
# Tuple unpacking with unnecessary parentheses
-c, *_ = a()
+(c, *_) = a()
# These should not be changed - parentheses are necessary
(d,) = a() # single-element tuple
```
## Ruff Output
```python
# Remove unnecessary parentheses from LHS of assignments
def a():
return [1, 2, 3]
# Single variable with unnecessary parentheses
(b) = a()[0]
# Tuple unpacking with unnecessary parentheses
(c, *_) = a()
# These should not be changed - parentheses are necessary
(d,) = a() # single-element tuple
e = (1 + 2) * 3 # RHS has precedence needs
```
## Black Output
```python
# Remove unnecessary parentheses from LHS of assignments
def a():
return [1, 2, 3]
# Single variable with unnecessary parentheses
b = a()[0]
# Tuple unpacking with unnecessary parentheses
c, *_ = a()
# These should not be changed - parentheses are necessary
(d,) = a() # single-element tuple
e = (1 + 2) * 3 # RHS has precedence needs
```

275
crates/ty/docs/rules.md generated
View File

@@ -8,7 +8,7 @@
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.20">0.0.1-alpha.20</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20ambiguous-protocol-member" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L541" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L538" target="_blank">View source</a>
</small>
@@ -80,7 +80,7 @@ def test(): -> "int":
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-non-callable" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L140" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L137" target="_blank">View source</a>
</small>
@@ -104,7 +104,7 @@ Calling a non-callable object will raise a `TypeError` at runtime.
Default level: <a href="../../rules#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.7">0.0.7</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-top-callable" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L158" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L155" target="_blank">View source</a>
</small>
@@ -135,7 +135,7 @@ def f(x: object):
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-argument-forms" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L209" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L206" target="_blank">View source</a>
</small>
@@ -167,7 +167,7 @@ f(int) # error
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-declarations" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L235" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L232" target="_blank">View source</a>
</small>
@@ -198,7 +198,7 @@ a = 1
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-metaclass" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L260" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L257" target="_blank">View source</a>
</small>
@@ -230,7 +230,7 @@ class C(A, B): ...
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-class-definition" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L286" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L283" target="_blank">View source</a>
</small>
@@ -262,7 +262,7 @@ class B(A): ...
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/1.0.0">1.0.0</a>) ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-type-alias-definition" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L312" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L309" target="_blank">View source</a>
</small>
@@ -290,7 +290,7 @@ type B = A
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.16">0.0.1-alpha.16</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20deprecated" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L356" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L353" target="_blank">View source</a>
</small>
@@ -317,7 +317,7 @@ old_func() # emits [deprecated] diagnostic
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'ignore'."><code>ignore</code></a> ·
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a>) ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20division-by-zero" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L334" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L331" target="_blank">View source</a>
</small>
@@ -346,7 +346,7 @@ false positives it can produce.
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-base" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L377" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L374" target="_blank">View source</a>
</small>
@@ -373,7 +373,7 @@ class B(A, A): ...
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.12">0.0.1-alpha.12</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-kw-only" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L398" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L395" target="_blank">View source</a>
</small>
@@ -529,7 +529,7 @@ def test(): -> "Literal[5]":
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20inconsistent-mro" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L624" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L621" target="_blank">View source</a>
</small>
@@ -559,7 +559,7 @@ class C(A, B): ...
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20index-out-of-bounds" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L648" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L645" target="_blank">View source</a>
</small>
@@ -585,7 +585,7 @@ t[3] # IndexError: tuple index out of range
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.12">0.0.1-alpha.12</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20instance-layout-conflict" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L430" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L427" target="_blank">View source</a>
</small>
@@ -674,7 +674,7 @@ an atypical memory layout.
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-argument-type" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L702" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L699" target="_blank">View source</a>
</small>
@@ -701,7 +701,7 @@ func("foo") # error: [invalid-argument-type]
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-assignment" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L742" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L739" target="_blank">View source</a>
</small>
@@ -729,7 +729,7 @@ a: int = ''
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-attribute-access" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2085" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2042" target="_blank">View source</a>
</small>
@@ -763,7 +763,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.19">0.0.1-alpha.19</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-await" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L764" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L761" target="_blank">View source</a>
</small>
@@ -799,7 +799,7 @@ asyncio.run(main())
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-base" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L794" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L791" target="_blank">View source</a>
</small>
@@ -823,7 +823,7 @@ class A(42): ... # error: [invalid-base]
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-context-manager" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L845" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L842" target="_blank">View source</a>
</small>
@@ -850,7 +850,7 @@ with 1:
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-declaration" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L866" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L863" target="_blank">View source</a>
</small>
@@ -879,7 +879,7 @@ a: str
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-exception-caught" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L889" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L886" target="_blank">View source</a>
</small>
@@ -923,7 +923,7 @@ except ZeroDivisionError:
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.28">0.0.1-alpha.28</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-explicit-override" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1755" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1712" target="_blank">View source</a>
</small>
@@ -965,7 +965,7 @@ class D(A):
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.35">0.0.1-alpha.35</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-frozen-dataclass-subclass" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2336" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2268" target="_blank">View source</a>
</small>
@@ -1009,7 +1009,7 @@ class NonFrozenChild(FrozenBase): # Error raised here
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-class" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L925" 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>
@@ -1077,7 +1077,7 @@ a = 20 / 0 # type: ignore
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.17">0.0.1-alpha.17</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-key" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L669" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L666" target="_blank">View source</a>
</small>
@@ -1116,7 +1116,7 @@ carol = Person(name="Carol", age=25) # typo!
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-legacy-type-variable" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L956" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L953" target="_blank">View source</a>
</small>
@@ -1151,7 +1151,7 @@ def f(t: TypeVar("U")): ...
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-metaclass" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1053" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1050" target="_blank">View source</a>
</small>
@@ -1185,7 +1185,7 @@ class B(metaclass=f): ...
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.20">0.0.1-alpha.20</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-method-override" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2238" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2170" target="_blank">View source</a>
</small>
@@ -1292,7 +1292,7 @@ Correct use of `@override` is enforced by ty's `invalid-explicit-override` rule.
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.19">0.0.1-alpha.19</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-named-tuple" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L576" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L573" target="_blank">View source</a>
</small>
@@ -1346,7 +1346,7 @@ AttributeError: Cannot overwrite NamedTuple attribute _asdict
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/1.0.0">1.0.0</a>) ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-newtype" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1029" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1026" target="_blank">View source</a>
</small>
@@ -1376,7 +1376,7 @@ Baz = NewType("Baz", int | str) # error: invalid base for `typing.NewType`
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-overload" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1080" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1077" target="_blank">View source</a>
</small>
@@ -1426,7 +1426,7 @@ def foo(x: int) -> int: ...
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-parameter-default" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1179" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1176" target="_blank">View source</a>
</small>
@@ -1452,7 +1452,7 @@ def f(a: int = ''): ...
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-paramspec" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L984" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L981" target="_blank">View source</a>
</small>
@@ -1483,7 +1483,7 @@ P2 = ParamSpec("S2") # error: ParamSpec name must match the variable it's assig
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-protocol" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L512" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L509" target="_blank">View source</a>
</small>
@@ -1517,7 +1517,7 @@ TypeError: Protocols can only inherit from other protocols, got <class 'int'>
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-raise" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1199" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1196" target="_blank">View source</a>
</small>
@@ -1566,7 +1566,7 @@ def g():
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-return-type" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L723" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L720" target="_blank">View source</a>
</small>
@@ -1591,7 +1591,7 @@ def func() -> int:
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-super-argument" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1242" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1239" target="_blank">View source</a>
</small>
@@ -1681,59 +1681,13 @@ class C: ...
- [Typing spec: The meaning of annotations](https://typing.python.org/en/latest/spec/annotations.html#the-meaning-of-annotations)
- [Typing spec: String annotations](https://typing.python.org/en/latest/spec/annotations.html#string-annotations)
## `invalid-total-ordering`
<small>
Default level: <a href="../../rules#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.10">0.0.10</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-total-ordering" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2374" target="_blank">View source</a>
</small>
**What it does**
Checks for classes decorated with `@functools.total_ordering` that don't
define any ordering method (`__lt__`, `__le__`, `__gt__`, or `__ge__`).
**Why is this bad?**
The `@total_ordering` decorator requires the class to define at least one
ordering method. If none is defined, Python raises a `ValueError` at runtime.
**Example**
```python
from functools import total_ordering
@total_ordering
class MyClass: # Error: no ordering method defined
def __eq__(self, other: object) -> bool:
return True
```
Use instead:
```python
from functools import total_ordering
@total_ordering
class MyClass:
def __eq__(self, other: object) -> bool:
return True
def __lt__(self, other: "MyClass") -> bool:
return True
```
## `invalid-type-alias-type`
<small>
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.6">0.0.1-alpha.6</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-alias-type" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1008" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1005" target="_blank">View source</a>
</small>
@@ -1760,7 +1714,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.29">0.0.1-alpha.29</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-arguments" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1474" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1471" target="_blank">View source</a>
</small>
@@ -1807,7 +1761,7 @@ Bar[int] # error: too few arguments
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-checking-constant" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1281" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1278" target="_blank">View source</a>
</small>
@@ -1837,7 +1791,7 @@ TYPE_CHECKING = ''
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-form" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1305" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1302" target="_blank">View source</a>
</small>
@@ -1867,7 +1821,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.11">0.0.1-alpha.11</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-call" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1357" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1354" target="_blank">View source</a>
</small>
@@ -1901,7 +1855,7 @@ f(10) # Error
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.11">0.0.1-alpha.11</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-definition" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1329" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1326" target="_blank">View source</a>
</small>
@@ -1935,7 +1889,7 @@ class C:
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-variable-constraints" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1385" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1382" target="_blank">View source</a>
</small>
@@ -1964,44 +1918,13 @@ T = TypeVar('T', bound=str) # valid bound TypeVar
[type variables]: https://docs.python.org/3/library/typing.html#typing.TypeVar
## `invalid-typed-dict-statement`
<small>
Default level: <a href="../../rules#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.9">0.0.9</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-typed-dict-statement" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2213" target="_blank">View source</a>
</small>
**What it does**
Detects statements other than annotated declarations in `TypedDict` class bodies.
**Why is this bad?**
`TypedDict` class bodies aren't allowed to contain any other types of statements. For
example, method definitions and field values aren't allowed. None of these will be
available on "instances of the `TypedDict`" at runtime (as `dict` is the runtime class of
all "`TypedDict` instances").
**Example**
```python
from typing import TypedDict
class Foo(TypedDict):
def bar(self): # error: [invalid-typed-dict-statement]
pass
```
## `missing-argument`
<small>
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-argument" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1414" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1411" target="_blank">View source</a>
</small>
@@ -2026,7 +1949,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x'
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.20">0.0.1-alpha.20</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-typed-dict-key" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2186" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2143" target="_blank">View source</a>
</small>
@@ -2059,7 +1982,7 @@ alice["age"] # KeyError
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20no-matching-overload" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1433" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1430" target="_blank">View source</a>
</small>
@@ -2088,7 +2011,7 @@ func("string") # error: [no-matching-overload]
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-iterable" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1515" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1512" target="_blank">View source</a>
</small>
@@ -2114,7 +2037,7 @@ for i in 34: # TypeError: 'int' object is not iterable
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-subscriptable" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1456" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1453" target="_blank">View source</a>
</small>
@@ -2138,7 +2061,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.29">0.0.1-alpha.29</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20override-of-final-method" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1728" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1685" target="_blank">View source</a>
</small>
@@ -2171,7 +2094,7 @@ class B(A):
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20parameter-already-assigned" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1566" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1563" target="_blank">View source</a>
</small>
@@ -2198,7 +2121,7 @@ f(1, x=2) # Error raised here
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20positional-only-parameter-as-kwarg" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1939" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1896" target="_blank">View source</a>
</small>
@@ -2225,7 +2148,7 @@ f(x=1) # Error raised here
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-missing-attribute" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1587" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1584" target="_blank">View source</a>
</small>
@@ -2253,7 +2176,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c'
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-missing-implicit-call" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L183" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L180" target="_blank">View source</a>
</small>
@@ -2285,7 +2208,7 @@ A()[0] # TypeError: 'A' object is not subscriptable
Default level: <a href="../../rules#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.22">0.0.1-alpha.22</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-missing-import" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1609" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1606" target="_blank">View source</a>
</small>
@@ -2322,7 +2245,7 @@ from module import a # ImportError: cannot import name 'a' from 'module'
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'ignore'."><code>ignore</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unresolved-reference" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1639" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1636" target="_blank">View source</a>
</small>
@@ -2386,7 +2309,7 @@ def test(): -> "int":
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20redundant-cast" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2113" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2070" target="_blank">View source</a>
</small>
@@ -2413,7 +2336,7 @@ cast(int, f()) # Redundant
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20static-assert-error" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2061" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2018" target="_blank">View source</a>
</small>
@@ -2437,59 +2360,13 @@ static_assert(1 + 1 == 3) # error: evaluates to `False`
static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known truthiness
```
## `subclass-of-dataclass-with-order`
<small>
Default level: <a href="../../rules#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.11">0.0.11</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20subclass-of-dataclass-with-order" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1688" target="_blank">View source</a>
</small>
**What it does**
Checks for classes that inherit from a dataclass with `order=True`.
**Why is this bad?**
When a dataclass has `order=True`, comparison methods (`__lt__`, `__le__`, `__gt__`, `__ge__`)
are generated that compare instances as tuples of their fields. These methods raise a
`TypeError` at runtime when comparing instances of different classes in the inheritance
hierarchy, even if one is a subclass of the other.
This violates the [Liskov Substitution Principle] because child class instances cannot be
used in all contexts where parent class instances are expected.
**Example**
```python
from dataclasses import dataclass
@dataclass(order=True)
class Parent:
value: int
class Child(Parent): # Ty emits a warning here
pass
# At runtime, this raises TypeError:
# Child(1) < Parent(2)
```
Consider using [`functools.total_ordering`] instead, which does not have this limitation.
[Liskov Substitution Principle]: https://en.wikipedia.org/wiki/Liskov_substitution_principle
[`functools.total_ordering`]: https://docs.python.org/3/library/functools.html#functools.total_ordering
## `subclass-of-final-class`
<small>
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20subclass-of-final-class" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1665" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1662" target="_blank">View source</a>
</small>
@@ -2518,7 +2395,7 @@ class B(A): ... # Error raised here
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.30">0.0.1-alpha.30</a>) ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20super-call-in-named-tuple-method" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1873" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1830" target="_blank">View source</a>
</small>
@@ -2552,7 +2429,7 @@ class F(NamedTuple):
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20too-many-positional-arguments" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1813" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1770" target="_blank">View source</a>
</small>
@@ -2579,7 +2456,7 @@ f("foo") # Error raised here
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20type-assertion-failure" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1791" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1748" target="_blank">View source</a>
</small>
@@ -2607,7 +2484,7 @@ def _(x: int):
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unavailable-implicit-super-arguments" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1834" 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>
@@ -2653,7 +2530,7 @@ class A:
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20undefined-reveal" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1900" 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>
@@ -2677,7 +2554,7 @@ reveal_type(1) # NameError: name 'reveal_type' is not defined
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unknown-argument" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1918" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1875" target="_blank">View source</a>
</small>
@@ -2704,7 +2581,7 @@ f(x=1, y=2) # Error raised here
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-attribute" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1960" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1917" target="_blank">View source</a>
</small>
@@ -2732,7 +2609,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo'
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.15">0.0.1-alpha.15</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-global" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2134" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2091" target="_blank">View source</a>
</small>
@@ -2790,7 +2667,7 @@ def g():
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-import" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1982" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1939" target="_blank">View source</a>
</small>
@@ -2815,7 +2692,7 @@ import foo # ModuleNotFoundError: No module named 'foo'
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-reference" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2001" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1958" target="_blank">View source</a>
</small>
@@ -2840,7 +2717,7 @@ print(x) # NameError: name 'x' is not defined
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.7">0.0.1-alpha.7</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-base" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L812" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L809" target="_blank">View source</a>
</small>
@@ -2879,7 +2756,7 @@ class D(C): ... # error: [unsupported-base]
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-bool-conversion" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1535" 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>
@@ -2916,7 +2793,7 @@ b1 < b2 < b1 # exception raised here
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-operator" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2020" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1977" target="_blank">View source</a>
</small>
@@ -2975,7 +2852,7 @@ a = 20 / 2
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20useless-overload-body" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1123" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1120" target="_blank">View source</a>
</small>
@@ -3038,7 +2915,7 @@ def foo(x: int | str) -> int | str:
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20zero-stepsize-in-slice" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2042" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1999" target="_blank">View source</a>
</small>

View File

@@ -3,7 +3,6 @@ auto-import-includes-modules,main.py,0,1
auto-import-includes-modules,main.py,1,7
auto-import-includes-modules,main.py,2,1
auto-import-skips-current-module,main.py,0,1
class-arg-completion,main.py,0,1
fstring-completions,main.py,0,1
higher-level-symbols-preferred,main.py,0,
higher-level-symbols-preferred,main.py,1,1
1 name file index rank
3 auto-import-includes-modules main.py 1 7
4 auto-import-includes-modules main.py 2 1
5 auto-import-skips-current-module main.py 0 1
class-arg-completion main.py 0 1
6 fstring-completions main.py 0 1
7 higher-level-symbols-preferred main.py 0
8 higher-level-symbols-preferred main.py 1 1

View File

@@ -1,2 +0,0 @@
[settings]
auto-import = false

View File

@@ -1 +0,0 @@
class Foo(m<CURSOR: metaclass>)

View File

@@ -1,5 +0,0 @@
[project]
name = "test"
version = "0.1.0"
requires-python = ">=3.13"
dependencies = []

View File

@@ -1,8 +0,0 @@
version = 1
revision = 3
requires-python = ">=3.13"
[[package]]
name = "test"
version = "0.1.0"
source = { virtual = "." }

View File

@@ -193,16 +193,15 @@ impl<'db> Completions<'db> {
/// when the completion context determines that the given suggestion
/// is never valid.
fn add_skip_query(&mut self, mut completion: Completion<'db>) -> bool {
// Tags completions with context-specific if they are
// known to be usable in a `raise` context and we have
// determined a raisable type `raisable_ty`.
// Tags completions with whether they are known to be usable in
// a `raise` context.
//
// It's possible that some completions are usable in a `raise`
// but aren't marked here. That is, false negatives are
// possible but false positives are not.
if let Some(raisable_ty) = self.context.raisable_ty {
if let Some(ty) = completion.ty {
completion.is_context_specific |= ty.is_assignable_to(self.db, raisable_ty);
completion.is_definitively_raisable = ty.is_assignable_to(self.db, raisable_ty);
}
}
if self.context.exclude(self.db, &completion) {
@@ -286,13 +285,13 @@ pub struct Completion<'db> {
/// Whether this item only exists for type checking purposes and
/// will be missing at runtime
pub is_type_check_only: bool,
/// Whether this item can definitively be used in the current context.
/// Whether this item can definitively be used in a `raise` context.
///
/// Some completions are computed based on contextual information.
/// If that's the case, we know this is a very precise completion
/// that should always be valid and can be preferred when
/// ordering completions.
pub is_context_specific: bool,
/// Note that this may not always be computed. (i.e., Only computed
/// when we are in a `raise` context.) And also note that if this
/// is `true`, then it's definitively usable in `raise`, but if
/// it's `false`, it _may_ still be usable in `raise`.
pub is_definitively_raisable: bool,
/// The documentation associated with this item, if
/// available.
pub documentation: Option<Docstring>,
@@ -316,7 +315,7 @@ impl<'db> Completion<'db> {
import: None,
builtin: semantic.builtin,
is_type_check_only,
is_context_specific: false,
is_definitively_raisable: false,
documentation,
}
}
@@ -399,7 +398,7 @@ impl<'db> Completion<'db> {
import: None,
builtin: false,
is_type_check_only: false,
is_context_specific: false,
is_definitively_raisable: false,
documentation: None,
}
}
@@ -415,7 +414,7 @@ impl<'db> Completion<'db> {
import: None,
builtin: true,
is_type_check_only: false,
is_context_specific: false,
is_definitively_raisable: false,
documentation: None,
}
}
@@ -434,7 +433,7 @@ impl<'db> Completion<'db> {
import: None,
builtin: false,
is_type_check_only: false,
is_context_specific: true,
is_definitively_raisable: false,
documentation,
}
}
@@ -995,7 +994,7 @@ impl<'db> CollectionContext<'db> {
#[allow(clippy::unused_self)]
fn rank<'c>(&self, c: &'c Completion<'_>) -> Rank<'c> {
Rank {
definitively_usable: if c.is_context_specific {
definitively_usable: if c.is_definitively_raisable {
Sort::Higher
} else {
Sort::Even
@@ -1184,6 +1183,7 @@ fn add_function_arg_completions<'db>(
if p.is_positional_only || set_function_args.contains(&p.name.as_str()) {
continue;
}
completions.add(Completion::argument(
&p.name,
p.ty,
@@ -1374,7 +1374,7 @@ fn add_unimported_completions<'db>(
builtin: false,
// TODO: `is_type_check_only` requires inferring the type of the symbol
is_type_check_only: false,
is_context_specific: false,
is_definitively_raisable: false,
documentation: None,
});
}
@@ -3088,9 +3088,9 @@ class Foo(<CURSOR>):
);
assert_snapshot!(builder.skip_keywords().skip_builtins().build().snapshot(), @"
metaclass=
Bar
Foo
metaclass=
");
}
@@ -3106,9 +3106,9 @@ class Bar: ...
);
assert_snapshot!(builder.skip_keywords().skip_builtins().build().snapshot(), @"
metaclass=
Bar
Foo
metaclass=
");
}
@@ -3124,9 +3124,9 @@ class Bar: ...
);
assert_snapshot!(builder.skip_keywords().skip_builtins().build().snapshot(), @"
metaclass=
Bar
Foo
metaclass=
");
}
@@ -3140,9 +3140,9 @@ class Foo(<CURSOR>",
);
assert_snapshot!(builder.skip_keywords().skip_builtins().build().snapshot(), @"
metaclass=
Bar
Foo
metaclass=
");
}
@@ -3804,8 +3804,8 @@ bar(o<CURSOR>
assert_snapshot!(
builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
@"
okay=
foo
okay=
"
);
}
@@ -3825,8 +3825,8 @@ bar(o<CURSOR>
assert_snapshot!(
builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
@"
okay=
foo
okay=
"
);
}
@@ -3940,10 +3940,10 @@ bar(o<CURSOR>
assert_snapshot!(
builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
@"
foo
okay=
okay_abc=
okay_okay=
foo
"
);
}
@@ -3961,9 +3961,9 @@ bar(<CURSOR>
);
assert_snapshot!(builder.skip_keywords().skip_builtins().build().snapshot(), @"
okay=
bar
foo
okay=
");
}

View File

@@ -218,7 +218,6 @@ fn render_markdown(docstring: &str) -> String {
output.push('\n');
}
}
first_line = false;
// If we're in a literal block and we find a non-empty dedented line, end the block
// TODO: we should remove all the trailing blank lines
@@ -274,22 +273,6 @@ fn render_markdown(docstring: &str) -> String {
block_indent = line_indent;
in_any_code = true;
in_markdown_with_fence = Some(fence.to_owned());
// Render the line verbatim without its indent and move on.
//
// If there's any indent this is really just Bad Syntax but it "makes sense"
// to someone writing docs like this:
//
// Returns:
// Some details...
// ```
// some_example()
// ```
// etc etc...
//
// We "make this work" by stripping the indent on the fences but preserving the
// full indent of the lines between the fences
output.push_str(line);
continue;
}
// If we're in a markdown code fence and this line seems to terminate it, end the block
} else if let Some(fence) = &in_markdown_with_fence
@@ -298,9 +281,6 @@ fn render_markdown(docstring: &str) -> String {
in_any_code = false;
block_indent = 0;
in_markdown_with_fence = None;
// Render the line without its indent and move on.
output.push_str(line);
continue;
}
// If we're not in a codeblock and we see something that signals a literal block, start one
@@ -466,6 +446,8 @@ fn render_markdown(docstring: &str) -> String {
// Print the line verbatim, it's in code
output.push_str(line);
}
first_line = false;
}
// Flush codeblock
if in_any_code {
@@ -1226,74 +1208,6 @@ mod tests {
");
}
// If an explicit markdown codefence is indented, eat the indent so it renders
// "the way the user expects" (as written this is basically invalid markdown,
// but it's nice if we handle it anyway because it makes visual sense).
#[test]
fn explicit_markdown_block_with_indent_tick() {
let docstring = r#"
My cool func...
Returns:
Some details
`````python
x_y = thing_do();
``` # this should't close the fence!
a_b = other_thing();
`````
And so on.
"#;
let docstring = Docstring::new(docstring.to_owned());
assert_snapshot!(docstring.render_markdown(), @r"
My cool func...
Returns:
&nbsp;&nbsp;&nbsp;&nbsp;Some details
`````python
x_y = thing_do();
``` # this should't close the fence!
a_b = other_thing();
`````
&nbsp;&nbsp;&nbsp;&nbsp;And so on.
");
}
// If an explicit markdown codefence is indented, eat the indent so it renders
// "the way the user expects" (as written this is basically invalid markdown,
// but it's nice if we handle it anyway because it makes visual sense).
#[test]
fn explicit_markdown_block_with_indent_tilde() {
let docstring = r#"
My cool func...
Returns:
Some details
~~~~~~python
x_y = thing_do();
~~~ # this should't close the fence!
a_b = other_thing();
~~~~~~
And so on.
"#;
let docstring = Docstring::new(docstring.to_owned());
assert_snapshot!(docstring.render_markdown(), @r"
My cool func...
Returns:
&nbsp;&nbsp;&nbsp;&nbsp;Some details
~~~~~~python
x_y = thing_do();
~~~ # this should't close the fence!
a_b = other_thing();
~~~~~~
&nbsp;&nbsp;&nbsp;&nbsp;And so on.
");
}
// What do we do when we hit the end of the docstring with an unclosed markdown block?
#[test]
fn explicit_markdown_block_with_unclosed_fence_tick() {
@@ -1353,7 +1267,7 @@ mod tests {
assert_snapshot!(docstring.render_markdown(), @r"
My cool func:
``````we still think this is a codefence```
``````we still think this is a codefence```
x_y = thing_do();
```````````` and are sloppy as heck with indentation and closing shrugggg
");
@@ -1376,7 +1290,7 @@ mod tests {
assert_snapshot!(docstring.render_markdown(), @r"
My cool func:
~~~~~~we still think this is a codefence~~~
~~~~~~we still think this is a codefence~~~
x_y = thing_do();
~~~~~~~~~~~~~ and are sloppy as heck with indentation and closing shrugggg
");

View File

@@ -619,7 +619,7 @@ mod tests {
list_snapshot(&db),
@r#"
[
Module::File("functools", "std-custom", "/typeshed/stdlib/functools.pyi", Module, Some(Functools)),
Module::File("functools", "std-custom", "/typeshed/stdlib/functools.pyi", Module, None),
]
"#,
);
@@ -662,7 +662,7 @@ mod tests {
@r#"
[
Module::File("asyncio", "std-custom", "/typeshed/stdlib/asyncio/__init__.pyi", Package, None),
Module::File("functools", "std-custom", "/typeshed/stdlib/functools.pyi", Module, Some(Functools)),
Module::File("functools", "std-custom", "/typeshed/stdlib/functools.pyi", Module, None),
Module::File("random", "std-custom", "/typeshed/stdlib/random.pyi", Module, None),
]
"#,
@@ -755,7 +755,7 @@ mod tests {
[
Module::File("asyncio", "std-custom", "/typeshed/stdlib/asyncio/__init__.pyi", Package, None),
Module::File("collections", "std-custom", "/typeshed/stdlib/collections/__init__.pyi", Package, Some(Collections)),
Module::File("functools", "std-custom", "/typeshed/stdlib/functools.pyi", Module, Some(Functools)),
Module::File("functools", "std-custom", "/typeshed/stdlib/functools.pyi", Module, None),
]
"#,
);
@@ -1091,7 +1091,7 @@ mod tests {
list_snapshot(&db),
@r#"
[
Module::File("functools", "std-custom", "/typeshed/stdlib/functools.pyi", Module, Some(Functools)),
Module::File("functools", "std-custom", "/typeshed/stdlib/functools.pyi", Module, None),
]
"#,
);
@@ -1107,7 +1107,7 @@ mod tests {
list_snapshot(&db),
@r#"
[
Module::File("functools", "std-custom", "/typeshed/stdlib/functools.pyi", Module, Some(Functools)),
Module::File("functools", "std-custom", "/typeshed/stdlib/functools.pyi", Module, None),
]
"#,
);
@@ -1129,7 +1129,7 @@ mod tests {
list_snapshot(&db),
@r#"
[
Module::File("functools", "std-custom", "/typeshed/stdlib/functools.pyi", Module, Some(Functools)),
Module::File("functools", "std-custom", "/typeshed/stdlib/functools.pyi", Module, None),
]
"#,
);
@@ -1191,7 +1191,7 @@ mod tests {
list_snapshot(&db),
@r#"
[
Module::File("functools", "std-custom", "/typeshed/stdlib/functools.pyi", Module, Some(Functools)),
Module::File("functools", "std-custom", "/typeshed/stdlib/functools.pyi", Module, None),
]
"#,
);

View File

@@ -320,7 +320,6 @@ pub enum KnownModule {
Abc,
Contextlib,
Dataclasses,
Functools,
Collections,
Inspect,
#[strum(serialize = "string.templatelib")]
@@ -352,7 +351,6 @@ impl KnownModule {
Self::Abc => "abc",
Self::Contextlib => "contextlib",
Self::Dataclasses => "dataclasses",
Self::Functools => "functools",
Self::Collections => "collections",
Self::Inspect => "inspect",
Self::TypeCheckerInternals => "_typeshed._type_checker_internals",
@@ -397,10 +395,6 @@ impl KnownModule {
pub const fn is_importlib(self) -> bool {
matches!(self, Self::ImportLib)
}
pub const fn is_functools(self) -> bool {
matches!(self, Self::Functools)
}
}
impl std::fmt::Display for KnownModule {

View File

@@ -349,83 +349,6 @@ GenericWithOrder[int](1) < GenericWithOrder[int](1)
GenericWithOrder[int](1) < GenericWithOrder[str]("a") # error: [unsupported-operator]
```
Subclassing a dataclass with `order=True` is problematic because comparing instances of different
classes in the inheritance hierarchy will raise a `TypeError` at runtime. The design of the stdlib
feature therefore violates the Liskov Substitution Principle:
```py
from dataclasses import dataclass
@dataclass(order=True)
class Parent:
value: int
class Child(Parent): # error: [subclass-of-dataclass-with-order]
pass
# The comparison methods generated by @dataclass(order=True) compare instances
# as tuples of their fields. At runtime, this raises TypeError when comparing
# instances of different classes in the hierarchy:
# Child(42) < Parent(42) # TypeError!
```
This also applies when the child class is also a dataclass:
```py
@dataclass(order=True)
class OrderedParent:
x: int
@dataclass
class OrderedChild(OrderedParent): # error: [subclass-of-dataclass-with-order]
y: str
```
If the child class also has `order=True`, the diagnostic is suppressed because the child overrides
all comparison methods:
```py
@dataclass(order=True)
class OrderedParent2:
x: int
@dataclass(order=True)
class OrderedChild2(OrderedParent2):
y: str
```
If the child class manually overrides all comparison methods, the diagnostic is also suppressed:
```py
@dataclass(order=True)
class OrderedParent3:
x: int
class ManualChild(OrderedParent3): # No warning - all comparison methods overridden
def __lt__(self, other: OrderedParent3) -> bool:
return True
def __le__(self, other: OrderedParent3) -> bool:
return True
def __gt__(self, other: OrderedParent3) -> bool:
return True
def __ge__(self, other: OrderedParent3) -> bool:
return True
```
If the parent dataclass does not have `order=True`, no warning is emitted:
```py
@dataclass
class UnorderedParent:
x: int
class UnorderedChild(UnorderedParent): # No warning
pass
```
If a class already defines one of the comparison methods, a `TypeError` is raised at runtime.
Ideally, we would emit a diagnostic in that case:
@@ -660,7 +583,7 @@ from module import NotFrozenBase
@final
@dataclass(frozen=True)
@total_ordering # error: [invalid-total-ordering]
@total_ordering
class FrozenChild(NotFrozenBase): # error: [invalid-frozen-dataclass-subclass]
y: str
```

View File

@@ -23,6 +23,17 @@ alice.role = "moderator"
bob = Member(name="Bob", tag="VIP")
```
## With `Literal` types
```py
from typing import Literal
import dataclasses
@dataclasses.dataclass
class Foo:
x: Literal["f"] = dataclasses.field(init=False, default="f")
```
## `default_factory`
The `default_factory` argument can be used to specify a callable that provides a default value for a

View File

@@ -1,246 +0,0 @@
# `functools.total_ordering`
The `@functools.total_ordering` decorator allows a class to define a single comparison method (like
`__lt__`), and the decorator automatically generates the remaining comparison methods (`__le__`,
`__gt__`, `__ge__`). Defining `__eq__` is optional, as it can be inherited from `object`.
## Basic usage
When a class defines `__eq__` and `__lt__`, the decorator synthesizes `__le__`, `__gt__`, and
`__ge__`:
```py
from functools import total_ordering
@total_ordering
class Student:
def __init__(self, grade: int):
self.grade = grade
def __eq__(self, other: object) -> bool:
if not isinstance(other, Student):
return NotImplemented
return self.grade == other.grade
def __lt__(self, other: "Student") -> bool:
return self.grade < other.grade
s1 = Student(85)
s2 = Student(90)
# User-defined comparison methods work as expected.
reveal_type(s1 == s2) # revealed: bool
reveal_type(s1 < s2) # revealed: bool
# Synthesized comparison methods are available.
reveal_type(s1 <= s2) # revealed: bool
reveal_type(s1 > s2) # revealed: bool
reveal_type(s1 >= s2) # revealed: bool
```
## Using `__gt__` as the root comparison method
When a class defines `__eq__` and `__gt__`, the decorator synthesizes `__lt__`, `__le__`, and
`__ge__`:
```py
from functools import total_ordering
@total_ordering
class Priority:
def __init__(self, level: int):
self.level = level
def __eq__(self, other: object) -> bool:
if not isinstance(other, Priority):
return NotImplemented
return self.level == other.level
def __gt__(self, other: "Priority") -> bool:
return self.level > other.level
p1 = Priority(1)
p2 = Priority(2)
# User-defined comparison methods work
reveal_type(p1 == p2) # revealed: bool
reveal_type(p1 > p2) # revealed: bool
# Synthesized comparison methods are available
reveal_type(p1 < p2) # revealed: bool
reveal_type(p1 <= p2) # revealed: bool
reveal_type(p1 >= p2) # revealed: bool
```
## Inherited `__eq__`
A class only needs to define a single comparison method. The `__eq__` method can be inherited from
`object`:
```py
from functools import total_ordering
@total_ordering
class Score:
def __init__(self, value: int):
self.value = value
def __lt__(self, other: "Score") -> bool:
return self.value < other.value
s1 = Score(85)
s2 = Score(90)
# `__eq__` is inherited from object.
reveal_type(s1 == s2) # revealed: bool
# Synthesized comparison methods are available.
reveal_type(s1 <= s2) # revealed: bool
reveal_type(s1 > s2) # revealed: bool
reveal_type(s1 >= s2) # revealed: bool
```
## Inherited ordering methods
The decorator also works when the ordering method is inherited from a superclass:
```py
from functools import total_ordering
class Base:
def __lt__(self, other: "Base") -> bool:
return True
@total_ordering
class Child(Base):
def __eq__(self, other: object) -> bool:
if not isinstance(other, Child):
return NotImplemented
return True
c1 = Child()
c2 = Child()
# Synthesized methods work even though `__lt__` is inherited.
reveal_type(c1 <= c2) # revealed: bool
reveal_type(c1 > c2) # revealed: bool
reveal_type(c1 >= c2) # revealed: bool
```
## Explicitly-defined methods are not overridden
When a class explicitly defines multiple comparison methods, the decorator does not override them.
We use a narrower return type (`Literal[True]`) to verify that the explicit methods are preserved:
```py
from functools import total_ordering
from typing import Literal
@total_ordering
class Temperature:
def __init__(self, celsius: float):
self.celsius = celsius
def __lt__(self, other: "Temperature") -> Literal[True]:
return True
def __gt__(self, other: "Temperature") -> Literal[True]:
return True
t1 = Temperature(20.0)
t2 = Temperature(25.0)
# User-defined methods preserve their return type.
reveal_type(t1 < t2) # revealed: Literal[True]
reveal_type(t1 > t2) # revealed: Literal[True]
# Synthesized methods have `bool` return type.
reveal_type(t1 <= t2) # revealed: bool
reveal_type(t1 >= t2) # revealed: bool
```
## Combined with `@dataclass`
The decorator works with `@dataclass`:
```py
from dataclasses import dataclass
from functools import total_ordering
@total_ordering
@dataclass
class Point:
x: int
y: int
def __lt__(self, other: "Point") -> bool:
return (self.x, self.y) < (other.x, other.y)
p1 = Point(1, 2)
p2 = Point(3, 4)
# Dataclass-synthesized `__eq__` is available.
reveal_type(p1 == p2) # revealed: bool
# User-defined comparison method works.
reveal_type(p1 < p2) # revealed: bool
# Synthesized comparison methods are available.
reveal_type(p1 <= p2) # revealed: bool
reveal_type(p1 > p2) # revealed: bool
reveal_type(p1 >= p2) # revealed: bool
```
## Missing ordering method
If a class has `@total_ordering` but doesn't define any ordering method (itself or in a superclass),
a diagnostic is emitted at the decorator site:
```py
from functools import total_ordering
@total_ordering # error: [invalid-total-ordering]
class NoOrdering:
def __eq__(self, other: object) -> bool:
return True
n1 = NoOrdering()
n2 = NoOrdering()
# Comparison operators also error because no methods were synthesized.
n1 <= n2 # error: [unsupported-operator]
n1 >= n2 # error: [unsupported-operator]
```
## Without the decorator
Without `@total_ordering`, classes that only define `__lt__` will not have `__le__` or `__ge__`
synthesized:
```py
class NoDecorator:
def __init__(self, value: int):
self.value = value
def __eq__(self, other: object) -> bool:
if not isinstance(other, NoDecorator):
return NotImplemented
return self.value == other.value
def __lt__(self, other: "NoDecorator") -> bool:
return self.value < other.value
n1 = NoDecorator(1)
n2 = NoDecorator(2)
# User-defined methods work.
reveal_type(n1 == n2) # revealed: bool
reveal_type(n1 < n2) # revealed: bool
# Note: `n1 > n2` works because Python reflects it to `n2 < n1`
reveal_type(n1 > n2) # revealed: bool
# These comparison operators are not available.
n1 <= n2 # error: [unsupported-operator]
n1 >= n2 # error: [unsupported-operator]
```

View File

@@ -475,26 +475,37 @@ from typing import NamedTuple
class Foo:
x: int
class Bar(Foo): # error: [subclass-of-dataclass-with-order]
class Bar(Foo):
def __lt__(self, other: Bar) -> bool: ... # error: [invalid-method-override]
# Specifying `order=True` on the subclass means that a `__lt__` method is generated that
# is incompatible with the generated `__lt__` method on the superclass. We don't emit the
# `subclass-of-dataclass-with-order` diagnostic here because the child class overrides all
# comparison methods.
# TODO: We should also emit `invalid-method-override` diagnostics for each generated
# comparison method since they have incompatible signatures.
# TODO: specifying `order=True` on the subclass means that a `__lt__` method is
# generated that is incompatible with the generated `__lt__` method on the superclass.
# We could consider detecting this and emitting a diagnostic, though maybe it shouldn't
# be `invalid-method-override` since we'd emit it on the class definition rather than
# on any method definition. Note also that no other type checker complains about this
# as of 2025-11-21.
@dataclass(order=True)
class Bar2(Foo):
y: str
# Although this class does not override any methods of `Foo`, the design of the
# `order=True` stdlib dataclasses feature itself violates the Liskov Substitution
# TODO: Although this class does not override any methods of `Foo`, the design of the
# `order=True` stdlib dataclasses feature itself arguably violates the Liskov Substitution
# Principle! Instances of `Bar3` cannot be substituted wherever an instance of `Foo` is
# expected, because the generated `__lt__` method on `Foo` raises an error unless the r.h.s.
# and `l.h.s.` have exactly the same `__class__` (it does not permit instances of `Foo` to
# be compared with instances of subclasses of `Foo`).
class Bar3(Foo): ... # error: [subclass-of-dataclass-with-order]
#
# Many users would probably like their type checkers to alert them to cases where instances
# of subclasses cannot be substituted for instances of superclasses, as this violates many
# assumptions a type checker will make and makes it likely that a type checker will fail to
# catch type errors elsewhere in the user's code. We could therefore consider treating all
# `order=True` dataclasses as implicitly `@final` in order to enforce soundness. However,
# this probably shouldn't be reported with the same error code as Liskov violations, since
# the error does not stem from any method signatures written by the user. The example is
# only included here for completeness.
#
# Note that no other type checker catches this error as of 2025-11-21.
class Bar3(Foo): ...
class Eggs:
def __lt__(self, other: Eggs) -> bool: ...

View File

@@ -530,7 +530,7 @@ from dataclasses import dataclass
class ParentDataclass:
x: int
class Child(ParentDataclass): # error: [subclass-of-dataclass-with-order]
class Child(ParentDataclass):
@override
def __lt__(self, other: ParentDataclass) -> bool: ... # fine
@@ -551,8 +551,10 @@ class MyNamedTupleChild(MyNamedTupleParent):
class MyTypedDict(TypedDict):
x: int
# error: [invalid-typed-dict-statement] "TypedDict class cannot have methods"
@override
# TODO: it's invalid to define a method on a `TypedDict` class,
# so we should emit a diagnostic here.
# It shouldn't be an `invalid-explicit-override` diagnostic, however.
def copy(self) -> Self: ...
class Grandparent(Any): ...

View File

@@ -61,7 +61,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/dataclasses/dataclasses.
6 |
7 | @final
8 | @dataclass(frozen=True)
9 | @total_ordering # error: [invalid-total-ordering]
9 | @total_ordering
10 | class FrozenChild(NotFrozenBase): # error: [invalid-frozen-dataclass-subclass]
11 | y: str
```
@@ -126,22 +126,6 @@ info: rule `invalid-frozen-dataclass-subclass` is enabled by default
```
```
error[invalid-total-ordering]: Class decorated with `@total_ordering` must define at least one ordering method
--> src/main.py:9:1
|
7 | @final
8 | @dataclass(frozen=True)
9 | @total_ordering # error: [invalid-total-ordering]
| ^^^^^^^^^^^^^^^ `FrozenChild` does not define `__lt__`, `__le__`, `__gt__`, or `__ge__`
10 | class FrozenChild(NotFrozenBase): # error: [invalid-frozen-dataclass-subclass]
11 | y: str
|
info: The decorator will raise `ValueError` at runtime
info: rule `invalid-total-ordering` is enabled by default
```
```
error[invalid-frozen-dataclass-subclass]: Frozen dataclass cannot inherit from non-frozen dataclass
--> src/main.py:8:1
@@ -149,7 +133,7 @@ error[invalid-frozen-dataclass-subclass]: Frozen dataclass cannot inherit from n
7 | @final
8 | @dataclass(frozen=True)
| ----------------------- `FrozenChild` dataclass parameters
9 | @total_ordering # error: [invalid-total-ordering]
9 | @total_ordering
10 | class FrozenChild(NotFrozenBase): # error: [invalid-frozen-dataclass-subclass]
| ^^^^^^^^^^^^-------------^ Subclass `FrozenChild` is frozen but base class `NotFrozenBase` is not
11 | y: str

View File

@@ -1,6 +1,5 @@
---
source: crates/ty_test/src/lib.rs
assertion_line: 623
expression: snapshot
---
@@ -21,70 +20,66 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/liskov.md
5 | class Foo:
6 | x: int
7 |
8 | class Bar(Foo): # error: [subclass-of-dataclass-with-order]
8 | class Bar(Foo):
9 | def __lt__(self, other: Bar) -> bool: ... # error: [invalid-method-override]
10 |
11 | # Specifying `order=True` on the subclass means that a `__lt__` method is generated that
12 | # is incompatible with the generated `__lt__` method on the superclass. We don't emit the
13 | # `subclass-of-dataclass-with-order` diagnostic here because the child class overrides all
14 | # comparison methods.
15 | # TODO: We should also emit `invalid-method-override` diagnostics for each generated
16 | # comparison method since they have incompatible signatures.
11 | # TODO: specifying `order=True` on the subclass means that a `__lt__` method is
12 | # generated that is incompatible with the generated `__lt__` method on the superclass.
13 | # We could consider detecting this and emitting a diagnostic, though maybe it shouldn't
14 | # be `invalid-method-override` since we'd emit it on the class definition rather than
15 | # on any method definition. Note also that no other type checker complains about this
16 | # as of 2025-11-21.
17 | @dataclass(order=True)
18 | class Bar2(Foo):
19 | y: str
20 |
21 | # Although this class does not override any methods of `Foo`, the design of the
22 | # `order=True` stdlib dataclasses feature itself violates the Liskov Substitution
21 | # TODO: Although this class does not override any methods of `Foo`, the design of the
22 | # `order=True` stdlib dataclasses feature itself arguably violates the Liskov Substitution
23 | # Principle! Instances of `Bar3` cannot be substituted wherever an instance of `Foo` is
24 | # expected, because the generated `__lt__` method on `Foo` raises an error unless the r.h.s.
25 | # and `l.h.s.` have exactly the same `__class__` (it does not permit instances of `Foo` to
26 | # be compared with instances of subclasses of `Foo`).
27 | class Bar3(Foo): ... # error: [subclass-of-dataclass-with-order]
28 |
29 | class Eggs:
30 | def __lt__(self, other: Eggs) -> bool: ...
31 |
32 | # TODO: the generated `Ham.__lt__` method here incompatibly overrides `Eggs.__lt__`.
33 | # We could consider emitting a diagnostic here. As of 2025-11-21, mypy reports a
34 | # diagnostic here but pyright and pyrefly do not.
35 | @dataclass(order=True)
36 | class Ham(Eggs):
37 | x: int
38 |
39 | class Baz(NamedTuple):
40 | x: int
41 |
42 | class Spam(Baz):
43 | def _asdict(self) -> tuple[int, ...]: ... # error: [invalid-method-override]
27 | #
28 | # Many users would probably like their type checkers to alert them to cases where instances
29 | # of subclasses cannot be substituted for instances of superclasses, as this violates many
30 | # assumptions a type checker will make and makes it likely that a type checker will fail to
31 | # catch type errors elsewhere in the user's code. We could therefore consider treating all
32 | # `order=True` dataclasses as implicitly `@final` in order to enforce soundness. However,
33 | # this probably shouldn't be reported with the same error code as Liskov violations, since
34 | # the error does not stem from any method signatures written by the user. The example is
35 | # only included here for completeness.
36 | #
37 | # Note that no other type checker catches this error as of 2025-11-21.
38 | class Bar3(Foo): ...
39 |
40 | class Eggs:
41 | def __lt__(self, other: Eggs) -> bool: ...
42 |
43 | # TODO: the generated `Ham.__lt__` method here incompatibly overrides `Eggs.__lt__`.
44 | # We could consider emitting a diagnostic here. As of 2025-11-21, mypy reports a
45 | # diagnostic here but pyright and pyrefly do not.
46 | @dataclass(order=True)
47 | class Ham(Eggs):
48 | x: int
49 |
50 | class Baz(NamedTuple):
51 | x: int
52 |
53 | class Spam(Baz):
54 | def _asdict(self) -> tuple[int, ...]: ... # error: [invalid-method-override]
```
# Diagnostics
```
warning[subclass-of-dataclass-with-order]: Class `Bar` inherits from dataclass `Foo` which has `order=True`
--> src/mdtest_snippet.pyi:8:11
|
6 | x: int
7 |
8 | class Bar(Foo): # error: [subclass-of-dataclass-with-order]
| ^^^
9 | def __lt__(self, other: Bar) -> bool: ... # error: [invalid-method-override]
|
info: Comparison of instances of the child class with instances of the parent class will raise `TypeError` at runtime
info: rule `subclass-of-dataclass-with-order` is enabled by default
```
```
error[invalid-method-override]: Invalid override of method `__lt__`
--> src/mdtest_snippet.pyi:9:9
|
8 | class Bar(Foo): # error: [subclass-of-dataclass-with-order]
8 | class Bar(Foo):
9 | def __lt__(self, other: Bar) -> bool: ... # error: [invalid-method-override]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `Foo.__lt__`
10 |
11 | # Specifying `order=True` on the subclass means that a `__lt__` method is generated that
11 | # TODO: specifying `order=True` on the subclass means that a `__lt__` method is
|
info: This violates the Liskov Substitution Principle
info: `Foo.__lt__` is a generated method created because `Foo` is a dataclass
@@ -99,39 +94,23 @@ info: rule `invalid-method-override` is enabled by default
```
```
warning[subclass-of-dataclass-with-order]: Class `Bar3` inherits from dataclass `Foo` which has `order=True`
--> src/mdtest_snippet.pyi:27:12
|
25 | # and `l.h.s.` have exactly the same `__class__` (it does not permit instances of `Foo` to
26 | # be compared with instances of subclasses of `Foo`).
27 | class Bar3(Foo): ... # error: [subclass-of-dataclass-with-order]
| ^^^
28 |
29 | class Eggs:
|
info: Comparison of instances of the child class with instances of the parent class will raise `TypeError` at runtime
info: rule `subclass-of-dataclass-with-order` is enabled by default
```
```
error[invalid-method-override]: Invalid override of method `_asdict`
--> src/mdtest_snippet.pyi:43:9
--> src/mdtest_snippet.pyi:54:9
|
42 | class Spam(Baz):
43 | def _asdict(self) -> tuple[int, ...]: ... # error: [invalid-method-override]
53 | class Spam(Baz):
54 | def _asdict(self) -> tuple[int, ...]: ... # error: [invalid-method-override]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `Baz._asdict`
|
info: This violates the Liskov Substitution Principle
info: `Baz._asdict` is a generated method created because `Baz` inherits from `typing.NamedTuple`
--> src/mdtest_snippet.pyi:39:7
--> src/mdtest_snippet.pyi:50:7
|
37 | x: int
38 |
39 | class Baz(NamedTuple):
48 | x: int
49 |
50 | class Baz(NamedTuple):
| ^^^^^^^^^^^^^^^ Definition of `Baz`
40 | x: int
51 | x: int
|
info: rule `invalid-method-override` is enabled by default

View File

@@ -1,103 +0,0 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: typed_dict.md - `TypedDict` - Only annotated declarations are allowed in the class body
mdtest path: crates/ty_python_semantic/resources/mdtest/typed_dict.md
---
# Python source files
## mdtest_snippet.py
```
1 | from typing import TypedDict
2 |
3 | class Foo(TypedDict):
4 | """docstring"""
5 |
6 | annotated_item: int
7 | """attribute docstring"""
8 |
9 | pass
10 |
11 | # As a non-standard but common extension, we interpret `...` as equivalent to `pass`.
12 | ...
13 |
14 | class Bar(TypedDict):
15 | a: int
16 | # error: [invalid-typed-dict-statement] "invalid statement in TypedDict class body"
17 | 42
18 | # error: [invalid-typed-dict-statement] "TypedDict item cannot have a value"
19 | b: str = "hello"
20 | # error: [invalid-typed-dict-statement] "TypedDict class cannot have methods"
21 | def bar(self): ...
22 | class Baz(Bar):
23 | # error: [invalid-typed-dict-statement]
24 | def baz(self):
25 | pass
```
# Diagnostics
```
error[invalid-typed-dict-statement]: invalid statement in TypedDict class body
--> src/mdtest_snippet.py:17:5
|
15 | a: int
16 | # error: [invalid-typed-dict-statement] "invalid statement in TypedDict class body"
17 | 42
| ^^
18 | # error: [invalid-typed-dict-statement] "TypedDict item cannot have a value"
19 | b: str = "hello"
|
info: Only annotated declarations (`<name>: <type>`) are allowed.
info: rule `invalid-typed-dict-statement` is enabled by default
```
```
error[invalid-typed-dict-statement]: TypedDict item cannot have a value
--> src/mdtest_snippet.py:19:14
|
17 | 42
18 | # error: [invalid-typed-dict-statement] "TypedDict item cannot have a value"
19 | b: str = "hello"
| ^^^^^^^
20 | # error: [invalid-typed-dict-statement] "TypedDict class cannot have methods"
21 | def bar(self): ...
|
info: rule `invalid-typed-dict-statement` is enabled by default
```
```
error[invalid-typed-dict-statement]: TypedDict class cannot have methods
--> src/mdtest_snippet.py:21:5
|
19 | b: str = "hello"
20 | # error: [invalid-typed-dict-statement] "TypedDict class cannot have methods"
21 | def bar(self): ...
| ^^^^^^^^^^^^^^^^^^
22 | class Baz(Bar):
23 | # error: [invalid-typed-dict-statement]
|
info: rule `invalid-typed-dict-statement` is enabled by default
```
```
error[invalid-typed-dict-statement]: TypedDict class cannot have methods
--> src/mdtest_snippet.py:24:5
|
22 | class Baz(Bar):
23 | # error: [invalid-typed-dict-statement]
24 | / def baz(self):
25 | | pass
| |____________^
|
info: rule `invalid-typed-dict-statement` is enabled by default
```

View File

@@ -2266,47 +2266,6 @@ def match_with_dict(u: Foo | Bar | dict):
reveal_type(u) # revealed: Foo | (dict[Unknown, Unknown] & ~<TypedDict with items 'tag'>)
```
## Only annotated declarations are allowed in the class body
<!-- snapshot-diagnostics -->
`TypedDict` class bodies are very restricted in what kinds of statements they can contain. Besides
annotated items, the only allowed statements are docstrings and `pass`. Annotated items are are also
not allowed to have a value.
```py
from typing import TypedDict
class Foo(TypedDict):
"""docstring"""
annotated_item: int
"""attribute docstring"""
pass
# As a non-standard but common extension, we interpret `...` as equivalent to `pass`.
...
class Bar(TypedDict):
a: int
# error: [invalid-typed-dict-statement] "invalid statement in TypedDict class body"
42
# error: [invalid-typed-dict-statement] "TypedDict item cannot have a value"
b: str = "hello"
# error: [invalid-typed-dict-statement] "TypedDict class cannot have methods"
def bar(self): ...
```
These rules are also enforced for `TypedDict` classes that don't directly inherit from `TypedDict`:
```py
class Baz(Bar):
# error: [invalid-typed-dict-statement]
def baz(self):
pass
```
[closed]: https://peps.python.org/pep-0728/#disallowing-extra-items-explicitly
[subtyping section]: https://typing.python.org/en/latest/spec/typeddict.html#subtyping-between-typeddict-types
[`typeddict`]: https://typing.python.org/en/latest/spec/typeddict.html

View File

@@ -4645,13 +4645,7 @@ impl<'db> Type<'db> {
let first_spec = specs_iter.next()?;
let mut builder = TupleSpecBuilder::from(&*first_spec);
for spec in specs_iter {
// Two tuples cannot have incompatible specs unless the tuples themselves
// are disjoint. `IntersectionBuilder` eagerly simplifies such
// intersections to `Never`, so this should always return `Some`.
let Some(intersected) = builder.intersect(db, &spec) else {
return Some(Cow::Owned(TupleSpec::homogeneous(Type::unknown())));
};
builder = intersected;
builder = builder.intersect(db, &spec);
}
Some(Cow::Owned(builder.build()))
}

View File

@@ -1122,7 +1122,6 @@ impl<'db> Bindings<'db> {
class_literal.type_check_only(db),
Some(params),
class_literal.dataclass_transformer_params(db),
class_literal.total_ordering(db),
)));
}
}

View File

@@ -1516,9 +1516,6 @@ pub struct ClassLiteral<'db> {
pub(crate) dataclass_params: Option<DataclassParams<'db>>,
pub(crate) dataclass_transformer_params: Option<DataclassTransformerParams<'db>>,
/// Whether this class is decorated with `@functools.total_ordering`
pub(crate) total_ordering: bool,
}
// The Salsa heap is tracked separately.
@@ -1543,17 +1540,6 @@ impl<'db> ClassLiteral<'db> {
self.is_known(db, KnownClass::Tuple)
}
/// Returns `true` if this class defines any ordering method (`__lt__`, `__le__`, `__gt__`,
/// `__ge__`) in its own body (not inherited). Used by `@total_ordering` to determine if
/// synthesis is valid.
#[salsa::tracked]
pub(crate) fn has_own_ordering_method(self, db: &'db dyn Db) -> bool {
let body_scope = self.body_scope(db);
["__lt__", "__le__", "__gt__", "__ge__"]
.iter()
.any(|method| !class_member(db, body_scope, method).is_undefined())
}
pub(crate) fn generic_context(self, db: &'db dyn Db) -> Option<GenericContext<'db>> {
// Several typeshed definitions examine `sys.version_info`. To break cycles, we hard-code
// the knowledge that this class is not generic.
@@ -2398,41 +2384,6 @@ impl<'db> ClassLiteral<'db> {
) -> Option<Type<'db>> {
let dataclass_params = self.dataclass_params(db);
// Handle `@functools.total_ordering`: synthesize comparison methods
// for classes that have `@total_ordering` and define at least one
// ordering method. The decorator requires at least one of __lt__,
// __le__, __gt__, or __ge__ to be defined (either in this class or
// inherited from a superclass, excluding `object`).
if self.total_ordering(db) && matches!(name, "__lt__" | "__le__" | "__gt__" | "__ge__") {
// Check if any class in the MRO (excluding object) defines at least one
// ordering method in its own body (not synthesized).
let has_ordering_method = self
.iter_mro(db, specialization)
.filter_map(super::class_base::ClassBase::into_class)
.filter(|class| !class.class_literal(db).0.is_known(db, KnownClass::Object))
.any(|class| class.class_literal(db).0.has_own_ordering_method(db));
if has_ordering_method {
let instance_ty =
Type::instance(db, self.apply_optional_specialization(db, specialization));
let signature = Signature::new(
Parameters::new(
db,
[
Parameter::positional_or_keyword(Name::new_static("self"))
.with_annotated_type(instance_ty),
Parameter::positional_or_keyword(Name::new_static("other"))
.with_annotated_type(instance_ty),
],
),
Some(KnownClass::Bool.to_instance(db)),
);
return Some(Type::function_like_callable(db, signature));
}
}
let field_policy = CodeGeneratorKind::from_class(db, self, specialization)?;
let mut transformer_params =

View File

@@ -120,13 +120,10 @@ pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) {
registry.register_lint(&REDUNDANT_CAST);
registry.register_lint(&UNRESOLVED_GLOBAL);
registry.register_lint(&MISSING_TYPED_DICT_KEY);
registry.register_lint(&INVALID_TYPED_DICT_STATEMENT);
registry.register_lint(&INVALID_METHOD_OVERRIDE);
registry.register_lint(&INVALID_EXPLICIT_OVERRIDE);
registry.register_lint(&SUPER_CALL_IN_NAMED_TUPLE_METHOD);
registry.register_lint(&SUBCLASS_OF_DATACLASS_WITH_ORDER);
registry.register_lint(&INVALID_FROZEN_DATACLASS_SUBCLASS);
registry.register_lint(&INVALID_TOTAL_ORDERING);
// String annotations
registry.register_lint(&BYTE_STRING_TYPE_ANNOTATION);
@@ -1685,46 +1682,6 @@ declare_lint! {
}
}
declare_lint! {
/// ## What it does
/// Checks for classes that inherit from a dataclass with `order=True`.
///
/// ## Why is this bad?
/// When a dataclass has `order=True`, comparison methods (`__lt__`, `__le__`, `__gt__`, `__ge__`)
/// are generated that compare instances as tuples of their fields. These methods raise a
/// `TypeError` at runtime when comparing instances of different classes in the inheritance
/// hierarchy, even if one is a subclass of the other.
///
/// This violates the [Liskov Substitution Principle] because child class instances cannot be
/// used in all contexts where parent class instances are expected.
///
/// ## Example
///
/// ```python
/// from dataclasses import dataclass
///
/// @dataclass(order=True)
/// class Parent:
/// value: int
///
/// class Child(Parent): # Ty emits a warning here
/// pass
///
/// # At runtime, this raises TypeError:
/// # Child(1) < Parent(2)
/// ```
///
/// Consider using [`functools.total_ordering`] instead, which does not have this limitation.
///
/// [Liskov Substitution Principle]: https://en.wikipedia.org/wiki/Liskov_substitution_principle
/// [`functools.total_ordering`]: https://docs.python.org/3/library/functools.html#functools.total_ordering
pub(crate) static SUBCLASS_OF_DATACLASS_WITH_ORDER = {
summary: "detects subclasses of dataclasses with `order=True`",
status: LintStatus::stable("0.0.11"),
default_level: Level::Warn,
}
}
declare_lint! {
/// ## What it does
/// Checks for methods on subclasses that override superclass methods decorated with `@final`.
@@ -2210,31 +2167,6 @@ declare_lint! {
}
}
declare_lint! {
/// ## What it does
/// Detects statements other than annotated declarations in `TypedDict` class bodies.
///
/// ## Why is this bad?
/// `TypedDict` class bodies aren't allowed to contain any other types of statements. For
/// example, method definitions and field values aren't allowed. None of these will be
/// available on "instances of the `TypedDict`" at runtime (as `dict` is the runtime class of
/// all "`TypedDict` instances").
///
/// ## Example
/// ```python
/// from typing import TypedDict
///
/// class Foo(TypedDict):
/// def bar(self): # error: [invalid-typed-dict-statement]
/// pass
/// ```
pub(crate) static INVALID_TYPED_DICT_STATEMENT = {
summary: "detects invalid statements in `TypedDict` class bodies",
status: LintStatus::stable("0.0.9"),
default_level: Level::Error,
}
}
declare_lint! {
/// ## What it does
/// Detects method overrides that violate the [Liskov Substitution Principle] ("LSP").
@@ -2371,46 +2303,6 @@ declare_lint! {
}
}
declare_lint! {
/// ## What it does
/// Checks for classes decorated with `@functools.total_ordering` that don't
/// define any ordering method (`__lt__`, `__le__`, `__gt__`, or `__ge__`).
///
/// ## Why is this bad?
/// The `@total_ordering` decorator requires the class to define at least one
/// ordering method. If none is defined, Python raises a `ValueError` at runtime.
///
/// ## Example
///
/// ```python
/// from functools import total_ordering
///
/// @total_ordering
/// class MyClass: # Error: no ordering method defined
/// def __eq__(self, other: object) -> bool:
/// return True
/// ```
///
/// Use instead:
///
/// ```python
/// from functools import total_ordering
///
/// @total_ordering
/// class MyClass:
/// def __eq__(self, other: object) -> bool:
/// return True
///
/// def __lt__(self, other: "MyClass") -> bool:
/// return True
/// ```
pub(crate) static INVALID_TOTAL_ORDERING = {
summary: "detects `@total_ordering` classes without an ordering method",
status: LintStatus::stable("0.0.10"),
default_level: Level::Error,
}
}
/// A collection of type check diagnostics.
#[derive(Default, Eq, PartialEq, get_size2::GetSize)]
pub struct TypeCheckDiagnostics {
@@ -4700,27 +4592,6 @@ pub(super) fn report_bad_frozen_dataclass_inheritance<'db>(
}
}
pub(super) fn report_invalid_total_ordering(
context: &InferContext<'_, '_>,
class: ClassLiteral<'_>,
decorator: &ast::Decorator,
) {
let db = context.db();
let Some(builder) = context.report_lint(&INVALID_TOTAL_ORDERING, decorator) else {
return;
};
let mut diagnostic = builder.into_diagnostic(
"Class decorated with `@total_ordering` must define at least one ordering method",
);
diagnostic.set_primary_message(format_args!(
"`{}` does not define `__lt__`, `__le__`, `__gt__`, or `__ge__`",
class.name(db)
));
diagnostic.info("The decorator will raise `ValueError` at runtime");
}
/// This function receives an unresolved `from foo import bar` import,
/// where `foo` can be resolved to a module but that module does not
/// have a `bar` member or submodule.

View File

@@ -1413,9 +1413,6 @@ pub enum KnownFunction {
/// `dataclasses.field`
Field,
/// `functools.total_ordering`
TotalOrdering,
/// `inspect.getattr_static`
GetattrStatic,
@@ -1504,7 +1501,6 @@ impl KnownFunction {
Self::Dataclass | Self::Field => {
matches!(module, KnownModule::Dataclasses)
}
Self::TotalOrdering => module.is_functools(),
Self::GetattrStatic => module.is_inspect(),
Self::IsAssignableTo
| Self::IsDisjointFrom
@@ -2072,7 +2068,6 @@ pub(crate) mod tests {
KnownFunction::ImportModule => KnownModule::ImportLib,
KnownFunction::NamedTuple => KnownModule::Collections,
KnownFunction::TotalOrdering => KnownModule::Functools,
};
let function_definition = known_module_symbol(&db, module, function_name)

View File

@@ -63,12 +63,11 @@ use crate::types::diagnostic::{
INVALID_LEGACY_TYPE_VARIABLE, INVALID_METACLASS, INVALID_NAMED_TUPLE, INVALID_NEWTYPE,
INVALID_OVERLOAD, INVALID_PARAMETER_DEFAULT, INVALID_PARAMSPEC, INVALID_PROTOCOL,
INVALID_TYPE_ARGUMENTS, INVALID_TYPE_FORM, INVALID_TYPE_GUARD_CALL,
INVALID_TYPE_VARIABLE_CONSTRAINTS, INVALID_TYPED_DICT_STATEMENT, IncompatibleBases,
NOT_SUBSCRIPTABLE, POSSIBLY_MISSING_ATTRIBUTE, POSSIBLY_MISSING_IMPLICIT_CALL,
POSSIBLY_MISSING_IMPORT, SUBCLASS_OF_DATACLASS_WITH_ORDER, SUBCLASS_OF_FINAL_CLASS,
TypedDictDeleteErrorKind, UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE, UNRESOLVED_GLOBAL,
UNRESOLVED_IMPORT, UNRESOLVED_REFERENCE, UNSUPPORTED_OPERATOR, USELESS_OVERLOAD_BODY,
hint_if_stdlib_attribute_exists_on_other_versions,
INVALID_TYPE_VARIABLE_CONSTRAINTS, IncompatibleBases, NOT_SUBSCRIPTABLE,
POSSIBLY_MISSING_ATTRIBUTE, POSSIBLY_MISSING_IMPLICIT_CALL, POSSIBLY_MISSING_IMPORT,
SUBCLASS_OF_FINAL_CLASS, TypedDictDeleteErrorKind, UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE,
UNRESOLVED_GLOBAL, UNRESOLVED_IMPORT, UNRESOLVED_REFERENCE, UNSUPPORTED_OPERATOR,
USELESS_OVERLOAD_BODY, hint_if_stdlib_attribute_exists_on_other_versions,
hint_if_stdlib_submodule_exists_on_other_versions, report_attempted_protocol_instantiation,
report_bad_dunder_set_call, report_bad_frozen_dataclass_inheritance,
report_cannot_delete_typed_dict_key, report_cannot_pop_required_field_on_typed_dict,
@@ -78,7 +77,7 @@ use crate::types::diagnostic::{
report_invalid_exception_caught, report_invalid_exception_cause,
report_invalid_exception_raised, report_invalid_exception_tuple_caught,
report_invalid_generator_function_return_type, report_invalid_key_on_typed_dict,
report_invalid_or_unsupported_base, report_invalid_return_type, report_invalid_total_ordering,
report_invalid_or_unsupported_base, report_invalid_return_type,
report_invalid_type_checking_constant, report_invalid_type_param_order,
report_named_tuple_field_with_leading_underscore,
report_namedtuple_field_without_default_after_field_with_default, report_not_subscriptable,
@@ -107,8 +106,8 @@ use crate::types::typed_dict::{
use crate::types::visitor::any_over_type;
use crate::types::{
BoundTypeVarIdentity, BoundTypeVarInstance, CallDunderError, CallableBinding, CallableType,
CallableTypeKind, ClassLiteral, ClassType, DataclassFlags, DataclassParams, DynamicType,
InternedType, IntersectionBuilder, IntersectionType, KnownClass, KnownInstanceType, KnownUnion,
CallableTypeKind, ClassLiteral, ClassType, DataclassParams, DynamicType, InternedType,
IntersectionBuilder, IntersectionType, KnownClass, KnownInstanceType, KnownUnion,
LintDiagnosticGuard, MemberLookupPolicy, MetaclassCandidate, PEP695TypeAliasType,
ParamSpecAttrKind, Parameter, ParameterForm, Parameters, Signature, SpecialFormType,
SubclassOfType, TrackedConstraintSet, Truthiness, Type, TypeAliasType, TypeAndQualifiers,
@@ -780,42 +779,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
);
}
}
if let Some(base_params) = base_class_literal.dataclass_params(self.db()) {
if base_params.flags(self.db()).contains(DataclassFlags::ORDER) {
// Suppress the diagnostic if the child class overrides all comparison
// methods, since the user has explicitly fixed the LSP violation.
// This includes the case where the child class also has `order=True`,
// which generates all four comparison methods.
let dominated_methods = ["__lt__", "__le__", "__gt__", "__ge__"];
let child_has_order = class
.dataclass_params(self.db())
.is_some_and(|p| p.flags(self.db()).contains(DataclassFlags::ORDER));
let all_overridden = child_has_order
|| dominated_methods.iter().all(|method| {
!class
.own_class_member(self.db(), None, None, method)
.is_undefined()
});
if !all_overridden {
if let Some(builder) = self.context.report_lint(
&SUBCLASS_OF_DATACLASS_WITH_ORDER,
&class_node.bases()[i],
) {
let mut diagnostic = builder.into_diagnostic(format_args!(
"Class `{}` inherits from dataclass `{}` which has `order=True`",
class.name(self.db()),
base_class.name(self.db()),
));
diagnostic.info(
"Comparison of instances of the child class with instances \
of the parent class will raise `TypeError` at runtime",
);
}
}
}
}
}
// (4) Check that the class's MRO is resolvable
@@ -889,39 +852,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
}
}
// (5) Check that @total_ordering has a valid ordering method in the MRO
if class.total_ordering(self.db()) {
let has_ordering_method = class
.iter_mro(self.db(), None)
.filter_map(super::super::class_base::ClassBase::into_class)
.filter(|base_class| {
!base_class
.class_literal(self.db())
.0
.is_known(self.db(), KnownClass::Object)
})
.any(|base_class| {
base_class
.class_literal(self.db())
.0
.has_own_ordering_method(self.db())
});
if !has_ordering_method {
// Find the @total_ordering decorator to report the diagnostic at its location
if let Some(decorator) = class_node.decorator_list.iter().find(|decorator| {
self.expression_type(&decorator.expression)
.as_function_literal()
.is_some_and(|function| {
function.is_known(self.db(), KnownFunction::TotalOrdering)
})
}) {
report_invalid_total_ordering(&self.context, class, decorator);
}
}
}
// (6) Check that the class's metaclass can be determined without error.
// (5) Check that the class's metaclass can be determined without error.
if let Err(metaclass_error) = class.try_metaclass(self.db()) {
match metaclass_error.reason() {
MetaclassErrorKind::Cycle => {
@@ -1123,67 +1054,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
if let Some(protocol) = class.into_protocol_class(self.db()) {
protocol.validate_members(&self.context);
}
// (9) If it's a `TypedDict` class, check that it doesn't include any invalid
// statements: https://typing.python.org/en/latest/spec/typeddict.html#class-based-syntax
//
// The body of the class definition defines the items of the `TypedDict` type. It
// may also contain a docstring or pass statements (primarily to allow the creation
// of an empty `TypedDict`). No other statements are allowed, and type checkers
// should report an error if any are present.
if class.is_typed_dict(self.db()) {
for stmt in &class_node.body {
match stmt {
// Annotated assignments are allowed (that's the whole point), but they're
// not allowed to have a value.
ast::Stmt::AnnAssign(ann_assign) => {
if let Some(value) = &ann_assign.value {
if let Some(builder) = self
.context
.report_lint(&INVALID_TYPED_DICT_STATEMENT, &**value)
{
builder.into_diagnostic(format_args!(
"TypedDict item cannot have a value"
));
}
}
continue;
}
// Pass statements are allowed.
ast::Stmt::Pass(_) => continue,
ast::Stmt::Expr(expr) => {
// Docstrings are allowed.
if matches!(*expr.value, ast::Expr::StringLiteral(_)) {
continue;
}
// As a non-standard but common extension, we also interpret `...` as
// equivalent to `pass`.
if matches!(*expr.value, ast::Expr::EllipsisLiteral(_)) {
continue;
}
}
// Everything else is forbidden.
_ => {}
}
if let Some(builder) = self
.context
.report_lint(&INVALID_TYPED_DICT_STATEMENT, stmt)
{
if matches!(stmt, ast::Stmt::FunctionDef(_)) {
builder.into_diagnostic(format_args!(
"TypedDict class cannot have methods"
));
} else {
let mut diagnostic = builder.into_diagnostic(format_args!(
"invalid statement in TypedDict class body"
));
diagnostic.info(
"Only annotated declarations (`<name>: <type>`) are allowed.",
);
}
}
}
}
}
}
@@ -2933,7 +2803,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
let mut type_check_only = false;
let mut dataclass_params = None;
let mut dataclass_transformer_params = None;
let mut total_ordering = false;
for decorator in decorator_list {
let decorator_ty = self.infer_decorator(decorator);
if decorator_ty
@@ -2944,14 +2813,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
continue;
}
if decorator_ty
.as_function_literal()
.is_some_and(|function| function.is_known(self.db(), KnownFunction::TotalOrdering))
{
total_ordering = true;
continue;
}
if let Type::DataclassDecorator(params) = decorator_ty {
dataclass_params = Some(params);
continue;
@@ -3039,7 +2900,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
type_check_only,
dataclass_params,
dataclass_transformer_params,
total_ordering,
)),
};

View File

@@ -357,6 +357,124 @@ impl<'db> Type<'db> {
}
match (self, target) {
// These branches here are quick optimisations to exclude
// fully static variants that we know are always mutually disjoint.
(
Type::BooleanLiteral(..)
| Type::IntLiteral(..)
| Type::StringLiteral(..)
| Type::BytesLiteral(..)
| Type::EnumLiteral(..)
| Type::WrapperDescriptor(..)
| Type::ModuleLiteral(..)
| Type::ClassLiteral(..)
| Type::SpecialForm(..),
Type::BooleanLiteral(..)
| Type::IntLiteral(..)
| Type::StringLiteral(..)
| Type::BytesLiteral(..)
| Type::EnumLiteral(..)
| Type::WrapperDescriptor(..)
| Type::ModuleLiteral(..)
| Type::ClassLiteral(..)
| Type::SpecialForm(..),
) => ConstraintSet::from(self == target),
(
Type::TypeIs(..)
| Type::TypeGuard(..)
| Type::BoundSuper(_)
| Type::PropertyInstance(_),
Type::LiteralString,
)
| (
Type::LiteralString,
Type::TypeIs(..)
| Type::TypeGuard(..)
| Type::BoundSuper(_)
| Type::PropertyInstance(_),
)
| (
Type::TypedDict(..),
Type::LiteralString
| Type::TypeIs(..)
| Type::TypeGuard(..)
| Type::BoundSuper(_)
| Type::PropertyInstance(_),
)
| (
Type::TypeGuard(..) | Type::LiteralString | Type::TypeIs(..),
Type::TypedDict(..) | Type::BoundSuper(_) | Type::PropertyInstance(_),
)
| (
Type::TypeIs(..)
| Type::TypeGuard(..)
| Type::LiteralString
| Type::TypedDict(..)
| Type::BoundSuper(_)
| Type::PropertyInstance(_),
Type::ClassLiteral(..)
| Type::BooleanLiteral(..)
| Type::IntLiteral(..)
| Type::BytesLiteral(..)
| Type::EnumLiteral(..)
| Type::WrapperDescriptor(..)
| Type::ModuleLiteral(..)
| Type::SpecialForm(..)
| Type::SubclassOf(..),
)
| (
Type::ClassLiteral(..)
| Type::BooleanLiteral(..)
| Type::IntLiteral(..)
| Type::BytesLiteral(..)
| Type::EnumLiteral(..)
| Type::WrapperDescriptor(..)
| Type::ModuleLiteral(..)
| Type::SubclassOf(_)
| Type::SpecialForm(..),
Type::TypeIs(..)
| Type::TypeGuard(..)
| Type::LiteralString
| Type::TypedDict(..)
| Type::BoundSuper(_)
| Type::PropertyInstance(_),
)
| (
Type::SubclassOf(..) | Type::GenericAlias(..),
Type::BooleanLiteral(..)
| Type::IntLiteral(..)
| Type::StringLiteral(..)
| Type::LiteralString
| Type::BytesLiteral(..)
| Type::EnumLiteral(..)
| Type::FunctionLiteral(..)
| Type::BoundMethod(..)
| Type::KnownBoundMethod(..)
| Type::WrapperDescriptor(..)
| Type::TypedDict(..)
| Type::ModuleLiteral(..)
| Type::BoundSuper(_)
| Type::PropertyInstance(_),
)
| (
Type::BooleanLiteral(..)
| Type::IntLiteral(..)
| Type::StringLiteral(..)
| Type::LiteralString
| Type::BytesLiteral(..)
| Type::EnumLiteral(..)
| Type::FunctionLiteral(..)
| Type::BoundMethod(..)
| Type::KnownBoundMethod(..)
| Type::TypedDict(..)
| Type::WrapperDescriptor(..)
| Type::ModuleLiteral(..)
| Type::BoundSuper(_)
| Type::PropertyInstance(_),
Type::SubclassOf(..) | Type::GenericAlias(..),
) => ConstraintSet::from(false),
// Everything is a subtype of `object`.
(_, Type::NominalInstance(instance)) if instance.is_object() => {
ConstraintSet::from(true)
@@ -840,27 +958,6 @@ impl<'db> Type<'db> {
)
}
// No literal type is a subtype of any other literal type, unless they are the same
// type (which is handled above). This case is not necessary from a correctness
// perspective (the fallback cases below will handle it correctly), but it is important
// for performance of simplifying large unions of literal types.
(
Type::StringLiteral(_)
| Type::IntLiteral(_)
| Type::BytesLiteral(_)
| Type::ClassLiteral(_)
| Type::FunctionLiteral(_)
| Type::ModuleLiteral(_)
| Type::EnumLiteral(_),
Type::StringLiteral(_)
| Type::IntLiteral(_)
| Type::BytesLiteral(_)
| Type::ClassLiteral(_)
| Type::FunctionLiteral(_)
| Type::ModuleLiteral(_)
| Type::EnumLiteral(_),
) => ConstraintSet::from(false),
(Type::Callable(self_callable), Type::Callable(other_callable)) => relation_visitor
.visit((self, target, relation), || {
self_callable.has_relation_to_impl(

View File

@@ -1931,14 +1931,12 @@ impl<'db> TupleSpecBuilder<'db> {
}
}
/// Return a new tuple-spec builder that reflects the intersection of this tuple and another
/// tuple, or `None` if the intersection is impossible (e.g., two fixed-length tuples with
/// different lengths).
/// Return a new tuple-spec builder that reflects the intersection of this tuple and another tuple.
///
/// For example, if `self` is a tuple-spec builder for `tuple[int, str]` and `other` is a
/// tuple-spec for `tuple[object, object]`, the result will be a tuple-spec builder for
/// `tuple[int, str]` (since `int & object` simplifies to `int`, and `str & object` to `str`).
pub(crate) fn intersect(mut self, db: &'db dyn Db, other: &TupleSpec<'db>) -> Option<Self> {
pub(crate) fn intersect(mut self, db: &'db dyn Db, other: &TupleSpec<'db>) -> Self {
match (&mut self, other) {
// Both fixed-length with the same length: element-wise intersection.
(TupleSpecBuilder::Fixed(our_elements), TupleSpec::Fixed(new_elements))
@@ -1947,23 +1945,24 @@ impl<'db> TupleSpecBuilder<'db> {
for (existing, new) in our_elements.iter_mut().zip(new_elements.all_elements()) {
*existing = IntersectionType::from_elements(db, [*existing, *new]);
}
Some(self)
return self;
}
// Fixed-length tuples with different lengths cannot intersect.
(TupleSpecBuilder::Fixed(_), TupleSpec::Fixed(_)) => None,
(TupleSpecBuilder::Fixed(our_elements), TupleSpec::Variable(var)) => {
if let Ok(tuple) = var.resize(db, TupleLength::Fixed(our_elements.len())) {
return self.intersect(db, &tuple);
}
}
(TupleSpecBuilder::Fixed(our_elements), TupleSpec::Variable(var)) => var
.resize(db, TupleLength::Fixed(our_elements.len()))
.ok()
.and_then(|tuple| self.intersect(db, &tuple)),
(TupleSpecBuilder::Variable { .. }, TupleSpec::Fixed(fixed)) => self
.clone()
.build()
.resize(db, TupleLength::Fixed(fixed.len()))
.ok()
.and_then(|tuple| TupleSpecBuilder::from(&tuple).intersect(db, other)),
(TupleSpecBuilder::Variable { .. }, TupleSpec::Fixed(fixed)) => {
if let Ok(tuple) = self
.clone()
.build()
.resize(db, TupleLength::Fixed(fixed.len()))
{
return TupleSpecBuilder::from(&tuple).intersect(db, other);
}
}
(
TupleSpecBuilder::Variable {
@@ -1983,20 +1982,29 @@ impl<'db> TupleSpecBuilder<'db> {
for (existing, new) in suffix.iter_mut().zip(var.suffix_elements()) {
*existing = IntersectionType::from_elements(db, [*existing, *new]);
}
return Some(self);
return self;
}
let self_built = self.clone().build();
let self_len = self_built.len();
var.resize(db, self_len)
.ok()
.and_then(|resized| self.intersect(db, &resized))
.or_else(|| {
self_built.resize(db, var.len()).ok().and_then(|resized| {
TupleSpecBuilder::from(&resized).intersect(db, other)
})
})
if let Ok(resized) = var.resize(db, self_len) {
return self.intersect(db, &resized);
} else if let Ok(resized) = self_built.resize(db, var.len()) {
return TupleSpecBuilder::from(&resized).intersect(db, other);
}
}
_ => {}
}
// TODO: probably incorrect? `tuple[int, str] & tuple[int, str, bytes]` should resolve to `Never`.
// So maybe this function should be fallible (return an `Option`)?
let intersected =
IntersectionType::from_elements(db, self.all_elements().chain(other.all_elements()));
TupleSpecBuilder::Variable {
prefix: vec![],
variable: intersected,
suffix: vec![],
}
}

View File

@@ -1575,9 +1575,6 @@ impl DocumentHandle {
{
db.project().remove_file(db, file);
}
// Bump the file's revision back to using the file system's revision.
file.sync(db);
} else {
// This can only fail when the path is a directory or it doesn't exists but the
// file should exists for this handler in this branch. This is because every
@@ -1601,8 +1598,6 @@ impl DocumentHandle {
if let Some(virtual_file) = db.files().try_virtual_file(virtual_path) {
db.project().close_file(db, virtual_file.file());
virtual_file.close(db);
// Bump the file's revision back to using the file system's revision.
virtual_file.sync(db);
} else {
tracing::warn!("Salsa virtual file does not exists for {}", virtual_path);
}

View File

@@ -70,40 +70,3 @@ fn multiline_token_client_supporting_multiline_tokens() -> Result<()> {
Ok(())
}
// Regression test for https://github.com/astral-sh/ty/issues/2346
#[test]
fn no_stale_tokens_after_opening_the_same_file_with_new_content() -> Result<()> {
let file_name = "src/foo";
let initial_content =
"def calculate_sum(a):\n # Version A: Basic math\n return a\n\nresult = calculate_sum(5)\n";
let mut server = TestServerBuilder::new()?
.enable_pull_diagnostics(true)
.enable_multiline_token_support(true)
.with_workspace(SystemPath::new("src"), None)?
.with_file(file_name, initial_content)?
.build()
.wait_until_workspaces_are_initialized();
server.open_text_document(file_name, initial_content, 0);
let initial_tokens = server
.semantic_tokens_full_request(&server.file_uri(file_name))
.unwrap();
server.close_text_document(file_name);
server.open_text_document(
file_name,
"# Version B: Basic greeting\ndef say_hello():\n print(\"Hello, World!\")\n\nsay_hello()\n",
0,
);
let new_tokens = server
.semantic_tokens_full_request(&server.file_uri(file_name))
.unwrap();
assert_ne!(initial_tokens, new_tokens);
Ok(())
}

View File

@@ -428,7 +428,6 @@ impl Workspace {
Ok(completions
.into_iter()
.map(|comp| {
let name = comp.insert.as_deref().unwrap_or(&comp.name).to_string();
let kind = comp.kind(&self.db).map(CompletionKind::from);
let type_display = comp.ty.map(|ty| ty.display(&self.db).to_string());
let import_edit = comp.import.as_ref().map(|edit| {
@@ -444,7 +443,7 @@ impl Workspace {
}
});
Completion {
name,
name: comp.name.into(),
kind,
detail: type_display,
module_name: comp.module_name.map(ToString::to_string),

View File

@@ -6,7 +6,6 @@ import {
useEffect,
useMemo,
useReducer,
useRef,
useState,
} from "react";
import { ErrorMessage, Header, setupMonaco, useTheme } from "shared";
@@ -25,22 +24,15 @@ export default function Playground() {
const [workspace, setWorkspace] = useState<Workspace | null>(null);
const [files, dispatchFiles] = useReducer(filesReducer, INIT_FILES_STATE);
const workspacePromiseRef = useRef<Promise<Workspace> | null>(null);
if (workspacePromiseRef.current == null) {
workspacePromiseRef.current = startPlayground().then((fetched) => {
const [workspacePromise] = useState<Promise<Workspace>>(() =>
startPlayground().then((fetched) => {
setVersion(fetched.version);
const workspace = new Workspace("/", PositionEncoding.Utf16, {});
restoreWorkspace(workspace, fetched.workspace, dispatchFiles, setError);
setWorkspace(workspace);
return workspace;
});
}
// This is safe as this is only called once on startup.
// We need useRef to avoid duplicate initialization when
// running locally due to react rendering
// everything twice in strict mode in debug builds.
// eslint-disable-next-line react-hooks/refs
const workspacePromise = workspacePromiseRef.current;
}),
);
const fileName = useMemo(() => {
return (

30
ty.schema.json generated
View File

@@ -786,16 +786,6 @@
}
]
},
"invalid-total-ordering": {
"title": "detects `@total_ordering` classes without an ordering method",
"description": "## What it does\nChecks for classes decorated with `@functools.total_ordering` that don't\ndefine any ordering method (`__lt__`, `__le__`, `__gt__`, or `__ge__`).\n\n## Why is this bad?\nThe `@total_ordering` decorator requires the class to define at least one\nordering method. If none is defined, Python raises a `ValueError` at runtime.\n\n## Example\n\n```python\nfrom functools import total_ordering\n\n@total_ordering\nclass MyClass: # Error: no ordering method defined\n def __eq__(self, other: object) -> bool:\n return True\n```\n\nUse instead:\n\n```python\nfrom functools import total_ordering\n\n@total_ordering\nclass MyClass:\n def __eq__(self, other: object) -> bool:\n return True\n\n def __lt__(self, other: \"MyClass\") -> bool:\n return True\n```",
"default": "error",
"oneOf": [
{
"$ref": "#/definitions/Level"
}
]
},
"invalid-type-alias-type": {
"title": "detects invalid TypeAliasType definitions",
"description": "## What it does\nChecks for the creation of invalid `TypeAliasType`s\n\n## Why is this bad?\nThere are several requirements that you must follow when creating a `TypeAliasType`.\n\n## Examples\n```python\nfrom typing import TypeAliasType\n\nIntOrStr = TypeAliasType(\"IntOrStr\", int | str) # okay\nNewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name must be a string literal\n```",
@@ -866,16 +856,6 @@
}
]
},
"invalid-typed-dict-statement": {
"title": "detects invalid statements in `TypedDict` class bodies",
"description": "## What it does\nDetects statements other than annotated declarations in `TypedDict` class bodies.\n\n## Why is this bad?\n`TypedDict` class bodies aren't allowed to contain any other types of statements. For\nexample, method definitions and field values aren't allowed. None of these will be\navailable on \"instances of the `TypedDict`\" at runtime (as `dict` is the runtime class of\nall \"`TypedDict` instances\").\n\n## Example\n```python\nfrom typing import TypedDict\n\nclass Foo(TypedDict):\n def bar(self): # error: [invalid-typed-dict-statement]\n pass\n```",
"default": "error",
"oneOf": [
{
"$ref": "#/definitions/Level"
}
]
},
"missing-argument": {
"title": "detects missing required arguments in a call",
"description": "## What it does\nChecks for missing required arguments in a call.\n\n## Why is this bad?\nFailing to provide a required argument will raise a `TypeError` at runtime.\n\n## Examples\n```python\ndef func(x: int): ...\nfunc() # TypeError: func() missing 1 required positional argument: 'x'\n```",
@@ -1026,16 +1006,6 @@
}
]
},
"subclass-of-dataclass-with-order": {
"title": "detects subclasses of dataclasses with `order=True`",
"description": "## What it does\nChecks for classes that inherit from a dataclass with `order=True`.\n\n## Why is this bad?\nWhen a dataclass has `order=True`, comparison methods (`__lt__`, `__le__`, `__gt__`, `__ge__`)\nare generated that compare instances as tuples of their fields. These methods raise a\n`TypeError` at runtime when comparing instances of different classes in the inheritance\nhierarchy, even if one is a subclass of the other.\n\nThis violates the [Liskov Substitution Principle] because child class instances cannot be\nused in all contexts where parent class instances are expected.\n\n## Example\n\n```python\nfrom dataclasses import dataclass\n\n@dataclass(order=True)\nclass Parent:\n value: int\n\nclass Child(Parent): # Ty emits a warning here\n pass\n\n# At runtime, this raises TypeError:\n# Child(1) < Parent(2)\n```\n\nConsider using [`functools.total_ordering`] instead, which does not have this limitation.\n\n[Liskov Substitution Principle]: https://en.wikipedia.org/wiki/Liskov_substitution_principle\n[`functools.total_ordering`]: https://docs.python.org/3/library/functools.html#functools.total_ordering",
"default": "warn",
"oneOf": [
{
"$ref": "#/definitions/Level"
}
]
},
"subclass-of-final-class": {
"title": "detects subclasses of final classes",
"description": "## What it does\nChecks for classes that subclass final classes.\n\n## Why is this bad?\nDecorating a class with `@final` declares to the type checker that it should not be subclassed.\n\n## Example\n\n```python\nfrom typing import final\n\n@final\nclass A: ...\nclass B(A): ... # Error raised here\n```",