Compare commits

..

1 Commits

Author SHA1 Message Date
Jack O'Connor
136dc15352 [ty] implement typing.NewType 2025-08-27 23:58:46 -07:00
52 changed files with 1123 additions and 853 deletions

View File

@@ -1,32 +1,5 @@
# Changelog
## 0.12.11
### Preview features
- \[`airflow`\] Extend `AIR311` and `AIR312` rules ([#20082](https://github.com/astral-sh/ruff/pull/20082))
- \[`airflow`\] Replace wrong path `airflow.io.storage` with `airflow.io.store` (`AIR311`) ([#20081](https://github.com/astral-sh/ruff/pull/20081))
- \[`flake8-async`\] Implement `blocking-http-call-httpx-in-async-function` (`ASYNC212`) ([#20091](https://github.com/astral-sh/ruff/pull/20091))
- \[`flake8-logging-format`\] Add auto-fix for f-string logging calls (`G004`) ([#19303](https://github.com/astral-sh/ruff/pull/19303))
- \[`flake8-use-pathlib`\] Add autofix for `PTH211` ([#20009](https://github.com/astral-sh/ruff/pull/20009))
- \[`flake8-use-pathlib`\] Make `PTH100` fix unsafe because it can change behavior ([#20100](https://github.com/astral-sh/ruff/pull/20100))
### Bug fixes
- \[`pyflakes`, `pylint`\] Fix false positives caused by `__class__` cell handling (`F841`, `PLE0117`) ([#20048](https://github.com/astral-sh/ruff/pull/20048))
- \[`pyflakes`\] Fix `allowed-unused-imports` matching for top-level modules (`F401`) ([#20115](https://github.com/astral-sh/ruff/pull/20115))
- \[`ruff`\] Fix false positive for t-strings in `default-factory-kwarg` (`RUF026`) ([#20032](https://github.com/astral-sh/ruff/pull/20032))
- \[`ruff`\] Preserve relative whitespace in multi-line expressions (`RUF033`) ([#19647](https://github.com/astral-sh/ruff/pull/19647))
### Rule changes
- \[`ruff`\] Handle empty t-strings in `unnecessary-empty-iterable-within-deque-call` (`RUF037`) ([#20045](https://github.com/astral-sh/ruff/pull/20045))
### Documentation
- Fix incorrect `D413` links in docstrings convention FAQ ([#20089](https://github.com/astral-sh/ruff/pull/20089))
- \[`flake8-use-pathlib`\] Update links to the table showing the correspondence between `os` and `pathlib` ([#20103](https://github.com/astral-sh/ruff/pull/20103))
## 0.12.10
### Preview features

8
Cargo.lock generated
View File

@@ -2743,7 +2743,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.12.11"
version = "0.12.10"
dependencies = [
"anyhow",
"argfile",
@@ -2870,7 +2870,6 @@ dependencies = [
"insta",
"matchit",
"path-slash",
"pathdiff",
"quick-junit",
"ruff_annotate_snippets",
"ruff_cache",
@@ -2999,7 +2998,7 @@ dependencies = [
[[package]]
name = "ruff_linter"
version = "0.12.11"
version = "0.12.10"
dependencies = [
"aho-corasick",
"anyhow",
@@ -3021,6 +3020,7 @@ dependencies = [
"memchr",
"natord",
"path-absolutize",
"pathdiff",
"pep440_rs",
"pyproject-toml",
"regex",
@@ -3337,7 +3337,7 @@ dependencies = [
[[package]]
name = "ruff_wasm"
version = "0.12.11"
version = "0.12.10"
dependencies = [
"console_error_panic_hook",
"console_log",

View File

@@ -148,8 +148,8 @@ curl -LsSf https://astral.sh/ruff/install.sh | sh
powershell -c "irm https://astral.sh/ruff/install.ps1 | iex"
# For a specific version.
curl -LsSf https://astral.sh/ruff/0.12.11/install.sh | sh
powershell -c "irm https://astral.sh/ruff/0.12.11/install.ps1 | iex"
curl -LsSf https://astral.sh/ruff/0.12.10/install.sh | sh
powershell -c "irm https://astral.sh/ruff/0.12.10/install.ps1 | iex"
```
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
@@ -182,7 +182,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.12.11
rev: v0.12.10
hooks:
# Run the linter.
- id: ruff-check

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.12.11"
version = "0.12.10"
publish = true
authors = { workspace = true }
edition = { workspace = true }

View File

@@ -15,7 +15,8 @@ use ruff_db::diagnostic::{
use ruff_linter::fs::relativize_path;
use ruff_linter::logging::LogLevel;
use ruff_linter::message::{
Emitter, EmitterContext, GithubEmitter, GroupedEmitter, SarifEmitter, TextEmitter,
Emitter, EmitterContext, GithubEmitter, GitlabEmitter, GroupedEmitter, SarifEmitter,
TextEmitter,
};
use ruff_linter::notify_user;
use ruff_linter::settings::flags::{self};
@@ -295,11 +296,7 @@ impl Printer {
GithubEmitter.emit(writer, &diagnostics.inner, &context)?;
}
OutputFormat::Gitlab => {
let config = DisplayDiagnosticConfig::default()
.format(DiagnosticFormat::Gitlab)
.preview(preview);
let value = DisplayDiagnostics::new(&context, &config, &diagnostics.inner);
write!(writer, "{value}")?;
GitlabEmitter::default().emit(writer, &diagnostics.inner, &context)?;
}
OutputFormat::Pylint => {
let config = DisplayDiagnosticConfig::default()

View File

@@ -20,59 +20,59 @@ exit_code: 1
{
"check_name": "F401",
"description": "F401: `os` imported but unused",
"severity": "major",
"fingerprint": "4dbad37161e65c72",
"location": {
"path": "input.py",
"positions": {
"begin": {
"line": 1,
"column": 8
"column": 8,
"line": 1
},
"end": {
"line": 1,
"column": 10
"column": 10,
"line": 1
}
}
}
},
"severity": "major"
},
{
"check_name": "F821",
"description": "F821: Undefined name `y`",
"severity": "major",
"fingerprint": "7af59862a085230",
"location": {
"path": "input.py",
"positions": {
"begin": {
"line": 2,
"column": 5
"column": 5,
"line": 2
},
"end": {
"line": 2,
"column": 6
"column": 6,
"line": 2
}
}
}
},
"severity": "major"
},
{
"check_name": "invalid-syntax",
"description": "invalid-syntax: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)",
"severity": "major",
"fingerprint": "e558cec859bb66e8",
"location": {
"path": "input.py",
"positions": {
"begin": {
"line": 3,
"column": 1
"column": 1,
"line": 3
},
"end": {
"line": 3,
"column": 6
"column": 6,
"line": 3
}
}
}
},
"severity": "major"
}
]
----- stderr -----

View File

@@ -450,6 +450,9 @@ fn benchmark_complex_constrained_attributes_2(criterion: &mut Criterion) {
r#"
class C:
def f(self: "C"):
self.a = ""
self.b = ""
if isinstance(self.a, str):
return
@@ -463,56 +466,6 @@ fn benchmark_complex_constrained_attributes_2(criterion: &mut Criterion) {
return
if isinstance(self.b, str):
return
if isinstance(self.b, str):
return
if isinstance(self.b, str):
return
self.a = ""
self.b = ""
"#,
)
},
|case| {
let Case { db, .. } = case;
let result = db.check();
assert_eq!(result.len(), 0);
},
BatchSize::SmallInput,
);
});
}
fn benchmark_complex_constrained_attributes_3(criterion: &mut Criterion) {
setup_rayon();
criterion.bench_function("ty_micro[complex_constrained_attributes_3]", |b| {
b.iter_batched_ref(
|| {
// This is a regression test for https://github.com/astral-sh/ty/issues/758
setup_micro_case(
r#"
class GridOut:
def __init__(self: "GridOut") -> None:
self._buffer = b""
def _read_size_or_line(self: "GridOut", size: int = -1):
if size > self._position:
size = self._position
pass
if size == 0:
return bytes()
while size > 0:
if self._buffer:
buf = self._buffer
self._buffer = b""
else:
buf = b""
if len(buf) > size:
self._buffer = buf
self._position -= len(self._buffer)
"#,
)
},
@@ -715,7 +668,6 @@ criterion_group!(
benchmark_tuple_implicit_instance_attributes,
benchmark_complex_constrained_attributes_1,
benchmark_complex_constrained_attributes_2,
benchmark_complex_constrained_attributes_3,
benchmark_many_enum_members,
);
criterion_group!(project, anyio, attrs, hydra, datetype);

View File

@@ -34,7 +34,6 @@ glob = { workspace = true }
ignore = { workspace = true, optional = true }
matchit = { workspace = true }
path-slash = { workspace = true }
pathdiff = { workspace = true }
quick-junit = { workspace = true, optional = true }
rustc-hash = { workspace = true }
salsa = { workspace = true }
@@ -54,7 +53,7 @@ web-time = { version = "1.1.0" }
etcetera = { workspace = true, optional = true }
[dev-dependencies]
insta = { workspace = true, features = ["filters"] }
insta = { workspace = true }
tempfile = { workspace = true }
[features]

View File

@@ -1435,11 +1435,6 @@ pub enum DiagnosticFormat {
/// Print diagnostics in the format expected by JUnit.
#[cfg(feature = "junit")]
Junit,
/// Print diagnostics in the JSON format used by GitLab [Code Quality] reports.
///
/// [Code Quality]: https://docs.gitlab.com/ee/ci/testing/code_quality.html#implement-a-custom-tool
#[cfg(feature = "serde")]
Gitlab,
}
/// A representation of the kinds of messages inside a diagnostic.

View File

@@ -31,8 +31,6 @@ mod azure;
mod concise;
mod full;
#[cfg(feature = "serde")]
mod gitlab;
#[cfg(feature = "serde")]
mod json;
#[cfg(feature = "serde")]
mod json_lines;
@@ -138,10 +136,6 @@ impl std::fmt::Display for DisplayDiagnostics<'_> {
DiagnosticFormat::Junit => {
junit::JunitRenderer::new(self.resolver).render(f, self.diagnostics)?;
}
#[cfg(feature = "serde")]
DiagnosticFormat::Gitlab => {
gitlab::GitlabRenderer::new(self.resolver).render(f, self.diagnostics)?;
}
}
Ok(())

View File

@@ -1,205 +0,0 @@
use std::{
collections::HashSet,
hash::{DefaultHasher, Hash, Hasher},
path::Path,
};
use ruff_source_file::LineColumn;
use serde::{Serialize, Serializer, ser::SerializeSeq};
use crate::diagnostic::{Diagnostic, Severity};
use super::FileResolver;
pub(super) struct GitlabRenderer<'a> {
resolver: &'a dyn FileResolver,
}
impl<'a> GitlabRenderer<'a> {
pub(super) fn new(resolver: &'a dyn FileResolver) -> Self {
Self { resolver }
}
}
impl GitlabRenderer<'_> {
pub(super) fn render(
&self,
f: &mut std::fmt::Formatter,
diagnostics: &[Diagnostic],
) -> std::fmt::Result {
write!(
f,
"{}",
serde_json::to_string_pretty(&SerializedMessages {
diagnostics,
resolver: self.resolver,
#[expect(
clippy::disallowed_methods,
reason = "We don't have access to a `System` here, \
and this is only intended for use by GitLab CI, \
which runs on a real `System`."
)]
project_dir: std::env::var("CI_PROJECT_DIR").ok().as_deref(),
})
.unwrap()
)
}
}
struct SerializedMessages<'a> {
diagnostics: &'a [Diagnostic],
resolver: &'a dyn FileResolver,
project_dir: Option<&'a str>,
}
impl Serialize for SerializedMessages<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut s = serializer.serialize_seq(Some(self.diagnostics.len()))?;
let mut fingerprints = HashSet::<u64>::with_capacity(self.diagnostics.len());
for diagnostic in self.diagnostics {
let location = diagnostic
.primary_span()
.map(|span| {
let file = span.file();
let positions = if self.resolver.is_notebook(file) {
// We can't give a reasonable location for the structured formats,
// so we show one that's clearly a fallback
Default::default()
} else {
let diagnostic_source = file.diagnostic_source(self.resolver);
let source_code = diagnostic_source.as_source_code();
span.range()
.map(|range| Positions {
begin: source_code.line_column(range.start()),
end: source_code.line_column(range.end()),
})
.unwrap_or_default()
};
let path = self.project_dir.as_ref().map_or_else(
|| file.relative_path(self.resolver).display().to_string(),
|project_dir| relativize_path_to(file.path(self.resolver), project_dir),
);
Location { path, positions }
})
.unwrap_or_default();
let mut message_fingerprint = fingerprint(diagnostic, &location.path, 0);
// Make sure that we do not get a fingerprint that is already in use
// by adding in the previously generated one.
while fingerprints.contains(&message_fingerprint) {
message_fingerprint = fingerprint(diagnostic, &location.path, message_fingerprint);
}
fingerprints.insert(message_fingerprint);
let description = diagnostic.body();
let check_name = diagnostic.secondary_code_or_id();
let severity = match diagnostic.severity() {
Severity::Info => "info",
Severity::Warning => "minor",
Severity::Error => "major",
// Another option here is `blocker`
Severity::Fatal => "critical",
};
let value = Message {
check_name,
// GitLab doesn't display the separate `check_name` field in a Code Quality report,
// so prepend it to the description too.
description: format!("{check_name}: {description}"),
severity,
fingerprint: format!("{:x}", message_fingerprint),
location,
};
s.serialize_element(&value)?;
}
s.end()
}
}
#[derive(Serialize)]
struct Message<'a> {
check_name: &'a str,
description: String,
severity: &'static str,
fingerprint: String,
location: Location,
}
/// The place in the source code where the issue was discovered.
///
/// According to the CodeClimate report format [specification] linked from the GitLab [docs], this
/// field is required, so we fall back on a default `path` and position if the diagnostic doesn't
/// have a primary span.
///
/// [specification]: https://github.com/codeclimate/platform/blob/master/spec/analyzers/SPEC.md#data-types
/// [docs]: https://docs.gitlab.com/ci/testing/code_quality/#code-quality-report-format
#[derive(Default, Serialize)]
struct Location {
path: String,
positions: Positions,
}
#[derive(Default, Serialize)]
struct Positions {
begin: LineColumn,
end: LineColumn,
}
/// Generate a unique fingerprint to identify a violation.
fn fingerprint(diagnostic: &Diagnostic, project_path: &str, salt: u64) -> u64 {
let mut hasher = DefaultHasher::new();
salt.hash(&mut hasher);
diagnostic.name().hash(&mut hasher);
project_path.hash(&mut hasher);
hasher.finish()
}
/// Convert an absolute path to be relative to the specified project root.
fn relativize_path_to<P: AsRef<Path>, R: AsRef<Path>>(path: P, project_root: R) -> String {
format!(
"{}",
pathdiff::diff_paths(&path, project_root)
.expect("Could not diff paths")
.display()
)
}
#[cfg(test)]
mod tests {
use crate::diagnostic::{
DiagnosticFormat,
render::tests::{create_diagnostics, create_syntax_error_diagnostics},
};
const FINGERPRINT_FILTERS: [(&str, &str); 1] = [(
r#""fingerprint": "[a-z0-9]+","#,
r#""fingerprint": "<redacted>","#,
)];
#[test]
fn output() {
let (env, diagnostics) = create_diagnostics(DiagnosticFormat::Gitlab);
insta::with_settings!({filters => FINGERPRINT_FILTERS}, {
insta::assert_snapshot!(env.render_diagnostics(&diagnostics));
});
}
#[test]
fn syntax_errors() {
let (env, diagnostics) = create_syntax_error_diagnostics(DiagnosticFormat::Gitlab);
insta::with_settings!({filters => FINGERPRINT_FILTERS}, {
insta::assert_snapshot!(env.render_diagnostics(&diagnostics));
});
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_linter"
version = "0.12.11"
version = "0.12.10"
publish = false
authors = { workspace = true }
edition = { workspace = true }
@@ -51,6 +51,7 @@ path-absolutize = { workspace = true, features = [
"once_cell_cache",
"use_unix_paths_on_wasm",
] }
pathdiff = { workspace = true }
pep440_rs = { workspace = true }
pyproject-toml = { workspace = true }
regex = { workspace = true }

View File

@@ -1,16 +0,0 @@
"""
Test: allowed-unused-imports-top-level-module
"""
# No errors
def f():
import hvplot
def f():
import hvplot.pandas
def f():
import hvplot.pandas.plots
def f():
from hvplot.pandas import scatter_matrix
def f():
from hvplot.pandas.plots import scatter_matrix

View File

@@ -58,3 +58,13 @@ pub fn relativize_path<P: AsRef<Path>>(path: P) -> String {
}
format!("{}", path.display())
}
/// Convert an absolute path to be relative to the specified project root.
pub fn relativize_path_to<P: AsRef<Path>, R: AsRef<Path>>(path: P, project_root: R) -> String {
format!(
"{}",
pathdiff::diff_paths(&path, project_root)
.expect("Could not diff paths")
.display()
)
}

View File

@@ -0,0 +1,174 @@
use std::collections::HashSet;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::io::Write;
use serde::ser::SerializeSeq;
use serde::{Serialize, Serializer};
use serde_json::json;
use ruff_db::diagnostic::Diagnostic;
use crate::fs::{relativize_path, relativize_path_to};
use crate::message::{Emitter, EmitterContext};
/// Generate JSON with violations in GitLab CI format
// https://docs.gitlab.com/ee/ci/testing/code_quality.html#implement-a-custom-tool
pub struct GitlabEmitter {
project_dir: Option<String>,
}
impl Default for GitlabEmitter {
fn default() -> Self {
Self {
project_dir: std::env::var("CI_PROJECT_DIR").ok(),
}
}
}
impl Emitter for GitlabEmitter {
fn emit(
&mut self,
writer: &mut dyn Write,
diagnostics: &[Diagnostic],
context: &EmitterContext,
) -> anyhow::Result<()> {
serde_json::to_writer_pretty(
writer,
&SerializedMessages {
diagnostics,
context,
project_dir: self.project_dir.as_deref(),
},
)?;
Ok(())
}
}
struct SerializedMessages<'a> {
diagnostics: &'a [Diagnostic],
context: &'a EmitterContext<'a>,
project_dir: Option<&'a str>,
}
impl Serialize for SerializedMessages<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut s = serializer.serialize_seq(Some(self.diagnostics.len()))?;
let mut fingerprints = HashSet::<u64>::with_capacity(self.diagnostics.len());
for diagnostic in self.diagnostics {
let filename = diagnostic.expect_ruff_filename();
let (start_location, end_location) = if self.context.is_notebook(&filename) {
// We can't give a reasonable location for the structured formats,
// so we show one that's clearly a fallback
Default::default()
} else {
(
diagnostic.expect_ruff_start_location(),
diagnostic.expect_ruff_end_location(),
)
};
let path = self.project_dir.as_ref().map_or_else(
|| relativize_path(&filename),
|project_dir| relativize_path_to(&filename, project_dir),
);
let mut message_fingerprint = fingerprint(diagnostic, &path, 0);
// Make sure that we do not get a fingerprint that is already in use
// by adding in the previously generated one.
while fingerprints.contains(&message_fingerprint) {
message_fingerprint = fingerprint(diagnostic, &path, message_fingerprint);
}
fingerprints.insert(message_fingerprint);
let description = diagnostic.body();
let check_name = diagnostic.secondary_code_or_id();
let value = json!({
"check_name": check_name,
// GitLab doesn't display the separate `check_name` field in a Code Quality report,
// so prepend it to the description too.
"description": format!("{check_name}: {description}"),
"severity": "major",
"fingerprint": format!("{:x}", message_fingerprint),
"location": {
"path": path,
"positions": {
"begin": start_location,
"end": end_location,
},
},
});
s.serialize_element(&value)?;
}
s.end()
}
}
/// Generate a unique fingerprint to identify a violation.
fn fingerprint(message: &Diagnostic, project_path: &str, salt: u64) -> u64 {
let mut hasher = DefaultHasher::new();
salt.hash(&mut hasher);
message.name().hash(&mut hasher);
project_path.hash(&mut hasher);
hasher.finish()
}
#[cfg(test)]
mod tests {
use insta::assert_snapshot;
use crate::message::GitlabEmitter;
use crate::message::tests::{
capture_emitter_output, create_diagnostics, create_syntax_error_diagnostics,
};
#[test]
fn output() {
let mut emitter = GitlabEmitter::default();
let content = capture_emitter_output(&mut emitter, &create_diagnostics());
assert_snapshot!(redact_fingerprint(&content));
}
#[test]
fn syntax_errors() {
let mut emitter = GitlabEmitter::default();
let content = capture_emitter_output(&mut emitter, &create_syntax_error_diagnostics());
assert_snapshot!(redact_fingerprint(&content));
}
// Redact the fingerprint because the default hasher isn't stable across platforms.
fn redact_fingerprint(content: &str) -> String {
static FINGERPRINT_HAY_KEY: &str = r#""fingerprint": ""#;
let mut output = String::with_capacity(content.len());
let mut last = 0;
for (start, _) in content.match_indices(FINGERPRINT_HAY_KEY) {
let fingerprint_hash_start = start + FINGERPRINT_HAY_KEY.len();
output.push_str(&content[last..fingerprint_hash_start]);
output.push_str("<redacted>");
last = fingerprint_hash_start
+ content[fingerprint_hash_start..]
.find('"')
.expect("Expected terminating quote");
}
output.push_str(&content[last..]);
output
}
}

View File

@@ -10,6 +10,7 @@ use ruff_db::diagnostic::{
use ruff_db::files::File;
pub use github::GithubEmitter;
pub use gitlab::GitlabEmitter;
pub use grouped::GroupedEmitter;
use ruff_notebook::NotebookIndex;
use ruff_source_file::SourceFile;
@@ -21,6 +22,7 @@ use crate::Fix;
use crate::registry::Rule;
mod github;
mod gitlab;
mod grouped;
mod sarif;
mod text;

View File

@@ -1,63 +1,63 @@
---
source: crates/ruff_db/src/diagnostic/render/gitlab.rs
expression: env.render_diagnostics(&diagnostics)
source: crates/ruff_linter/src/message/gitlab.rs
expression: redact_fingerprint(&content)
---
[
{
"check_name": "F401",
"description": "F401: `os` imported but unused",
"severity": "major",
"fingerprint": "<redacted>",
"location": {
"path": "fib.py",
"positions": {
"begin": {
"line": 1,
"column": 8
"column": 8,
"line": 1
},
"end": {
"line": 1,
"column": 10
"column": 10,
"line": 1
}
}
}
},
"severity": "major"
},
{
"check_name": "F841",
"description": "F841: Local variable `x` is assigned to but never used",
"severity": "major",
"fingerprint": "<redacted>",
"location": {
"path": "fib.py",
"positions": {
"begin": {
"line": 6,
"column": 5
"column": 5,
"line": 6
},
"end": {
"line": 6,
"column": 6
"column": 6,
"line": 6
}
}
}
},
"severity": "major"
},
{
"check_name": "F821",
"description": "F821: Undefined name `a`",
"severity": "major",
"fingerprint": "<redacted>",
"location": {
"path": "undef.py",
"positions": {
"begin": {
"line": 1,
"column": 4
"column": 4,
"line": 1
},
"end": {
"line": 1,
"column": 5
"column": 5,
"line": 1
}
}
}
},
"severity": "major"
}
]

View File

@@ -1,44 +1,44 @@
---
source: crates/ruff_db/src/diagnostic/render/gitlab.rs
expression: env.render_diagnostics(&diagnostics)
source: crates/ruff_linter/src/message/gitlab.rs
expression: redact_fingerprint(&content)
---
[
{
"check_name": "invalid-syntax",
"description": "invalid-syntax: Expected one or more symbol names after import",
"severity": "major",
"fingerprint": "<redacted>",
"location": {
"path": "syntax_errors.py",
"positions": {
"begin": {
"line": 1,
"column": 15
"column": 15,
"line": 1
},
"end": {
"line": 2,
"column": 1
"column": 1,
"line": 2
}
}
}
},
"severity": "major"
},
{
"check_name": "invalid-syntax",
"description": "invalid-syntax: Expected ')', found newline",
"severity": "major",
"fingerprint": "<redacted>",
"location": {
"path": "syntax_errors.py",
"positions": {
"begin": {
"line": 3,
"column": 12
"column": 12,
"line": 3
},
"end": {
"line": 4,
"column": 1
"column": 1,
"line": 4
}
}
}
},
"severity": "major"
}
]

View File

@@ -376,22 +376,6 @@ mod tests {
Ok(())
}
#[test_case(Rule::UnusedImport, Path::new("F401_35.py"))]
fn f401_allowed_unused_imports_top_level_module(rule_code: Rule, path: &Path) -> Result<()> {
let diagnostics = test_path(
Path::new("pyflakes").join(path).as_path(),
&LinterSettings {
pyflakes: pyflakes::settings::Settings {
allowed_unused_imports: vec!["hvplot".to_string()],
..pyflakes::settings::Settings::default()
},
..LinterSettings::for_rule(rule_code)
},
)?;
assert_diagnostics!(diagnostics);
Ok(())
}
#[test]
fn f841_dummy_variable_rgx() -> Result<()> {
let diagnostics = test_path(

View File

@@ -334,7 +334,7 @@ pub(crate) fn unused_import(checker: &Checker, scope: &Scope) {
.allowed_unused_imports
.iter()
.any(|allowed_unused_import| {
let allowed_unused_import = QualifiedName::user_defined(allowed_unused_import);
let allowed_unused_import = QualifiedName::from_dotted_name(allowed_unused_import);
import.qualified_name().starts_with(&allowed_unused_import)
})
{

View File

@@ -1,4 +0,0 @@
---
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
---

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_wasm"
version = "0.12.11"
version = "0.12.10"
publish = false
authors = { workspace = true }
edition = { workspace = true }

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

@@ -36,7 +36,7 @@ def test(): -> "int":
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-non-callable) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L112)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L113)
</small>
**What it does**
@@ -58,7 +58,7 @@ Calling a non-callable object will raise a `TypeError` at runtime.
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-argument-forms) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L156)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L157)
</small>
**What it does**
@@ -88,7 +88,7 @@ f(int) # error
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-declarations) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L182)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L183)
</small>
**What it does**
@@ -117,7 +117,7 @@ a = 1
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-metaclass) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L207)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L208)
</small>
**What it does**
@@ -147,7 +147,7 @@ class C(A, B): ...
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-class-definition) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L233)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L234)
</small>
**What it does**
@@ -177,7 +177,7 @@ class B(A): ...
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-base) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L298)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L299)
</small>
**What it does**
@@ -202,7 +202,7 @@ class B(A, A): ...
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-kw-only) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L319)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L320)
</small>
**What it does**
@@ -306,7 +306,7 @@ def test(): -> "Literal[5]":
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20inconsistent-mro) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L522)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L523)
</small>
**What it does**
@@ -334,7 +334,7 @@ class C(A, B): ...
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20index-out-of-bounds) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L546)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L547)
</small>
**What it does**
@@ -358,7 +358,7 @@ t[3] # IndexError: tuple index out of range
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20instance-layout-conflict) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L351)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L352)
</small>
**What it does**
@@ -445,7 +445,7 @@ an atypical memory layout.
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-argument-type) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L591)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L592)
</small>
**What it does**
@@ -470,7 +470,7 @@ func("foo") # error: [invalid-argument-type]
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-assignment) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L631)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L632)
</small>
**What it does**
@@ -496,7 +496,7 @@ a: int = ''
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-attribute-access) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1665)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1687)
</small>
**What it does**
@@ -528,7 +528,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-await) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L653)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L654)
</small>
**What it does**
@@ -562,7 +562,7 @@ asyncio.run(main())
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-base) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L683)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L684)
</small>
**What it does**
@@ -584,7 +584,7 @@ class A(42): ... # error: [invalid-base]
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-context-manager) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L734)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L735)
</small>
**What it does**
@@ -609,7 +609,7 @@ with 1:
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-declaration) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L755)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L756)
</small>
**What it does**
@@ -636,7 +636,7 @@ a: str
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-exception-caught) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L778)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L779)
</small>
**What it does**
@@ -678,7 +678,7 @@ except ZeroDivisionError:
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-class) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L814)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L815)
</small>
**What it does**
@@ -709,7 +709,7 @@ class C[U](Generic[T]): ...
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-key) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L566)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L567)
</small>
**What it does**
@@ -738,7 +738,7 @@ alice["height"] # KeyError: 'height'
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-legacy-type-variable) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L840)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L841)
</small>
**What it does**
@@ -771,7 +771,7 @@ def f(t: TypeVar("U")): ...
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-metaclass) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L889)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L911)
</small>
**What it does**
@@ -803,7 +803,7 @@ class B(metaclass=f): ...
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-named-tuple) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L496)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L497)
</small>
**What it does**
@@ -828,12 +828,37 @@ in a class's bases list.
TypeError: can only inherit from a NamedTuple type and Generic
```
## `invalid-newtype`
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-newtype) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L890)
</small>
**What it does**
Checks for the creation of invalid `NewType`s
**Why is this bad?**
There are several requirements that you must follow when creating a `NewType`.
**Examples**
```python
from typing import NewType
Foo = NewType("Foo", int) # okay
Bar = NewType(get_name(), int) # error: NewType name must be a string literal
```
## `invalid-overload`
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-overload) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L916)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L938)
</small>
**What it does**
@@ -881,7 +906,7 @@ def foo(x: int) -> int: ...
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-parameter-default) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L959)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L981)
</small>
**What it does**
@@ -905,7 +930,7 @@ def f(a: int = ''): ...
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-protocol) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L433)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L434)
</small>
**What it does**
@@ -937,7 +962,7 @@ TypeError: Protocols can only inherit from other protocols, got <class 'int'>
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-raise) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L979)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1001)
</small>
Checks for `raise` statements that raise non-exceptions or use invalid
@@ -984,7 +1009,7 @@ def g():
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-return-type) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L612)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L613)
</small>
**What it does**
@@ -1007,7 +1032,7 @@ def func() -> int:
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-super-argument) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1022)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1044)
</small>
**What it does**
@@ -1061,7 +1086,7 @@ TODO #14889
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-alias-type) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L868)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L869)
</small>
**What it does**
@@ -1086,7 +1111,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-checking-constant) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1061)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1083)
</small>
**What it does**
@@ -1114,7 +1139,7 @@ TYPE_CHECKING = ''
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-form) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1085)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1107)
</small>
**What it does**
@@ -1142,7 +1167,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-call) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1137)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1159)
</small>
**What it does**
@@ -1174,7 +1199,7 @@ f(10) # Error
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-definition) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1109)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1131)
</small>
**What it does**
@@ -1206,7 +1231,7 @@ class C:
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-variable-constraints) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1165)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1187)
</small>
**What it does**
@@ -1239,7 +1264,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-argument) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1194)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1216)
</small>
**What it does**
@@ -1262,7 +1287,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x'
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-typed-dict-key) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1764)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1786)
</small>
**What it does**
@@ -1293,7 +1318,7 @@ alice["age"] # KeyError
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20no-matching-overload) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1213)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1235)
</small>
**What it does**
@@ -1320,7 +1345,7 @@ func("string") # error: [no-matching-overload]
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20non-subscriptable) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1236)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1258)
</small>
**What it does**
@@ -1342,7 +1367,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-iterable) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1254)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1276)
</small>
**What it does**
@@ -1366,7 +1391,7 @@ for i in 34: # TypeError: 'int' object is not iterable
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20parameter-already-assigned) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1305)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1327)
</small>
**What it does**
@@ -1420,7 +1445,7 @@ def test(): -> "int":
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20static-assert-error) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1641)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1663)
</small>
**What it does**
@@ -1448,7 +1473,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20subclass-of-final-class) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1396)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1418)
</small>
**What it does**
@@ -1475,7 +1500,7 @@ class B(A): ... # Error raised here
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20too-many-positional-arguments) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1441)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1463)
</small>
**What it does**
@@ -1500,7 +1525,7 @@ f("foo") # Error raised here
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20type-assertion-failure) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1419)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1441)
</small>
**What it does**
@@ -1526,7 +1551,7 @@ def _(x: int):
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unavailable-implicit-super-arguments) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1462)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1484)
</small>
**What it does**
@@ -1570,7 +1595,7 @@ class A:
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unknown-argument) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1519)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1541)
</small>
**What it does**
@@ -1595,7 +1620,7 @@ f(x=1, y=2) # Error raised here
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-attribute) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1540)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1562)
</small>
**What it does**
@@ -1621,7 +1646,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo'
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-import) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1562)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1584)
</small>
**What it does**
@@ -1644,7 +1669,7 @@ import foo # ModuleNotFoundError: No module named 'foo'
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-reference) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1581)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1603)
</small>
**What it does**
@@ -1667,7 +1692,7 @@ print(x) # NameError: name 'x' is not defined
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-bool-conversion) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1274)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1296)
</small>
**What it does**
@@ -1702,7 +1727,7 @@ b1 < b2 < b1 # exception raised here
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-operator) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1600)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1622)
</small>
**What it does**
@@ -1728,7 +1753,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A'
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20zero-stepsize-in-slice) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1622)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1644)
</small>
**What it does**
@@ -1751,7 +1776,7 @@ l[1:10:0] # ValueError: slice step cannot be zero
<small>
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20ambiguous-protocol-member) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L461)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L462)
</small>
**What it does**
@@ -1790,7 +1815,7 @@ class SubProto(BaseProto, Protocol):
<small>
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20deprecated) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L277)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L278)
</small>
**What it does**
@@ -1843,7 +1868,7 @@ a = 20 / 0 # type: ignore
<small>
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-attribute) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1326)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1348)
</small>
**What it does**
@@ -1869,7 +1894,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c'
<small>
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-implicit-call) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L130)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L131)
</small>
**What it does**
@@ -1899,7 +1924,7 @@ A()[0] # TypeError: 'A' object is not subscriptable
<small>
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-import) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1348)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1370)
</small>
**What it does**
@@ -1929,7 +1954,7 @@ from module import a # ImportError: cannot import name 'a' from 'module'
<small>
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20redundant-cast) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1693)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1715)
</small>
**What it does**
@@ -1954,7 +1979,7 @@ cast(int, f()) # Redundant
<small>
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20undefined-reveal) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1501)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1523)
</small>
**What it does**
@@ -2005,7 +2030,7 @@ a = 20 / 0 # ty: ignore[division-by-zero]
<small>
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-global) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1714)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1736)
</small>
**What it does**
@@ -2059,7 +2084,7 @@ def g():
<small>
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-base) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L701)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L702)
</small>
**What it does**
@@ -2096,7 +2121,7 @@ class D(C): ... # error: [unsupported-base]
<small>
Default level: [`ignore`](../rules.md#rule-levels "This lint has a default level of 'ignore'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20division-by-zero) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L259)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L260)
</small>
**What it does**
@@ -2118,7 +2143,7 @@ Dividing by zero raises a `ZeroDivisionError` at runtime.
<small>
Default level: [`ignore`](../rules.md#rule-levels "This lint has a default level of 'ignore'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unresolved-reference) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1374)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1396)
</small>
**What it does**

View File

@@ -165,16 +165,11 @@ impl<'db> DefinitionsOrTargets<'db> {
ty_python_semantic::types::TypeDefinition::Module(module) => {
ResolvedDefinition::Module(module.file(db)?)
}
ty_python_semantic::types::TypeDefinition::Class(definition) => {
ResolvedDefinition::Definition(definition)
}
ty_python_semantic::types::TypeDefinition::Function(definition) => {
ResolvedDefinition::Definition(definition)
}
ty_python_semantic::types::TypeDefinition::TypeVar(definition) => {
ResolvedDefinition::Definition(definition)
}
ty_python_semantic::types::TypeDefinition::TypeAlias(definition) => {
ty_python_semantic::types::TypeDefinition::Class(definition)
| ty_python_semantic::types::TypeDefinition::Function(definition)
| ty_python_semantic::types::TypeDefinition::TypeVar(definition)
| ty_python_semantic::types::TypeDefinition::TypeAlias(definition)
| ty_python_semantic::types::TypeDefinition::NewType(definition) => {
ResolvedDefinition::Definition(definition)
}
};

View File

@@ -1,7 +1,5 @@
# NewType
Currently, ty doesn't support `typing.NewType` in type annotations.
## Valid forms
```py
@@ -12,13 +10,44 @@ X = GenericAlias(type, ())
A = NewType("A", int)
# TODO: typeshed for `typing.GenericAlias` uses `type` for the first argument. `NewType` should be special-cased
# to be compatible with `type`
# error: [invalid-argument-type] "Argument to function `__new__` is incorrect: Expected `type`, found `NewType`"
# error: [invalid-argument-type] "Argument to function `__new__` is incorrect: Expected `type`, found `A`"
B = GenericAlias(A, ())
def _(
a: A,
b: B,
):
reveal_type(a) # revealed: @Todo(Support for `typing.NewType` instances in type expressions)
reveal_type(a) # revealed: A
reveal_type(b) # revealed: @Todo(Support for `typing.GenericAlias` instances in type expressions)
```
## Subtyping
```py
from typing_extensions import NewType
Foo = NewType("Foo", int)
Bar = NewType("Bar", Foo)
Foo(42)
Foo(Foo(42)) # allowed: `Foo` is a subtype of `int`.
Foo(Bar(Foo(42))) # allowed: `Bar` is a subtype of `int`.
Foo(True) # allowed: `bool` is a subtype of `int`.
Foo("fourty-two") # error: [invalid-argument-type]
def f(_: int): ...
def g(_: Foo): ...
def h(_: Bar): ...
f(42)
f(Foo(42))
f(Bar(Foo(42)))
g(42) # error: [invalid-argument-type]
g(Foo(42))
g(Bar(Foo(42)))
h(42) # error: [invalid-argument-type]
h(Foo(42)) # error: [invalid-argument-type]
h(Bar(Foo(42)))
```

View File

@@ -2288,26 +2288,6 @@ class H:
self.x = other.x or self.x
```
An attribute definition can be guarded by a condition involving that attribute. This is a regression
test for <https://github.com/astral-sh/ty/issues/692>:
```py
from typing import Literal
def check(x) -> Literal[False]:
return False
class Toggle:
def __init__(self: "Toggle"):
if not self.x:
self.x: Literal[True] = True
if check(self.y):
self.y = True
reveal_type(Toggle().x) # revealed: Literal[True]
reveal_type(Toggle().y) # revealed: Unknown | Literal[True]
```
### Builtin types attributes
This test can probably be removed eventually, but we currently include it because we do not yet

View File

@@ -1564,24 +1564,6 @@ if True:
from module import symbol
```
## Non-definitely bound symbols in conditions
When a non-definitely bound symbol is used as a (part of a) condition, we always infer an ambiguous
truthiness. If we didn't do that, `x` would be considered definitely bound in the following example:
```py
def _(flag: bool):
if flag:
ALWAYS_TRUE_IF_BOUND = True
# error: [possibly-unresolved-reference] "Name `ALWAYS_TRUE_IF_BOUND` used when possibly not defined"
if True and ALWAYS_TRUE_IF_BOUND:
x = 1
# error: [possibly-unresolved-reference] "Name `x` used when possibly not defined"
x
```
## Unreachable code
A closely related feature is the ability to detect unreachable code. For example, we do not emit a

View File

@@ -135,10 +135,6 @@ impl<'db> Place<'db> {
Place::Unbound => Place::Unbound,
}
}
pub(crate) const fn is_definitely_bound(&self) -> bool {
matches!(self, Place::Type(_, Boundness::Bound))
}
}
impl<'db> From<LookupResult<'db>> for PlaceAndQualifiers<'db> {

View File

@@ -209,7 +209,6 @@ use crate::semantic_index::predicate::{
};
use crate::types::{
IntersectionBuilder, Truthiness, Type, UnionBuilder, UnionType, infer_expression_type,
static_expression_truthiness,
};
/// A ternary formula that defines under what conditions a binding is visible. (A ternary formula
@@ -822,7 +821,8 @@ impl ReachabilityConstraints {
fn analyze_single(db: &dyn Db, predicate: &Predicate) -> Truthiness {
match predicate.node {
PredicateNode::Expression(test_expr) => {
static_expression_truthiness(db, test_expr).negate_if(!predicate.is_positive)
let ty = infer_expression_type(db, test_expr);
ty.bool(db).negate_if(!predicate.is_positive)
}
PredicateNode::ReturnsNever(CallableAndCallExpr {
callable,

View File

@@ -598,7 +598,7 @@ impl<'db> UseDefMap<'db> {
.is_always_false()
}
pub(crate) fn declaration_reachability(
pub(crate) fn is_declaration_reachable(
&self,
db: &dyn crate::Db,
declaration: &DeclarationWithConstraint<'db>,
@@ -610,7 +610,7 @@ impl<'db> UseDefMap<'db> {
)
}
pub(crate) fn binding_reachability(
pub(crate) fn is_binding_reachable(
&self,
db: &dyn crate::Db,
binding: &BindingWithConstraints<'_, 'db>,

View File

@@ -24,7 +24,7 @@ pub use self::diagnostic::TypeCheckDiagnostics;
pub(crate) use self::diagnostic::register_lints;
pub(crate) use self::infer::{
infer_deferred_types, infer_definition_types, infer_expression_type, infer_expression_types,
infer_scope_types, static_expression_truthiness,
infer_scope_types,
};
pub(crate) use self::signatures::{CallableSignature, Signature};
pub(crate) use self::subclass_of::{SubclassOfInner, SubclassOfType};
@@ -731,20 +731,20 @@ impl<'db> Type<'db> {
fn is_none(&self, db: &'db dyn Db) -> bool {
self.into_nominal_instance()
.is_some_and(|instance| instance.class(db).is_known(db, KnownClass::NoneType))
.and_then(|instance| instance.class_if_not_newtype(db))
.is_some_and(|class| class.is_known(db, KnownClass::NoneType))
}
fn is_bool(&self, db: &'db dyn Db) -> bool {
self.into_nominal_instance()
.is_some_and(|instance| instance.class(db).is_known(db, KnownClass::Bool))
.and_then(|instance| instance.class_if_not_newtype(db))
.is_some_and(|class| class.is_known(db, KnownClass::Bool))
}
pub(crate) fn is_notimplemented(&self, db: &'db dyn Db) -> bool {
self.into_nominal_instance().is_some_and(|instance| {
instance
.class(db)
.is_known(db, KnownClass::NotImplementedType)
})
self.into_nominal_instance()
.and_then(|instance| instance.class_if_not_newtype(db))
.is_some_and(|class| class.is_known(db, KnownClass::NotImplementedType))
}
pub(crate) fn is_object(&self, db: &'db dyn Db) -> bool {
@@ -1932,8 +1932,13 @@ impl<'db> Type<'db> {
return C::unsatisfiable(db);
}
let class_literal = instance.class(db).class_literal(db).0;
C::from_bool(db, is_single_member_enum(db, class_literal))
C::from_bool(
db,
instance.class_if_not_newtype(db).is_some_and(|class| {
let class_literal = class.class_literal(db).0;
is_single_member_enum(db, class_literal)
}),
)
}
_ => C::unsatisfiable(db),
}
@@ -2232,7 +2237,7 @@ impl<'db> Type<'db> {
// (<https://github.com/rust-lang/rust/issues/129967>)
(Type::ProtocolInstance(protocol), nominal @ Type::NominalInstance(n))
| (nominal @ Type::NominalInstance(n), Type::ProtocolInstance(protocol))
if n.class(db).is_final(db) =>
if n.class_ignoring_newtype(db).is_final(db) =>
{
visitor.visit((self, other), || {
any_protocol_members_absent_or_disjoint(db, protocol, nominal, visitor)
@@ -2288,22 +2293,33 @@ impl<'db> Type<'db> {
},
(Type::SpecialForm(special_form), Type::NominalInstance(instance))
| (Type::NominalInstance(instance), Type::SpecialForm(special_form)) => {
C::from_bool(db, !special_form.is_instance_of(db, instance.class(db)))
}
| (Type::NominalInstance(instance), Type::SpecialForm(special_form)) => C::from_bool(
db,
!instance
.class_if_not_newtype(db)
.is_some_and(|class| special_form.is_instance_of(db, class)),
),
(Type::KnownInstance(known_instance), Type::NominalInstance(instance))
| (Type::NominalInstance(instance), Type::KnownInstance(known_instance)) => {
C::from_bool(db, !known_instance.is_instance_of(db, instance.class(db)))
C::from_bool(
db,
!instance
.class_if_not_newtype(db)
.is_some_and(|class| known_instance.is_instance_of(db, class)),
)
}
(Type::BooleanLiteral(..) | Type::TypeIs(_), Type::NominalInstance(instance))
| (Type::NominalInstance(instance), Type::BooleanLiteral(..) | Type::TypeIs(_)) => {
// A `Type::BooleanLiteral()` must be an instance of exactly `bool`
// (it cannot be an instance of a `bool` subclass)
KnownClass::Bool
.when_subclass_of::<C>(db, instance.class(db))
.negate(db)
C::from_bool(
db,
!instance
.class_if_not_newtype(db)
.is_some_and(|class| KnownClass::Bool.is_subclass_of(db, class)),
)
}
(Type::BooleanLiteral(..) | Type::TypeIs(_), _)
@@ -2313,9 +2329,12 @@ impl<'db> Type<'db> {
| (Type::NominalInstance(instance), Type::IntLiteral(..)) => {
// A `Type::IntLiteral()` must be an instance of exactly `int`
// (it cannot be an instance of an `int` subclass)
KnownClass::Int
.when_subclass_of::<C>(db, instance.class(db))
.negate(db)
C::from_bool(
db,
!instance
.class_if_not_newtype(db)
.is_some_and(|class| KnownClass::Int.is_subclass_of(db, class)),
)
}
(Type::IntLiteral(..), _) | (_, Type::IntLiteral(..)) => C::always_satisfiable(db),
@@ -2327,9 +2346,12 @@ impl<'db> Type<'db> {
| (Type::NominalInstance(instance), Type::StringLiteral(..) | Type::LiteralString) => {
// A `Type::StringLiteral()` or a `Type::LiteralString` must be an instance of exactly `str`
// (it cannot be an instance of a `str` subclass)
KnownClass::Str
.when_subclass_of::<C>(db, instance.class(db))
.negate(db)
C::from_bool(
db,
!instance
.class_if_not_newtype(db)
.is_some_and(|class| KnownClass::Str.is_subclass_of(db, class)),
)
}
(Type::LiteralString, Type::LiteralString) => C::unsatisfiable(db),
@@ -2339,9 +2361,12 @@ impl<'db> Type<'db> {
| (Type::NominalInstance(instance), Type::BytesLiteral(..)) => {
// A `Type::BytesLiteral()` must be an instance of exactly `bytes`
// (it cannot be an instance of a `bytes` subclass)
KnownClass::Bytes
.when_subclass_of::<C>(db, instance.class(db))
.negate(db)
C::from_bool(
db,
!instance
.class_if_not_newtype(db)
.is_some_and(|class| KnownClass::Bytes.is_subclass_of(db, class)),
)
}
(Type::EnumLiteral(enum_literal), instance @ Type::NominalInstance(_))
@@ -2373,9 +2398,12 @@ impl<'db> Type<'db> {
| (Type::NominalInstance(instance), Type::FunctionLiteral(..)) => {
// A `Type::FunctionLiteral()` must be an instance of exactly `types.FunctionType`
// (it cannot be an instance of a `types.FunctionType` subclass)
KnownClass::FunctionType
.when_subclass_of::<C>(db, instance.class(db))
.negate(db)
C::from_bool(
db,
!instance
.class_if_not_newtype(db)
.is_some_and(|class| KnownClass::FunctionType.is_subclass_of(db, class)),
)
}
(Type::BoundMethod(_), other) | (other, Type::BoundMethod(_)) => KnownClass::MethodType
@@ -2426,7 +2454,7 @@ impl<'db> Type<'db> {
| (
instance @ Type::NominalInstance(nominal),
Type::Callable(_) | Type::DataclassDecorator(_) | Type::DataclassTransformer(_),
) if nominal.class(db).is_final(db) => instance
) if nominal.class_ignoring_newtype(db).is_final(db) => instance
.member_lookup_with_policy(
db,
Name::new_static("__call__"),
@@ -2788,7 +2816,9 @@ impl<'db> Type<'db> {
// `Type::NominalInstance(type)` is equivalent to looking up the name in the
// MRO of the class `object`.
Type::NominalInstance(instance)
if instance.class(db).is_known(db, KnownClass::Type) =>
if instance
.class_ignoring_newtype(db)
.is_known(db, KnownClass::Type) =>
{
if policy.mro_no_object_fallback() {
Some(Place::Unbound.into())
@@ -2908,7 +2938,9 @@ impl<'db> Type<'db> {
Type::Dynamic(_) | Type::Never => Place::bound(self).into(),
Type::NominalInstance(instance) => instance.class(db).instance_member(db, name),
Type::NominalInstance(instance) => instance
.class_ignoring_newtype(db)
.instance_member(db, name),
Type::ProtocolInstance(protocol) => protocol.instance_member(db, name),
@@ -3428,7 +3460,9 @@ impl<'db> Type<'db> {
Type::NominalInstance(instance)
if matches!(name.as_str(), "major" | "minor")
&& instance.class(db).is_known(db, KnownClass::VersionInfo) =>
&& instance
.class_ignoring_newtype(db)
.is_known(db, KnownClass::VersionInfo) =>
{
let python_version = Program::get(db).python_version(db);
let segment = if name == "major" {
@@ -3506,7 +3540,7 @@ impl<'db> Type<'db> {
// resolve the attribute.
if matches!(
self.into_nominal_instance()
.and_then(|instance| instance.class(db).known(db)),
.and_then(|instance| instance.class_ignoring_newtype(db).known(db)),
Some(KnownClass::ModuleType | KnownClass::GenericAlias)
) {
return Place::Unbound.into();
@@ -3826,7 +3860,7 @@ impl<'db> Type<'db> {
Type::TypeVar(_) => Truthiness::Ambiguous,
Type::NominalInstance(instance) => instance
.class(db)
.class_ignoring_newtype(db)
.known(db)
.and_then(KnownClass::bool)
.map(Ok)
@@ -4407,6 +4441,21 @@ impl<'db> Type<'db> {
.into()
}
Some(KnownClass::NewType) => Binding::single(
self,
Signature::new(
Parameters::new([
Parameter::positional_or_keyword(Name::new_static("name"))
.with_annotated_type(Type::LiteralString),
Parameter::positional_or_keyword(Name::new_static("tp")),
]),
// The true return type is a KnownInstanceType::NewType, but each callsite
// gets its own unique...instance...of that type.
None,
),
)
.into(),
Some(KnownClass::Object) => {
// ```py
// class object:
@@ -4801,6 +4850,16 @@ impl<'db> Type<'db> {
Type::EnumLiteral(enum_literal) => enum_literal.enum_class_instance(db).bindings(db),
Type::KnownInstance(KnownInstanceType::NewType(newtype)) => Binding::single(
self,
Signature::new(
Parameters::new([Parameter::positional_only(None)
.with_annotated_type(newtype.base(db).to_type(db))]),
Some(Type::newtype_nominal_instance(newtype)),
),
)
.into(),
Type::KnownInstance(known_instance) => {
known_instance.instance_fallback(db).bindings(db)
}
@@ -5275,9 +5334,10 @@ impl<'db> Type<'db> {
};
match self {
Type::NominalInstance(instance) => {
instance.class(db).iter_mro(db).find_map(from_class_base)
}
Type::NominalInstance(instance) => instance
.class_ignoring_newtype(db)
.iter_mro(db)
.find_map(from_class_base),
Type::ProtocolInstance(instance) => {
if let Protocol::FromClass(class) = instance.inner {
class.iter_mro(db).find_map(from_class_base)
@@ -5635,6 +5695,7 @@ impl<'db> Type<'db> {
invalid_expressions: smallvec::smallvec_inline![InvalidTypeExpression::Generic],
fallback_type: Type::unknown(),
}),
KnownInstanceType::NewType(newtype) => Ok(Type::newtype_nominal_instance(*newtype)),
},
Type::SpecialForm(special_form) => match special_form {
@@ -5812,32 +5873,37 @@ impl<'db> Type<'db> {
Type::Dynamic(_) => Ok(*self),
Type::NominalInstance(instance) => match instance.class(db).known(db) {
Some(KnownClass::TypeVar) => Ok(todo_type!(
"Support for `typing.TypeVar` instances in type expressions"
)),
Some(
KnownClass::ParamSpec | KnownClass::ParamSpecArgs | KnownClass::ParamSpecKwargs,
) => Ok(todo_type!("Support for `typing.ParamSpec`")),
Some(KnownClass::TypeVarTuple) => Ok(todo_type!(
"Support for `typing.TypeVarTuple` instances in type expressions"
)),
Some(KnownClass::NewType) => Ok(todo_type!(
"Support for `typing.NewType` instances in type expressions"
)),
Some(KnownClass::GenericAlias) => Ok(todo_type!(
"Support for `typing.GenericAlias` instances in type expressions"
)),
Some(KnownClass::UnionType) => Ok(todo_type!(
"Support for `types.UnionType` instances in type expressions"
)),
_ => Err(InvalidTypeExpressionError {
invalid_expressions: smallvec::smallvec_inline![
InvalidTypeExpression::InvalidType(*self, scope_id)
],
fallback_type: Type::unknown(),
}),
},
Type::NominalInstance(instance) => {
// TODO: Should NewType wrapping be supported here?
match instance.class_ignoring_newtype(db).known(db) {
Some(KnownClass::TypeVar) => Ok(todo_type!(
"Support for `typing.TypeVar` instances in type expressions"
)),
Some(
KnownClass::ParamSpec
| KnownClass::ParamSpecArgs
| KnownClass::ParamSpecKwargs,
) => Ok(todo_type!("Support for `typing.ParamSpec`")),
Some(KnownClass::TypeVarTuple) => Ok(todo_type!(
"Support for `typing.TypeVarTuple` instances in type expressions"
)),
Some(KnownClass::NewType) => Ok(todo_type!(
"Support for `typing.NewType` instances in type expressions"
)),
Some(KnownClass::GenericAlias) => Ok(todo_type!(
"Support for `typing.GenericAlias` instances in type expressions"
)),
Some(KnownClass::UnionType) => Ok(todo_type!(
"Support for `types.UnionType` instances in type expressions"
)),
_ => Err(InvalidTypeExpressionError {
invalid_expressions: smallvec::smallvec_inline![
InvalidTypeExpression::InvalidType(*self, scope_id)
],
fallback_type: Type::unknown(),
}),
}
}
Type::Intersection(_) => Ok(todo_type!("Type::Intersection.in_type_expression")),
@@ -6342,7 +6408,12 @@ impl<'db> Type<'db> {
}
Self::GenericAlias(alias) => Some(TypeDefinition::Class(alias.definition(db))),
Self::NominalInstance(instance) => {
Some(TypeDefinition::Class(instance.class(db).definition(db)))
let (instance_class_type, instance_newtype) = instance.class_and_newtype(db);
if let Some(newtype) = instance_newtype {
Some(TypeDefinition::NewType(newtype.definition(db)))
} else {
Some(TypeDefinition::Class(instance_class_type.definition(db)))
}
}
Self::KnownInstance(instance) => match instance {
KnownInstanceType::TypeVar(var) => {
@@ -6375,17 +6446,18 @@ impl<'db> Type<'db> {
| Self::PropertyInstance(_)
| Self::BoundSuper(_) => self.to_meta_type(db).definition(db),
Self::NonInferableTypeVar(bound_typevar) |
Self::TypeVar(bound_typevar) => Some(TypeDefinition::TypeVar(bound_typevar.typevar(db).definition(db)?)),
Self::NonInferableTypeVar(bound_typevar) | Self::TypeVar(bound_typevar) => Some(
TypeDefinition::TypeVar(bound_typevar.typevar(db).definition(db)?),
),
Self::ProtocolInstance(protocol) => match protocol.inner {
Protocol::FromClass(class) => Some(TypeDefinition::Class(class.definition(db))),
Protocol::Synthesized(_) => None,
},
Type::TypedDict(typed_dict) => {
Some(TypeDefinition::Class(typed_dict.defining_class().definition(db)))
}
Type::TypedDict(typed_dict) => Some(TypeDefinition::Class(
typed_dict.defining_class().definition(db),
)),
Self::Union(_) | Self::Intersection(_) => None,
@@ -6466,7 +6538,7 @@ impl<'db> Type<'db> {
match self {
Type::GenericAlias(generic) => Some(generic.origin(db)),
Type::NominalInstance(instance) => {
if let ClassType::Generic(generic) = instance.class(db) {
if let ClassType::Generic(generic) = instance.class_ignoring_newtype(db) {
Some(generic.origin(db))
} else {
None
@@ -6712,6 +6784,10 @@ pub enum KnownInstanceType<'db> {
/// A single instance of `dataclasses.Field`
Field(FieldInstance<'db>),
/// An identity function created with `typing.NewType(name, base)`, which behaves like a
/// subclass of `base` in type expressions. See `NewTypeInstance` for an example.
NewType(NewTypeInstance<'db>),
}
fn walk_known_instance_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
@@ -6736,6 +6812,11 @@ fn walk_known_instance_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
KnownInstanceType::Field(field) => {
visitor.visit_type(db, field.default_type(db));
}
KnownInstanceType::NewType(newtype) => {
if let ClassType::Generic(generic_alias) = newtype.base_class_type(db) {
visitor.visit_generic_alias_type(db, generic_alias);
}
}
}
}
@@ -6757,6 +6838,7 @@ impl<'db> KnownInstanceType<'db> {
Self::Deprecated(deprecated)
}
Self::Field(field) => Self::Field(field.normalized_impl(db, visitor)),
Self::NewType(newtype) => Self::NewType(newtype.normalized_impl(db, visitor)),
}
}
@@ -6767,6 +6849,7 @@ impl<'db> KnownInstanceType<'db> {
Self::TypeAliasType(_) => KnownClass::TypeAliasType,
Self::Deprecated(_) => KnownClass::Deprecated,
Self::Field(_) => KnownClass::Field,
Self::NewType(_) => KnownClass::NewType,
}
}
@@ -6817,6 +6900,9 @@ impl<'db> KnownInstanceType<'db> {
field.default_type(self.db).display(self.db).fmt(f)?;
f.write_str("]")
}
KnownInstanceType::NewType(declaration) => {
f.write_str(declaration.name(self.db))
}
}
}
}
@@ -6910,10 +6996,6 @@ bitflags! {
const NOT_REQUIRED = 1 << 4;
/// `typing_extensions.ReadOnly`
const READ_ONLY = 1 << 5;
/// An implicit instance attribute which is possibly unbound according
/// to local control flow within the method it is defined in. This flag
/// overrules the `Boundness` information on `PlaceAndQualifiers`.
const POSSIBLY_UNBOUND_IMPLICIT_ATTRIBUTE = 1 << 6;
}
}
@@ -7189,6 +7271,158 @@ impl<'db> FieldInstance<'db> {
}
}
/// A `typing.NewType` declaration, either from the perspective of the
/// identity-function-that-acts-like-a-subclass-in-type-expressions returned by the call to
/// `NewType`, or from the perspective of instances of that subclass. For example:
///
/// ```py
/// from typing import NewType
/// Foo = NewType("Foo", int)
/// x = Foo(42)
/// ```
///
/// The revealed types there are:
/// - `NewType`: `Type::ClassLiteral(ClassLiteral)` with `KnownClass::NewType`.
/// - `Foo`: `Type::KnownInstance(KnownInstanceType::NewType(NewTypeInstance { .. }))`
/// - `x`: `Type::NominalInstance(...(NominalInstanceInner::NewType(NewTypeInstance { .. }))`
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
#[derive(PartialOrd, Ord)]
pub struct NewTypeInstance<'db> {
/// The name of this NewType (e.g. `"Foo"`)
#[returns(ref)]
pub name: ast::name::Name,
/// The binding where this NewType is first created.
pub definition: Definition<'db>,
// The base class of this NewType (e.g. `int`), which could be a (specialized) class type or
// could be another NewType.
pub base: NewTypeBase<'db>,
}
impl get_size2::GetSize for NewTypeInstance<'_> {}
impl<'db> NewTypeInstance<'db> {
// Walk the `NewTypeBase` chain to find the underlying `ClassType`.
fn base_class_type(self, db: &'db dyn Db) -> ClassType<'db> {
let mut base = self.base(db);
loop {
match base {
NewTypeBase::ClassType(class) => return class,
NewTypeBase::NewType(newtype) => base = newtype.base(db),
}
}
}
fn normalized_impl(self, db: &'db dyn Db, visitor: &NormalizedVisitor<'db>) -> Self {
let normalized_base = match self.base(db) {
NewTypeBase::ClassType(base_class) => {
NewTypeBase::ClassType(base_class.normalized_impl(db, visitor))
}
NewTypeBase::NewType(base_newtype) => {
NewTypeBase::NewType(base_newtype.normalized_impl(db, visitor))
}
};
Self::new(
db,
self.name(db).clone(),
self.definition(db),
normalized_base,
)
}
fn materialize(self, db: &'db dyn Db, materialization_kind: MaterializationKind) -> Self {
let materialized_base = match self.base(db) {
NewTypeBase::ClassType(base_class) => {
NewTypeBase::ClassType(base_class.materialize(db, materialization_kind))
}
NewTypeBase::NewType(base_newtype) => {
NewTypeBase::NewType(base_newtype.materialize(db, materialization_kind))
}
};
Self::new(
db,
self.name(db).clone(),
self.definition(db),
materialized_base,
)
}
fn apply_type_mapping_impl<'a>(
self,
db: &'db dyn Db,
type_mapping: &TypeMapping<'a, 'db>,
visitor: &ApplyTypeMappingVisitor<'db>,
) -> Self {
let mapped_base = match self.base(db) {
NewTypeBase::ClassType(base_class) => NewTypeBase::ClassType(
base_class.apply_type_mapping_impl(db, type_mapping, visitor),
),
NewTypeBase::NewType(base_newtype) => NewTypeBase::NewType(
base_newtype.apply_type_mapping_impl(db, type_mapping, visitor),
),
};
Self::new(db, self.name(db).clone(), self.definition(db), mapped_base)
}
fn is_subtype_of(self, db: &'db dyn Db, other: Self) -> bool {
let mut candidate_newtype = self;
loop {
if candidate_newtype == other {
// Either `self` is `other`, or it's a (transitive) NewType wrapper of it.
return true;
}
match candidate_newtype.base(db) {
NewTypeBase::NewType(base_newtype) => candidate_newtype = base_newtype,
// Classes can't inherit from NewTypes, so if we reach the base `ClassType` without
// seeing `other`, then we can't be a subtype of it.
NewTypeBase::ClassType(_) => return false,
}
}
}
fn has_relation_to_impl<C: Constraints<'db>>(
self,
db: &'db dyn Db,
other: Self,
_: TypeRelation,
_: &HasRelationToVisitor<'db, C>,
) -> C {
// TODO: Is this correct?
C::from_bool(db, self.is_subtype_of(db, other))
}
pub(super) fn is_disjoint_from_impl<C: Constraints<'db>>(
self,
db: &'db dyn Db,
other: Self,
_: &IsDisjointVisitor<'db, C>,
) -> C {
// Two NewTypes are disjoint if they're not equal and neither inherits from the other.
// (NewTypes have single inheritance, and a regular class can't inherit from a NewType, so
// it's not possible for some third type to multiply-inherit from both.)
C::from_bool(
db,
!self.is_subtype_of(db, other) && !other.is_subtype_of(db, self),
)
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, get_size2::GetSize)]
pub enum NewTypeBase<'db> {
ClassType(ClassType<'db>),
NewType(NewTypeInstance<'db>),
}
impl<'db> NewTypeBase<'db> {
fn to_type(self, db: &'db dyn Db) -> Type<'db> {
match self {
NewTypeBase::ClassType(base_class) => Type::instance(db, base_class),
NewTypeBase::NewType(base_newtype) => Type::newtype_nominal_instance(base_newtype),
}
}
}
/// Whether this typevar was created via the legacy `TypeVar` constructor, using PEP 695 syntax,
/// or an implicit typevar like `Self` was used.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, get_size2::GetSize)]
@@ -8624,7 +8858,7 @@ impl TypeRelation {
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, get_size2::GetSize)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Truthiness {
/// For an object `x`, `bool(x)` will always return `True`
AlwaysTrue,
@@ -9980,7 +10214,9 @@ impl<'db> SuperOwnerKind<'db> {
Either::Left(ClassBase::Dynamic(dynamic).mro(db, None))
}
SuperOwnerKind::Class(class) => Either::Right(class.iter_mro(db)),
SuperOwnerKind::Instance(instance) => Either::Right(instance.class(db).iter_mro(db)),
SuperOwnerKind::Instance(instance) => {
Either::Right(instance.class_ignoring_newtype(db).iter_mro(db))
}
}
}
@@ -9992,11 +10228,11 @@ impl<'db> SuperOwnerKind<'db> {
}
}
fn into_class(self, db: &'db dyn Db) -> Option<ClassType<'db>> {
fn into_class_ignoring_newtype(self, db: &'db dyn Db) -> Option<ClassType<'db>> {
match self {
SuperOwnerKind::Dynamic(_) => None,
SuperOwnerKind::Class(class) => Some(class),
SuperOwnerKind::Instance(instance) => Some(instance.class(db)),
SuperOwnerKind::Instance(instance) => Some(instance.class_ignoring_newtype(db)),
}
}
@@ -10115,7 +10351,7 @@ impl<'db> BoundSuperType<'db> {
let Some(pivot_class) = pivot_class.into_class() else {
return Some(owner);
};
let Some(owner_class) = owner.into_class(db) else {
let Some(owner_class) = owner.into_class_ignoring_newtype(db) else {
return Some(owner);
};
if owner_class.is_subclass_of(db, pivot_class) {
@@ -10216,7 +10452,7 @@ impl<'db> BoundSuperType<'db> {
.expect("Calling `find_name_in_mro` on dynamic type should return `Some`");
}
SuperOwnerKind::Class(class) => class,
SuperOwnerKind::Instance(instance) => instance.class(db),
SuperOwnerKind::Instance(instance) => instance.class_ignoring_newtype(db),
};
let (class_literal, _) = class.class_literal(db);

View File

@@ -631,7 +631,14 @@ impl<'db> IntersectionBuilder<'db> {
self
}
Type::NominalInstance(instance)
if enum_metadata(self.db, instance.class(self.db).class_literal(self.db).0)
if !instance.is_newtype()
&& enum_metadata(
self.db,
instance
.class_ignoring_newtype(self.db)
.class_literal(self.db)
.0,
)
.is_some() =>
{
let mut contains_enum_literal_as_negative_element = false;
@@ -657,9 +664,13 @@ impl<'db> IntersectionBuilder<'db> {
self.add_positive_impl(
Type::Union(UnionType::new(
db,
enum_member_literals(db, instance.class(db).class_literal(db).0, None)
.expect("Calling `enum_member_literals` on an enum class")
.collect::<Box<[_]>>(),
enum_member_literals(
db,
instance.class_ignoring_newtype(db).class_literal(db).0,
None,
)
.expect("Calling `enum_member_literals` on an enum class")
.collect::<Box<[_]>>(),
)),
seen_aliases,
)
@@ -854,7 +865,8 @@ impl<'db> InnerIntersectionBuilder<'db> {
_ => {
let known_instance = new_positive
.into_nominal_instance()
.and_then(|instance| instance.class(db).known(db));
.and_then(|instance| instance.class_if_not_newtype(db))
.and_then(|class| class.known(db));
if known_instance == Some(KnownClass::Object) {
// `object & T` -> `T`; it is always redundant to add `object` to an intersection
@@ -874,7 +886,9 @@ impl<'db> InnerIntersectionBuilder<'db> {
new_positive = Type::BooleanLiteral(false);
}
Type::NominalInstance(instance)
if instance.class(db).is_known(db, KnownClass::Bool) =>
if instance
.class_if_not_newtype(db)
.is_some_and(|class| class.is_known(db, KnownClass::Bool)) =>
{
match new_positive {
// `bool & AlwaysTruthy` -> `Literal[True]`
@@ -968,7 +982,8 @@ impl<'db> InnerIntersectionBuilder<'db> {
self.positive
.iter()
.filter_map(|ty| ty.into_nominal_instance())
.filter_map(|instance| instance.class(db).known(db))
.filter_map(|instance| instance.class_if_not_newtype(db))
.filter_map(|class| class.known(db))
.any(KnownClass::is_bool)
};

View File

@@ -260,7 +260,9 @@ impl<'a, 'db> FromIterator<(Argument<'a>, Option<Type<'db>>)> for CallArguments<
pub(crate) fn is_expandable_type<'db>(db: &'db dyn Db, ty: Type<'db>) -> bool {
match ty {
Type::NominalInstance(instance) => {
let class = instance.class(db);
let Some(class) = instance.class_if_not_newtype(db) else {
return false;
};
class.is_known(db, KnownClass::Bool)
|| instance.tuple_spec(db).is_some_and(|spec| match &*spec {
Tuple::Fixed(fixed_length_tuple) => fixed_length_tuple
@@ -282,7 +284,7 @@ fn expand_type<'db>(db: &'db dyn Db, ty: Type<'db>) -> Option<Vec<Type<'db>>> {
// NOTE: Update `is_expandable_type` if this logic changes accordingly.
match ty {
Type::NominalInstance(instance) => {
let class = instance.class(db);
let class = instance.class_if_not_newtype(db)?;
if class.is_known(db, KnownClass::Bool) {
return Some(vec![

View File

@@ -20,7 +20,9 @@ use crate::semantic_index::{
};
use crate::types::constraints::{Constraints, IteratorConstraintsExtension};
use crate::types::context::InferContext;
use crate::types::diagnostic::{INVALID_LEGACY_TYPE_VARIABLE, INVALID_TYPE_ALIAS_TYPE};
use crate::types::diagnostic::{
INVALID_LEGACY_TYPE_VARIABLE, INVALID_NEWTYPE, INVALID_TYPE_ALIAS_TYPE,
};
use crate::types::enums::enum_metadata;
use crate::types::function::{DataclassTransformerParams, KnownFunction};
use crate::types::generics::{GenericContext, Specialization, walk_specialization};
@@ -31,10 +33,10 @@ use crate::types::typed_dict::typed_dict_params_from_class_def;
use crate::types::{
ApplyTypeMappingVisitor, Binding, BoundSuperError, BoundSuperType, CallableType,
DataclassParams, DeprecatedInstance, HasRelationToVisitor, IsEquivalentVisitor,
KnownInstanceType, ManualPEP695TypeAliasType, MaterializationKind, NormalizedVisitor,
PropertyInstanceType, StringLiteralType, TypeAliasType, TypeMapping, TypeRelation,
TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind, TypedDictParams, VarianceInferable,
declaration_type, infer_definition_types, todo_type,
KnownInstanceType, ManualPEP695TypeAliasType, MaterializationKind, NewTypeBase,
NewTypeInstance, NormalizedVisitor, PropertyInstanceType, StringLiteralType, TypeAliasType,
TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind,
TypedDictParams, VarianceInferable, declaration_type, infer_definition_types, todo_type,
};
use crate::{
Db, FxIndexMap, FxOrderSet, Program,
@@ -1317,7 +1319,8 @@ impl<'db> Field<'db> {
pub(crate) fn is_kw_only_sentinel(&self, db: &'db dyn Db) -> bool {
self.declared_ty
.into_nominal_instance()
.is_some_and(|instance| instance.class(db).is_known(db, KnownClass::KwOnly))
.and_then(|instance| instance.class_if_not_newtype(db))
.is_some_and(|class| class.is_known(db, KnownClass::KwOnly))
}
}
@@ -2736,20 +2739,17 @@ impl<'db> ClassLiteral<'db> {
// self.name: <annotation>
// self.name: <annotation> = …
let reachability = use_def_map(db, method_scope)
.declaration_reachability(db, &attribute_declaration);
if reachability.is_always_false() {
if use_def_map(db, method_scope)
.is_declaration_reachable(db, &attribute_declaration)
.is_always_false()
{
continue;
}
let annotation = declaration_type(db, declaration);
let mut annotation =
let annotation =
Place::bound(annotation.inner).with_qualifiers(annotation.qualifiers);
if reachability.is_ambiguous() {
annotation.qualifiers |= TypeQualifiers::POSSIBLY_UNBOUND_IMPLICIT_ATTRIBUTE;
}
if let Some(all_qualifiers) = annotation.is_bare_final() {
if let Some(value) = assignment.value(&module) {
// If we see an annotated assignment with a bare `Final` as in
@@ -2792,7 +2792,7 @@ impl<'db> ClassLiteral<'db> {
.all_reachable_symbol_bindings(method_place)
.find_map(|bind| {
(bind.binding.is_defined_and(|def| def == method))
.then(|| class_map.binding_reachability(db, &bind))
.then(|| class_map.is_binding_reachable(db, &bind))
})
.unwrap_or(Truthiness::AlwaysFalse)
} else {
@@ -2821,16 +2821,12 @@ impl<'db> ClassLiteral<'db> {
continue;
};
match method_map
.binding_reachability(db, &attribute_assignment)
.is_binding_reachable(db, &attribute_assignment)
.and(is_method_reachable)
{
Truthiness::AlwaysTrue => {
Truthiness::AlwaysTrue | Truthiness::Ambiguous => {
is_attribute_bound = true;
}
Truthiness::Ambiguous => {
is_attribute_bound = true;
qualifiers |= TypeQualifiers::POSSIBLY_UNBOUND_IMPLICIT_ATTRIBUTE;
}
Truthiness::AlwaysFalse => {
continue;
}
@@ -2841,7 +2837,7 @@ impl<'db> ClassLiteral<'db> {
// TODO: this is incomplete logic since the attributes bound after termination are considered reachable.
let unbound_reachability = unbound_binding
.as_ref()
.map(|binding| method_map.binding_reachability(db, binding))
.map(|binding| method_map.is_binding_reachable(db, binding))
.unwrap_or(Truthiness::AlwaysFalse);
if unbound_reachability
@@ -4252,14 +4248,6 @@ impl KnownClass {
.is_ok_and(|class| class.is_subclass_of(db, None, other))
}
pub(super) fn when_subclass_of<'db, C: Constraints<'db>>(
self,
db: &'db dyn Db,
other: ClassType<'db>,
) -> C {
C::from_bool(db, self.is_subclass_of(db, other))
}
/// Return the module in which we should look up the definition for this class
fn canonical_module(self, db: &dyn Db) -> KnownModule {
match self {
@@ -4976,6 +4964,69 @@ impl KnownClass {
)));
}
KnownClass::NewType => {
let assigned_to = index
.try_expression(ast::ExprRef::from(call_expression))
.and_then(|expr| expr.assigned_to(db));
let Some(target) = assigned_to.as_ref().and_then(|assigned_to| {
match assigned_to.node(module).targets.as_slice() {
[ast::Expr::Name(target)] => Some(target),
_ => None,
}
}) else {
if let Some(builder) = context.report_lint(&INVALID_NEWTYPE, call_expression) {
builder.into_diagnostic(
"A `typing.NewType` must be immediately assigned to a variable",
);
}
return;
};
let definition = index.expect_single_definition(target);
let [Some(name), Some(supertype), ..] = overload.parameter_types() else {
return;
};
let Some(name) = name.into_string_literal() else {
if let Some(builder) = context.report_lint(&INVALID_NEWTYPE, call_expression) {
builder.into_diagnostic(
"The name of a `typing.NewType` must be a string literal",
);
}
return;
};
let newtype_base = match supertype {
Type::ClassLiteral(class_literal) => {
NewTypeBase::ClassType(class_literal.default_specialization(db))
}
Type::GenericAlias(alias) => NewTypeBase::ClassType(ClassType::Generic(*alias)),
Type::KnownInstance(KnownInstanceType::NewType(newtype)) => {
NewTypeBase::NewType(*newtype)
}
_ => {
if let Some(builder) =
context.report_lint(&INVALID_NEWTYPE, call_expression)
{
builder.into_diagnostic(
"The second argument to `typing.NewType` must be a class or another `NewType`",
);
}
return;
}
};
overload.set_return_type(Type::KnownInstance(KnownInstanceType::NewType(
NewTypeInstance::new(
db,
ast::name::Name::new(name.value(db)),
definition,
newtype_base,
),
)));
}
_ => {}
}
}

View File

@@ -80,7 +80,9 @@ impl<'db> ClassBase<'db> {
Type::ClassLiteral(literal) => Some(Self::Class(literal.default_specialization(db))),
Type::GenericAlias(generic) => Some(Self::Class(ClassType::Generic(generic))),
Type::NominalInstance(instance)
if instance.class(db).is_known(db, KnownClass::GenericAlias) =>
if instance
.class_if_not_newtype(db)
.is_some_and(|class| class.is_known(db, KnownClass::GenericAlias)) =>
{
Self::try_from_type(db, todo_type!("GenericAlias instance"), subclass)
}
@@ -164,7 +166,8 @@ impl<'db> ClassBase<'db> {
KnownInstanceType::TypeAliasType(_)
| KnownInstanceType::TypeVar(_)
| KnownInstanceType::Deprecated(_)
| KnownInstanceType::Field(_) => None,
| KnownInstanceType::Field(_)
| KnownInstanceType::NewType(_) => None,
},
Type::SpecialForm(special_form) => match special_form {

View File

@@ -12,6 +12,7 @@ pub enum TypeDefinition<'db> {
Function(Definition<'db>),
TypeVar(Definition<'db>),
TypeAlias(Definition<'db>),
NewType(Definition<'db>),
}
impl TypeDefinition<'_> {
@@ -21,7 +22,8 @@ impl TypeDefinition<'_> {
Self::Class(definition)
| Self::Function(definition)
| Self::TypeVar(definition)
| Self::TypeAlias(definition) => {
| Self::TypeAlias(definition)
| Self::NewType(definition) => {
let module = parsed_module(db, definition.file(db)).load(db);
Some(definition.focus_range(db, &module))
}
@@ -38,7 +40,8 @@ impl TypeDefinition<'_> {
Self::Class(definition)
| Self::Function(definition)
| Self::TypeVar(definition)
| Self::TypeAlias(definition) => {
| Self::TypeAlias(definition)
| Self::NewType(definition) => {
let module = parsed_module(db, definition.file(db)).load(db);
Some(definition.full_range(db, &module))
}

View File

@@ -61,6 +61,7 @@ pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) {
registry.register_lint(&INVALID_GENERIC_CLASS);
registry.register_lint(&INVALID_LEGACY_TYPE_VARIABLE);
registry.register_lint(&INVALID_TYPE_ALIAS_TYPE);
registry.register_lint(&INVALID_NEWTYPE);
registry.register_lint(&INVALID_METACLASS);
registry.register_lint(&INVALID_OVERLOAD);
registry.register_lint(&INVALID_PARAMETER_DEFAULT);
@@ -886,6 +887,27 @@ declare_lint! {
}
}
declare_lint! {
/// ## What it does
/// Checks for the creation of invalid `NewType`s
///
/// ## Why is this bad?
/// There are several requirements that you must follow when creating a `NewType`.
///
/// ## Examples
/// ```python
/// from typing import NewType
///
/// Foo = NewType("Foo", int) # okay
/// Bar = NewType(get_name(), int) # error: NewType name must be a string literal
/// ```
pub(crate) static INVALID_NEWTYPE = {
summary: "detects invalid NewType definitions",
status: LintStatus::preview("1.0.0"),
default_level: Level::Error,
}
}
declare_lint! {
/// ## What it does
/// Checks for arguments to `metaclass=` that are invalid.
@@ -1976,7 +1998,8 @@ pub(super) fn report_invalid_assignment(
fn type_to_class_literal<'db>(ty: Type<'db>, db: &'db dyn crate::Db) -> Option<ClassLiteral<'db>> {
match ty {
Type::ClassLiteral(class) => Some(class),
Type::NominalInstance(instance) => match instance.class(db) {
// TODO: How should we handle NewTypes here?
Type::NominalInstance(instance) => match instance.class_ignoring_newtype(db) {
crate::types::class::ClassType::NonGeneric(class) => Some(class),
crate::types::class::ClassType::Generic(alias) => Some(alias.origin(db)),
},
@@ -2591,7 +2614,13 @@ pub(crate) fn report_undeclared_protocol_member(
SubclassOfInner::Dynamic(DynamicType::Any) => return true,
SubclassOfInner::Dynamic(_) => return false,
},
Type::NominalInstance(instance) => instance.class(db),
Type::NominalInstance(instance) => {
if let Some(class) = instance.class_if_not_newtype(db) {
class
} else {
return false;
}
}
_ => return false,
};

View File

@@ -187,21 +187,22 @@ impl Display for DisplayRepresentation<'_> {
Type::Dynamic(dynamic) => dynamic.fmt(f),
Type::Never => f.write_str("Never"),
Type::NominalInstance(instance) => {
let class = instance.class(self.db);
let (class, newtype) = instance.class_and_newtype(self.db);
match (class, class.known(self.db)) {
(_, Some(KnownClass::NoneType)) => f.write_str("None"),
(_, Some(KnownClass::NoDefaultType)) => f.write_str("NoDefault"),
(ClassType::Generic(alias), Some(KnownClass::Tuple)) => alias
match (newtype, class, class.known(self.db)) {
(Some(newtype), _, _) => f.write_str(newtype.name(self.db)),
(None, _, Some(KnownClass::NoneType)) => f.write_str("None"),
(None, _, Some(KnownClass::NoDefaultType)) => f.write_str("NoDefault"),
(None, ClassType::Generic(alias), Some(KnownClass::Tuple)) => alias
.specialization(self.db)
.tuple(self.db)
.expect("Specialization::tuple() should always return `Some()` for `KnownClass::Tuple`")
.display_with(self.db, self.settings)
.fmt(f),
(ClassType::NonGeneric(class), _) => {
(None, ClassType::NonGeneric(class), _) => {
self.write_maybe_qualified_class(f, class)
},
(ClassType::Generic(alias), _) => alias.display_with(self.db, self.settings).fmt(f),
(None, ClassType::Generic(alias), _) => alias.display_with(self.db, self.settings).fmt(f),
}
}
Type::ProtocolInstance(protocol) => match protocol.inner {

View File

@@ -130,26 +130,28 @@ pub(crate) fn enum_metadata<'db>(
// Some types are specifically disallowed for enum members.
return None;
}
Type::NominalInstance(instance) => match instance.class(db).known(db) {
// enum.nonmember
Some(KnownClass::Nonmember) => return None,
Type::NominalInstance(instance) => {
match instance.class_ignoring_newtype(db).known(db) {
// enum.nonmember
Some(KnownClass::Nonmember) => return None,
// enum.member
Some(KnownClass::Member) => Some(
ty.member(db, "value")
.place
.ignore_possibly_unbound()
.unwrap_or(Type::unknown()),
),
// enum.member
Some(KnownClass::Member) => Some(
ty.member(db, "value")
.place
.ignore_possibly_unbound()
.unwrap_or(Type::unknown()),
),
// enum.auto
Some(KnownClass::Auto) => {
auto_counter += 1;
Some(Type::IntLiteral(auto_counter))
// enum.auto
Some(KnownClass::Auto) => {
auto_counter += 1;
Some(Type::IntLiteral(auto_counter))
}
_ => None,
}
_ => None,
},
}
_ => None,
};
@@ -208,7 +210,10 @@ pub(crate) fn enum_metadata<'db>(
PlaceAndQualifiers {
place: Place::Type(Type::NominalInstance(instance), _),
..
} if instance.class(db).is_known(db, KnownClass::Member) => {
} if instance
.class_ignoring_newtype(db) // TODO: what could it mean to wrap `enum.member` in a NewType?
.is_known(db, KnownClass::Member) =>
{
// If the attribute is specifically declared with `enum.member`, it is considered a member
}
_ => {

View File

@@ -953,7 +953,7 @@ fn is_instance_truthiness<'db>(
let is_instance = |ty: &Type<'_>| {
if let Type::NominalInstance(instance) = ty {
if instance
.class(db)
.class_ignoring_newtype(db)
.iter_mro(db)
.filter_map(ClassBase::into_class)
.any(|c| match c {

View File

@@ -1119,9 +1119,9 @@ impl<'db> SpecializationBuilder<'db> {
// Extract formal_alias if this is a generic class
let formal_alias = match formal {
Type::NominalInstance(formal_nominal) => {
formal_nominal.class(self.db).into_generic_alias()
}
Type::NominalInstance(formal_nominal) => formal_nominal
.class_if_not_newtype(self.db)
.and_then(ClassType::into_generic_alias),
// TODO: This will only handle classes that explicit implement a generic protocol
// by listing it as a base class. To handle classes that implicitly implement a
// generic protocol, we will need to check the types of the protocol members to be
@@ -1135,7 +1135,10 @@ impl<'db> SpecializationBuilder<'db> {
if let Some(formal_alias) = formal_alias {
let formal_origin = formal_alias.origin(self.db);
for base in actual_nominal.class(self.db).iter_mro(self.db) {
for base in actual_nominal
.class_ignoring_newtype(self.db)
.iter_mro(self.db)
{
let ClassBase::Class(ClassType::Generic(base_alias)) = base else {
continue;
};

View File

@@ -95,7 +95,8 @@ impl<'db> AllMembers<'db> {
),
Type::NominalInstance(instance) => {
let (class_literal, _specialization) = instance.class(db).class_literal(db);
let (class_literal, _specialization) =
instance.class_ignoring_newtype(db).class_literal(db);
self.extend_with_instance_members(db, ty, class_literal);
}
@@ -211,7 +212,9 @@ impl<'db> AllMembers<'db> {
match ty {
Type::NominalInstance(instance)
if matches!(
instance.class(db).known(db),
instance
.class_if_not_newtype(db)
.and_then(|class| class.known(db)),
Some(
KnownClass::TypeVar
| KnownClass::TypeVarTuple

View File

@@ -337,45 +337,6 @@ fn single_expression_cycle_initial<'db>(
Type::Never
}
/// Returns the statically-known truthiness of a given expression.
///
/// Returns [`Truthiness::Ambiguous`] in case any non-definitely bound places
/// were encountered while inferring the type of the expression.
#[salsa::tracked(cycle_fn=static_expression_truthiness_cycle_recover, cycle_initial=static_expression_truthiness_cycle_initial, heap_size=get_size2::GetSize::get_heap_size)]
pub(crate) fn static_expression_truthiness<'db>(
db: &'db dyn Db,
expression: Expression<'db>,
) -> Truthiness {
let inference = infer_expression_types(db, expression);
if !inference.all_places_definitely_bound() {
return Truthiness::Ambiguous;
}
let file = expression.file(db);
let module = parsed_module(db, file).load(db);
let node = expression.node_ref(db, &module);
inference.expression_type(node).bool(db)
}
#[expect(clippy::trivially_copy_pass_by_ref)]
fn static_expression_truthiness_cycle_recover<'db>(
_db: &'db dyn Db,
_value: &Truthiness,
_count: u32,
_expression: Expression<'db>,
) -> salsa::CycleRecoveryAction<Truthiness> {
salsa::CycleRecoveryAction::Iterate
}
fn static_expression_truthiness_cycle_initial<'db>(
_db: &'db dyn Db,
_expression: Expression<'db>,
) -> Truthiness {
Truthiness::Ambiguous
}
/// Infer the types for an [`Unpack`] operation.
///
/// This infers the expression type and performs structural match against the target expression
@@ -696,9 +657,6 @@ struct ExpressionInferenceExtra<'db> {
///
/// Falls back to `Type::Never` if an expression is missing.
cycle_fallback: bool,
/// `true` if all places in this expression are definitely bound
all_definitely_bound: bool,
}
impl<'db> ExpressionInference<'db> {
@@ -707,7 +665,6 @@ impl<'db> ExpressionInference<'db> {
Self {
extra: Some(Box::new(ExpressionInferenceExtra {
cycle_fallback: true,
all_definitely_bound: true,
..ExpressionInferenceExtra::default()
})),
expressions: FxHashMap::default(),
@@ -741,14 +698,6 @@ impl<'db> ExpressionInference<'db> {
fn fallback_type(&self) -> Option<Type<'db>> {
self.is_cycle_callback().then_some(Type::Never)
}
/// Returns true if all places in this expression are definitely bound.
pub(crate) fn all_places_definitely_bound(&self) -> bool {
self.extra
.as_ref()
.map(|e| e.all_definitely_bound)
.unwrap_or(true)
}
}
/// Whether the intersection type is on the left or right side of the comparison.
@@ -898,9 +847,6 @@ pub(super) struct TypeInferenceBuilder<'db, 'ast> {
///
/// This is used only when constructing a cycle-recovery `TypeInference`.
cycle_fallback: bool,
/// `true` if all places in this expression are definitely bound
all_definitely_bound: bool,
}
impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
@@ -934,7 +880,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
deferred: VecSet::default(),
undecorated_type: None,
cycle_fallback: false,
all_definitely_bound: true,
}
}
@@ -1878,7 +1823,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
Type::BooleanLiteral(_) | Type::IntLiteral(_) => {}
Type::NominalInstance(instance)
if matches!(
instance.class(self.db()).known(self.db()),
instance.class_ignoring_newtype(self.db()).known(self.db()),
Some(KnownClass::Float | KnownClass::Int | KnownClass::Bool)
) => {}
_ => return false,
@@ -4059,7 +4004,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
// Super instances do not allow attribute assignment
Type::NominalInstance(instance)
if instance.class(db).is_known(db, KnownClass::Super) =>
if instance
.class_ignoring_newtype(db)
.is_known(db, KnownClass::Super) =>
{
if emit_diagnostics {
if let Some(builder) = self.context.report_lint(&INVALID_ASSIGNMENT, target) {
@@ -6329,6 +6276,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
| KnownClass::TypeVar
| KnownClass::TypeAliasType
| KnownClass::Deprecated
| KnownClass::NewType
)
) || (
// Constructor calls to `tuple` and subclasses of `tuple` are handled in `Type::Bindings`,
@@ -6669,7 +6617,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
let (resolved, constraint_keys) =
self.infer_place_load(PlaceExprRef::from(&expr), ast::ExprRef::Name(name_node));
let resolved_after_fallback = resolved
resolved
// Not found in the module's explicitly declared global symbols?
// Check the "implicit globals" such as `__doc__`, `__file__`, `__name__`, etc.
// These are looked up as attributes on `types.ModuleType`.
@@ -6705,14 +6653,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
} else {
Place::Unbound.into()
}
});
if !resolved_after_fallback.place.is_definitely_bound() {
self.all_definitely_bound = false;
}
let ty =
resolved_after_fallback.unwrap_with_diagnostic(|lookup_error| match lookup_error {
})
.unwrap_with_diagnostic(|lookup_error| match lookup_error {
LookupError::Unbound(qualifiers) => {
self.report_unresolved_reference(name_node);
TypeAndQualifiers::new(Type::unknown(), qualifiers)
@@ -6723,9 +6665,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
}
type_when_bound
}
});
ty.inner_type()
})
.inner_type()
}
fn infer_local_place_load(
@@ -7155,7 +7096,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
}
fn narrow_expr_with_applicable_constraints<'r>(
&mut self,
&self,
target: impl Into<ast::ExprRef<'r>>,
target_ty: Type<'db>,
constraint_keys: &[(FileScopeId, ConstraintKey)],
@@ -7198,19 +7139,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
assigned_type = Some(ty);
}
}
let fallback_place = value_type.member(db, &attr.id);
if !fallback_place.place.is_definitely_bound()
|| fallback_place
.qualifiers
.contains(TypeQualifiers::POSSIBLY_UNBOUND_IMPLICIT_ATTRIBUTE)
{
self.all_definitely_bound = false;
}
let resolved_type =
fallback_place.map_type(|ty| {
self.narrow_expr_with_applicable_constraints(attribute, ty, &constraint_keys)
}).unwrap_with_diagnostic(|lookup_error| match lookup_error {
let resolved_type = value_type
.member(db, &attr.id)
.map_type(|ty| self.narrow_expr_with_applicable_constraints(attribute, ty, &constraint_keys))
.unwrap_with_diagnostic(|lookup_error| match lookup_error {
LookupError::Unbound(_) => {
let report_unresolved_attribute = self.is_reachable(attribute);
@@ -9227,7 +9160,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
Type::Dynamic(DynamicType::TodoUnpack) => Err(GenericContextError::NotYetSupported),
Type::NominalInstance(nominal)
if matches!(
nominal.class(self.db()).known(self.db()),
nominal.class_ignoring_newtype(self.db()).known(self.db()),
Some(KnownClass::TypeVarTuple | KnownClass::ParamSpec)
) =>
{
@@ -9271,8 +9204,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
Some(ty @ (Type::IntLiteral(_) | Type::BooleanLiteral(_))) => SliceArg::Arg(ty),
Some(ty @ Type::NominalInstance(instance))
if instance
.class(self.db())
.is_known(self.db(), KnownClass::NoneType) =>
.class_if_not_newtype(self.db())
.is_some_and(|class| class.is_known(self.db(), KnownClass::NoneType)) =>
{
SliceArg::Arg(ty)
}
@@ -9318,7 +9251,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
declarations,
deferred,
cycle_fallback,
all_definitely_bound,
// Ignored; only relevant to definition regions
undecorated_type: _,
@@ -9345,7 +9277,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
);
let extra =
(cycle_fallback || !bindings.is_empty() || !diagnostics.is_empty() || !all_definitely_bound).then(|| {
(cycle_fallback || !bindings.is_empty() || !diagnostics.is_empty()).then(|| {
if bindings.len() > 20 {
tracing::debug!(
"Inferred expression region `{:?}` contains {} bindings. Lookups by linear scan might be slow.",
@@ -9358,7 +9290,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
bindings: bindings.into_boxed_slice(),
diagnostics,
cycle_fallback,
all_definitely_bound,
})
});
@@ -9384,7 +9315,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
deferred,
cycle_fallback,
undecorated_type,
all_definitely_bound: _,
// builder only state
typevar_binding_context: _,
deferred_state: _,
@@ -9451,7 +9382,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
deferred: _,
bindings: _,
declarations: _,
all_definitely_bound: _,
// Ignored; only relevant to definition regions
undecorated_type: _,
@@ -10499,6 +10429,16 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
self.infer_type_expression(&subscript.slice);
todo_type!("Generic PEP-695 type alias")
}
KnownInstanceType::NewType(newtype) => {
self.infer_type_expression(&subscript.slice);
if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) {
builder.into_diagnostic(format_args!(
"`{}` is a `NewType` and cannot be specialized",
newtype.name(self.db())
));
}
Type::unknown()
}
},
Type::Dynamic(DynamicType::Todo(_)) => {
self.infer_type_expression(slice);
@@ -11214,7 +11154,8 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
}
Type::NominalInstance(nominal)
if nominal
.class(self.db())
// TODO: What could NewType mean here?
.class_ignoring_newtype(self.db())
.is_known(self.db(), KnownClass::ParamSpec) =>
{
return Some(Parameters::todo());

View File

@@ -13,8 +13,8 @@ use crate::types::protocol_class::walk_protocol_interface;
use crate::types::tuple::{TupleSpec, TupleType};
use crate::types::{
ApplyTypeMappingVisitor, ClassBase, HasRelationToVisitor, IsDisjointVisitor,
IsEquivalentVisitor, MaterializationKind, NormalizedVisitor, TypeMapping, TypeRelation,
VarianceInferable,
IsEquivalentVisitor, MaterializationKind, NewTypeInstance, NormalizedVisitor, TypeMapping,
TypeRelation, VarianceInferable,
};
use crate::{Db, FxOrderSet};
@@ -41,6 +41,10 @@ impl<'db> Type<'db> {
}
}
pub(crate) fn newtype_nominal_instance(newtype: NewTypeInstance<'db>) -> Self {
Type::NominalInstance(NominalInstanceType(NominalInstanceInner::NewType(newtype)))
}
pub(crate) fn tuple(tuple: Option<TupleType<'db>>) -> Self {
let Some(tuple) = tuple else {
return Type::Never;
@@ -75,7 +79,7 @@ impl<'db> Type<'db> {
/// **Private** helper function to create a `Type::NominalInstance` from a class that
/// is known not to be `Any`, a protocol class, or a typed dict class.
fn non_tuple_instance(class: ClassType<'db>) -> Self {
Type::NominalInstance(NominalInstanceType(NominalInstanceInner::NonTuple(class)))
Type::NominalInstance(NominalInstanceType(NominalInstanceInner::Class(class)))
}
pub(crate) const fn into_nominal_instance(self) -> Option<NominalInstanceType<'db>> {
@@ -130,56 +134,75 @@ pub(super) fn walk_nominal_instance_type<'db, V: super::visitor::TypeVisitor<'db
nominal: NominalInstanceType<'db>,
visitor: &V,
) {
visitor.visit_type(db, nominal.class(db).into());
visitor.visit_type(db, nominal.class_ignoring_newtype(db).into());
}
impl<'db> NominalInstanceType<'db> {
pub(super) fn class(&self, db: &'db dyn Db) -> ClassType<'db> {
pub(super) fn class_and_newtype(
&self,
db: &'db dyn Db,
) -> (ClassType<'db>, Option<NewTypeInstance<'db>>) {
match self.0 {
NominalInstanceInner::ExactTuple(tuple) => tuple.to_class_type(db),
NominalInstanceInner::NonTuple(class) => class,
NominalInstanceInner::ExactTuple(tuple) => (tuple.to_class_type(db), None),
NominalInstanceInner::NewType(newtype) => (newtype.base_class_type(db), Some(newtype)),
NominalInstanceInner::Class(class) => (class, None),
}
}
pub(super) fn class_ignoring_newtype(&self, db: &'db dyn Db) -> ClassType<'db> {
self.class_and_newtype(db).0
}
pub(super) fn class_if_not_newtype(&self, db: &'db dyn Db) -> Option<ClassType<'db>> {
match self.0 {
NominalInstanceInner::NewType(_) => None,
_ => Some(self.class_ignoring_newtype(db)),
}
}
pub(super) fn is_newtype(&self) -> bool {
matches!(self.0, NominalInstanceInner::NewType(_))
}
/// If this is an instance type where the class has a tuple spec, returns the tuple spec.
///
/// I.e., for the type `tuple[int, str]`, this will return the tuple spec `[int, str]`.
/// For a subclass of `tuple[int, str]`, it will return the same tuple spec.
pub(super) fn tuple_spec(&self, db: &'db dyn Db) -> Option<Cow<'db, TupleSpec<'db>>> {
let from_class_type = |class: ClassType<'db>| {
// Avoid an expensive MRO traversal for common stdlib classes.
if class
.known(db)
.is_some_and(|known_class| !known_class.is_tuple_subclass())
{
return None;
}
class
.iter_mro(db)
.filter_map(ClassBase::into_class)
.find_map(|class| match class.known(db)? {
// N.B. this is a pure optimisation: iterating through the MRO would give us
// the correct tuple spec for `sys._version_info`, since we special-case the class
// in `ClassLiteral::explicit_bases()` so that it is inferred as inheriting from
// a tuple type with the correct spec for the user's configured Python version and platform.
KnownClass::VersionInfo => Some(Cow::Owned(TupleSpec::version_info_spec(db))),
KnownClass::Tuple => Some(
class
.into_generic_alias()
.and_then(|alias| {
Some(Cow::Borrowed(alias.specialization(db).tuple(db)?))
})
.unwrap_or_else(|| Cow::Owned(TupleSpec::homogeneous(Type::unknown()))),
),
_ => None,
})
};
match self.0 {
NominalInstanceInner::ExactTuple(tuple) => Some(Cow::Borrowed(tuple.tuple(db))),
NominalInstanceInner::NonTuple(class) => {
// Avoid an expensive MRO traversal for common stdlib classes.
if class
.known(db)
.is_some_and(|known_class| !known_class.is_tuple_subclass())
{
return None;
}
class
.iter_mro(db)
.filter_map(ClassBase::into_class)
.find_map(|class| match class.known(db)? {
// N.B. this is a pure optimisation: iterating through the MRO would give us
// the correct tuple spec for `sys._version_info`, since we special-case the class
// in `ClassLiteral::explicit_bases()` so that it is inferred as inheriting from
// a tuple type with the correct spec for the user's configured Python version and platform.
KnownClass::VersionInfo => {
Some(Cow::Owned(TupleSpec::version_info_spec(db)))
}
KnownClass::Tuple => Some(
class
.into_generic_alias()
.and_then(|alias| {
Some(Cow::Borrowed(alias.specialization(db).tuple(db)?))
})
.unwrap_or_else(|| {
Cow::Owned(TupleSpec::homogeneous(Type::unknown()))
}),
),
_ => None,
})
NominalInstanceInner::NewType(declaration) => {
from_class_type(declaration.base_class_type(db))
}
NominalInstanceInner::Class(class) => from_class_type(class),
}
}
@@ -187,7 +210,10 @@ impl<'db> NominalInstanceType<'db> {
pub(super) fn is_object(self, db: &'db dyn Db) -> bool {
match self.0 {
NominalInstanceInner::ExactTuple(_) => false,
NominalInstanceInner::NonTuple(class) => class.is_object(db),
NominalInstanceInner::NewType(declaration) => {
declaration.base_class_type(db).is_object(db)
}
NominalInstanceInner::Class(class) => class.is_object(db),
}
}
@@ -204,7 +230,8 @@ impl<'db> NominalInstanceType<'db> {
pub(super) fn own_tuple_spec(&self, db: &'db dyn Db) -> Option<Cow<'db, TupleSpec<'db>>> {
match self.0 {
NominalInstanceInner::ExactTuple(tuple) => Some(Cow::Borrowed(tuple.tuple(db))),
NominalInstanceInner::NonTuple(_) => None,
NominalInstanceInner::NewType(_) => None,
NominalInstanceInner::Class(_) => None,
}
}
@@ -216,7 +243,8 @@ impl<'db> NominalInstanceType<'db> {
pub(crate) fn slice_literal(self, db: &'db dyn Db) -> Option<SliceLiteral> {
let class = match self.0 {
NominalInstanceInner::ExactTuple(_) => return None,
NominalInstanceInner::NonTuple(class) => class,
NominalInstanceInner::NewType(_) => return None,
NominalInstanceInner::Class(class) => class,
};
let (class, Some(specialization)) = class.class_literal(db) else {
return None;
@@ -232,7 +260,9 @@ impl<'db> NominalInstanceType<'db> {
Type::IntLiteral(n) => i32::try_from(*n).map(Some).ok(),
Type::BooleanLiteral(b) => Some(Some(i32::from(*b))),
Type::NominalInstance(instance)
if instance.class(db).is_known(db, KnownClass::NoneType) =>
if instance
.class_ignoring_newtype(db)
.is_known(db, KnownClass::NoneType) =>
{
Some(None)
}
@@ -254,7 +284,10 @@ impl<'db> NominalInstanceType<'db> {
NominalInstanceInner::ExactTuple(tuple) => {
Type::tuple(tuple.normalized_impl(db, visitor))
}
NominalInstanceInner::NonTuple(class) => {
NominalInstanceInner::NewType(newtype) => {
Type::newtype_nominal_instance(newtype.normalized_impl(db, visitor))
}
NominalInstanceInner::Class(class) => {
Type::non_tuple_instance(class.normalized_impl(db, visitor))
}
}
@@ -269,7 +302,10 @@ impl<'db> NominalInstanceType<'db> {
NominalInstanceInner::ExactTuple(tuple) => {
Type::tuple(tuple.materialize(db, materialization_kind))
}
NominalInstanceInner::NonTuple(class) => {
NominalInstanceInner::NewType(newtype) => {
Type::newtype_nominal_instance(newtype.materialize(db, materialization_kind))
}
NominalInstanceInner::Class(class) => {
Type::non_tuple_instance(class.materialize(db, materialization_kind))
}
}
@@ -283,13 +319,25 @@ impl<'db> NominalInstanceType<'db> {
visitor: &HasRelationToVisitor<'db, C>,
) -> C {
match (self.0, other.0) {
(NominalInstanceInner::NewType(newtype1), NominalInstanceInner::NewType(newtype2)) => {
newtype1.has_relation_to_impl(db, newtype2, relation, visitor)
}
// A non-NewType is never a subtype of a NewType.
(_, NominalInstanceInner::NewType(_)) => C::unsatisfiable(db),
(NominalInstanceInner::NewType(newtype), _) => {
Self(NominalInstanceInner::Class(newtype.base_class_type(db)))
.has_relation_to_impl(db, other, relation, visitor)
}
(
NominalInstanceInner::ExactTuple(tuple1),
NominalInstanceInner::ExactTuple(tuple2),
) => tuple1.has_relation_to_impl(db, tuple2, relation, visitor),
_ => self
.class(db)
.has_relation_to_impl(db, other.class(db), relation, visitor),
_ => self.class_ignoring_newtype(db).has_relation_to_impl(
db,
other.class_ignoring_newtype(db),
relation,
visitor,
),
}
}
@@ -304,7 +352,7 @@ impl<'db> NominalInstanceType<'db> {
NominalInstanceInner::ExactTuple(tuple1),
NominalInstanceInner::ExactTuple(tuple2),
) => tuple1.is_equivalent_to_impl(db, tuple2, visitor),
(NominalInstanceInner::NonTuple(class1), NominalInstanceInner::NonTuple(class2)) => {
(NominalInstanceInner::Class(class1), NominalInstanceInner::Class(class2)) => {
class1.is_equivalent_to_impl(db, class2, visitor)
}
_ => C::unsatisfiable(db),
@@ -327,10 +375,14 @@ impl<'db> NominalInstanceType<'db> {
}
}
result.or(db, || {
C::from_bool(
db,
!(self.class(db)).could_coexist_in_mro_with(db, other.class(db)),
)
let (self_class, self_newtype) = self.class_and_newtype(db);
let (other_class, other_newtype) = other.class_and_newtype(db);
match (self_newtype, other_newtype) {
(Some(self_newtype), Some(other_newtype)) => {
self_newtype.is_disjoint_from_impl(db, other_newtype, visitor)
}
_ => C::from_bool(db, !self_class.could_coexist_in_mro_with(db, other_class)),
}
})
}
@@ -342,7 +394,8 @@ impl<'db> NominalInstanceType<'db> {
// See:
// https://docs.python.org/3/reference/expressions.html#parenthesized-forms
NominalInstanceInner::ExactTuple(_) => false,
NominalInstanceInner::NonTuple(class) => class
NominalInstanceInner::NewType(_) => false,
NominalInstanceInner::Class(class) => class
.known(db)
.map(KnownClass::is_singleton)
.unwrap_or_else(|| is_single_member_enum(db, class.class_literal(db).0)),
@@ -350,18 +403,24 @@ impl<'db> NominalInstanceType<'db> {
}
pub(super) fn is_single_valued(self, db: &'db dyn Db) -> bool {
match self.0 {
NominalInstanceInner::ExactTuple(tuple) => tuple.is_single_valued(db),
NominalInstanceInner::NonTuple(class) => class
let class_type_is_single_valued = |class: ClassType<'db>| {
class
.known(db)
.and_then(KnownClass::is_single_valued)
.or_else(|| Some(self.tuple_spec(db)?.is_single_valued(db)))
.unwrap_or_else(|| is_single_member_enum(db, class.class_literal(db).0)),
.unwrap_or_else(|| is_single_member_enum(db, class.class_literal(db).0))
};
match self.0 {
NominalInstanceInner::ExactTuple(tuple) => tuple.is_single_valued(db),
NominalInstanceInner::NewType(newtype) => {
class_type_is_single_valued(newtype.base_class_type(db))
}
NominalInstanceInner::Class(class) => class_type_is_single_valued(class),
}
}
pub(super) fn to_meta_type(self, db: &'db dyn Db) -> Type<'db> {
SubclassOfType::from(db, self.class(db))
SubclassOfType::from(db, self.class_ignoring_newtype(db))
}
pub(super) fn apply_type_mapping_impl<'a>(
@@ -374,7 +433,10 @@ impl<'db> NominalInstanceType<'db> {
NominalInstanceInner::ExactTuple(tuple) => {
Type::tuple(tuple.apply_type_mapping_impl(db, type_mapping, visitor))
}
NominalInstanceInner::NonTuple(class) => {
NominalInstanceInner::NewType(newtype) => Type::newtype_nominal_instance(
newtype.apply_type_mapping_impl(db, type_mapping, visitor),
),
NominalInstanceInner::Class(class) => {
Type::non_tuple_instance(class.apply_type_mapping_impl(db, type_mapping, visitor))
}
}
@@ -390,7 +452,12 @@ impl<'db> NominalInstanceType<'db> {
NominalInstanceInner::ExactTuple(tuple) => {
tuple.find_legacy_typevars(db, binding_context, typevars);
}
NominalInstanceInner::NonTuple(class) => {
NominalInstanceInner::NewType(newtype) => {
newtype
.base_class_type(db)
.find_legacy_typevars(db, binding_context, typevars);
}
NominalInstanceInner::Class(class) => {
class.find_legacy_typevars(db, binding_context, typevars);
}
}
@@ -413,12 +480,19 @@ enum NominalInstanceInner<'db> {
/// Note that the type `tuple[int, str]` includes subtypes of `tuple[int, str]`,
/// but those subtypes would be represented using the `NonTuple` variant.
ExactTuple(TupleType<'db>),
/// A `NewType` instance type, e.g. `NewType("Foo", int)`.
///
/// These instances are the return values of the return values of (sic) `typing.NewType`. See
/// `NewTypeInstance` for an example.
NewType(NewTypeInstance<'db>),
/// Any instance type that does not represent some kind of instance of the
/// builtin `tuple` class.
///
/// This variant includes types that are subtypes of "exact tuple" types,
/// because they represent "all instances of a class that is a tuple subclass".
NonTuple(ClassType<'db>),
Class(ClassType<'db>),
}
pub(crate) struct SliceLiteral {
@@ -429,7 +503,7 @@ pub(crate) struct SliceLiteral {
impl<'db> VarianceInferable<'db> for NominalInstanceType<'db> {
fn variance_of(self, db: &'db dyn Db, typevar: BoundTypeVarInstance<'db>) -> TypeVarVariance {
self.class(db).variance_of(db, typevar)
self.class_ignoring_newtype(db).variance_of(db, typevar)
}
}

View File

@@ -554,7 +554,9 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
}
// Treat `bool` as `Literal[True, False]`.
Type::NominalInstance(instance)
if instance.class(db).is_known(db, KnownClass::Bool) =>
if instance
.class_if_not_newtype(db)
.is_some_and(|class| class.is_known(db, KnownClass::Bool)) =>
{
UnionType::from_elements(
db,
@@ -565,13 +567,22 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
}
// Treat enums as a union of their members.
Type::NominalInstance(instance)
if enum_metadata(db, instance.class(db).class_literal(db).0).is_some() =>
if !instance.is_newtype()
&& enum_metadata(
db,
instance.class_ignoring_newtype(db).class_literal(db).0,
)
.is_some() =>
{
UnionType::from_elements(
db,
enum_member_literals(db, instance.class(db).class_literal(db).0, None)
.expect("Calling `enum_member_literals` on an enum class")
.map(|ty| filter_to_cannot_be_equal(db, ty, rhs_ty)),
enum_member_literals(
db,
instance.class_ignoring_newtype(db).class_literal(db).0,
None,
)
.expect("Calling `enum_member_literals` on an enum class")
.map(|ty| filter_to_cannot_be_equal(db, ty, rhs_ty)),
)
}
_ => {
@@ -596,7 +607,9 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
fn evaluate_expr_ne(&mut self, lhs_ty: Type<'db>, rhs_ty: Type<'db>) -> Option<Type<'db>> {
match (lhs_ty, rhs_ty) {
(Type::NominalInstance(instance), Type::IntLiteral(i))
if instance.class(self.db).is_known(self.db, KnownClass::Bool) =>
if instance
.class_if_not_newtype(self.db)
.is_some_and(|class| class.is_known(self.db, KnownClass::Bool)) =>
{
if i == 0 {
Some(Type::BooleanLiteral(false).negate(self.db))

View File

@@ -153,7 +153,7 @@ impl Ty {
.place
.expect_type();
debug_assert!(
matches!(ty, Type::NominalInstance(instance) if is_single_member_enum(db, instance.class(db).class_literal(db).0))
matches!(ty, Type::NominalInstance(instance) if is_single_member_enum(db, instance.class_ignoring_newtype(db).class_literal(db).0))
);
ty
}

View File

@@ -131,7 +131,15 @@ pub(super) fn union_or_intersection_elements_ordering<'db>(
(_, Type::TypeIs(_)) => Ordering::Greater,
(Type::NominalInstance(left), Type::NominalInstance(right)) => {
left.class(db).cmp(&right.class(db))
let (left_class, left_newtype) = left.class_and_newtype(db);
let (right_class, right_newtype) = right.class_and_newtype(db);
// NewTypes order after regular classes.
match (left_newtype, right_newtype) {
(None, None) => left_class.cmp(&right_class),
(Some(_), None) => Ordering::Greater,
(None, Some(_)) => Ordering::Less,
(Some(left_newtype), Some(right_newtype)) => left_newtype.cmp(&right_newtype),
}
}
(Type::NominalInstance(_), _) => Ordering::Less,
(_, Type::NominalInstance(_)) => Ordering::Greater,
@@ -180,7 +188,17 @@ pub(super) fn union_or_intersection_elements_ordering<'db>(
(SuperOwnerKind::Class(_), _) => Ordering::Less,
(_, SuperOwnerKind::Class(_)) => Ordering::Greater,
(SuperOwnerKind::Instance(left), SuperOwnerKind::Instance(right)) => {
left.class(db).cmp(&right.class(db))
let (left_class, left_newtype) = left.class_and_newtype(db);
let (right_class, right_newtype) = right.class_and_newtype(db);
// NewTypes order after regular classes.
match (left_newtype, right_newtype) {
(None, None) => left_class.cmp(&right_class),
(Some(_), None) => Ordering::Greater,
(None, Some(_)) => Ordering::Less,
(Some(left_newtype), Some(right_newtype)) => {
left_newtype.cmp(&right_newtype)
}
}
}
(SuperOwnerKind::Instance(_), _) => Ordering::Less,
(_, SuperOwnerKind::Instance(_)) => Ordering::Greater,

View File

@@ -80,7 +80,7 @@ You can add the following configuration to `.gitlab-ci.yml` to run a `ruff forma
stage: build
interruptible: true
image:
name: ghcr.io/astral-sh/ruff:0.12.11-alpine
name: ghcr.io/astral-sh/ruff:0.12.10-alpine
before_script:
- cd $CI_PROJECT_DIR
- ruff --version
@@ -106,7 +106,7 @@ Ruff can be used as a [pre-commit](https://pre-commit.com) hook via [`ruff-pre-c
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.12.11
rev: v0.12.10
hooks:
# Run the linter.
- id: ruff-check
@@ -119,7 +119,7 @@ To enable lint fixes, add the `--fix` argument to the lint hook:
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.12.11
rev: v0.12.10
hooks:
# Run the linter.
- id: ruff-check
@@ -133,7 +133,7 @@ To avoid running on Jupyter Notebooks, remove `jupyter` from the list of allowed
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.12.11
rev: v0.12.10
hooks:
# Run the linter.
- id: ruff-check

View File

@@ -369,7 +369,7 @@ This tutorial has focused on Ruff's command-line interface, but Ruff can also be
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.12.11
rev: v0.12.10
hooks:
# Run the linter.
- id: ruff

View File

@@ -4,7 +4,7 @@ build-backend = "maturin"
[project]
name = "ruff"
version = "0.12.11"
version = "0.12.10"
description = "An extremely fast Python linter and code formatter, written in Rust."
authors = [{ name = "Astral Software Inc.", email = "hey@astral.sh" }]
readme = "README.md"

View File

@@ -1,6 +1,6 @@
[project]
name = "scripts"
version = "0.12.11"
version = "0.12.10"
description = ""
authors = ["Charles Marsh <charlie.r.marsh@gmail.com>"]

10
ty.schema.json generated
View File

@@ -581,6 +581,16 @@
}
]
},
"invalid-newtype": {
"title": "detects invalid NewType definitions",
"description": "## What it does\nChecks for the creation of invalid `NewType`s\n\n## Why is this bad?\nThere are several requirements that you must follow when creating a `NewType`.\n\n## Examples\n```python\nfrom typing import NewType\n\nFoo = NewType(\"Foo\", int) # okay\nBar = NewType(get_name(), int) # error: NewType name must be a string literal\n```",
"default": "error",
"oneOf": [
{
"$ref": "#/definitions/Level"
}
]
},
"invalid-overload": {
"title": "detects invalid `@overload` usages",
"description": "## What it does\nChecks for various invalid `@overload` usages.\n\n## Why is this bad?\nThe `@overload` decorator is used to define functions and methods that accepts different\ncombinations of arguments and return different types based on the arguments passed. This is\nmainly beneficial for type checkers. But, if the `@overload` usage is invalid, the type\nchecker may not be able to provide correct type information.\n\n## Example\n\nDefining only one overload:\n\n```py\nfrom typing import overload\n\n@overload\ndef foo(x: int) -> int: ...\ndef foo(x: int | None) -> int | None:\n return x\n```\n\nOr, not providing an implementation for the overloaded definition:\n\n```py\nfrom typing import overload\n\n@overload\ndef foo() -> None: ...\n@overload\ndef foo(x: int) -> int: ...\n```\n\n## References\n- [Python documentation: `@overload`](https://docs.python.org/3/library/typing.html#typing.overload)",