Compare commits

...

20 Commits

Author SHA1 Message Date
Brent Westbrook
cabdd969ec convert to serializable diagnostics 2025-07-21 16:09:52 -04:00
Brent Westbrook
2e5c8b9799 Revert "custom serializers"
This reverts commit e1219bc27c.
2025-07-21 16:09:43 -04:00
Brent Westbrook
e1219bc27c custom serializers 2025-07-21 16:09:27 -04:00
Micha Reiser
926e83323a [ty] Avoid rechecking the entire project when changing the opened files (#19463) 2025-07-21 18:05:03 +02:00
Micha Reiser
5cace28c3e [ty] Add warning for unknown TY_MEMORY_REPORT value (#19465) 2025-07-21 14:29:24 +00:00
github-actions[bot]
3785e13231 [ty] Sync vendored typeshed stubs (#19461)
Co-authored-by: typeshedbot <>
Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
2025-07-21 14:01:42 +01:00
Alex Waygood
c2380fa0e2 [ty] Extend tuple __len__ and __bool__ special casing to also cover tuple subclasses (#19289)
Co-authored-by: Brent Westbrook
2025-07-21 12:50:46 +00:00
Alex Waygood
4dec44ae49 [ty] bump docstring-adder pin (#19458) 2025-07-21 13:38:40 +01:00
David Peter
b6579eaf04 [ty] Disallow assignment to Final class attributes (#19457)
## Summary

Emit errors for the following assignments:
```py
class C:
    CLASS_LEVEL_CONSTANT: Final[int] = 1

C.CLASS_LEVEL_CONSTANT = 2
C().CLASS_LEVEL_CONSTANT = 2
```

## Test Plan

Updated and new MD tests
2025-07-21 14:27:56 +02:00
renovate[bot]
f063c0e874 Update dependency ruff to v0.12.4 (#19442)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-21 08:32:09 +02:00
renovate[bot]
6a65734ee3 Update pre-commit hook astral-sh/ruff-pre-commit to v0.12.4 (#19443)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-21 08:31:55 +02:00
renovate[bot]
00066e094c Update rui314/setup-mold digest to 702b190 (#19441)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-21 08:31:47 +02:00
renovate[bot]
37a1958374 Update taiki-e/install-action action to v2.56.19 (#19448)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-21 08:31:18 +02:00
renovate[bot]
2535d791ae Update Rust crate strum_macros to v0.27.2 (#19447)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-21 08:31:07 +02:00
renovate[bot]
05c4399e7b Update Rust crate strum to v0.27.2 (#19446)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-21 08:30:51 +02:00
renovate[bot]
b18434b0f6 Update Rust crate rand to v0.9.2 (#19444)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-21 08:30:07 +02:00
renovate[bot]
17779c9a17 Update Rust crate serde_json to v1.0.141 (#19445)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-21 08:29:51 +02:00
Dylan
53fc0614da Fix unreachable panic in parser (#19183)
Parsing the (invalid) expression `f"{\t"i}"` caused a panic because the
`TStringMiddle` character was "unreachable" due the way the parser
recovered from the line continuation (it ate the t-string start).

The cause of the issue is as follows: 

The parser begins parsing the f-string and expects to see a list of
objects, essentially alternating between _interpolated elements_ and
ordinary strings. It is happy to see the first left brace, but then
there is a lexical error caused by the line-continuation character. So
instead of the parser seeing a list of elements with just one member, it
sees a list that starts like this:

- Interpolated element with an invalid token, stored as a `Name`
- Something else built from tokens beginning with `TStringStart` and
`TStringMiddle`

When it sees the `TStringStart` error recovery says "that's a list
element I don't know what to do with, let's skip it". When it sees
`TStringMiddle` it says "oh, that looks like the middle of _some
interpolated string_ so let's try to parse it as one of the literal
elements of my `FString`". Unfortunately, the function being used to
parse individual list elements thinks (arguably correctly) that it's not
possible to have a `TStringMiddle` sitting in your `FString`, and hits
`unreachable`.

Two potential ways (among many) to solve this issue are:

1. Allow a `TStringMiddle` as a valid "literal" part of an f-string
during parsing (with the hope/understanding that this would only occur
in an invalid context)
2. Skip the `TStringMiddle` as an "unexpected/invalid list item" in the
same way that we skipped `TStringStart`.

I have opted for the second approach since it seems somehow more morally
correct, even though it loses more information. To implement this, the
recovery context needs to know whether we are in an f-string or t-string
- hence the changes to that enum. As a bonus we get slightly more
specific error messages in some cases.

Closes #18860
2025-07-20 22:04:14 +00:00
Dan Parizher
59249f483b [ruff] Support byte strings (RUF055) (#18926)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title? (Please prefix
with `[ty]` for ty pull
  requests.)
- Does this pull request include references to any relevant issues?
-->

## Summary

<!-- What's the purpose of the change? What does it do, and why? -->

Closes #18739

## Test Plan

<!-- How was it tested? -->

---------

Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
2025-07-20 17:58:40 -04:00
Micha Reiser
84e76f4d04 [ty] Avoid second lookup for infer_maybe_standalone_expression (#19439) 2025-07-20 18:22:04 +02:00
632 changed files with 2262 additions and 2309 deletions

View File

@@ -238,13 +238,13 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- name: "Install mold"
uses: rui314/setup-mold@85c79d00377f0d32cdbae595a46de6f7c2fa6599 # v1
uses: rui314/setup-mold@702b1908b5edf30d71a8d1666b724e0f0c6fa035 # v1
- name: "Install cargo nextest"
uses: taiki-e/install-action@c07504cae06f832dc8de08911c9a9c5cddb0d2d3 # v2.56.13
uses: taiki-e/install-action@c99cc51b309eee71a866715cfa08c922f11cf898 # v2.56.19
with:
tool: cargo-nextest
- name: "Install cargo insta"
uses: taiki-e/install-action@c07504cae06f832dc8de08911c9a9c5cddb0d2d3 # v2.56.13
uses: taiki-e/install-action@c99cc51b309eee71a866715cfa08c922f11cf898 # v2.56.19
with:
tool: cargo-insta
- name: ty mdtests (GitHub annotations)
@@ -296,13 +296,13 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- name: "Install mold"
uses: rui314/setup-mold@85c79d00377f0d32cdbae595a46de6f7c2fa6599 # v1
uses: rui314/setup-mold@702b1908b5edf30d71a8d1666b724e0f0c6fa035 # v1
- name: "Install cargo nextest"
uses: taiki-e/install-action@c07504cae06f832dc8de08911c9a9c5cddb0d2d3 # v2.56.13
uses: taiki-e/install-action@c99cc51b309eee71a866715cfa08c922f11cf898 # v2.56.19
with:
tool: cargo-nextest
- name: "Install cargo insta"
uses: taiki-e/install-action@c07504cae06f832dc8de08911c9a9c5cddb0d2d3 # v2.56.13
uses: taiki-e/install-action@c99cc51b309eee71a866715cfa08c922f11cf898 # v2.56.19
with:
tool: cargo-insta
- name: "Run tests"
@@ -325,7 +325,7 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- name: "Install cargo nextest"
uses: taiki-e/install-action@c07504cae06f832dc8de08911c9a9c5cddb0d2d3 # v2.56.13
uses: taiki-e/install-action@c99cc51b309eee71a866715cfa08c922f11cf898 # v2.56.19
with:
tool: cargo-nextest
- name: "Run tests"
@@ -381,7 +381,7 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- name: "Install mold"
uses: rui314/setup-mold@85c79d00377f0d32cdbae595a46de6f7c2fa6599 # v1
uses: rui314/setup-mold@702b1908b5edf30d71a8d1666b724e0f0c6fa035 # v1
- name: "Build"
run: cargo build --release --locked
@@ -406,7 +406,7 @@ jobs:
MSRV: ${{ steps.msrv.outputs.value }}
run: rustup default "${MSRV}"
- name: "Install mold"
uses: rui314/setup-mold@85c79d00377f0d32cdbae595a46de6f7c2fa6599 # v1
uses: rui314/setup-mold@702b1908b5edf30d71a8d1666b724e0f0c6fa035 # v1
- name: "Build tests"
shell: bash
env:
@@ -903,7 +903,7 @@ jobs:
run: rustup show
- name: "Install codspeed"
uses: taiki-e/install-action@c07504cae06f832dc8de08911c9a9c5cddb0d2d3 # v2.56.13
uses: taiki-e/install-action@c99cc51b309eee71a866715cfa08c922f11cf898 # v2.56.19
with:
tool: cargo-codspeed
@@ -936,7 +936,7 @@ jobs:
run: rustup show
- name: "Install codspeed"
uses: taiki-e/install-action@c07504cae06f832dc8de08911c9a9c5cddb0d2d3 # v2.56.13
uses: taiki-e/install-action@c99cc51b309eee71a866715cfa08c922f11cf898 # v2.56.19
with:
tool: cargo-codspeed

View File

@@ -38,7 +38,7 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- name: "Install mold"
uses: rui314/setup-mold@85c79d00377f0d32cdbae595a46de6f7c2fa6599 # v1
uses: rui314/setup-mold@702b1908b5edf30d71a8d1666b724e0f0c6fa035 # v1
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- name: Build ruff
# A debug build means the script runs slower once it gets started,

View File

@@ -34,6 +34,10 @@ env:
# and which all three workers push to.
UPSTREAM_BRANCH: typeshedbot/sync-typeshed
# The path to the directory that contains the vendored typeshed stubs,
# relative to the root of the Ruff repository.
VENDORED_TYPESHED: crates/ty_vendored/vendor/typeshed
jobs:
# Sync typeshed stubs, and sync all docstrings available on Linux.
# Push the changes to a new branch on the upstream repository.
@@ -64,20 +68,20 @@ jobs:
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
- name: Sync typeshed stubs
run: |
rm -rf ruff/crates/ty_vendored/vendor/typeshed
mkdir ruff/crates/ty_vendored/vendor/typeshed
cp typeshed/README.md ruff/crates/ty_vendored/vendor/typeshed
cp typeshed/LICENSE ruff/crates/ty_vendored/vendor/typeshed
rm -rf "ruff/${VENDORED_TYPESHED}"
mkdir "ruff/${VENDORED_TYPESHED}"
cp typeshed/README.md "ruff/${VENDORED_TYPESHED}"
cp typeshed/LICENSE "ruff/${VENDORED_TYPESHED}"
# The pyproject.toml file is needed by a later job for the black configuration.
# It's deleted before creating the PR.
cp typeshed/pyproject.toml ruff/crates/ty_vendored/vendor/typeshed
cp typeshed/pyproject.toml "ruff/${VENDORED_TYPESHED}"
cp -r typeshed/stdlib ruff/crates/ty_vendored/vendor/typeshed/stdlib
rm -rf ruff/crates/ty_vendored/vendor/typeshed/stdlib/@tests
git -C typeshed rev-parse HEAD > ruff/crates/ty_vendored/vendor/typeshed/source_commit.txt
cp -r typeshed/stdlib "ruff/${VENDORED_TYPESHED}/stdlib"
rm -rf "ruff/${VENDORED_TYPESHED}/stdlib/@tests"
git -C typeshed rev-parse HEAD > "ruff/${VENDORED_TYPESHED}/source_commit.txt"
cd ruff
git checkout -b typeshedbot/sync-typeshed
git checkout -b "${UPSTREAM_BRANCH}"
git add .
git commit -m "Sync typeshed. Source commit: https://github.com/python/typeshed/commit/$(git -C ../typeshed rev-parse HEAD)" --allow-empty
- name: Sync Linux docstrings
@@ -167,17 +171,17 @@ jobs:
# consistent with the other typeshed stubs around them.
# Typeshed formats code using black in their CI, so we just invoke
# black on the stubs the same way that typeshed does.
uvx black crates/ty_vendored/vendor/typeshed/stdlib --config crates/ty_vendored/vendor/typeshed/pyproject.toml || true
uvx black "${VENDORED_TYPESHED}/stdlib" --config "${VENDORED_TYPESHED}/pyproject.toml" || true
git commit -am "Format codemodded docstrings" --allow-empty
rm crates/ty_vendored/vendor/typeshed/pyproject.toml
rm "${VENDORED_TYPESHED}/pyproject.toml"
git commit -am "Remove pyproject.toml file"
git push
- name: Create a PR
if: ${{ success() }}
run: |
gh pr list --repo "$GITHUB_REPOSITORY" --head typeshedbot/sync-typeshed --json id --jq length | grep 1 && exit 0 # exit if there is existing pr
gh pr list --repo "${GITHUB_REPOSITORY}" --head "${UPSTREAM_BRANCH}" --json id --jq length | grep 1 && exit 0 # exit if there is existing pr
gh pr create --title "[ty] Sync vendored typeshed stubs" --body "Close and reopen this PR to trigger CI" --label "ty"
create-issue-on-failure:

View File

@@ -81,7 +81,7 @@ repos:
pass_filenames: false # This makes it a lot faster
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.12.3
rev: v0.12.4
hooks:
- id: ruff-format
- id: ruff

21
Cargo.lock generated
View File

@@ -2561,9 +2561,9 @@ dependencies = [
[[package]]
name = "rand"
version = "0.9.1"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [
"rand_chacha 0.9.0",
"rand_core 0.9.3",
@@ -3041,7 +3041,7 @@ version = "0.0.0"
dependencies = [
"anyhow",
"itertools 0.14.0",
"rand 0.9.1",
"rand 0.9.2",
"ruff_diagnostics",
"ruff_source_file",
"ruff_text_size",
@@ -3535,9 +3535,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.140"
version = "1.0.141"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3"
dependencies = [
"itoa",
"memchr",
@@ -3727,23 +3727,22 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "strum"
version = "0.27.1"
version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32"
checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf"
dependencies = [
"strum_macros",
]
[[package]]
name = "strum_macros"
version = "0.27.1"
version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8"
checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7"
dependencies = [
"heck",
"proc-macro2",
"quote",
"rustversion",
"syn",
]
@@ -4564,7 +4563,7 @@ checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d"
dependencies = [
"getrandom 0.3.3",
"js-sys",
"rand 0.9.1",
"rand 0.9.2",
"uuid-macro-internal",
"wasm-bindgen",
]

View File

@@ -18,14 +18,12 @@ use rustc_hash::FxHashMap;
use tempfile::NamedTempFile;
use ruff_cache::{CacheKey, CacheKeyHasher};
use ruff_db::diagnostic::Diagnostic;
use ruff_db::diagnostic::{Diagnostic, SerializableDiagnostics};
use ruff_diagnostics::Fix;
use ruff_linter::message::create_lint_diagnostic;
use ruff_linter::package::PackageRoot;
use ruff_linter::{VERSION, warn_user};
use ruff_macros::CacheKey;
use ruff_notebook::NotebookIndex;
use ruff_source_file::SourceFileBuilder;
use ruff_text_size::{TextRange, TextSize};
use ruff_workspace::Settings;
use ruff_workspace::resolver::Resolver;
@@ -345,22 +343,7 @@ impl FileCache {
let diagnostics = if lint.messages.is_empty() {
Vec::new()
} else {
let file = SourceFileBuilder::new(path.to_string_lossy(), &*lint.source).finish();
lint.messages
.iter()
.map(|msg| {
create_lint_diagnostic(
&msg.body,
msg.suggestion.as_ref(),
msg.range,
msg.fix.clone(),
msg.parent,
file.clone(),
msg.noqa_offset,
msg.rule,
)
})
.collect()
lint.messages.to_diagnostics()
};
let notebook_indexes = if let Some(notebook_index) = lint.notebook_index.as_ref() {
FxHashMap::from_iter([(path.to_string_lossy().to_string(), notebook_index.clone())])
@@ -415,7 +398,8 @@ pub(crate) struct LintCacheData {
/// Imports made.
// pub(super) imports: ImportMap,
/// Diagnostic messages.
pub(super) messages: Vec<CacheMessage>,
#[bincode(with_serde)]
pub(super) messages: SerializableDiagnostics,
/// Source code of the file.
///
/// # Notes
@@ -438,30 +422,7 @@ impl LintCacheData {
String::new() // No messages, no need to keep the source!
};
let messages = diagnostics
.iter()
// Parse the kebab-case rule name into a `Rule`. This will fail for syntax errors, so
// this also serves to filter them out, but we shouldn't be caching files with syntax
// errors anyway.
.filter_map(|msg| Some((msg.name().parse().ok()?, msg)))
.map(|(rule, msg)| {
// Make sure that all message use the same source file.
assert_eq!(
msg.expect_ruff_source_file(),
diagnostics.first().unwrap().expect_ruff_source_file(),
"message uses a different source file"
);
CacheMessage {
rule,
body: msg.body().to_string(),
suggestion: msg.suggestion().map(ToString::to_string),
range: msg.expect_range(),
parent: msg.parent(),
fix: msg.fix().cloned(),
noqa_offset: msg.noqa_offset(),
}
})
.collect();
let messages = SerializableDiagnostics::new(diagnostics);
Self {
messages,

View File

@@ -9,6 +9,9 @@ use ruff_text_size::{Ranged, TextRange, TextSize};
pub use self::render::{DisplayDiagnostic, DisplayDiagnostics, FileResolver, Input};
use crate::{Db, files::File};
#[cfg(feature = "serde")]
pub use serde_diagnostics::SerializableDiagnostics;
mod render;
mod stylesheet;
@@ -790,6 +793,7 @@ impl Annotation {
/// These tags are used to provide additional information about the annotation.
/// and are passed through to the language server protocol.
#[derive(Debug, Clone, Eq, PartialEq, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum DiagnosticTag {
/// Unused or unnecessary code. Used for unused parameters, unreachable code, etc.
Unnecessary,
@@ -804,6 +808,7 @@ pub enum DiagnosticTag {
///
/// Rules use kebab case, e.g. `no-foo`.
#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct LintName(&'static str);
impl LintName {
@@ -844,6 +849,7 @@ impl PartialEq<&str> for LintName {
/// Uniquely identifies the kind of a diagnostic.
#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum DiagnosticId {
Panic,
@@ -1141,6 +1147,7 @@ impl From<crate::files::FileRange> for Span {
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Severity {
Info,
Warning,
@@ -1344,6 +1351,7 @@ impl std::fmt::Display for ConciseMessage<'_> {
/// a blanket trait implementation for `IntoDiagnosticMessage` for
/// anything that implements `std::fmt::Display`.
#[derive(Clone, Debug, Eq, PartialEq, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DiagnosticMessage(Box<str>);
impl DiagnosticMessage {
@@ -1407,7 +1415,11 @@ impl<T: std::fmt::Display> IntoDiagnosticMessage for T {
///
/// For Ruff rules this means the noqa code.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Hash, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(transparent))]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(transparent)
)]
pub struct SecondaryCode(String);
impl SecondaryCode {
@@ -1452,3 +1464,205 @@ impl From<&SecondaryCode> for SecondaryCode {
value.clone()
}
}
#[cfg(feature = "serde")]
mod serde_diagnostics {
use std::sync::Arc;
use ruff_diagnostics::Fix;
use ruff_source_file::{SourceFile, SourceFileBuilder};
use ruff_text_size::{TextRange, TextSize};
use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize};
use super::{
Annotation, Diagnostic, DiagnosticId, DiagnosticInner, DiagnosticMessage, DiagnosticTag,
LintName, SecondaryCode, Severity, Span, SubDiagnostic, SubDiagnosticInner, UnifiedFile,
};
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct SerializableDiagnostics {
source_files: FxHashMap<String, String>,
diagnostics: Vec<SerializableDiagnostic>,
}
impl SerializableDiagnostics {
pub fn new(diagnostics: &[Diagnostic]) -> Self {
let mut source_files = FxHashMap::default();
let mut serializable_diagnostics = Vec::with_capacity(diagnostics.len());
for diagnostic in diagnostics {
let mut subs = Vec::with_capacity(diagnostic.inner.subs.len());
for sub in &diagnostic.inner.subs {
subs.push(SerializableSubDiagnostic {
severity: sub.inner.severity,
message: sub.inner.message.clone(),
annotations: serializable_annotations(
&mut source_files,
&sub.inner.annotations,
),
});
}
serializable_diagnostics.push(SerializableDiagnostic {
id: diagnostic.inner.id,
severity: diagnostic.inner.severity,
message: diagnostic.inner.message.clone(),
annotations: serializable_annotations(
&mut source_files,
&diagnostic.inner.annotations,
),
subs,
fix: diagnostic.inner.fix.clone(),
parent: diagnostic.inner.parent,
noqa_offset: diagnostic.inner.noqa_offset,
secondary_code: diagnostic.inner.secondary_code.clone(),
});
}
Self {
source_files,
diagnostics: serializable_diagnostics,
}
}
pub fn is_empty(&self) -> bool {
self.diagnostics.is_empty()
}
pub fn to_diagnostics(&self) -> Vec<Diagnostic> {
let source_files: FxHashMap<&str, SourceFile> = self
.source_files
.iter()
.map(|(name, contents)| {
(
name.as_str(),
SourceFileBuilder::new(name.clone(), contents.clone()).finish(),
)
})
.collect();
self.diagnostics
.iter()
.map(|diag| Diagnostic {
inner: Arc::new(DiagnosticInner {
id: diag.id,
severity: diag.severity,
message: diag.message.clone(),
annotations: annotations(&source_files, &diag.annotations),
subs: subdiagnostics(&source_files, &diag.subs),
fix: diag.fix.clone(),
parent: diag.parent,
noqa_offset: diag.noqa_offset,
secondary_code: diag.secondary_code.clone(),
}),
})
.collect()
}
}
fn serializable_annotations(
source_files: &mut FxHashMap<String, String>,
annotations: &[Annotation],
) -> Vec<SerializableAnnotation> {
let mut serializable_annotations = Vec::with_capacity(annotations.len());
for annotation in annotations {
let file = annotation.span.expect_ruff_file();
source_files.insert(file.name().to_string(), file.source_text().to_string());
serializable_annotations.push(SerializableAnnotation {
span: SerializableSpan {
name: file.name().to_string(),
range: annotation.span.range,
},
message: annotation.message.clone(),
is_primary: annotation.is_primary,
tags: annotation.tags.clone(),
});
}
serializable_annotations
}
fn annotations(
source_files: &FxHashMap<&str, SourceFile>,
serializable_annotations: &[SerializableAnnotation],
) -> Vec<Annotation> {
serializable_annotations
.iter()
.map(|ann| {
let span = Span {
file: UnifiedFile::Ruff(
source_files
.get(ann.span.name.as_str())
.expect("Expected source file in cache")
.clone(),
),
range: ann.span.range,
};
Annotation {
span,
message: ann.message.clone(),
is_primary: ann.is_primary,
tags: ann.tags.clone(),
}
})
.collect()
}
fn subdiagnostics(
source_files: &FxHashMap<&str, SourceFile>,
serializable_annotations: &[SerializableSubDiagnostic],
) -> Vec<SubDiagnostic> {
serializable_annotations
.iter()
.map(|sub| SubDiagnostic {
inner: Box::new(SubDiagnosticInner {
severity: sub.severity,
message: sub.message.clone(),
annotations: annotations(source_files, &sub.annotations),
}),
})
.collect()
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct SerializableDiagnostic {
id: DiagnosticId,
severity: Severity,
message: DiagnosticMessage,
annotations: Vec<SerializableAnnotation>,
subs: Vec<SerializableSubDiagnostic>,
fix: Option<Fix>,
parent: Option<TextSize>,
noqa_offset: Option<TextSize>,
secondary_code: Option<SecondaryCode>,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct SerializableAnnotation {
span: SerializableSpan,
message: Option<DiagnosticMessage>,
is_primary: bool,
tags: Vec<DiagnosticTag>,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct SerializableSubDiagnostic {
severity: Severity,
message: DiagnosticMessage,
annotations: Vec<SerializableAnnotation>,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct SerializableSpan {
name: String,
range: Option<TextRange>,
}
impl<'de> Deserialize<'de> for LintName {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?.into_boxed_str();
Ok(LintName::of(Box::leak(s)))
}
}
}

View File

@@ -79,3 +79,8 @@ def in_type_def():
from typing import cast
a = 'int'
cast('f"{a}"','11')
# Regression test for parser bug
# https://github.com/astral-sh/ruff/issues/18860
def fuzz_bug():
c('{\t"i}')

View File

@@ -0,0 +1,24 @@
import re
b_src = b"abc"
# Should be replaced with `b_src.replace(rb"x", b"y")`
re.sub(rb"x", b"y", b_src)
# Should be replaced with `b_src.startswith(rb"abc")`
if re.match(rb"abc", b_src):
pass
# Should be replaced with `rb"x" in b_src`
if re.search(rb"x", b_src):
pass
# Should be replaced with `b_src.split(rb"abc")`
re.split(rb"abc", b_src)
# Patterns containing metacharacters should NOT be replaced
re.sub(rb"ab[c]", b"", b_src)
re.match(rb"ab[c]", b_src)
re.search(rb"ab[c]", b_src)
re.fullmatch(rb"ab[c]", b_src)
re.split(rb"ab[c]", b_src)

View File

@@ -534,6 +534,7 @@ mod tests {
#[test_case(Rule::UnnecessaryRegularExpression, Path::new("RUF055_0.py"))]
#[test_case(Rule::UnnecessaryRegularExpression, Path::new("RUF055_1.py"))]
#[test_case(Rule::UnnecessaryRegularExpression, Path::new("RUF055_2.py"))]
#[test_case(Rule::UnnecessaryRegularExpression, Path::new("RUF055_3.py"))]
#[test_case(Rule::PytestRaisesAmbiguousPattern, Path::new("RUF043.py"))]
#[test_case(Rule::IndentedFormFeed, Path::new("RUF054.py"))]
#[test_case(Rule::ImplicitClassVarInDataclass, Path::new("RUF045.py"))]

View File

@@ -1,8 +1,8 @@
use itertools::Itertools;
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::{
Arguments, CmpOp, Expr, ExprAttribute, ExprCall, ExprCompare, ExprContext, ExprStringLiteral,
ExprUnaryOp, Identifier, UnaryOp,
Arguments, CmpOp, Expr, ExprAttribute, ExprBytesLiteral, ExprCall, ExprCompare, ExprContext,
ExprStringLiteral, ExprUnaryOp, Identifier, UnaryOp,
};
use ruff_python_semantic::analyze::typing::find_binding_value;
use ruff_python_semantic::{Modules, SemanticModel};
@@ -72,6 +72,9 @@ impl Violation for UnnecessaryRegularExpression {
}
}
const METACHARACTERS: [char; 12] = ['.', '^', '$', '*', '+', '?', '{', '[', '\\', '|', '(', ')'];
const ESCAPABLE_SINGLE_CHARACTERS: &str = "abfnrtv";
/// RUF055
pub(crate) fn unnecessary_regular_expression(checker: &Checker, call: &ExprCall) {
// adapted from unraw_re_pattern
@@ -96,16 +99,19 @@ pub(crate) fn unnecessary_regular_expression(checker: &Checker, call: &ExprCall)
};
// For now, restrict this rule to string literals and variables that can be resolved to literals
let Some(string_lit) = resolve_string_literal(re_func.pattern, semantic) else {
let Some(literal) = resolve_literal(re_func.pattern, semantic) else {
return;
};
// For now, reject any regex metacharacters. Compare to the complete list
// from https://docs.python.org/3/howto/regex.html#matching-characters
let has_metacharacters = string_lit
.value
.to_str()
.contains(['.', '^', '$', '*', '+', '?', '{', '[', '\\', '|', '(', ')']);
let has_metacharacters = match &literal {
Literal::Str(str_lit) => str_lit.value.to_str().contains(METACHARACTERS),
Literal::Bytes(bytes_lit) => bytes_lit
.value
.iter()
.any(|part| part.iter().any(|&b| METACHARACTERS.contains(&(b as char)))),
};
if has_metacharacters {
return;
@@ -186,28 +192,48 @@ impl<'a> ReFunc<'a> {
// version
("sub", 3) => {
let repl = call.arguments.find_argument_value("repl", 1)?;
let lit = resolve_string_literal(repl, semantic)?;
let lit = resolve_literal(repl, semantic)?;
let mut fixable = true;
for (c, next) in lit.value.chars().tuple_windows() {
// `\0` (or any other ASCII digit) and `\g` have special meaning in `repl` strings.
// Meanwhile, nearly all other escapes of ASCII letters in a `repl` string causes
// `re.PatternError` to be raised at runtime.
//
// If we see that the escaped character is an alphanumeric ASCII character,
// we should only emit a diagnostic suggesting to replace the `re.sub()` call with
// `str.replace`if we can detect that the escaped character is one that is both
// valid in a `repl` string *and* does not have any special meaning in a REPL string.
//
// It's out of scope for this rule to change invalid `re.sub()` calls into something
// that would not raise an exception at runtime. They should be left as-is.
if c == '\\' && next.is_ascii_alphanumeric() {
if "abfnrtv".contains(next) {
fixable = false;
} else {
return None;
match lit {
Literal::Str(lit_str) => {
// Perform escape analysis for replacement literals.
for (c, next) in lit_str.value.to_str().chars().tuple_windows() {
// `\\0` (or any other ASCII digit) and `\\g` have special meaning in `repl` strings.
// Meanwhile, nearly all other escapes of ASCII letters in a `repl` string causes
// `re.PatternError` to be raised at runtime.
//
// If we see that the escaped character is an alphanumeric ASCII character,
// we should only emit a diagnostic suggesting to replace the `re.sub()` call with
// `str.replace`if we can detect that the escaped character is one that is both
// valid in a `repl` string *and* does not have any special meaning in a REPL string.
//
// It's out of scope for this rule to change invalid `re.sub()` calls into something
// that would not raise an exception at runtime. They should be left as-is.
if c == '\\' && next.is_ascii_alphanumeric() {
if ESCAPABLE_SINGLE_CHARACTERS.contains(next) {
fixable = false;
} else {
return None;
}
}
}
}
Literal::Bytes(lit_bytes) => {
for part in &lit_bytes.value {
for (byte, next) in part.iter().copied().tuple_windows() {
if byte == b'\\' && (next as char).is_ascii_alphanumeric() {
if ESCAPABLE_SINGLE_CHARACTERS.contains(next as char) {
fixable = false;
} else {
return None;
}
}
}
}
}
}
Some(ReFunc {
kind: ReFuncKind::Sub {
repl: fixable.then_some(repl),
@@ -329,6 +355,43 @@ impl<'a> ReFunc<'a> {
}
}
/// A literal that can be either a string or a bytes literal.
enum Literal<'a> {
Str(&'a ExprStringLiteral),
Bytes(&'a ExprBytesLiteral),
}
/// Try to resolve `name` to either a string or bytes literal in `semantic`.
fn resolve_literal<'a>(name: &'a Expr, semantic: &'a SemanticModel) -> Option<Literal<'a>> {
if let Some(str_lit) = resolve_string_literal(name, semantic) {
return Some(Literal::Str(str_lit));
}
if let Some(bytes_lit) = resolve_bytes_literal(name, semantic) {
return Some(Literal::Bytes(bytes_lit));
}
None
}
/// Try to resolve `name` to an [`ExprBytesLiteral`] in `semantic`.
fn resolve_bytes_literal<'a>(
name: &'a Expr,
semantic: &'a SemanticModel,
) -> Option<&'a ExprBytesLiteral> {
if name.is_bytes_literal_expr() {
return name.as_bytes_literal_expr();
}
if let Some(name_expr) = name.as_name_expr() {
let binding = semantic.binding(semantic.only_binding(name_expr)?);
let value = find_binding_value(binding, semantic)?;
if value.is_bytes_literal_expr() {
return value.as_bytes_literal_expr();
}
}
None
}
/// Try to resolve `name` to an [`ExprStringLiteral`] in `semantic`.
fn resolve_string_literal<'a>(
name: &'a Expr,

View File

@@ -0,0 +1,80 @@
---
source: crates/ruff_linter/src/rules/ruff/mod.rs
---
RUF055_3.py:6:1: RUF055 [*] Plain string pattern passed to `re` function
|
5 | # Should be replaced with `b_src.replace(rb"x", b"y")`
6 | re.sub(rb"x", b"y", b_src)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF055
7 |
8 | # Should be replaced with `b_src.startswith(rb"abc")`
|
= help: Replace with `b_src.replace(rb"x", b"y")`
Safe fix
3 3 | b_src = b"abc"
4 4 |
5 5 | # Should be replaced with `b_src.replace(rb"x", b"y")`
6 |-re.sub(rb"x", b"y", b_src)
6 |+b_src.replace(rb"x", b"y")
7 7 |
8 8 | # Should be replaced with `b_src.startswith(rb"abc")`
9 9 | if re.match(rb"abc", b_src):
RUF055_3.py:9:4: RUF055 [*] Plain string pattern passed to `re` function
|
8 | # Should be replaced with `b_src.startswith(rb"abc")`
9 | if re.match(rb"abc", b_src):
| ^^^^^^^^^^^^^^^^^^^^^^^^ RUF055
10 | pass
|
= help: Replace with `b_src.startswith(rb"abc")`
Safe fix
6 6 | re.sub(rb"x", b"y", b_src)
7 7 |
8 8 | # Should be replaced with `b_src.startswith(rb"abc")`
9 |-if re.match(rb"abc", b_src):
9 |+if b_src.startswith(rb"abc"):
10 10 | pass
11 11 |
12 12 | # Should be replaced with `rb"x" in b_src`
RUF055_3.py:13:4: RUF055 [*] Plain string pattern passed to `re` function
|
12 | # Should be replaced with `rb"x" in b_src`
13 | if re.search(rb"x", b_src):
| ^^^^^^^^^^^^^^^^^^^^^^^ RUF055
14 | pass
|
= help: Replace with `rb"x" in b_src`
Safe fix
10 10 | pass
11 11 |
12 12 | # Should be replaced with `rb"x" in b_src`
13 |-if re.search(rb"x", b_src):
13 |+if rb"x" in b_src:
14 14 | pass
15 15 |
16 16 | # Should be replaced with `b_src.split(rb"abc")`
RUF055_3.py:17:1: RUF055 [*] Plain string pattern passed to `re` function
|
16 | # Should be replaced with `b_src.split(rb"abc")`
17 | re.split(rb"abc", b_src)
| ^^^^^^^^^^^^^^^^^^^^^^^^ RUF055
18 |
19 | # Patterns containing metacharacters should NOT be replaced
|
= help: Replace with `b_src.split(rb"abc")`
Safe fix
14 14 | pass
15 15 |
16 16 | # Should be replaced with `b_src.split(rb"abc")`
17 |-re.split(rb"abc", b_src)
17 |+b_src.split(rb"abc")
18 18 |
19 19 | # Patterns containing metacharacters should NOT be replaced
20 20 | re.sub(rb"ab[c]", b"", b_src)

View File

@@ -1527,7 +1527,7 @@ impl<'src> Parser<'src> {
self.bump(kind.start_token());
let elements = self.parse_interpolated_string_elements(
flags,
InterpolatedStringElementsKind::Regular,
InterpolatedStringElementsKind::Regular(kind),
kind,
);

View File

@@ -8,6 +8,7 @@ use ruff_text_size::{Ranged, TextRange, TextSize};
use crate::error::UnsupportedSyntaxError;
use crate::parser::expression::ExpressionContext;
use crate::parser::progress::{ParserProgress, TokenId};
use crate::string::InterpolatedStringKind;
use crate::token::TokenValue;
use crate::token_set::TokenSet;
use crate::token_source::{TokenSource, TokenSourceCheckpoint};
@@ -799,7 +800,7 @@ impl WithItemKind {
}
}
#[derive(Debug, PartialEq, Copy, Clone)]
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
enum InterpolatedStringElementsKind {
/// The regular f-string elements.
///
@@ -807,7 +808,7 @@ enum InterpolatedStringElementsKind {
/// ```py
/// f"hello {x:.2f} world"
/// ```
Regular,
Regular(InterpolatedStringKind),
/// The f-string elements are part of the format specifier.
///
@@ -819,15 +820,13 @@ enum InterpolatedStringElementsKind {
}
impl InterpolatedStringElementsKind {
const fn list_terminators(self) -> TokenSet {
const fn list_terminator(self) -> TokenKind {
match self {
InterpolatedStringElementsKind::Regular => {
TokenSet::new([TokenKind::FStringEnd, TokenKind::TStringEnd])
}
InterpolatedStringElementsKind::Regular(string_kind) => string_kind.end_token(),
// test_ok fstring_format_spec_terminator
// f"hello {x:} world"
// f"hello {x:.3f} world"
InterpolatedStringElementsKind::FormatSpec => TokenSet::new([TokenKind::Rbrace]),
InterpolatedStringElementsKind::FormatSpec => TokenKind::Rbrace,
}
}
}
@@ -1121,7 +1120,7 @@ impl RecoveryContextKind {
.then_some(ListTerminatorKind::Regular),
},
RecoveryContextKind::InterpolatedStringElements(kind) => {
if p.at_ts(kind.list_terminators()) {
if p.at(kind.list_terminator()) {
Some(ListTerminatorKind::Regular)
} else {
// test_err unterminated_fstring_newline_recovery
@@ -1177,13 +1176,23 @@ impl RecoveryContextKind {
) || p.at_name_or_soft_keyword()
}
RecoveryContextKind::WithItems(_) => p.at_expr(),
RecoveryContextKind::InterpolatedStringElements(_) => matches!(
p.current_token_kind(),
// Literal element
TokenKind::FStringMiddle | TokenKind::TStringMiddle
// Expression element
| TokenKind::Lbrace
),
RecoveryContextKind::InterpolatedStringElements(elements_kind) => {
match elements_kind {
InterpolatedStringElementsKind::Regular(interpolated_string_kind) => {
p.current_token_kind() == interpolated_string_kind.middle_token()
|| p.current_token_kind() == TokenKind::Lbrace
}
InterpolatedStringElementsKind::FormatSpec => {
matches!(
p.current_token_kind(),
// Literal element
TokenKind::FStringMiddle | TokenKind::TStringMiddle
// Expression element
| TokenKind::Lbrace
)
}
}
}
}
}
@@ -1272,8 +1281,8 @@ impl RecoveryContextKind {
),
},
RecoveryContextKind::InterpolatedStringElements(kind) => match kind {
InterpolatedStringElementsKind::Regular => ParseErrorType::OtherError(
"Expected an f-string or t-string element or the end of the f-string or t-string".to_string(),
InterpolatedStringElementsKind::Regular(string_kind) => ParseErrorType::OtherError(
format!("Expected an element of or the end of the {string_kind}"),
),
InterpolatedStringElementsKind::FormatSpec => ParseErrorType::OtherError(
"Expected an f-string or t-string element or a '}'".to_string(),
@@ -1316,8 +1325,9 @@ bitflags! {
const WITH_ITEMS_PARENTHESIZED = 1 << 25;
const WITH_ITEMS_PARENTHESIZED_EXPRESSION = 1 << 26;
const WITH_ITEMS_UNPARENTHESIZED = 1 << 28;
const FT_STRING_ELEMENTS = 1 << 29;
const FT_STRING_ELEMENTS_IN_FORMAT_SPEC = 1 << 30;
const F_STRING_ELEMENTS = 1 << 29;
const T_STRING_ELEMENTS = 1 << 30;
const FT_STRING_ELEMENTS_IN_FORMAT_SPEC = 1 << 31;
}
}
@@ -1371,7 +1381,13 @@ impl RecoveryContext {
WithItemKind::Unparenthesized => RecoveryContext::WITH_ITEMS_UNPARENTHESIZED,
},
RecoveryContextKind::InterpolatedStringElements(kind) => match kind {
InterpolatedStringElementsKind::Regular => RecoveryContext::FT_STRING_ELEMENTS,
InterpolatedStringElementsKind::Regular(InterpolatedStringKind::FString) => {
RecoveryContext::F_STRING_ELEMENTS
}
InterpolatedStringElementsKind::Regular(InterpolatedStringKind::TString) => {
RecoveryContext::T_STRING_ELEMENTS
}
InterpolatedStringElementsKind::FormatSpec => {
RecoveryContext::FT_STRING_ELEMENTS_IN_FORMAT_SPEC
}
@@ -1442,8 +1458,11 @@ impl RecoveryContext {
RecoveryContext::WITH_ITEMS_UNPARENTHESIZED => {
RecoveryContextKind::WithItems(WithItemKind::Unparenthesized)
}
RecoveryContext::FT_STRING_ELEMENTS => RecoveryContextKind::InterpolatedStringElements(
InterpolatedStringElementsKind::Regular,
RecoveryContext::F_STRING_ELEMENTS => RecoveryContextKind::InterpolatedStringElements(
InterpolatedStringElementsKind::Regular(InterpolatedStringKind::FString),
),
RecoveryContext::T_STRING_ELEMENTS => RecoveryContextKind::InterpolatedStringElements(
InterpolatedStringElementsKind::Regular(InterpolatedStringKind::TString),
),
RecoveryContext::FT_STRING_ELEMENTS_IN_FORMAT_SPEC => {
RecoveryContextKind::InterpolatedStringElements(

View File

@@ -0,0 +1,10 @@
---
source: crates/ruff_python_parser/src/parser/tests.rs
expression: error
---
ParseError {
error: Lexical(
LineContinuationError,
),
location: 3..4,
}

View File

@@ -0,0 +1,12 @@
---
source: crates/ruff_python_parser/src/parser/tests.rs
expression: error
---
ParseError {
error: Lexical(
TStringError(
SingleRbrace,
),
),
location: 8..9,
}

View File

@@ -134,3 +134,26 @@ foo.bar[0].baz[2].egg??
.unwrap();
insta::assert_debug_snapshot!(parsed.syntax());
}
#[test]
fn test_fstring_expr_inner_line_continuation_and_t_string() {
let source = r#"f'{\t"i}'"#;
let parsed = parse_expression(source);
let error = parsed.unwrap_err();
insta::assert_debug_snapshot!(error);
}
#[test]
fn test_fstring_expr_inner_line_continuation_newline_t_string() {
let source = r#"f'{\
t"i}'"#;
let parsed = parse_expression(source);
let error = parsed.unwrap_err();
insta::assert_debug_snapshot!(error);
}

View File

@@ -41,7 +41,7 @@ impl From<StringType> for Expr {
}
}
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum InterpolatedStringKind {
FString,
TString,

View File

@@ -124,5 +124,5 @@ Module(
|
1 | f"{lambda x: x}"
| ^ Syntax Error: Expected an f-string or t-string element or the end of the f-string or t-string
| ^ Syntax Error: Expected an element of or the end of the f-string
|

View File

@@ -221,7 +221,7 @@ Module(
2 | 'hello'
3 | f'world {x}
4 | )
| ^ Syntax Error: Expected an f-string or t-string element or the end of the f-string or t-string
| ^ Syntax Error: Expected an element of or the end of the f-string
5 | 1 + 1
6 | (
|

View File

@@ -128,5 +128,5 @@ Module(
|
1 | # parse_options: {"target-version": "3.14"}
2 | t"{lambda x: x}"
| ^ Syntax Error: Expected an f-string or t-string element or the end of the f-string or t-string
| ^ Syntax Error: Expected an element of or the end of the t-string
|

View File

@@ -156,7 +156,12 @@ fn run_check(args: CheckCommand) -> anyhow::Result<ExitStatus> {
Ok("short") => write!(stdout, "{}", db.salsa_memory_dump().display_short())?,
Ok("mypy_primer") => write!(stdout, "{}", db.salsa_memory_dump().display_mypy_primer())?,
Ok("full") => write!(stdout, "{}", db.salsa_memory_dump().display_full())?,
_ => {}
Ok(other) => {
tracing::warn!(
"Unknown value for `TY_MEMORY_REPORT`: `{other}`. Valid values are `short`, `mypy_primer`, and `full`."
);
}
Err(_) => {}
}
std::mem::forget(db);

View File

@@ -1618,6 +1618,8 @@ Answer.<CURSOR>
__text_signature__ :: str | None
__type_params__ :: tuple[TypeVar | ParamSpec | TypeVarTuple, ...]
__weakrefoffset__ :: int
_add_alias_ :: def _add_alias_(self, name: str) -> None
_add_value_alias_ :: def _add_value_alias_(self, value: Any) -> None
_generate_next_value_ :: def _generate_next_value_(name: str, start: int, count: int, last_values: list[Any]) -> Any
_ignore_ :: str | list[str]
_member_map_ :: dict[str, Enum]

View File

@@ -198,14 +198,14 @@ mod tests {
assert_snapshot!(test.goto_type_definition(), @r#"
info[goto-type-definition]: Type definition
--> stdlib/builtins.pyi:892:7
--> stdlib/builtins.pyi:890:7
|
890 | def __getitem__(self, key: int, /) -> str | int | None: ...
891 |
892 | class str(Sequence[str]):
888 | def __getitem__(self, key: int, /) -> str | int | None: ...
889 |
890 | class str(Sequence[str]):
| ^^^
893 | """str(object='') -> str
894 | str(bytes_or_buffer[, encoding[, errors]]) -> str
891 | """str(object='') -> str
892 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
info: Source
--> main.py:4:13
@@ -227,14 +227,14 @@ mod tests {
assert_snapshot!(test.goto_type_definition(), @r#"
info[goto-type-definition]: Type definition
--> stdlib/builtins.pyi:892:7
--> stdlib/builtins.pyi:890:7
|
890 | def __getitem__(self, key: int, /) -> str | int | None: ...
891 |
892 | class str(Sequence[str]):
888 | def __getitem__(self, key: int, /) -> str | int | None: ...
889 |
890 | class str(Sequence[str]):
| ^^^
893 | """str(object='') -> str
894 | str(bytes_or_buffer[, encoding[, errors]]) -> str
891 | """str(object='') -> str
892 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
info: Source
--> main.py:2:22
@@ -343,14 +343,14 @@ mod tests {
assert_snapshot!(test.goto_type_definition(), @r#"
info[goto-type-definition]: Type definition
--> stdlib/builtins.pyi:892:7
--> stdlib/builtins.pyi:890:7
|
890 | def __getitem__(self, key: int, /) -> str | int | None: ...
891 |
892 | class str(Sequence[str]):
888 | def __getitem__(self, key: int, /) -> str | int | None: ...
889 |
890 | class str(Sequence[str]):
| ^^^
893 | """str(object='') -> str
894 | str(bytes_or_buffer[, encoding[, errors]]) -> str
891 | """str(object='') -> str
892 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
info: Source
--> main.py:4:18
@@ -378,14 +378,14 @@ mod tests {
// is an int. Navigating to `str` would match pyright's behavior.
assert_snapshot!(test.goto_type_definition(), @r#"
info[goto-type-definition]: Type definition
--> stdlib/builtins.pyi:338:7
--> stdlib/builtins.pyi:337:7
|
336 | _LiteralInteger = _PositiveInteger | _NegativeInteger | Literal[0] # noqa: Y026 # TODO: Use TypeAlias once mypy bugs are fixed
337 |
338 | class int:
335 | _LiteralInteger = _PositiveInteger | _NegativeInteger | Literal[0] # noqa: Y026 # TODO: Use TypeAlias once mypy bugs are fixed
336 |
337 | class int:
| ^^^
339 | """int([x]) -> integer
340 | int(x, base=10) -> integer
338 | """int([x]) -> integer
339 | int(x, base=10) -> integer
|
info: Source
--> main.py:4:18
@@ -412,14 +412,14 @@ f(**kwargs<CURSOR>)
assert_snapshot!(test.goto_type_definition(), @r#"
info[goto-type-definition]: Type definition
--> stdlib/builtins.pyi:2892:7
--> stdlib/builtins.pyi:2888:7
|
2890 | """See PEP 585"""
2891 |
2892 | class dict(MutableMapping[_KT, _VT]):
2886 | """See PEP 585"""
2887 |
2888 | class dict(MutableMapping[_KT, _VT]):
| ^^^^
2893 | """dict() -> new empty dictionary
2894 | dict(mapping) -> new dictionary initialized from a mapping object's
2889 | """dict() -> new empty dictionary
2890 | dict(mapping) -> new dictionary initialized from a mapping object's
|
info: Source
--> main.py:6:5
@@ -443,14 +443,14 @@ f(**kwargs<CURSOR>)
assert_snapshot!(test.goto_type_definition(), @r#"
info[goto-type-definition]: Type definition
--> stdlib/builtins.pyi:892:7
--> stdlib/builtins.pyi:890:7
|
890 | def __getitem__(self, key: int, /) -> str | int | None: ...
891 |
892 | class str(Sequence[str]):
888 | def __getitem__(self, key: int, /) -> str | int | None: ...
889 |
890 | class str(Sequence[str]):
| ^^^
893 | """str(object='') -> str
894 | str(bytes_or_buffer[, encoding[, errors]]) -> str
891 | """str(object='') -> str
892 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
info: Source
--> main.py:3:17
@@ -536,14 +536,14 @@ f(**kwargs<CURSOR>)
assert_snapshot!(test.goto_type_definition(), @r#"
info[goto-type-definition]: Type definition
--> stdlib/builtins.pyi:892:7
--> stdlib/builtins.pyi:890:7
|
890 | def __getitem__(self, key: int, /) -> str | int | None: ...
891 |
892 | class str(Sequence[str]):
888 | def __getitem__(self, key: int, /) -> str | int | None: ...
889 |
890 | class str(Sequence[str]):
| ^^^
893 | """str(object='') -> str
894 | str(bytes_or_buffer[, encoding[, errors]]) -> str
891 | """str(object='') -> str
892 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
info: Source
--> main.py:4:27
@@ -567,13 +567,13 @@ f(**kwargs<CURSOR>)
assert_snapshot!(test.goto_type_definition(), @r#"
info[goto-type-definition]: Type definition
--> stdlib/types.pyi:922:11
--> stdlib/types.pyi:921:11
|
920 | if sys.version_info >= (3, 10):
921 | @final
922 | class NoneType:
919 | if sys.version_info >= (3, 10):
920 | @final
921 | class NoneType:
| ^^^^^^^^
923 | """The type of the None singleton."""
922 | """The type of the None singleton."""
|
info: Source
--> main.py:3:17
@@ -584,14 +584,14 @@ f(**kwargs<CURSOR>)
|
info[goto-type-definition]: Type definition
--> stdlib/builtins.pyi:892:7
--> stdlib/builtins.pyi:890:7
|
890 | def __getitem__(self, key: int, /) -> str | int | None: ...
891 |
892 | class str(Sequence[str]):
888 | def __getitem__(self, key: int, /) -> str | int | None: ...
889 |
890 | class str(Sequence[str]):
| ^^^
893 | """str(object='') -> str
894 | str(bytes_or_buffer[, encoding[, errors]]) -> str
891 | """str(object='') -> str
892 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
info: Source
--> main.py:3:17

View File

@@ -252,6 +252,7 @@ impl Project {
.map(IOErrorDiagnostic::to_diagnostic),
);
let open_files = self.open_files(db);
let check_start = ruff_db::Instant::now();
let file_diagnostics = std::sync::Mutex::new(vec![]);
@@ -269,11 +270,30 @@ impl Project {
tracing::debug_span!(parent: project_span, "check_file", ?file);
let _entered = check_file_span.entered();
let result = check_file_impl(&db, file);
file_diagnostics
.lock()
.unwrap()
.extend(result.iter().map(Clone::clone));
match check_file_impl(&db, file) {
Ok(diagnostics) => {
file_diagnostics
.lock()
.unwrap()
.extend(diagnostics.iter().map(Clone::clone));
// This is outside `check_file_impl` to avoid that opening or closing
// a file invalidates the `check_file_impl` query of every file!
if !open_files.contains(&file) {
// The module has already been parsed by `check_file_impl`.
// We only retrieve it here so that we can call `clear` on it.
let parsed = parsed_module(&db, file);
// Drop the AST now that we are done checking this file. It is not currently open,
// so it is unlikely to be accessed again soon. If any queries need to access the AST
// from across files, it will be re-parsed.
parsed.clear();
}
}
Err(io_error) => {
file_diagnostics.lock().unwrap().push(io_error.clone());
}
}
reporter.report_file(&file);
});
@@ -300,7 +320,10 @@ impl Project {
return Vec::new();
}
check_file_impl(db, file).iter().map(Clone::clone).collect()
match check_file_impl(db, file) {
Ok(diagnostics) => diagnostics.to_vec(),
Err(diagnostic) => vec![diagnostic.clone()],
}
}
/// Opens a file in the project.
@@ -484,22 +507,19 @@ impl Project {
}
}
#[salsa::tracked(returns(deref), heap_size=get_size2::GetSize::get_heap_size)]
pub(crate) fn check_file_impl(db: &dyn Db, file: File) -> Box<[Diagnostic]> {
#[salsa::tracked(returns(ref), heap_size=get_size2::GetSize::get_heap_size)]
pub(crate) fn check_file_impl(db: &dyn Db, file: File) -> Result<Box<[Diagnostic]>, Diagnostic> {
let mut diagnostics: Vec<Diagnostic> = Vec::new();
// Abort checking if there are IO errors.
let source = source_text(db, file);
if let Some(read_error) = source.read_error() {
diagnostics.push(
IOErrorDiagnostic {
file: Some(file),
error: read_error.clone().into(),
}
.to_diagnostic(),
);
return diagnostics.into_boxed_slice();
return Err(IOErrorDiagnostic {
file: Some(file),
error: read_error.clone().into(),
}
.to_diagnostic());
}
let parsed = parsed_module(db, file);
@@ -529,13 +549,6 @@ pub(crate) fn check_file_impl(db: &dyn Db, file: File) -> Box<[Diagnostic]> {
}
}
if !db.project().open_fileset(db).contains(&file) {
// Drop the AST now that we are done checking this file. It is not currently open,
// so it is unlikely to be accessed again soon. If any queries need to access the AST
// from across files, it will be re-parsed.
parsed.clear();
}
diagnostics.sort_unstable_by_key(|diagnostic| {
diagnostic
.primary_span()
@@ -544,7 +557,7 @@ pub(crate) fn check_file_impl(db: &dyn Db, file: File) -> Box<[Diagnostic]> {
.start()
});
diagnostics.into_boxed_slice()
Ok(diagnostics.into_boxed_slice())
}
#[derive(Debug)]
@@ -762,10 +775,11 @@ mod tests {
assert_eq!(source_text(&db, file).as_str(), "");
assert_eq!(
check_file_impl(&db, file)
.iter()
.map(|diagnostic| diagnostic.primary_message().to_string())
.collect::<Vec<_>>(),
vec!["Failed to read file: No such file or directory".to_string()]
.as_ref()
.unwrap_err()
.primary_message()
.to_string(),
"Failed to read file: No such file or directory".to_string()
);
let events = db.take_salsa_events();
@@ -778,6 +792,8 @@ mod tests {
assert_eq!(source_text(&db, file).as_str(), "");
assert_eq!(
check_file_impl(&db, file)
.as_ref()
.unwrap()
.iter()
.map(|diagnostic| diagnostic.primary_message().to_string())
.collect::<Vec<_>>(),

View File

@@ -498,7 +498,7 @@ class C:
reveal_type(C.__init__) # revealed: (self: C, instance_variable_no_default: int, instance_variable: int = Literal[1]) -> None
c = C(1)
# TODO: this should be an error
# error: [invalid-assignment] "Cannot assign to final attribute `instance_variable` on type `C`"
c.instance_variable = 2
```

View File

@@ -72,7 +72,14 @@ reveal_type(my_bool(0)) # revealed: bool
## Truthy values
```toml
[environment]
python-version = "3.11"
```
```py
from typing import Literal
reveal_type(bool(1)) # revealed: Literal[True]
reveal_type(bool((0,))) # revealed: Literal[True]
reveal_type(bool("NON EMPTY")) # revealed: Literal[True]
@@ -81,6 +88,42 @@ reveal_type(bool(True)) # revealed: Literal[True]
def foo(): ...
reveal_type(bool(foo)) # revealed: Literal[True]
class SingleElementTupleSubclass(tuple[int]): ...
reveal_type(bool(SingleElementTupleSubclass((0,)))) # revealed: Literal[True]
reveal_type(SingleElementTupleSubclass.__bool__) # revealed: (self: tuple[int], /) -> Literal[True]
reveal_type(SingleElementTupleSubclass().__bool__) # revealed: () -> Literal[True]
# Unknown length, but we know the length is guaranteed to be >=2
class MixedTupleSubclass(tuple[int, *tuple[str, ...], bytes]): ...
reveal_type(bool(MixedTupleSubclass((1, b"foo")))) # revealed: Literal[True]
reveal_type(MixedTupleSubclass.__bool__) # revealed: (self: tuple[int, *tuple[str, ...], bytes], /) -> Literal[True]
reveal_type(MixedTupleSubclass().__bool__) # revealed: () -> Literal[True]
# Unknown length with an overridden `__bool__`:
class VariadicTupleSubclassWithDunderBoolOverride(tuple[int, ...]):
def __bool__(self) -> Literal[True]:
return True
reveal_type(bool(VariadicTupleSubclassWithDunderBoolOverride((1,)))) # revealed: Literal[True]
reveal_type(VariadicTupleSubclassWithDunderBoolOverride.__bool__) # revealed: def __bool__(self) -> Literal[True]
# revealed: bound method VariadicTupleSubclassWithDunderBoolOverride.__bool__() -> Literal[True]
reveal_type(VariadicTupleSubclassWithDunderBoolOverride().__bool__)
# Same again but for a subclass of a fixed-length tuple:
class EmptyTupleSubclassWithDunderBoolOverride(tuple[()]):
# TODO: we should reject this override as a Liskov violation:
def __bool__(self) -> Literal[True]:
return True
reveal_type(bool(EmptyTupleSubclassWithDunderBoolOverride(()))) # revealed: Literal[True]
reveal_type(EmptyTupleSubclassWithDunderBoolOverride.__bool__) # revealed: def __bool__(self) -> Literal[True]
# revealed: bound method EmptyTupleSubclassWithDunderBoolOverride.__bool__() -> Literal[True]
reveal_type(EmptyTupleSubclassWithDunderBoolOverride().__bool__)
```
## Falsy values
@@ -92,6 +135,12 @@ reveal_type(bool(None)) # revealed: Literal[False]
reveal_type(bool("")) # revealed: Literal[False]
reveal_type(bool(False)) # revealed: Literal[False]
reveal_type(bool()) # revealed: Literal[False]
class EmptyTupleSubclass(tuple[()]): ...
reveal_type(bool(EmptyTupleSubclass())) # revealed: Literal[False]
reveal_type(EmptyTupleSubclass.__bool__) # revealed: (self: tuple[()], /) -> Literal[False]
reveal_type(EmptyTupleSubclass().__bool__) # revealed: () -> Literal[False]
```
## Ambiguous values
@@ -100,6 +149,13 @@ reveal_type(bool()) # revealed: Literal[False]
reveal_type(bool([])) # revealed: bool
reveal_type(bool({})) # revealed: bool
reveal_type(bool(set())) # revealed: bool
class VariadicTupleSubclass(tuple[int, ...]): ...
def f(x: tuple[int, ...], y: VariadicTupleSubclass):
reveal_type(bool(x)) # revealed: bool
reveal_type(x.__bool__) # revealed: () -> bool
reveal_type(y.__bool__) # revealed: () -> bool
```
## `__bool__` returning `NoReturn`

View File

@@ -65,6 +65,51 @@ reveal_type(len((*[], 1, 2))) # revealed: Literal[3]
reveal_type(len((*[], *{}))) # revealed: Literal[2]
```
Tuple subclasses:
```py
class EmptyTupleSubclass(tuple[()]): ...
class Length1TupleSubclass(tuple[int]): ...
class Length2TupleSubclass(tuple[int, str]): ...
class UnknownLengthTupleSubclass(tuple[int, ...]): ...
reveal_type(len(EmptyTupleSubclass())) # revealed: Literal[0]
reveal_type(len(Length1TupleSubclass((1,)))) # revealed: Literal[1]
reveal_type(len(Length2TupleSubclass((1, "foo")))) # revealed: Literal[2]
reveal_type(len(UnknownLengthTupleSubclass((1, 2, 3)))) # revealed: int
reveal_type(tuple[int, int].__len__) # revealed: (self: tuple[int, int], /) -> Literal[2]
reveal_type(tuple[int, ...].__len__) # revealed: (self: tuple[int, ...], /) -> int
def f(x: tuple[int, int], y: tuple[int, ...]):
reveal_type(x.__len__) # revealed: () -> Literal[2]
reveal_type(y.__len__) # revealed: () -> int
reveal_type(EmptyTupleSubclass.__len__) # revealed: (self: tuple[()], /) -> Literal[0]
reveal_type(EmptyTupleSubclass().__len__) # revealed: () -> Literal[0]
reveal_type(UnknownLengthTupleSubclass.__len__) # revealed: (self: tuple[int, ...], /) -> int
reveal_type(UnknownLengthTupleSubclass().__len__) # revealed: () -> int
```
If `__len__` is overridden, we use the overridden return type:
```py
from typing import Literal
class UnknownLengthSubclassWithDunderLenOverridden(tuple[int, ...]):
def __len__(self) -> Literal[42]:
return 42
reveal_type(len(UnknownLengthSubclassWithDunderLenOverridden())) # revealed: Literal[42]
class FixedLengthSubclassWithDunderLenOverridden(tuple[int]):
# TODO: we should complain about this as a Liskov violation (incompatible override)
def __len__(self) -> Literal[42]:
return 42
reveal_type(len(FixedLengthSubclassWithDunderLenOverridden((1,)))) # revealed: Literal[42]
```
### Lists, sets and dictionaries
```py

View File

@@ -29,16 +29,16 @@ error[invalid-argument-type]: Argument to function `loads` is incorrect
| ^ Expected `str | bytes | bytearray`, found `Literal[5]`
|
info: Function defined here
--> stdlib/json/__init__.pyi:219:5
--> stdlib/json/__init__.pyi:218:5
|
217 | """
218 |
219 | def loads(
216 | """
217 |
218 | def loads(
| ^^^^^
220 | s: str | bytes | bytearray,
219 | s: str | bytes | bytearray,
| -------------------------- Parameter declared here
221 | *,
222 | cls: type[JSONDecoder] | None = None,
220 | *,
221 | cls: type[JSONDecoder] | None = None,
|
info: rule `invalid-argument-type` is enabled by default

View File

@@ -551,6 +551,11 @@ static_assert(is_subtype_of(Never, AlwaysFalsy))
### `AlwaysTruthy` and `AlwaysFalsy`
```toml
[environment]
python-version = "3.11"
```
```py
from ty_extensions import AlwaysTruthy, AlwaysFalsy, Intersection, Not, is_subtype_of, static_assert
from typing_extensions import Literal, LiteralString
@@ -588,6 +593,30 @@ static_assert(is_subtype_of(Intersection[LiteralString, Not[Literal["", "a"]]],
static_assert(is_subtype_of(Intersection[LiteralString, Not[Literal[""]]], Not[AlwaysFalsy]))
# error: [static-assert-error]
static_assert(is_subtype_of(Intersection[LiteralString, Not[Literal["", "a"]]], Not[AlwaysFalsy]))
class Length2TupleSubclass(tuple[int, str]): ...
static_assert(is_subtype_of(Length2TupleSubclass, AlwaysTruthy))
class EmptyTupleSubclass(tuple[()]): ...
static_assert(is_subtype_of(EmptyTupleSubclass, AlwaysFalsy))
class TupleSubclassWithAtLeastLength2(tuple[int, *tuple[str, ...], bytes]): ...
static_assert(is_subtype_of(TupleSubclassWithAtLeastLength2, AlwaysTruthy))
class UnknownLength(tuple[int, ...]): ...
static_assert(not is_subtype_of(UnknownLength, AlwaysTruthy))
static_assert(not is_subtype_of(UnknownLength, AlwaysFalsy))
class Invalid(tuple[int, str]):
# TODO: we should emit an error here (Liskov violation)
def __bool__(self) -> Literal[False]:
return False
static_assert(is_subtype_of(Invalid, AlwaysFalsy))
```
### `TypeGuard` and `TypeIs`

View File

@@ -170,26 +170,37 @@ Assignments to attributes qualified with `Final` are also not allowed:
```py
from typing import Final
class C:
FINAL_A: Final[int] = 1
FINAL_B: Final = 1
class Meta(type):
META_FINAL_A: Final[int] = 1
META_FINAL_B: Final = 1
class C(metaclass=Meta):
CLASS_FINAL_A: Final[int] = 1
CLASS_FINAL_B: Final = 1
def __init__(self):
self.FINAL_C: Final[int] = 1
self.FINAL_D: Final = 1
self.INSTANCE_FINAL_A: Final[int] = 1
self.INSTANCE_FINAL_B: Final = 1
# TODO: these should be errors (that mention `Final`)
C.FINAL_A = 2
# error: [invalid-assignment] "Object of type `Literal[2]` is not assignable to attribute `FINAL_B` of type `Literal[1]`"
C.FINAL_B = 2
# error: [invalid-assignment] "Cannot assign to final attribute `META_FINAL_A` on type `<class 'C'>`"
C.META_FINAL_A = 2
# error: [invalid-assignment] "Cannot assign to final attribute `META_FINAL_B` on type `<class 'C'>`"
C.META_FINAL_B = 2
# error: [invalid-assignment] "Cannot assign to final attribute `CLASS_FINAL_A` on type `<class 'C'>`"
C.CLASS_FINAL_A = 2
# error: [invalid-assignment] "Cannot assign to final attribute `CLASS_FINAL_B` on type `<class 'C'>`"
C.CLASS_FINAL_B = 2
# TODO: these should be errors (that mention `Final`)
c = C()
c.FINAL_A = 2
# error: [invalid-assignment] "Object of type `Literal[2]` is not assignable to attribute `FINAL_B` of type `Literal[1]`"
c.FINAL_B = 2
c.FINAL_C = 2
c.FINAL_D = 2
# error: [invalid-assignment] "Cannot assign to final attribute `CLASS_FINAL_A` on type `C`"
c.CLASS_FINAL_A = 2
# error: [invalid-assignment] "Cannot assign to final attribute `CLASS_FINAL_B` on type `C`"
c.CLASS_FINAL_B = 2
# TODO: this should be an error
c.INSTANCE_FINAL_A = 2
# TODO: this should be an error
c.INSTANCE_FINAL_B = 2
```
## Mutability

View File

@@ -56,7 +56,7 @@ use crate::types::infer::infer_unpack_types;
use crate::types::mro::{Mro, MroError, MroIterator};
pub(crate) use crate::types::narrow::infer_narrowing_constraint;
use crate::types::signatures::{Parameter, ParameterForm, Parameters, walk_signature};
use crate::types::tuple::{TupleSpec, TupleType};
use crate::types::tuple::TupleType;
pub use crate::util::diagnostics::add_inferred_python_version_hint_to_diagnostic;
use crate::{Db, FxOrderSet, Module, Program};
pub(crate) use class::{ClassLiteral, ClassType, GenericAlias, KnownClass};
@@ -3508,14 +3508,7 @@ impl<'db> Type<'db> {
Type::BooleanLiteral(bool) => Truthiness::from(*bool),
Type::StringLiteral(str) => Truthiness::from(!str.value(db).is_empty()),
Type::BytesLiteral(bytes) => Truthiness::from(!bytes.value(db).is_empty()),
Type::Tuple(tuple) => match tuple.tuple(db).len().size_hint() {
// The tuple type is AlwaysFalse if it contains only the empty tuple
(_, Some(0)) => Truthiness::AlwaysFalse,
// The tuple type is AlwaysTrue if its inhabitants must always have length >=1
(minimum, _) if minimum > 0 => Truthiness::AlwaysTrue,
// The tuple type is Ambiguous if its inhabitants could be of any length
_ => Truthiness::Ambiguous,
},
Type::Tuple(tuple) => tuple.truthiness(db),
};
Ok(truthiness)
@@ -3542,10 +3535,12 @@ impl<'db> Type<'db> {
let usize_len = match self {
Type::BytesLiteral(bytes) => Some(bytes.python_len(db)),
Type::StringLiteral(string) => Some(string.python_len(db)),
Type::Tuple(tuple) => match tuple.tuple(db) {
TupleSpec::Fixed(tuple) => Some(tuple.len()),
TupleSpec::Variable(_) => None,
},
// N.B. This is strictly-speaking redundant, since the `__len__` method on tuples
// is special-cased in `ClassType::own_class_member`. However, it's probably more
// efficient to short-circuit here and check against the tuple spec directly,
// rather than going through the `__len__` method.
Type::Tuple(tuple) => tuple.tuple(db).len().into_fixed_length(),
_ => None,
};

View File

@@ -574,9 +574,39 @@ impl<'db> ClassType<'db> {
/// traverse through the MRO until it finds the member.
pub(super) fn own_class_member(self, db: &'db dyn Db, name: &str) -> PlaceAndQualifiers<'db> {
let (class_literal, specialization) = self.class_literal(db);
class_literal
.own_class_member(db, specialization, name)
.map_type(|ty| ty.apply_optional_specialization(db, specialization))
let synthesize_tuple_method = |return_type| {
let parameters =
Parameters::new([Parameter::positional_only(Some(Name::new_static("self")))
.with_annotated_type(Type::instance(db, self))]);
let synthesized_dunder_method =
CallableType::function_like(db, Signature::new(parameters, Some(return_type)));
Place::bound(synthesized_dunder_method).into()
};
match name {
"__len__" if class_literal.is_known(db, KnownClass::Tuple) => {
let return_type = specialization
.and_then(|spec| spec.tuple(db).len().into_fixed_length())
.and_then(|len| i64::try_from(len).ok())
.map(Type::IntLiteral)
.unwrap_or_else(|| KnownClass::Int.to_instance(db));
synthesize_tuple_method(return_type)
}
"__bool__" if class_literal.is_known(db, KnownClass::Tuple) => {
let return_type = specialization
.map(|spec| spec.tuple(db).truthiness().into_type(db))
.unwrap_or_else(|| KnownClass::Bool.to_instance(db));
synthesize_tuple_method(return_type)
}
_ => class_literal
.own_class_member(db, specialization, name)
.map_type(|ty| ty.apply_optional_specialization(db, specialization)),
}
}
/// Look up an instance attribute (available in `__dict__`) of the given name.

View File

@@ -3363,6 +3363,24 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
assignable
};
// Return true (and emit a diagnostic) if this is an invalid assignment to a `Final` attribute.
let invalid_assignment_to_final = |qualifiers: TypeQualifiers| -> bool {
if qualifiers.contains(TypeQualifiers::FINAL) {
if emit_diagnostics {
if let Some(builder) = self.context.report_lint(&INVALID_ASSIGNMENT, target) {
builder.into_diagnostic(format_args!(
"Cannot assign to final attribute `{attribute}` \
on type `{}`",
object_ty.display(db)
));
}
}
true
} else {
false
}
};
match object_ty {
Type::Union(union) => {
if union.elements(self.db()).iter().all(|elem| {
@@ -3558,8 +3576,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
}
PlaceAndQualifiers {
place: Place::Type(meta_attr_ty, meta_attr_boundness),
qualifiers: _,
qualifiers,
} => {
if invalid_assignment_to_final(qualifiers) {
return false;
}
let assignable_to_meta_attr =
if let Place::Type(meta_dunder_set, _) =
meta_attr_ty.class_member(db, "__set__".into()).place
@@ -3669,8 +3691,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
match object_ty.class_member(db, attribute.into()) {
PlaceAndQualifiers {
place: Place::Type(meta_attr_ty, meta_attr_boundness),
qualifiers: _,
qualifiers,
} => {
if invalid_assignment_to_final(qualifiers) {
return false;
}
let assignable_to_meta_attr = if let Place::Type(meta_dunder_set, _) =
meta_attr_ty.class_member(db, "__set__".into()).place
{
@@ -3733,11 +3759,17 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
place: Place::Unbound,
..
} => {
if let Place::Type(class_attr_ty, class_attr_boundness) = object_ty
if let PlaceAndQualifiers {
place: Place::Type(class_attr_ty, class_attr_boundness),
qualifiers,
} = object_ty
.find_name_in_mro(db, attribute)
.expect("called on Type::ClassLiteral or Type::SubclassOf")
.place
{
if invalid_assignment_to_final(qualifiers) {
return false;
}
if class_attr_boundness == Boundness::PossiblyUnbound {
report_possibly_unbound_attribute(
&self.context,
@@ -4860,15 +4892,24 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
}
fn infer_maybe_standalone_expression(&mut self, expression: &ast::Expr) -> Type<'db> {
if self.index.is_standalone_expression(expression) {
self.infer_standalone_expression(expression)
if let Some(standalone_expression) = self.index.try_expression(expression) {
self.infer_standalone_expression_impl(expression, standalone_expression)
} else {
self.infer_expression(expression)
}
}
#[track_caller]
fn infer_standalone_expression(&mut self, expression: &ast::Expr) -> Type<'db> {
let standalone_expression = self.index.expression(expression);
self.infer_standalone_expression_impl(expression, standalone_expression)
}
fn infer_standalone_expression_impl(
&mut self,
expression: &ast::Expr,
standalone_expression: Expression<'db>,
) -> Type<'db> {
let types = infer_expression_types(self.db(), standalone_expression);
self.extend(types);

View File

@@ -22,6 +22,7 @@ use std::hash::Hash;
use itertools::{Either, EitherOrBoth, Itertools};
use crate::types::Truthiness;
use crate::types::class::{ClassType, KnownClass};
use crate::types::{
Type, TypeMapping, TypeRelation, TypeTransformer, TypeVarInstance, TypeVarVariance,
@@ -76,6 +77,13 @@ impl TupleLength {
None => "unlimited".to_string(),
}
}
pub(crate) fn into_fixed_length(self) -> Option<usize> {
match self {
TupleLength::Fixed(len) => Some(len),
TupleLength::Variable(_, _) => None,
}
}
}
/// # Ordering
@@ -240,6 +248,10 @@ impl<'db> TupleType<'db> {
pub(crate) fn is_single_valued(self, db: &'db dyn Db) -> bool {
self.tuple(db).is_single_valued(db)
}
pub(crate) fn truthiness(self, db: &'db dyn Db) -> Truthiness {
self.tuple(db).truthiness()
}
}
/// A tuple spec describes the contents of a tuple type, which might be fixed- or variable-length.
@@ -967,6 +979,17 @@ impl<T> Tuple<T> {
}
}
pub(crate) fn truthiness(&self) -> Truthiness {
match self.len().size_hint() {
// The tuple type is AlwaysFalse if it contains only the empty tuple
(_, Some(0)) => Truthiness::AlwaysFalse,
// The tuple type is AlwaysTrue if its inhabitants must always have length >=1
(minimum, _) if minimum > 0 => Truthiness::AlwaysTrue,
// The tuple type is Ambiguous if its inhabitants could be of any length
_ => Truthiness::Ambiguous,
}
}
pub(crate) fn is_empty(&self) -> bool {
match self {
Tuple::Fixed(tuple) => tuple.is_empty(),

View File

@@ -1 +1 @@
84e41f2853d7af3d651d620f093031cba849bd1d
08225953c98cfd375d80bc88865e5aae77d2c07f

View File

@@ -1,5 +1,4 @@
"""
Record of phased-in incompatible language changes.
"""Record of phased-in incompatible language changes.
Each line is of the form:

View File

@@ -1,6 +1,4 @@
"""
Accelerator module for asyncio
"""
"""Accelerator module for asyncio"""
import sys
from asyncio.events import AbstractEventLoop

View File

@@ -1,5 +1,4 @@
"""
Bisection algorithms.
"""Bisection algorithms.
This module provides support for maintaining a list in sorted order without
having to sort the list after each insertion. For long lists of items with

View File

@@ -1,6 +1,4 @@
"""
_blake2b provides BLAKE2b for hashlib
"""
"""_blake2b provides BLAKE2b for hashlib"""
from _typeshed import ReadableBuffer
from typing import ClassVar, final

View File

@@ -1 +1,7 @@
"""A minimal subset of the locale module used at interpreter startup
(imported by the _io module), in order to reduce startup time.
Don't import directly from third-party code; use the `locale` module instead!
"""
def getpreferredencoding(do_setlocale: bool = True) -> str: ...

View File

@@ -1,5 +1,4 @@
"""
Abstract Base Classes (ABCs) for collections, according to PEP 3119.
"""Abstract Base Classes (ABCs) for collections, according to PEP 3119.
Unit tests are in test_collections.
"""

View File

@@ -1,6 +1,4 @@
"""
Internal classes used by the gzip, lzma and bz2 modules
"""
"""Internal classes used by the gzip, lzma and bz2 modules"""
# _compression is replaced by compression._common._streams on Python 3.14+ (PEP-784)

View File

@@ -1,6 +1,4 @@
"""
Context Variables
"""
"""Context Variables"""
import sys
from collections.abc import Callable, Iterator, Mapping

View File

@@ -1,6 +1,4 @@
"""
CSV parsing and writing.
"""
"""CSV parsing and writing."""
import csv
import sys

View File

@@ -1,6 +1,4 @@
"""
Create and manipulate C compatible data types in Python.
"""
"""Create and manipulate C compatible data types in Python."""
import _typeshed
import sys

View File

@@ -1,6 +1,4 @@
"""
C decimal arithmetic module
"""
"""C decimal arithmetic module"""
import sys
from decimal import (

View File

@@ -1,10 +1,10 @@
"""
Core implementation of import.
"""Core implementation of import.
This module is NOT meant to be directly imported! It has been designed such
that it can be bootstrapped into Python as the implementation of import. As
such it requires the injection of specific modules and attributes in order to
work. One should use importlib as the public-facing version of this module.
"""
import importlib.abc
@@ -114,7 +114,13 @@ class BuiltinImporter(importlib.abc.MetaPathFinder, importlib.abc.InspectLoader)
# MetaPathFinder
if sys.version_info < (3, 12):
@classmethod
def find_module(cls, fullname: str, path: Sequence[str] | None = None) -> importlib.abc.Loader | None: ...
def find_module(cls, fullname: str, path: Sequence[str] | None = None) -> importlib.abc.Loader | None:
"""Find the built-in module.
If 'path' is ever specified then the search is considered a failure.
This method is deprecated. Use find_spec() instead.
"""
@classmethod
def find_spec(
@@ -142,7 +148,11 @@ class BuiltinImporter(importlib.abc.MetaPathFinder, importlib.abc.InspectLoader)
# Loader
if sys.version_info < (3, 12):
@staticmethod
def module_repr(module: types.ModuleType) -> str: ...
def module_repr(module: types.ModuleType) -> str:
"""Return repr for the module.
The method is deprecated. The import machinery does the job itself.
"""
if sys.version_info >= (3, 10):
@staticmethod
def create_module(spec: ModuleSpec) -> types.ModuleType | None:
@@ -170,7 +180,11 @@ class FrozenImporter(importlib.abc.MetaPathFinder, importlib.abc.InspectLoader):
# MetaPathFinder
if sys.version_info < (3, 12):
@classmethod
def find_module(cls, fullname: str, path: Sequence[str] | None = None) -> importlib.abc.Loader | None: ...
def find_module(cls, fullname: str, path: Sequence[str] | None = None) -> importlib.abc.Loader | None:
"""Find a frozen module.
This method is deprecated. Use find_spec() instead.
"""
@classmethod
def find_spec(
@@ -198,7 +212,11 @@ class FrozenImporter(importlib.abc.MetaPathFinder, importlib.abc.InspectLoader):
# Loader
if sys.version_info < (3, 12):
@staticmethod
def module_repr(m: types.ModuleType) -> str: ...
def module_repr(m: types.ModuleType) -> str:
"""Return repr for the module.
The method is deprecated. The import machinery does the job itself.
"""
if sys.version_info >= (3, 10):
@staticmethod
def create_module(spec: ModuleSpec) -> types.ModuleType | None:
@@ -206,7 +224,7 @@ class FrozenImporter(importlib.abc.MetaPathFinder, importlib.abc.InspectLoader):
else:
@classmethod
def create_module(cls, spec: ModuleSpec) -> types.ModuleType | None:
"""Set __file__, if able."""
"""Use default semantics for module creation."""
@staticmethod
def exec_module(module: types.ModuleType) -> None: ...

View File

@@ -1,10 +1,10 @@
"""
Core implementation of path-based import.
"""Core implementation of path-based import.
This module is NOT meant to be directly imported! It has been designed such
that it can be bootstrapped into Python as the implementation of import. As
such it requires the injection of specific modules and attributes in order to
work. One should use importlib as the public-facing version of this module.
"""
import _ast
@@ -94,7 +94,11 @@ class WindowsRegistryFinder(importlib.abc.MetaPathFinder):
if sys.version_info < (3, 12):
@classmethod
def find_module(cls, fullname: str, path: Sequence[str] | None = None) -> importlib.abc.Loader | None: ...
def find_module(cls, fullname: str, path: Sequence[str] | None = None) -> importlib.abc.Loader | None:
"""Find module named in the registry.
This method is deprecated. Use find_spec() instead.
"""
@classmethod
def find_spec(
@@ -114,7 +118,7 @@ class PathFinder(importlib.abc.MetaPathFinder):
@classmethod
def invalidate_caches(cls) -> None:
"""Call the invalidate_caches() method on all path entry finders
stored in sys.path_importer_cache (where implemented).
stored in sys.path_importer_caches (where implemented).
"""
if sys.version_info >= (3, 10):
@staticmethod
@@ -147,7 +151,12 @@ class PathFinder(importlib.abc.MetaPathFinder):
"""
if sys.version_info < (3, 12):
@classmethod
def find_module(cls, fullname: str, path: Sequence[str] | None = None) -> importlib.abc.Loader | None: ...
def find_module(cls, fullname: str, path: Sequence[str] | None = None) -> importlib.abc.Loader | None:
"""find the module on sys.path or 'path' based on sys.path_hooks and
sys.path_importer_cache.
This method is deprecated. Use find_spec() instead.
"""
SOURCE_SUFFIXES: list[str]
DEBUG_BYTECODE_SUFFIXES: list[str]

View File

@@ -1,5 +1,4 @@
"""
This module provides an interface to the GNU DBM (GDBM) library.
"""This module provides an interface to the GNU DBM (GDBM) library.
This module is quite similar to the dbm module, but uses GDBM instead to
provide some additional functionality. Please note that the file formats

View File

@@ -1,6 +1,4 @@
"""
OpenSSL interface for hashlib module
"""
"""OpenSSL interface for hashlib module"""
import sys
from _typeshed import ReadableBuffer

View File

@@ -1,5 +1,4 @@
"""
Heap queue algorithm (a.k.a. priority queue).
"""Heap queue algorithm (a.k.a. priority queue).
Heaps are arrays for which a[k] <= a[2*k+1] and a[k] <= a[2*k+2] for
all k, counting elements from 0. For the sake of comparison,
@@ -30,13 +29,12 @@ maintains the heap invariant!
"""
import sys
from typing import Any, Final, TypeVar
_T = TypeVar("_T") # list items must be comparable
from _typeshed import SupportsRichComparisonT as _T # All type variable use in this module requires comparability.
from typing import Final
__about__: Final[str]
def heapify(heap: list[Any], /) -> None: # list items must be comparable
def heapify(heap: list[_T], /) -> None:
"""Transform list into a heap, in-place, in O(len(heap)) time."""
def heappop(heap: list[_T], /) -> _T:
@@ -65,7 +63,7 @@ def heapreplace(heap: list[_T], item: _T, /) -> _T:
"""
if sys.version_info >= (3, 14):
def heapify_max(heap: list[Any], /) -> None: # list items must be comparable
def heapify_max(heap: list[_T], /) -> None:
"""Maxheap variant of heapify."""
def heappop_max(heap: list[_T], /) -> _T:

View File

@@ -1,6 +1,4 @@
"""
(Extremely) low-level import machinery bits as used by importlib.
"""
"""(Extremely) low-level import machinery bits as used by importlib."""
import sys
import types

View File

@@ -1,5 +1,4 @@
"""
This module provides primitive operations to manage Python interpreters.
"""This module provides primitive operations to manage Python interpreters.
The 'interpreters' module provides a more convenient interface.
"""

View File

@@ -1,5 +1,4 @@
"""
This module provides primitive operations to manage Python interpreters.
"""This module provides primitive operations to manage Python interpreters.
The 'interpreters' module provides a more convenient interface.
"""

View File

@@ -1,5 +1,4 @@
"""
This module provides primitive operations to manage Python interpreters.
"""This module provides primitive operations to manage Python interpreters.
The 'interpreters' module provides a more convenient interface.
"""

View File

@@ -1,5 +1,4 @@
"""
The io module provides the Python interfaces to stream handling. The
"""The io module provides the Python interfaces to stream handling. The
builtin open function is defined in this module.
At the top of the I/O hierarchy is the abstract base class IOBase. It

View File

@@ -1,6 +1,4 @@
"""
json speedups
"""
"""json speedups"""
from collections.abc import Callable
from typing import Any, final

View File

@@ -1,6 +1,4 @@
"""
Support for POSIX locales.
"""
"""Support for POSIX locales."""
import sys
from _typeshed import StrPath

View File

@@ -1,6 +1,4 @@
"""
Fast profiler
"""
"""Fast profiler"""
import sys
from _typeshed import structseq

View File

@@ -1,8 +1,8 @@
"""
Shared support for scanning document type declarations in HTML and XHTML.
"""Shared support for scanning document type declarations in HTML and XHTML.
This module is used as a foundation for the html.parser module. It has no
documented public API and should not be used directly.
"""
import sys

View File

@@ -1,6 +1,4 @@
"""
Documentation
"""
"""Documentation"""
import sys

View File

@@ -1,5 +1,4 @@
"""
Operator interface.
"""Operator interface.
This module exports a set of functions implemented in C corresponding
to the intrinsic operators of Python. For example, operator.add(x, y)

View File

@@ -1,6 +1,4 @@
"""
Shared OS X support functions.
"""
"""Shared OS X support functions."""
from collections.abc import Iterable, Sequence
from typing import Final, TypeVar

View File

@@ -1,6 +1,4 @@
"""
Optimized C implementation for the Python pickle module.
"""
"""Optimized C implementation for the Python pickle module."""
from _typeshed import ReadableBuffer, SupportsWrite
from collections.abc import Callable, Iterable, Iterator, Mapping

View File

@@ -1,6 +1,4 @@
"""
A POSIX helper for the subprocess module.
"""
"""A POSIX helper for the subprocess module."""
import sys
from _typeshed import StrOrBytesPath

View File

@@ -1,6 +1,4 @@
"""
Python decimal arithmetic module
"""
"""Python decimal arithmetic module"""
# This is a slight lie, the implementations aren't exactly identical
# However, in all likelihood, the differences are inconsequential

View File

@@ -1,5 +1,4 @@
"""
C implementation of the Python queue module.
"""C implementation of the Python queue module.
This module is an implementation detail, please do not use it directly.
"""

View File

@@ -1,6 +1,4 @@
"""
Module implements the Mersenne Twister random number generator.
"""
"""Module implements the Mersenne Twister random number generator."""
from typing_extensions import TypeAlias

View File

@@ -1,5 +1,4 @@
"""
The objects used by the site module to add custom builtins.
"""

View File

@@ -1,5 +1,4 @@
"""
Implementation module for socket operations.
"""Implementation module for socket operations.
See the socket module for documentation.
"""
@@ -890,7 +889,14 @@ class socket:
operations are disabled.
"""
if sys.platform == "win32":
def ioctl(self, control: int, option: int | tuple[int, int, int] | bool, /) -> None: ...
def ioctl(self, control: int, option: int | tuple[int, int, int] | bool, /) -> None:
"""ioctl(cmd, option) -> long
Control the socket with WSAIoctl syscall. Currently supported 'cmd' values are
SIO_RCVALL: 'option' must be one of the socket.RCVALL_* constants.
SIO_KEEPALIVE_VALS: 'option' is a tuple of (onoff, timeout, interval).
SIO_LOOPBACK_FAST_PATH: 'option' is a boolean value, and is disabled by default
"""
def listen(self, backlog: int = ..., /) -> None:
"""listen([backlog])
@@ -1091,7 +1097,14 @@ class socket:
@overload
def setsockopt(self, level: int, optname: int, value: None, optlen: int, /) -> None: ...
if sys.platform == "win32":
def share(self, process_id: int, /) -> bytes: ...
def share(self, process_id: int, /) -> bytes:
"""share(process_id) -> bytes
Share the socket with another process. The target process id
must be provided and the resulting bytes object passed to the target
process. There the shared socket can be instantiated by calling
socket.fromshare().
"""
def shutdown(self, how: int, /) -> None:
"""shutdown(flag)

View File

@@ -1,5 +1,4 @@
"""
Implementation module for SSL socket operations. See the socket module
"""Implementation module for SSL socket operations. See the socket module
for documentation.
"""

View File

@@ -1,5 +1,4 @@
"""
S_IFMT_: file type bits
"""S_IFMT_: file type bits
S_IFDIR: directory
S_IFCHR: character device
S_IFBLK: block device

View File

@@ -1,5 +1,4 @@
"""
Functions to convert between Python values and C structs.
"""Functions to convert between Python values and C structs.
Python bytes objects are used to hold the data representing the C struct
and also as format strings (explained below) to describe the layout of data
in the C struct.

View File

@@ -1,5 +1,4 @@
"""
This module provides primitive operations to write multi-threaded programs.
"""This module provides primitive operations to write multi-threaded programs.
The 'threading' module provides a more convenient interface.
"""

View File

@@ -1,5 +1,4 @@
"""
Thread-local objects.
"""Thread-local objects.
(Note that this module provides a Python version of the threading.local
class. Depending on the version of Python you're using, there may be a

View File

@@ -1,6 +1,4 @@
"""
Debug module to trace memory blocks allocated by Python.
"""
"""Debug module to trace memory blocks allocated by Python."""
from collections.abc import Sequence
from tracemalloc import _FrameTuple, _TraceTuple

View File

@@ -82,19 +82,21 @@ class SupportsNext(Protocol[_T_co]):
class SupportsAnext(Protocol[_T_co]):
def __anext__(self) -> Awaitable[_T_co]: ...
# Comparison protocols
class SupportsBool(Protocol):
def __bool__(self) -> bool: ...
# Comparison protocols
class SupportsDunderLT(Protocol[_T_contra]):
def __lt__(self, other: _T_contra, /) -> bool: ...
def __lt__(self, other: _T_contra, /) -> SupportsBool: ...
class SupportsDunderGT(Protocol[_T_contra]):
def __gt__(self, other: _T_contra, /) -> bool: ...
def __gt__(self, other: _T_contra, /) -> SupportsBool: ...
class SupportsDunderLE(Protocol[_T_contra]):
def __le__(self, other: _T_contra, /) -> bool: ...
def __le__(self, other: _T_contra, /) -> SupportsBool: ...
class SupportsDunderGE(Protocol[_T_contra]):
def __ge__(self, other: _T_contra, /) -> bool: ...
def __ge__(self, other: _T_contra, /) -> SupportsBool: ...
class SupportsAllComparisons(
SupportsDunderLT[Any], SupportsDunderGT[Any], SupportsDunderLE[Any], SupportsDunderGE[Any], Protocol

View File

@@ -1,5 +1,4 @@
"""
_warnings provides basic warning filtering support.
"""_warnings provides basic warning filtering support.
It is a helper module to speed up interpreter start-up.
"""

View File

@@ -1,6 +1,4 @@
"""
Weak-reference support module.
"""
"""Weak-reference support module."""
from collections.abc import Callable
from typing import Any, TypeVar, overload

View File

@@ -1,6 +1,4 @@
"""
Implementation module for Zstandard compression.
"""
"""Implementation module for Zstandard compression."""
from _typeshed import ReadableBuffer
from collections.abc import Mapping

View File

@@ -1,6 +1,4 @@
"""
Abstract Base Classes (ABCs) according to PEP 3119.
"""
"""Abstract Base Classes (ABCs) according to PEP 3119."""
import _typeshed
import sys

View File

@@ -1,5 +1,4 @@
"""
Stuff to parse AIFF-C and AIFF files.
"""Stuff to parse AIFF-C and AIFF files.
Unless explicitly stated otherwise, the description below is true
both for AIFF-C files and AIFF files.

View File

@@ -1,6 +1,4 @@
"""
Helpers for introspecting and wrapping annotations.
"""
"""Helpers for introspecting and wrapping annotations."""
import sys
from typing import Literal

View File

@@ -1,5 +1,4 @@
"""
Command-line parsing library
"""Command-line parsing library
This module is an optparse-inspired command-line parsing library that:
@@ -728,16 +727,40 @@ else:
class _ArgumentGroup(_ActionsContainer):
title: str | None
_group_actions: list[Action]
def __init__(
self,
container: _ActionsContainer,
title: str | None = None,
description: str | None = None,
*,
prefix_chars: str = ...,
argument_default: Any = ...,
conflict_handler: str = ...,
) -> None: ...
if sys.version_info >= (3, 14):
@overload
def __init__(
self,
container: _ActionsContainer,
title: str | None = None,
description: str | None = None,
*,
argument_default: Any = ...,
conflict_handler: str = ...,
) -> None: ...
@overload
@deprecated("Undocumented `prefix_chars` parameter is deprecated since Python 3.14.")
def __init__(
self,
container: _ActionsContainer,
title: str | None = None,
description: str | None = None,
*,
prefix_chars: str,
argument_default: Any = ...,
conflict_handler: str = ...,
) -> None: ...
else:
def __init__(
self,
container: _ActionsContainer,
title: str | None = None,
description: str | None = None,
*,
prefix_chars: str = ...,
argument_default: Any = ...,
conflict_handler: str = ...,
) -> None: ...
# undocumented
class _MutuallyExclusiveGroup(_ArgumentGroup):
@@ -971,9 +994,9 @@ class _SubParsersAction(Action, Generic[_ArgumentParserT]):
fromfile_prefix_chars: str | None = ...,
argument_default: Any = ...,
conflict_handler: str = ...,
add_help: bool = ...,
allow_abbrev: bool = ...,
exit_on_error: bool = ...,
add_help: bool = True,
allow_abbrev: bool = True,
exit_on_error: bool = True,
suggest_on_error: bool = False,
color: bool = False,
**kwargs: Any, # Accepting any additional kwargs for custom parser classes
@@ -997,9 +1020,9 @@ class _SubParsersAction(Action, Generic[_ArgumentParserT]):
fromfile_prefix_chars: str | None = ...,
argument_default: Any = ...,
conflict_handler: str = ...,
add_help: bool = ...,
allow_abbrev: bool = ...,
exit_on_error: bool = ...,
add_help: bool = True,
allow_abbrev: bool = True,
exit_on_error: bool = True,
**kwargs: Any, # Accepting any additional kwargs for custom parser classes
) -> _ArgumentParserT: ...
else:
@@ -1020,9 +1043,9 @@ class _SubParsersAction(Action, Generic[_ArgumentParserT]):
fromfile_prefix_chars: str | None = ...,
argument_default: Any = ...,
conflict_handler: str = ...,
add_help: bool = ...,
allow_abbrev: bool = ...,
exit_on_error: bool = ...,
add_help: bool = True,
allow_abbrev: bool = True,
exit_on_error: bool = True,
**kwargs: Any, # Accepting any additional kwargs for custom parser classes
) -> _ArgumentParserT: ...

View File

@@ -1,5 +1,4 @@
"""
This module defines an object type which can efficiently represent
"""This module defines an object type which can efficiently represent
an array of basic values: characters, integers, floating-point
numbers. Arrays are sequence types and behave very much like lists,
except that the type of objects stored in them is constrained.
@@ -151,10 +150,7 @@ class array(MutableSequence[_T]):
"""
else:
def index(self, v: _T, /) -> int: # type: ignore[override]
"""Return index of first occurrence of v in the array.
Raise ValueError if the value is not present.
"""
"""Return index of first occurrence of v in the array."""
def insert(self, i: int, v: _T, /) -> None:
"""Insert a new item v into the array before position i."""

View File

@@ -1,5 +1,4 @@
"""
The `ast` module helps Python applications to process trees of the Python
abstract syntax grammar. The abstract syntax itself might change with
each Python release; this module helps to find out programmatically what
@@ -1333,12 +1332,16 @@ class Constant(expr):
# Aliases for value, for backwards compatibility
@deprecated("Will be removed in Python 3.14; use value instead")
@property
def n(self) -> _ConstantValue: ...
def n(self) -> _ConstantValue:
"""Deprecated. Use value instead."""
@n.setter
def n(self, value: _ConstantValue) -> None: ...
@deprecated("Will be removed in Python 3.14; use value instead")
@property
def s(self) -> _ConstantValue: ...
def s(self) -> _ConstantValue:
"""Deprecated. Use value instead."""
@s.setter
def s(self, value: _ConstantValue) -> None: ...

View File

@@ -1,5 +1,4 @@
"""
A class supporting chat-style (command/response) protocols.
"""A class supporting chat-style (command/response) protocols.
This class adds support for 'chat' style protocols - where one side
sends a 'command', and the other sends a response (examples would be

View File

@@ -1,6 +1,4 @@
"""
The asyncio package, tracking PEP 3156.
"""
"""The asyncio package, tracking PEP 3156."""
# This condition is so big, it's clearer to keep to platform condition in two blocks
# Can't NOQA on a specific line: https://github.com/plinss/flake8-noqa/issues/22

View File

@@ -1,5 +1,4 @@
"""
Base implementation of event loop.
"""Base implementation of event loop.
The event loop can be broken up into a multiplexer (the part
responsible for notifying us of I/O events) and the event loop proper,
@@ -191,7 +190,7 @@ class BaseEventLoop(AbstractEventLoop):
"""
else:
def create_task(self, coro: _CoroutineLike[_T], *, name: object = None) -> Task[_T]:
"""Schedule or begin executing a coroutine object.
"""Schedule a coroutine object.
Return a task object.
"""
@@ -571,15 +570,7 @@ class BaseEventLoop(AbstractEventLoop):
ssl: _SSLContext = None,
ssl_handshake_timeout: float | None = None,
ssl_shutdown_timeout: float | None = None,
) -> tuple[Transport, _ProtocolT]:
"""Handle an accepted connection.
This is used by servers that accept connections outside of
asyncio but that use asyncio to handle connections.
This method is a coroutine. When completed, the coroutine
returns a (transport, protocol) pair.
"""
) -> tuple[Transport, _ProtocolT]: ...
else:
async def start_tls(
self,
@@ -800,11 +791,6 @@ class BaseEventLoop(AbstractEventLoop):
"""
else:
async def shutdown_default_executor(self) -> None:
"""Schedule the shutdown of the default executor.
The timeout parameter specifies the amount of time the executor will
be given to finish joining. The default value is None, which means
that the executor will be given an unlimited amount of time.
"""
"""Schedule the shutdown of the default executor."""
def __del__(self) -> None: ...

View File

@@ -1,6 +1,4 @@
"""
Event loop and event loop policy.
"""
"""Event loop and event loop policy."""
import ssl
import sys
@@ -493,9 +491,6 @@ class AbstractEventLoop:
they all set this flag when being created. This option is not
supported on Windows.
keep_alive set to True keeps connections active by enabling the
periodic transmission of messages.
ssl_handshake_timeout is the time in seconds that an SSL server
will wait for completion of the SSL handshake before aborting the
connection. Default is 60s.
@@ -583,17 +578,10 @@ class AbstractEventLoop:
they all set this flag when being created. This option is not
supported on Windows.
keep_alive set to True keeps connections active by enabling the
periodic transmission of messages.
ssl_handshake_timeout is the time in seconds that an SSL server
will wait for completion of the SSL handshake before aborting the
connection. Default is 60s.
ssl_shutdown_timeout is the time in seconds that an SSL server
will wait for completion of the SSL shutdown procedure
before aborting the connection. Default is 30s.
start_serving set to True (default) causes the created server
to start accepting connections immediately. When set to False,
the user should await Server.start_serving() or Server.serve_forever()
@@ -727,9 +715,6 @@ class AbstractEventLoop:
ssl_handshake_timeout is the time in seconds that an SSL server
will wait for the SSL handshake to complete (defaults to 60s).
ssl_shutdown_timeout is the time in seconds that an SSL server
will wait for the SSL shutdown to finish (defaults to 30s).
start_serving set to True (default) causes the created server
to start accepting connections immediately. When set to False,
the user should await Server.start_serving() or Server.serve_forever()

View File

@@ -1,6 +1,4 @@
"""
asyncio exceptions.
"""
"""asyncio exceptions."""
import sys

View File

@@ -1,6 +1,4 @@
"""
A Future class similar to the one in PEP 3148.
"""
"""A Future class similar to the one in PEP 3148."""
import sys
from _asyncio import Future as Future

View File

@@ -1,6 +1,4 @@
"""
Introspection utils for tasks call graphs.
"""
"""Introspection utils for tasks call graphs."""
from _typeshed import SupportsWrite
from asyncio import Future

View File

@@ -1,6 +1,4 @@
"""
Synchronization primitives.
"""
"""Synchronization primitives."""
import enum
import sys

View File

@@ -1,6 +1,4 @@
"""
Logging configuration.
"""
"""Logging configuration."""
import logging

View File

@@ -1,6 +1,4 @@
"""
Event loop mixins.
"""
"""Event loop mixins."""
import sys
import threading

View File

@@ -1,5 +1,4 @@
"""
Event loop using a proactor and related classes.
"""Event loop using a proactor and related classes.
A proactor is a "notify-on-completion" multiplexer. Currently a
proactor is only implemented on Windows with IOCP.

View File

@@ -1,6 +1,4 @@
"""
Abstract Protocol base classes.
"""
"""Abstract Protocol base classes."""
from _typeshed import ReadableBuffer
from asyncio import transports

View File

@@ -1,4 +1,5 @@
import sys
from _typeshed import SupportsRichComparisonT
from asyncio.events import AbstractEventLoop
from types import GenericAlias
from typing import Any, Generic, TypeVar
@@ -146,7 +147,7 @@ class Queue(Generic[_T], _LoopBoundMixin): # noqa: Y059
the queue, which may unblock callers of join().
"""
class PriorityQueue(Queue[_T]):
class PriorityQueue(Queue[SupportsRichComparisonT]):
"""A subclass of Queue; retrieves entries in priority order (lowest first).
Entries are typically tuples of the form: (priority number, data).

Some files were not shown because too many files have changed in this diff Show More