Compare commits
20 Commits
micha/remo
...
brent/cach
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cabdd969ec | ||
|
|
2e5c8b9799 | ||
|
|
e1219bc27c | ||
|
|
926e83323a | ||
|
|
5cace28c3e | ||
|
|
3785e13231 | ||
|
|
c2380fa0e2 | ||
|
|
4dec44ae49 | ||
|
|
b6579eaf04 | ||
|
|
f063c0e874 | ||
|
|
6a65734ee3 | ||
|
|
00066e094c | ||
|
|
37a1958374 | ||
|
|
2535d791ae | ||
|
|
05c4399e7b | ||
|
|
b18434b0f6 | ||
|
|
17779c9a17 | ||
|
|
53fc0614da | ||
|
|
59249f483b | ||
|
|
84e76f4d04 |
22
.github/workflows/ci.yaml
vendored
22
.github/workflows/ci.yaml
vendored
@@ -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
|
||||
|
||||
|
||||
2
.github/workflows/daily_fuzz.yaml
vendored
2
.github/workflows/daily_fuzz.yaml
vendored
@@ -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,
|
||||
|
||||
28
.github/workflows/sync_typeshed.yaml
vendored
28
.github/workflows/sync_typeshed.yaml
vendored
@@ -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:
|
||||
|
||||
@@ -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
21
Cargo.lock
generated
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}')
|
||||
|
||||
24
crates/ruff_linter/resources/test/fixtures/ruff/RUF055_3.py
vendored
Normal file
24
crates/ruff_linter/resources/test/fixtures/ruff/RUF055_3.py
vendored
Normal 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)
|
||||
@@ -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"))]
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
source: crates/ruff_python_parser/src/parser/tests.rs
|
||||
expression: error
|
||||
---
|
||||
ParseError {
|
||||
error: Lexical(
|
||||
LineContinuationError,
|
||||
),
|
||||
location: 3..4,
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
source: crates/ruff_python_parser/src/parser/tests.rs
|
||||
expression: error
|
||||
---
|
||||
ParseError {
|
||||
error: Lexical(
|
||||
TStringError(
|
||||
SingleRbrace,
|
||||
),
|
||||
),
|
||||
location: 8..9,
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
|
||||
|
||||
@@ -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 | (
|
||||
|
|
||||
|
||||
@@ -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
|
||||
|
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<_>>(),
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -1 +1 @@
|
||||
84e41f2853d7af3d651d620f093031cba849bd1d
|
||||
08225953c98cfd375d80bc88865e5aae77d2c07f
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
"""
|
||||
Record of phased-in incompatible language changes.
|
||||
"""Record of phased-in incompatible language changes.
|
||||
|
||||
Each line is of the form:
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"""
|
||||
Accelerator module for asyncio
|
||||
"""
|
||||
"""Accelerator module for asyncio"""
|
||||
|
||||
import sys
|
||||
from asyncio.events import AbstractEventLoop
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"""
|
||||
_blake2b provides BLAKE2b for hashlib
|
||||
"""
|
||||
"""_blake2b provides BLAKE2b for hashlib"""
|
||||
|
||||
from _typeshed import ReadableBuffer
|
||||
from typing import ClassVar, final
|
||||
|
||||
@@ -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: ...
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"""
|
||||
Context Variables
|
||||
"""
|
||||
"""Context Variables"""
|
||||
|
||||
import sys
|
||||
from collections.abc import Callable, Iterator, Mapping
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"""
|
||||
CSV parsing and writing.
|
||||
"""
|
||||
"""CSV parsing and writing."""
|
||||
|
||||
import csv
|
||||
import sys
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"""
|
||||
C decimal arithmetic module
|
||||
"""
|
||||
"""C decimal arithmetic module"""
|
||||
|
||||
import sys
|
||||
from decimal import (
|
||||
|
||||
@@ -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: ...
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"""
|
||||
OpenSSL interface for hashlib module
|
||||
"""
|
||||
"""OpenSSL interface for hashlib module"""
|
||||
|
||||
import sys
|
||||
from _typeshed import ReadableBuffer
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"""
|
||||
json speedups
|
||||
"""
|
||||
"""json speedups"""
|
||||
|
||||
from collections.abc import Callable
|
||||
from typing import Any, final
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"""
|
||||
Support for POSIX locales.
|
||||
"""
|
||||
"""Support for POSIX locales."""
|
||||
|
||||
import sys
|
||||
from _typeshed import StrPath
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"""
|
||||
Fast profiler
|
||||
"""
|
||||
"""Fast profiler"""
|
||||
|
||||
import sys
|
||||
from _typeshed import structseq
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"""
|
||||
Documentation
|
||||
"""
|
||||
"""Documentation"""
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"""
|
||||
A POSIX helper for the subprocess module.
|
||||
"""
|
||||
"""A POSIX helper for the subprocess module."""
|
||||
|
||||
import sys
|
||||
from _typeshed import StrOrBytesPath
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
"""
|
||||
|
||||
The objects used by the site module to add custom builtins.
|
||||
"""
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"""
|
||||
Weak-reference support module.
|
||||
"""
|
||||
"""Weak-reference support module."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from typing import Any, TypeVar, overload
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"""
|
||||
Implementation module for Zstandard compression.
|
||||
"""
|
||||
"""Implementation module for Zstandard compression."""
|
||||
|
||||
from _typeshed import ReadableBuffer
|
||||
from collections.abc import Mapping
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"""
|
||||
Abstract Base Classes (ABCs) according to PEP 3119.
|
||||
"""
|
||||
"""Abstract Base Classes (ABCs) according to PEP 3119."""
|
||||
|
||||
import _typeshed
|
||||
import sys
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"""
|
||||
Helpers for introspecting and wrapping annotations.
|
||||
"""
|
||||
"""Helpers for introspecting and wrapping annotations."""
|
||||
|
||||
import sys
|
||||
from typing import Literal
|
||||
|
||||
@@ -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: ...
|
||||
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -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: ...
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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: ...
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"""
|
||||
asyncio exceptions.
|
||||
"""
|
||||
"""asyncio exceptions."""
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"""
|
||||
Introspection utils for tasks call graphs.
|
||||
"""
|
||||
"""Introspection utils for tasks call graphs."""
|
||||
|
||||
from _typeshed import SupportsWrite
|
||||
from asyncio import Future
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"""
|
||||
Synchronization primitives.
|
||||
"""
|
||||
"""Synchronization primitives."""
|
||||
|
||||
import enum
|
||||
import sys
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"""
|
||||
Logging configuration.
|
||||
"""
|
||||
"""Logging configuration."""
|
||||
|
||||
import logging
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"""
|
||||
Event loop mixins.
|
||||
"""
|
||||
"""Event loop mixins."""
|
||||
|
||||
import sys
|
||||
import threading
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"""
|
||||
Abstract Protocol base classes.
|
||||
"""
|
||||
"""Abstract Protocol base classes."""
|
||||
|
||||
from _typeshed import ReadableBuffer
|
||||
from asyncio import transports
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user