Compare commits

..

1 Commits

Author SHA1 Message Date
Carl Meyer
e3876e6d6d [ty] fix assigning a typevar to a union with itself 2025-05-06 16:43:02 -07:00
124 changed files with 1215 additions and 4889 deletions

View File

@@ -1,8 +1,5 @@
blank_issues_enabled: true
contact_links:
- name: Report an issue with ty
url: https://github.com/astral-sh/ty/issues/new/choose
about: Please report issues for our type checker ty in the ty repository.
- name: Documentation
url: https://docs.astral.sh/ruff
about: Please consult the documentation before creating an issue.

View File

@@ -366,15 +366,6 @@ uvx --from ./python/ruff-ecosystem ruff-ecosystem format ruff "./target/debug/ru
See the [ruff-ecosystem package](https://github.com/astral-sh/ruff/tree/main/python/ruff-ecosystem) for more details.
## Upgrading Rust
1. Change the `channel` in `./rust-toolchain.toml` to the new Rust version (`<latest>`)
1. Change the `rust-version` in the `./Cargo.toml` to `<latest> - 2` (e.g. 1.84 if the latest is 1.86)
1. Run `cargo clippy --fix --allow-dirty --allow-staged` to fix new clippy warnings
1. Create and merge the PR
1. Bump the Rust version in Ruff's conda forge recipe. See [this PR](https://github.com/conda-forge/ruff-feedstock/pull/266) for an example.
1. Enjoy the new Rust version!
## Benchmarking and Profiling
We have several ways of benchmarking and profiling Ruff:

7
Cargo.lock generated
View File

@@ -3237,7 +3237,7 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "salsa"
version = "0.21.1"
source = "git+https://github.com/salsa-rs/salsa.git?rev=ef93d3830ffb8dc621fef1ba7b234ccef9dc3639#ef93d3830ffb8dc621fef1ba7b234ccef9dc3639"
source = "git+https://github.com/salsa-rs/salsa.git?rev=b2b82bccdbef3e7ce7f302c52f43a0c98ac7177a#b2b82bccdbef3e7ce7f302c52f43a0c98ac7177a"
dependencies = [
"boxcar",
"compact_str",
@@ -3260,12 +3260,12 @@ dependencies = [
[[package]]
name = "salsa-macro-rules"
version = "0.21.1"
source = "git+https://github.com/salsa-rs/salsa.git?rev=ef93d3830ffb8dc621fef1ba7b234ccef9dc3639#ef93d3830ffb8dc621fef1ba7b234ccef9dc3639"
source = "git+https://github.com/salsa-rs/salsa.git?rev=b2b82bccdbef3e7ce7f302c52f43a0c98ac7177a#b2b82bccdbef3e7ce7f302c52f43a0c98ac7177a"
[[package]]
name = "salsa-macros"
version = "0.21.1"
source = "git+https://github.com/salsa-rs/salsa.git?rev=ef93d3830ffb8dc621fef1ba7b234ccef9dc3639#ef93d3830ffb8dc621fef1ba7b234ccef9dc3639"
source = "git+https://github.com/salsa-rs/salsa.git?rev=b2b82bccdbef3e7ce7f302c52f43a0c98ac7177a#b2b82bccdbef3e7ce7f302c52f43a0c98ac7177a"
dependencies = [
"heck",
"proc-macro2",
@@ -3955,7 +3955,6 @@ dependencies = [
"anyhow",
"argfile",
"clap",
"clap_complete_command",
"colored 3.0.0",
"countme",
"crossbeam",

View File

@@ -124,7 +124,7 @@ rayon = { version = "1.10.0" }
regex = { version = "1.10.2" }
rustc-hash = { version = "2.0.0" }
# When updating salsa, make sure to also update the revision in `fuzz/Cargo.toml`
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "ef93d3830ffb8dc621fef1ba7b234ccef9dc3639" }
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "b2b82bccdbef3e7ce7f302c52f43a0c98ac7177a" }
schemars = { version = "0.8.16" }
seahash = { version = "4.1.0" }
serde = { version = "1.0.197", features = ["derive"] }

View File

@@ -232,15 +232,6 @@ impl Diagnostic {
pub fn primary_tags(&self) -> Option<&[DiagnosticTag]> {
self.primary_annotation().map(|ann| ann.tags.as_slice())
}
/// Returns a key that can be used to sort two diagnostics into the canonical order
/// in which they should appear when rendered.
pub fn rendering_sort_key<'a>(&'a self, db: &'a dyn Db) -> impl Ord + 'a {
RenderingSortKey {
db,
diagnostic: self,
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
@@ -252,64 +243,6 @@ struct DiagnosticInner {
subs: Vec<SubDiagnostic>,
}
struct RenderingSortKey<'a> {
db: &'a dyn Db,
diagnostic: &'a Diagnostic,
}
impl Ord for RenderingSortKey<'_> {
// We sort diagnostics in a way that keeps them in source order
// and grouped by file. After that, we fall back to severity
// (with fatal messages sorting before info messages) and then
// finally the diagnostic ID.
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
if let (Some(span1), Some(span2)) = (
self.diagnostic.primary_span(),
other.diagnostic.primary_span(),
) {
let order = span1
.file()
.path(self.db)
.as_str()
.cmp(span2.file().path(self.db).as_str());
if order.is_ne() {
return order;
}
if let (Some(range1), Some(range2)) = (span1.range(), span2.range()) {
let order = range1.start().cmp(&range2.start());
if order.is_ne() {
return order;
}
}
}
// Reverse so that, e.g., Fatal sorts before Info.
let order = self
.diagnostic
.severity()
.cmp(&other.diagnostic.severity())
.reverse();
if order.is_ne() {
return order;
}
self.diagnostic.id().cmp(&other.diagnostic.id())
}
}
impl PartialOrd for RenderingSortKey<'_> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for RenderingSortKey<'_> {
fn eq(&self, other: &Self) -> bool {
self.cmp(other).is_eq()
}
}
impl Eq for RenderingSortKey<'_> {}
/// A collection of information subservient to a diagnostic.
///
/// A sub-diagnostic is always rendered after the parent diagnostic it is

View File

@@ -61,7 +61,7 @@ pub fn max_parallelism() -> NonZeroUsize {
#[cfg(test)]
mod tests {
use std::sync::{Arc, Mutex};
use std::sync::Arc;
use crate::files::Files;
use crate::system::TestSystem;
@@ -69,8 +69,6 @@ mod tests {
use crate::vendored::VendoredFileSystem;
use crate::Db;
type Events = Arc<Mutex<Vec<salsa::Event>>>;
/// Database that can be used for testing.
///
/// Uses an in memory filesystem and it stubs out the vendored files by default.
@@ -81,37 +79,36 @@ mod tests {
files: Files,
system: TestSystem,
vendored: VendoredFileSystem,
events: Events,
events: Arc<std::sync::Mutex<Vec<salsa::Event>>>,
}
impl TestDb {
pub(crate) fn new() -> Self {
let events = Events::default();
Self {
storage: salsa::Storage::new(Some(Box::new({
let events = events.clone();
move |event| {
tracing::trace!("event: {:?}", event);
let mut events = events.lock().unwrap();
events.push(event);
}
}))),
storage: salsa::Storage::default(),
system: TestSystem::default(),
vendored: VendoredFileSystem::default(),
events,
events: std::sync::Arc::default(),
files: Files::default(),
}
}
/// Empties the internal store of salsa events that have been emitted,
/// and returns them as a `Vec` (equivalent to [`std::mem::take`]).
///
/// ## Panics
/// If there are pending database snapshots.
pub(crate) fn take_salsa_events(&mut self) -> Vec<salsa::Event> {
let mut events = self.events.lock().unwrap();
let inner = Arc::get_mut(&mut self.events)
.expect("expected no pending salsa database snapshots.");
std::mem::take(&mut *events)
std::mem::take(inner.get_mut().unwrap())
}
/// Clears the emitted salsa events.
///
/// ## Panics
/// If there are pending database snapshots.
pub(crate) fn clear_salsa_events(&mut self) {
self.take_salsa_events();
}
@@ -151,5 +148,12 @@ mod tests {
}
#[salsa::db]
impl salsa::Database for TestDb {}
impl salsa::Database for TestDb {
fn salsa_event(&self, event: &dyn Fn() -> salsa::Event) {
let event = event();
tracing::trace!("event: {:?}", event);
let mut events = self.events.lock().unwrap();
events.push(event);
}
}
}

View File

@@ -98,4 +98,6 @@ impl Db for ModuleDb {
}
#[salsa::db]
impl salsa::Database for ModuleDb {}
impl salsa::Database for ModuleDb {
fn salsa_event(&self, _event: &dyn Fn() -> salsa::Event) {}
}

View File

@@ -84,9 +84,3 @@ str(
'''Lorem
ipsum''' # Comment
).foo
# https://github.com/astral-sh/ruff/issues/17606
bool(True)and None
int(1)and None
float(1.)and None
bool(True)and()

View File

@@ -15,34 +15,6 @@ use ruff_python_ast::PythonVersion;
/// Dunder names are not meant to be called explicitly and, in most cases, can
/// be replaced with builtins or operators.
///
/// ## Fix safety
/// This fix is always unsafe. When replacing dunder method calls with operators
/// or builtins, the behavior can change in the following ways:
///
/// 1. Types may implement only a subset of related dunder methods. Calling a
/// missing dunder method directly returns `NotImplemented`, but using the
/// equivalent operator raises a `TypeError`.
/// ```python
/// class C: pass
/// c = C()
/// c.__gt__(1) # before fix: NotImplemented
/// c > 1 # after fix: raises TypeError
/// ```
/// 2. Instance-assigned dunder methods are ignored by operators and builtins.
/// ```python
/// class C: pass
/// c = C()
/// c.__bool__ = lambda: False
/// c.__bool__() # before fix: False
/// bool(c) # after fix: True
/// ```
///
/// 3. Even with built-in types, behavior can differ.
/// ```python
/// (1).__gt__(1.0) # before fix: NotImplemented
/// 1 > 1.0 # after fix: False
/// ```
///
/// ## Example
/// ```python
/// three = (3.0).__str__()

View File

@@ -161,14 +161,13 @@ pub(crate) fn native_literals(
keywords,
range: _,
},
range: call_range,
range: _,
} = call;
if !keywords.is_empty() || args.len() > 1 {
return;
}
let tokens = checker.tokens();
let semantic = checker.semantic();
let Some(builtin) = semantic.resolve_builtin_symbol(func) else {
@@ -245,20 +244,7 @@ pub(crate) fn native_literals(
let arg_code = checker.locator().slice(arg);
let mut needs_space = false;
// Look for the `Rpar` token of the call expression and check if there is a keyword token right
// next to it without any space separating them. Without this check, the fix for this
// rule would create a syntax error.
// Ex) `bool(True)and None` no space between `)` and the keyword `and`.
//
// Subtract 1 from the end of the range to include `Rpar` token in the slice.
if let [paren_token, next_token, ..] = tokens.after(call_range.sub_end(1.into()).end())
{
needs_space = next_token.kind().is_keyword()
&& paren_token.range().end() == next_token.range().start();
}
let mut content = match (parent_expr, literal_type, has_unary_op) {
let content = match (parent_expr, literal_type, has_unary_op) {
// Expressions including newlines must be parenthesised to be valid syntax
(_, _, true) if find_newline(arg_code).is_some() => format!("({arg_code})"),
@@ -279,10 +265,6 @@ pub(crate) fn native_literals(
_ => arg_code.to_string(),
};
if needs_space {
content.push(' ');
}
let applicability = if checker.comment_ranges().intersects(call.range) {
Applicability::Unsafe
} else {

View File

@@ -602,8 +602,6 @@ UP018.py:83:1: UP018 [*] Unnecessary `str` call (rewrite as a literal)
85 | | ipsum''' # Comment
86 | | ).foo
| |_^ UP018
87 |
88 | # https://github.com/astral-sh/ruff/issues/17606
|
= help: Replace with string literal
@@ -617,80 +615,3 @@ UP018.py:83:1: UP018 [*] Unnecessary `str` call (rewrite as a literal)
86 |-).foo
83 |+'''Lorem
84 |+ ipsum'''.foo
87 85 |
88 86 | # https://github.com/astral-sh/ruff/issues/17606
89 87 | bool(True)and None
UP018.py:89:1: UP018 [*] Unnecessary `bool` call (rewrite as a literal)
|
88 | # https://github.com/astral-sh/ruff/issues/17606
89 | bool(True)and None
| ^^^^^^^^^^ UP018
90 | int(1)and None
91 | float(1.)and None
|
= help: Replace with boolean literal
Safe fix
86 86 | ).foo
87 87 |
88 88 | # https://github.com/astral-sh/ruff/issues/17606
89 |-bool(True)and None
89 |+True and None
90 90 | int(1)and None
91 91 | float(1.)and None
92 92 | bool(True)and()
UP018.py:90:1: UP018 [*] Unnecessary `int` call (rewrite as a literal)
|
88 | # https://github.com/astral-sh/ruff/issues/17606
89 | bool(True)and None
90 | int(1)and None
| ^^^^^^ UP018
91 | float(1.)and None
92 | bool(True)and()
|
= help: Replace with integer literal
Safe fix
87 87 |
88 88 | # https://github.com/astral-sh/ruff/issues/17606
89 89 | bool(True)and None
90 |-int(1)and None
90 |+1 and None
91 91 | float(1.)and None
92 92 | bool(True)and()
UP018.py:91:1: UP018 [*] Unnecessary `float` call (rewrite as a literal)
|
89 | bool(True)and None
90 | int(1)and None
91 | float(1.)and None
| ^^^^^^^^^ UP018
92 | bool(True)and()
|
= help: Replace with float literal
Safe fix
88 88 | # https://github.com/astral-sh/ruff/issues/17606
89 89 | bool(True)and None
90 90 | int(1)and None
91 |-float(1.)and None
91 |+1. and None
92 92 | bool(True)and()
UP018.py:92:1: UP018 [*] Unnecessary `bool` call (rewrite as a literal)
|
90 | int(1)and None
91 | float(1.)and None
92 | bool(True)and()
| ^^^^^^^^^^ UP018
|
= help: Replace with boolean literal
Safe fix
89 89 | bool(True)and None
90 90 | int(1)and None
91 91 | float(1.)and None
92 |-bool(True)and()
92 |+True and()

View File

@@ -1,3 +1,2 @@
sum(x for x in range(10), 5)
total(1, 2, x for x in range(5), 6)
sum(x for x in range(10),)

View File

@@ -1,3 +1 @@
zip((x for x in range(10)), (y for y in range(10)))
sum(x for x in range(10))
sum((x for x in range(10)),)

View File

@@ -661,120 +661,117 @@ impl<'src> Parser<'src> {
let mut seen_keyword_argument = false; // foo = 1
let mut seen_keyword_unpacking = false; // **foo
let has_trailing_comma =
self.parse_comma_separated_list(RecoveryContextKind::Arguments, |parser| {
let argument_start = parser.node_start();
if parser.eat(TokenKind::DoubleStar) {
self.parse_comma_separated_list(RecoveryContextKind::Arguments, |parser| {
let argument_start = parser.node_start();
if parser.eat(TokenKind::DoubleStar) {
let value = parser.parse_conditional_expression_or_higher();
keywords.push(ast::Keyword {
arg: None,
value: value.expr,
range: parser.node_range(argument_start),
});
seen_keyword_unpacking = true;
} else {
let start = parser.node_start();
let mut parsed_expr = parser
.parse_named_expression_or_higher(ExpressionContext::starred_conditional());
match parser.current_token_kind() {
TokenKind::Async | TokenKind::For => {
if parsed_expr.is_unparenthesized_starred_expr() {
parser.add_error(
ParseErrorType::IterableUnpackingInComprehension,
&parsed_expr,
);
}
parsed_expr = Expr::Generator(parser.parse_generator_expression(
parsed_expr.expr,
start,
Parenthesized::No,
))
.into();
}
_ => {
if seen_keyword_unpacking && parsed_expr.is_unparenthesized_starred_expr() {
parser.add_error(
ParseErrorType::InvalidArgumentUnpackingOrder,
&parsed_expr,
);
}
}
}
let arg_range = parser.node_range(start);
if parser.eat(TokenKind::Equal) {
seen_keyword_argument = true;
let arg = if let ParsedExpr {
expr: Expr::Name(ident_expr),
is_parenthesized,
} = parsed_expr
{
// test_ok parenthesized_kwarg_py37
// # parse_options: {"target-version": "3.7"}
// f((a)=1)
// test_err parenthesized_kwarg_py38
// # parse_options: {"target-version": "3.8"}
// f((a)=1)
// f((a) = 1)
// f( ( a ) = 1)
if is_parenthesized {
parser.add_unsupported_syntax_error(
UnsupportedSyntaxErrorKind::ParenthesizedKeywordArgumentName,
arg_range,
);
}
ast::Identifier {
id: ident_expr.id,
range: ident_expr.range,
}
} else {
// TODO(dhruvmanila): Parser shouldn't drop the `parsed_expr` if it's
// not a name expression. We could add the expression into `args` but
// that means the error is a missing comma instead.
parser.add_error(
ParseErrorType::OtherError("Expected a parameter name".to_string()),
&parsed_expr,
);
ast::Identifier {
id: Name::empty(),
range: parsed_expr.range(),
}
};
let value = parser.parse_conditional_expression_or_higher();
keywords.push(ast::Keyword {
arg: None,
arg: Some(arg),
value: value.expr,
range: parser.node_range(argument_start),
});
seen_keyword_unpacking = true;
} else {
let start = parser.node_start();
let mut parsed_expr = parser
.parse_named_expression_or_higher(ExpressionContext::starred_conditional());
match parser.current_token_kind() {
TokenKind::Async | TokenKind::For => {
if parsed_expr.is_unparenthesized_starred_expr() {
parser.add_error(
ParseErrorType::IterableUnpackingInComprehension,
&parsed_expr,
);
}
parsed_expr = Expr::Generator(parser.parse_generator_expression(
parsed_expr.expr,
start,
Parenthesized::No,
))
.into();
}
_ => {
if seen_keyword_unpacking
&& parsed_expr.is_unparenthesized_starred_expr()
{
parser.add_error(
ParseErrorType::InvalidArgumentUnpackingOrder,
&parsed_expr,
);
}
}
}
let arg_range = parser.node_range(start);
if parser.eat(TokenKind::Equal) {
seen_keyword_argument = true;
let arg = if let ParsedExpr {
expr: Expr::Name(ident_expr),
is_parenthesized,
} = parsed_expr
{
// test_ok parenthesized_kwarg_py37
// # parse_options: {"target-version": "3.7"}
// f((a)=1)
// test_err parenthesized_kwarg_py38
// # parse_options: {"target-version": "3.8"}
// f((a)=1)
// f((a) = 1)
// f( ( a ) = 1)
if is_parenthesized {
parser.add_unsupported_syntax_error(
UnsupportedSyntaxErrorKind::ParenthesizedKeywordArgumentName,
arg_range,
);
}
ast::Identifier {
id: ident_expr.id,
range: ident_expr.range,
}
} else {
// TODO(dhruvmanila): Parser shouldn't drop the `parsed_expr` if it's
// not a name expression. We could add the expression into `args` but
// that means the error is a missing comma instead.
if !parsed_expr.is_unparenthesized_starred_expr() {
if seen_keyword_unpacking {
parser.add_error(
ParseErrorType::OtherError("Expected a parameter name".to_string()),
ParseErrorType::PositionalAfterKeywordUnpacking,
&parsed_expr,
);
} else if seen_keyword_argument {
parser.add_error(
ParseErrorType::PositionalAfterKeywordArgument,
&parsed_expr,
);
ast::Identifier {
id: Name::empty(),
range: parsed_expr.range(),
}
};
let value = parser.parse_conditional_expression_or_higher();
keywords.push(ast::Keyword {
arg: Some(arg),
value: value.expr,
range: parser.node_range(argument_start),
});
} else {
if !parsed_expr.is_unparenthesized_starred_expr() {
if seen_keyword_unpacking {
parser.add_error(
ParseErrorType::PositionalAfterKeywordUnpacking,
&parsed_expr,
);
} else if seen_keyword_argument {
parser.add_error(
ParseErrorType::PositionalAfterKeywordArgument,
&parsed_expr,
);
}
}
args.push(parsed_expr.expr);
}
args.push(parsed_expr.expr);
}
});
}
});
self.expect(TokenKind::Rpar);
@@ -784,7 +781,7 @@ impl<'src> Parser<'src> {
keywords: keywords.into_boxed_slice(),
};
self.validate_arguments(&arguments, has_trailing_comma);
self.validate_arguments(&arguments);
arguments
}
@@ -2524,9 +2521,9 @@ impl<'src> Parser<'src> {
/// Performs the following validations on the function call arguments:
/// 1. There aren't any duplicate keyword argument
/// 2. If there are more than one argument (positional or keyword) or a single argument with a
/// trailing comma, all generator expressions present should be parenthesized.
fn validate_arguments(&mut self, arguments: &ast::Arguments, has_trailing_comma: bool) {
/// 2. If there are more than one argument (positional or keyword), all generator expressions
/// present should be parenthesized.
fn validate_arguments(&mut self, arguments: &ast::Arguments) {
let mut all_arg_names =
FxHashSet::with_capacity_and_hasher(arguments.keywords.len(), FxBuildHasher);
@@ -2544,7 +2541,7 @@ impl<'src> Parser<'src> {
}
}
if has_trailing_comma || arguments.len() > 1 {
if arguments.len() > 1 {
for arg in &*arguments.args {
if let Some(ast::ExprGenerator {
range,
@@ -2553,14 +2550,11 @@ impl<'src> Parser<'src> {
}) = arg.as_generator_expr()
{
// test_ok args_unparenthesized_generator
// zip((x for x in range(10)), (y for y in range(10)))
// sum(x for x in range(10))
// sum((x for x in range(10)),)
// test_err args_unparenthesized_generator
// sum(x for x in range(10), 5)
// total(1, 2, x for x in range(5), 6)
// sum(x for x in range(10),)
self.add_error(ParseErrorType::UnparenthesizedGeneratorExpression, range);
}
}

View File

@@ -539,19 +539,17 @@ impl<'src> Parser<'src> {
}
/// Parses a comma separated list of elements where each element is parsed
/// using the given `parse_element` function.
/// sing the given `parse_element` function.
///
/// The difference between this function and `parse_comma_separated_list_into_vec`
/// is that this function does not return the parsed elements. Instead, it is the
/// caller's responsibility to handle the parsed elements. This is the reason
/// that the `parse_element` parameter is bound to [`FnMut`] instead of [`Fn`].
///
/// Returns `true` if there is a trailing comma present.
fn parse_comma_separated_list(
&mut self,
recovery_context_kind: RecoveryContextKind,
mut parse_element: impl FnMut(&mut Parser<'src>),
) -> bool {
) {
let mut progress = ParserProgress::default();
let saved_context = self.recovery_context;
@@ -661,8 +659,6 @@ impl<'src> Parser<'src> {
}
self.recovery_context = saved_context;
trailing_comma_range.is_some()
}
#[cold]

View File

@@ -1,13 +1,14 @@
---
source: crates/ruff_python_parser/tests/fixtures.rs
input_file: crates/ruff_python_parser/resources/inline/err/args_unparenthesized_generator.py
snapshot_kind: text
---
## AST
```
Module(
ModModule {
range: 0..92,
range: 0..65,
body: [
Expr(
StmtExpr {
@@ -193,82 +194,6 @@ Module(
),
},
),
Expr(
StmtExpr {
range: 65..91,
value: Call(
ExprCall {
range: 65..91,
func: Name(
ExprName {
range: 65..68,
id: Name("sum"),
ctx: Load,
},
),
arguments: Arguments {
range: 68..91,
args: [
Generator(
ExprGenerator {
range: 69..89,
elt: Name(
ExprName {
range: 69..70,
id: Name("x"),
ctx: Load,
},
),
generators: [
Comprehension {
range: 71..89,
target: Name(
ExprName {
range: 75..76,
id: Name("x"),
ctx: Store,
},
),
iter: Call(
ExprCall {
range: 80..89,
func: Name(
ExprName {
range: 80..85,
id: Name("range"),
ctx: Load,
},
),
arguments: Arguments {
range: 85..89,
args: [
NumberLiteral(
ExprNumberLiteral {
range: 86..88,
value: Int(
10,
),
},
),
],
keywords: [],
},
},
),
ifs: [],
is_async: false,
},
],
parenthesized: false,
},
),
],
keywords: [],
},
},
),
},
),
],
},
)
@@ -279,7 +204,6 @@ Module(
1 | sum(x for x in range(10), 5)
| ^^^^^^^^^^^^^^^^^^^^ Syntax Error: Unparenthesized generator expression cannot be used here
2 | total(1, 2, x for x in range(5), 6)
3 | sum(x for x in range(10),)
|
@@ -287,13 +211,4 @@ Module(
1 | sum(x for x in range(10), 5)
2 | total(1, 2, x for x in range(5), 6)
| ^^^^^^^^^^^^^^^^^^^ Syntax Error: Unparenthesized generator expression cannot be used here
3 | sum(x for x in range(10),)
|
|
1 | sum(x for x in range(10), 5)
2 | total(1, 2, x for x in range(5), 6)
3 | sum(x for x in range(10),)
| ^^^^^^^^^^^^^^^^^^^^ Syntax Error: Unparenthesized generator expression cannot be used here
|

View File

@@ -1,195 +1,67 @@
---
source: crates/ruff_python_parser/tests/fixtures.rs
input_file: crates/ruff_python_parser/resources/inline/ok/args_unparenthesized_generator.py
snapshot_kind: text
---
## AST
```
Module(
ModModule {
range: 0..107,
range: 0..26,
body: [
Expr(
StmtExpr {
range: 0..51,
range: 0..25,
value: Call(
ExprCall {
range: 0..51,
range: 0..25,
func: Name(
ExprName {
range: 0..3,
id: Name("zip"),
ctx: Load,
},
),
arguments: Arguments {
range: 3..51,
args: [
Generator(
ExprGenerator {
range: 4..26,
elt: Name(
ExprName {
range: 5..6,
id: Name("x"),
ctx: Load,
},
),
generators: [
Comprehension {
range: 7..25,
target: Name(
ExprName {
range: 11..12,
id: Name("x"),
ctx: Store,
},
),
iter: Call(
ExprCall {
range: 16..25,
func: Name(
ExprName {
range: 16..21,
id: Name("range"),
ctx: Load,
},
),
arguments: Arguments {
range: 21..25,
args: [
NumberLiteral(
ExprNumberLiteral {
range: 22..24,
value: Int(
10,
),
},
),
],
keywords: [],
},
},
),
ifs: [],
is_async: false,
},
],
parenthesized: true,
},
),
Generator(
ExprGenerator {
range: 28..50,
elt: Name(
ExprName {
range: 29..30,
id: Name("y"),
ctx: Load,
},
),
generators: [
Comprehension {
range: 31..49,
target: Name(
ExprName {
range: 35..36,
id: Name("y"),
ctx: Store,
},
),
iter: Call(
ExprCall {
range: 40..49,
func: Name(
ExprName {
range: 40..45,
id: Name("range"),
ctx: Load,
},
),
arguments: Arguments {
range: 45..49,
args: [
NumberLiteral(
ExprNumberLiteral {
range: 46..48,
value: Int(
10,
),
},
),
],
keywords: [],
},
},
),
ifs: [],
is_async: false,
},
],
parenthesized: true,
},
),
],
keywords: [],
},
},
),
},
),
Expr(
StmtExpr {
range: 52..77,
value: Call(
ExprCall {
range: 52..77,
func: Name(
ExprName {
range: 52..55,
id: Name("sum"),
ctx: Load,
},
),
arguments: Arguments {
range: 55..77,
range: 3..25,
args: [
Generator(
ExprGenerator {
range: 56..76,
range: 4..24,
elt: Name(
ExprName {
range: 56..57,
range: 4..5,
id: Name("x"),
ctx: Load,
},
),
generators: [
Comprehension {
range: 58..76,
range: 6..24,
target: Name(
ExprName {
range: 62..63,
range: 10..11,
id: Name("x"),
ctx: Store,
},
),
iter: Call(
ExprCall {
range: 67..76,
range: 15..24,
func: Name(
ExprName {
range: 67..72,
range: 15..20,
id: Name("range"),
ctx: Load,
},
),
arguments: Arguments {
range: 72..76,
range: 20..24,
args: [
NumberLiteral(
ExprNumberLiteral {
range: 73..75,
range: 21..23,
value: Int(
10,
),
@@ -214,82 +86,6 @@ Module(
),
},
),
Expr(
StmtExpr {
range: 78..106,
value: Call(
ExprCall {
range: 78..106,
func: Name(
ExprName {
range: 78..81,
id: Name("sum"),
ctx: Load,
},
),
arguments: Arguments {
range: 81..106,
args: [
Generator(
ExprGenerator {
range: 82..104,
elt: Name(
ExprName {
range: 83..84,
id: Name("x"),
ctx: Load,
},
),
generators: [
Comprehension {
range: 85..103,
target: Name(
ExprName {
range: 89..90,
id: Name("x"),
ctx: Store,
},
),
iter: Call(
ExprCall {
range: 94..103,
func: Name(
ExprName {
range: 94..99,
id: Name("range"),
ctx: Load,
},
),
arguments: Arguments {
range: 99..103,
args: [
NumberLiteral(
ExprNumberLiteral {
range: 100..102,
value: Int(
10,
),
},
),
],
keywords: [],
},
},
),
ifs: [],
is_async: false,
},
],
parenthesized: true,
},
),
],
keywords: [],
},
},
),
},
),
],
},
)

View File

@@ -22,7 +22,6 @@ ty_server = { workspace = true }
anyhow = { workspace = true }
argfile = { workspace = true }
clap = { workspace = true, features = ["wrap_help", "string"] }
clap_complete_command = { workspace = true }
colored = { workspace = true }
countme = { workspace = true, features = ["enable"] }
crossbeam = { workspace = true }

View File

@@ -24,10 +24,6 @@ pub(crate) enum Command {
/// Display ty's version
Version,
/// Generate shell completion
#[clap(hide = true)]
GenerateShellCompletion { shell: clap_complete_command::Shell },
}
#[derive(Debug, Parser)]

View File

@@ -7,7 +7,7 @@ use std::sync::Mutex;
use crate::args::{Args, CheckCommand, Command, TerminalColor};
use crate::logging::setup_tracing;
use anyhow::{anyhow, Context};
use clap::{CommandFactory, Parser};
use clap::Parser;
use colored::Colorize;
use crossbeam::channel as crossbeam_channel;
use rayon::ThreadPoolBuilder;
@@ -68,10 +68,6 @@ fn run() -> anyhow::Result<ExitStatus> {
Command::Server => run_server().map(|()| ExitStatus::Success),
Command::Check(check_args) => run_check(check_args),
Command::Version => version().map(|()| ExitStatus::Success),
Command::GenerateShellCompletion { shell } => {
shell.generate(&mut Args::command(), &mut stdout());
Ok(ExitStatus::Success)
}
}
}

View File

@@ -6,7 +6,6 @@ use ruff_text_size::TextSize;
use crate::Db;
#[derive(Debug, Clone)]
pub struct Completion {
pub label: String,
}

View File

@@ -6,7 +6,7 @@ pub trait Db: SemanticDb + Upcast<dyn SemanticDb> + Upcast<dyn SourceDb> {}
#[cfg(test)]
pub(crate) mod tests {
use std::sync::{Arc, Mutex};
use std::sync::Arc;
use super::Db;
use ruff_db::files::{File, Files};
@@ -16,8 +16,6 @@ pub(crate) mod tests {
use ty_python_semantic::lint::{LintRegistry, RuleSelection};
use ty_python_semantic::{default_lint_registry, Db as SemanticDb, Program};
type Events = Arc<Mutex<Vec<salsa::Event>>>;
#[salsa::db]
#[derive(Clone)]
pub(crate) struct TestDb {
@@ -25,35 +23,31 @@ pub(crate) mod tests {
files: Files,
system: TestSystem,
vendored: VendoredFileSystem,
events: Events,
events: Arc<std::sync::Mutex<Vec<salsa::Event>>>,
rule_selection: Arc<RuleSelection>,
}
#[expect(dead_code)]
impl TestDb {
pub(crate) fn new() -> Self {
let events = Events::default();
Self {
storage: salsa::Storage::new(Some(Box::new({
let events = events.clone();
move |event| {
tracing::trace!("event: {event:?}");
let mut events = events.lock().unwrap();
events.push(event);
}
}))),
storage: salsa::Storage::default(),
system: TestSystem::default(),
vendored: ty_vendored::file_system().clone(),
events,
events: Arc::default(),
files: Files::default(),
rule_selection: Arc::new(RuleSelection::from_registry(default_lint_registry())),
}
}
/// Takes the salsa events.
///
/// ## Panics
/// If there are any pending salsa snapshots.
pub(crate) fn take_salsa_events(&mut self) -> Vec<salsa::Event> {
let mut events = self.events.lock().unwrap();
let inner = Arc::get_mut(&mut self.events).expect("no pending salsa snapshots");
let events = inner.get_mut().unwrap();
std::mem::take(&mut *events)
}
@@ -133,5 +127,12 @@ pub(crate) mod tests {
impl Db for TestDb {}
#[salsa::db]
impl salsa::Database for TestDb {}
impl salsa::Database for TestDb {
fn salsa_event(&self, event: &dyn Fn() -> salsa::Event) {
let event = event();
tracing::trace!("event: {event:?}");
let mut events = self.events.lock().unwrap();
events.push(event);
}
}
}

View File

@@ -37,19 +37,7 @@ impl ProjectDatabase {
{
let mut db = Self {
project: None,
storage: salsa::Storage::new(if tracing::enabled!(tracing::Level::TRACE) {
Some(Box::new({
move |event: Event| {
if matches!(event.kind, salsa::EventKind::WillCheckCancellation) {
return;
}
tracing::trace!("Salsa event: {event:?}");
}
}))
} else {
None
}),
storage: salsa::Storage::default(),
files: Files::default(),
system: Arc::new(system),
};
@@ -168,7 +156,20 @@ impl SourceDb for ProjectDatabase {
}
#[salsa::db]
impl salsa::Database for ProjectDatabase {}
impl salsa::Database for ProjectDatabase {
fn salsa_event(&self, event: &dyn Fn() -> Event) {
if !tracing::enabled!(tracing::Level::TRACE) {
return;
}
let event = event();
if matches!(event.kind, salsa::EventKind::WillCheckCancellation) {
return;
}
tracing::trace!("Salsa event: {event:?}");
}
}
#[salsa::db]
impl Db for ProjectDatabase {
@@ -205,7 +206,9 @@ mod format {
#[cfg(test)]
pub(crate) mod tests {
use std::sync::{Arc, Mutex};
use std::sync::Arc;
use salsa::Event;
use ruff_db::files::Files;
use ruff_db::system::{DbWithTestSystem, System, TestSystem};
@@ -218,13 +221,11 @@ pub(crate) mod tests {
use crate::DEFAULT_LINT_REGISTRY;
use crate::{Project, ProjectMetadata};
type Events = Arc<Mutex<Vec<salsa::Event>>>;
#[salsa::db]
#[derive(Clone)]
pub(crate) struct TestDb {
storage: salsa::Storage<Self>,
events: Events,
events: Arc<std::sync::Mutex<Vec<Event>>>,
files: Files,
system: TestSystem,
vendored: VendoredFileSystem,
@@ -233,19 +234,12 @@ pub(crate) mod tests {
impl TestDb {
pub(crate) fn new(project: ProjectMetadata) -> Self {
let events = Events::default();
let mut db = Self {
storage: salsa::Storage::new(Some(Box::new({
let events = events.clone();
move |event| {
let mut events = events.lock().unwrap();
events.push(event);
}
}))),
storage: salsa::Storage::default(),
system: TestSystem::default(),
vendored: ty_vendored::file_system().clone(),
files: Files::default(),
events,
events: Arc::default(),
project: None,
};
@@ -257,9 +251,13 @@ pub(crate) mod tests {
impl TestDb {
/// Takes the salsa events.
///
/// ## Panics
/// If there are any pending salsa snapshots.
pub(crate) fn take_salsa_events(&mut self) -> Vec<salsa::Event> {
let mut events = self.events.lock().unwrap();
let inner = Arc::get_mut(&mut self.events).expect("no pending salsa snapshots");
let events = inner.get_mut().unwrap();
std::mem::take(&mut *events)
}
}
@@ -334,5 +332,10 @@ pub(crate) mod tests {
}
#[salsa::db]
impl salsa::Database for TestDb {}
impl salsa::Database for TestDb {
fn salsa_event(&self, event: &dyn Fn() -> Event) {
let mut events = self.events.lock().unwrap();
events.push(event());
}
}
}

View File

@@ -219,10 +219,34 @@ impl Project {
.unwrap()
.into_inner()
.unwrap();
// We sort diagnostics in a way that keeps them in source order
// and grouped by file. After that, we fall back to severity
// (with fatal messages sorting before info messages) and then
// finally the diagnostic ID.
file_diagnostics.sort_by(|d1, d2| {
if let (Some(span1), Some(span2)) = (d1.primary_span(), d2.primary_span()) {
let order = span1
.file()
.path(db)
.as_str()
.cmp(span2.file().path(db).as_str());
if order.is_ne() {
return order;
}
file_diagnostics.sort_by(|left, right| {
left.rendering_sort_key(db)
.cmp(&right.rendering_sort_key(db))
if let (Some(range1), Some(range2)) = (span1.range(), span2.range()) {
let order = range1.start().cmp(&range2.start());
if order.is_ne() {
return order;
}
}
}
// Reverse so that, e.g., Fatal sorts before Info.
let order = d1.severity().cmp(&d2.severity()).reverse();
if order.is_ne() {
return order;
}
d1.id().cmp(&d2.id())
});
diagnostics.extend(file_diagnostics);
diagnostics

View File

@@ -76,7 +76,7 @@ from typing_extensions import Annotated
class C(Annotated[int, "foo"]): ...
# TODO: Should be `tuple[Literal[C], Literal[int], Literal[object]]`
reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, @Todo(Inference of subscript on special form), <class 'object'>]
reveal_type(C.__mro__) # revealed: tuple[Literal[C], @Todo(Inference of subscript on special form), Literal[object]]
```
### Not parameterized
@@ -88,5 +88,5 @@ from typing_extensions import Annotated
# error: [invalid-base]
class C(Annotated): ...
reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, Unknown, <class 'object'>]
reveal_type(C.__mro__) # revealed: tuple[Literal[C], Unknown, Literal[object]]
```

View File

@@ -59,7 +59,7 @@ from typing import Any
class SubclassOfAny(Any): ...
reveal_type(SubclassOfAny.__mro__) # revealed: tuple[<class 'SubclassOfAny'>, Any, <class 'object'>]
reveal_type(SubclassOfAny.__mro__) # revealed: tuple[Literal[SubclassOfAny], Any, Literal[object]]
x: SubclassOfAny = 1 # error: [invalid-assignment]
y: int = SubclassOfAny()

View File

@@ -1,190 +0,0 @@
# Self
`Self` is treated as if it were a `TypeVar` bound to the class it's being used on.
`typing.Self` is only available in Python 3.11 and later.
## Methods
```toml
[environment]
python-version = "3.11"
```
```py
from typing import Self
class Shape:
def set_scale(self: Self, scale: float) -> Self:
reveal_type(self) # revealed: Self
return self
def nested_type(self) -> list[Self]:
return [self]
def nested_func(self: Self) -> Self:
def inner() -> Self:
reveal_type(self) # revealed: Self
return self
return inner()
def implicit_self(self) -> Self:
# TODO: first argument in a method should be considered as "typing.Self"
reveal_type(self) # revealed: Unknown
return self
reveal_type(Shape().nested_type()) # revealed: @Todo(specialized non-generic class)
reveal_type(Shape().nested_func()) # revealed: Shape
class Circle(Shape):
def set_scale(self: Self, scale: float) -> Self:
reveal_type(self) # revealed: Self
return self
class Outer:
class Inner:
def foo(self: Self) -> Self:
reveal_type(self) # revealed: Self
return self
```
## Class Methods
```toml
[environment]
python-version = "3.11"
```
```py
from typing import Self, TypeVar
class Shape:
def foo(self: Self) -> Self:
return self
@classmethod
def bar(cls: type[Self]) -> Self:
# TODO: type[Shape]
reveal_type(cls) # revealed: @Todo(unsupported type[X] special form)
return cls()
class Circle(Shape): ...
reveal_type(Shape().foo()) # revealed: Shape
# TODO: Shape
reveal_type(Shape.bar()) # revealed: Unknown
```
## Attributes
```toml
[environment]
python-version = "3.11"
```
```py
from typing import Self
class LinkedList:
value: int
next_node: Self
def next(self: Self) -> Self:
reveal_type(self.value) # revealed: int
return self.next_node
reveal_type(LinkedList().next()) # revealed: LinkedList
```
## Generic Classes
```toml
[environment]
python-version = "3.11"
```
```py
from typing import Self, Generic, TypeVar
T = TypeVar("T")
class Container(Generic[T]):
value: T
def set_value(self: Self, value: T) -> Self:
return self
int_container: Container[int] = Container[int]()
reveal_type(int_container) # revealed: Container[int]
reveal_type(int_container.set_value(1)) # revealed: Container[int]
```
## Protocols
TODO: <https://typing.python.org/en/latest/spec/generics.html#use-in-protocols>
## Annotations
```toml
[environment]
python-version = "3.11"
```
```py
from typing import Self
class Shape:
def union(self: Self, other: Self | None):
reveal_type(other) # revealed: Self | None
return self
```
## Invalid Usage
`Self` cannot be used in the signature of a function or variable.
```toml
[environment]
python-version = "3.11"
```
```py
from typing import Self, Generic, TypeVar
T = TypeVar("T")
# error: [invalid-type-form]
def x(s: Self): ...
# error: [invalid-type-form]
b: Self
# TODO: "Self" cannot be used in a function with a `self` or `cls` parameter that has a type annotation other than "Self"
class Foo:
# TODO: rejected Self because self has a different type
def has_existing_self_annotation(self: T) -> Self:
return self # error: [invalid-return-type]
def return_concrete_type(self) -> Self:
# TODO: tell user to use "Foo" instead of "Self"
# error: [invalid-return-type]
return Foo()
@staticmethod
# TODO: reject because of staticmethod
def make() -> Self:
# error: [invalid-return-type]
return Foo()
class Bar(Generic[T]):
foo: T
def bar(self) -> T:
return self.foo
# error: [invalid-type-form]
class Baz(Bar[Self]): ...
class MyMetaclass(type):
# TODO: rejected
def __new__(cls) -> Self:
return super().__new__(cls)
```

View File

@@ -85,25 +85,25 @@ import typing
class ListSubclass(typing.List): ...
# TODO: generic protocols
# revealed: tuple[<class 'ListSubclass'>, <class 'list'>, <class 'MutableSequence'>, <class 'Sequence'>, <class 'Reversible'>, <class 'Collection'>, <class 'Iterable'>, <class 'Container'>, @Todo(`Protocol[]` subscript), typing.Generic, <class 'object'>]
# revealed: tuple[Literal[ListSubclass], Literal[list], Literal[MutableSequence], Literal[Sequence], Literal[Reversible], Literal[Collection], Literal[Iterable], Literal[Container], @Todo(`Protocol[]` subscript), typing.Generic, Literal[object]]
reveal_type(ListSubclass.__mro__)
class DictSubclass(typing.Dict): ...
# TODO: generic protocols
# revealed: tuple[<class 'DictSubclass'>, <class 'dict[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection'>, <class 'Iterable'>, <class 'Container'>, @Todo(`Protocol[]` subscript), typing.Generic, typing.Generic[_KT, _VT_co], <class 'object'>]
# revealed: tuple[Literal[DictSubclass], Literal[dict[Unknown, Unknown]], Literal[MutableMapping[Unknown, Unknown]], Literal[Mapping[Unknown, Unknown]], Literal[Collection], Literal[Iterable], Literal[Container], @Todo(`Protocol[]` subscript), typing.Generic, typing.Generic[_KT, _VT_co], Literal[object]]
reveal_type(DictSubclass.__mro__)
class SetSubclass(typing.Set): ...
# TODO: generic protocols
# revealed: tuple[<class 'SetSubclass'>, <class 'set'>, <class 'MutableSet'>, <class 'AbstractSet'>, <class 'Collection'>, <class 'Iterable'>, <class 'Container'>, @Todo(`Protocol[]` subscript), typing.Generic, <class 'object'>]
# revealed: tuple[Literal[SetSubclass], Literal[set], Literal[MutableSet], Literal[AbstractSet], Literal[Collection], Literal[Iterable], Literal[Container], @Todo(`Protocol[]` subscript), typing.Generic, Literal[object]]
reveal_type(SetSubclass.__mro__)
class FrozenSetSubclass(typing.FrozenSet): ...
# TODO: generic protocols
# revealed: tuple[<class 'FrozenSetSubclass'>, <class 'frozenset'>, <class 'AbstractSet'>, <class 'Collection'>, <class 'Iterable'>, <class 'Container'>, @Todo(`Protocol[]` subscript), typing.Generic, <class 'object'>]
# TODO: should have `Generic`, should not have `Unknown`
# revealed: tuple[Literal[FrozenSetSubclass], Literal[frozenset], Unknown, Literal[object]]
reveal_type(FrozenSetSubclass.__mro__)
####################
@@ -113,30 +113,30 @@ reveal_type(FrozenSetSubclass.__mro__)
class ChainMapSubclass(typing.ChainMap): ...
# TODO: generic protocols
# revealed: tuple[<class 'ChainMapSubclass'>, <class 'ChainMap[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection'>, <class 'Iterable'>, <class 'Container'>, @Todo(`Protocol[]` subscript), typing.Generic, typing.Generic[_KT, _VT_co], <class 'object'>]
# revealed: tuple[Literal[ChainMapSubclass], Literal[ChainMap[Unknown, Unknown]], Literal[MutableMapping[Unknown, Unknown]], Literal[Mapping[Unknown, Unknown]], Literal[Collection], Literal[Iterable], Literal[Container], @Todo(`Protocol[]` subscript), typing.Generic, typing.Generic[_KT, _VT_co], Literal[object]]
reveal_type(ChainMapSubclass.__mro__)
class CounterSubclass(typing.Counter): ...
# TODO: Should be (CounterSubclass, Counter, dict, MutableMapping, Mapping, Collection, Sized, Iterable, Container, Generic, object)
# revealed: tuple[<class 'CounterSubclass'>, <class 'Counter[Unknown]'>, <class 'dict[Unknown, int]'>, <class 'MutableMapping[Unknown, int]'>, <class 'Mapping[Unknown, int]'>, <class 'Collection'>, <class 'Iterable'>, <class 'Container'>, @Todo(`Protocol[]` subscript), typing.Generic, typing.Generic[_KT, _VT_co], typing.Generic[_T], <class 'object'>]
# revealed: tuple[Literal[CounterSubclass], Literal[Counter[Unknown]], Literal[dict[Unknown, int]], Literal[MutableMapping[Unknown, int]], Literal[Mapping[Unknown, int]], Literal[Collection], Literal[Iterable], Literal[Container], @Todo(`Protocol[]` subscript), typing.Generic, typing.Generic[_KT, _VT_co], typing.Generic[_T], Literal[object]]
reveal_type(CounterSubclass.__mro__)
class DefaultDictSubclass(typing.DefaultDict): ...
# TODO: Should be (DefaultDictSubclass, defaultdict, dict, MutableMapping, Mapping, Collection, Sized, Iterable, Container, Generic, object)
# revealed: tuple[<class 'DefaultDictSubclass'>, <class 'defaultdict[Unknown, Unknown]'>, <class 'dict[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection'>, <class 'Iterable'>, <class 'Container'>, @Todo(`Protocol[]` subscript), typing.Generic, typing.Generic[_KT, _VT_co], <class 'object'>]
# revealed: tuple[Literal[DefaultDictSubclass], Literal[defaultdict[Unknown, Unknown]], Literal[dict[Unknown, Unknown]], Literal[MutableMapping[Unknown, Unknown]], Literal[Mapping[Unknown, Unknown]], Literal[Collection], Literal[Iterable], Literal[Container], @Todo(`Protocol[]` subscript), typing.Generic, typing.Generic[_KT, _VT_co], Literal[object]]
reveal_type(DefaultDictSubclass.__mro__)
class DequeSubclass(typing.Deque): ...
# TODO: generic protocols
# revealed: tuple[<class 'DequeSubclass'>, <class 'deque'>, <class 'MutableSequence'>, <class 'Sequence'>, <class 'Reversible'>, <class 'Collection'>, <class 'Iterable'>, <class 'Container'>, @Todo(`Protocol[]` subscript), typing.Generic, <class 'object'>]
# revealed: tuple[Literal[DequeSubclass], Literal[deque], Literal[MutableSequence], Literal[Sequence], Literal[Reversible], Literal[Collection], Literal[Iterable], Literal[Container], @Todo(`Protocol[]` subscript), typing.Generic, Literal[object]]
reveal_type(DequeSubclass.__mro__)
class OrderedDictSubclass(typing.OrderedDict): ...
# TODO: Should be (OrderedDictSubclass, OrderedDict, dict, MutableMapping, Mapping, Collection, Sized, Iterable, Container, Generic, object)
# revealed: tuple[<class 'OrderedDictSubclass'>, <class 'OrderedDict[Unknown, Unknown]'>, <class 'dict[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection'>, <class 'Iterable'>, <class 'Container'>, @Todo(`Protocol[]` subscript), typing.Generic, typing.Generic[_KT, _VT_co], <class 'object'>]
# revealed: tuple[Literal[OrderedDictSubclass], Literal[OrderedDict[Unknown, Unknown]], Literal[dict[Unknown, Unknown]], Literal[MutableMapping[Unknown, Unknown]], Literal[Mapping[Unknown, Unknown]], Literal[Collection], Literal[Iterable], Literal[Container], @Todo(`Protocol[]` subscript), typing.Generic, typing.Generic[_KT, _VT_co], Literal[object]]
reveal_type(OrderedDictSubclass.__mro__)
```

View File

@@ -30,7 +30,7 @@ def i(callback: Callable[Concatenate[int, P], R_co], *args: P.args, **kwargs: P.
class Foo:
def method(self, x: Self):
reveal_type(x) # revealed: Self
reveal_type(x) # revealed: @Todo(Support for `typing.Self`)
```
## Type expressions
@@ -73,7 +73,7 @@ class E(Concatenate): ... # error: [invalid-base]
class F(Callable): ...
class G(Generic): ... # error: [invalid-base] "Cannot inherit from plain `Generic`"
reveal_type(F.__mro__) # revealed: tuple[<class 'F'>, @Todo(Support for Callable as a base class), <class 'object'>]
reveal_type(F.__mro__) # revealed: tuple[Literal[F], @Todo(Support for Callable as a base class), Literal[object]]
```
## Subscriptability

View File

@@ -54,11 +54,11 @@ c_instance.declared_and_bound = False
c_instance.declared_and_bound = "incompatible"
# mypy shows no error here, but pyright raises "reportAttributeAccessIssue"
# error: [unresolved-attribute] "Attribute `inferred_from_value` can only be accessed on instances, not on the class object `<class 'C'>` itself."
# error: [unresolved-attribute] "Attribute `inferred_from_value` can only be accessed on instances, not on the class object `Literal[C]` itself."
reveal_type(C.inferred_from_value) # revealed: Unknown
# mypy shows no error here, but pyright raises "reportAttributeAccessIssue"
# error: [invalid-attribute-access] "Cannot assign to instance attribute `inferred_from_value` from the class object `<class 'C'>`"
# error: [invalid-attribute-access] "Cannot assign to instance attribute `inferred_from_value` from the class object `Literal[C]`"
C.inferred_from_value = "overwritten on class"
# This assignment is fine:
@@ -90,11 +90,11 @@ reveal_type(c_instance.declared_and_bound) # revealed: str | None
# Note that both mypy and pyright show no error in this case! So we may reconsider this in
# the future, if it turns out to produce too many false positives. We currently emit:
# error: [unresolved-attribute] "Attribute `declared_and_bound` can only be accessed on instances, not on the class object `<class 'C'>` itself."
# error: [unresolved-attribute] "Attribute `declared_and_bound` can only be accessed on instances, not on the class object `Literal[C]` itself."
reveal_type(C.declared_and_bound) # revealed: Unknown
# Same as above. Mypy and pyright do not show an error here.
# error: [invalid-attribute-access] "Cannot assign to instance attribute `declared_and_bound` from the class object `<class 'C'>`"
# error: [invalid-attribute-access] "Cannot assign to instance attribute `declared_and_bound` from the class object `Literal[C]`"
C.declared_and_bound = "overwritten on class"
# error: [invalid-assignment] "Object of type `Literal[1]` is not assignable to attribute `declared_and_bound` of type `str | None`"
@@ -115,10 +115,10 @@ c_instance = C()
reveal_type(c_instance.only_declared) # revealed: str
# Mypy and pyright do not show an error here. We treat this as a pure instance variable.
# error: [unresolved-attribute] "Attribute `only_declared` can only be accessed on instances, not on the class object `<class 'C'>` itself."
# error: [unresolved-attribute] "Attribute `only_declared` can only be accessed on instances, not on the class object `Literal[C]` itself."
reveal_type(C.only_declared) # revealed: Unknown
# error: [invalid-attribute-access] "Cannot assign to instance attribute `only_declared` from the class object `<class 'C'>`"
# error: [invalid-attribute-access] "Cannot assign to instance attribute `only_declared` from the class object `Literal[C]`"
C.only_declared = "overwritten on class"
```
@@ -191,10 +191,10 @@ reveal_type(c_instance.declared_only) # revealed: bytes
reveal_type(c_instance.declared_and_bound) # revealed: bool
# error: [unresolved-attribute] "Attribute `inferred_from_value` can only be accessed on instances, not on the class object `<class 'C'>` itself."
# error: [unresolved-attribute] "Attribute `inferred_from_value` can only be accessed on instances, not on the class object `Literal[C]` itself."
reveal_type(C.inferred_from_value) # revealed: Unknown
# error: [invalid-attribute-access] "Cannot assign to instance attribute `inferred_from_value` from the class object `<class 'C'>`"
# error: [invalid-attribute-access] "Cannot assign to instance attribute `inferred_from_value` from the class object `Literal[C]`"
C.inferred_from_value = "overwritten on class"
```
@@ -718,7 +718,7 @@ reveal_type(C.pure_class_variable) # revealed: Unknown
# TODO: should be no error when descriptor protocol is supported
# and the assignment is properly attributed to the class method.
# error: [invalid-attribute-access] "Cannot assign to instance attribute `pure_class_variable` from the class object `<class 'C'>`"
# error: [invalid-attribute-access] "Cannot assign to instance attribute `pure_class_variable` from the class object `Literal[C]`"
C.pure_class_variable = "overwritten on class"
# TODO: should be `Unknown | Literal["value set in class method"]` or
@@ -925,7 +925,7 @@ def _(flag: bool):
reveal_type(C1.y) # revealed: int | str
C1.y = 100
# error: [invalid-assignment] "Object of type `Literal["problematic"]` is not assignable to attribute `y` on type `<class 'C1'> | <class 'C1'>`"
# error: [invalid-assignment] "Object of type `Literal["problematic"]` is not assignable to attribute `y` on type `Literal[C1, C1]`"
C1.y = "problematic"
class C2:
@@ -1002,10 +1002,10 @@ def _(flag1: bool, flag2: bool):
C = C1 if flag1 else C2 if flag2 else C3
# error: [possibly-unbound-attribute] "Attribute `x` on type `<class 'C1'> | <class 'C2'> | <class 'C3'>` is possibly unbound"
# error: [possibly-unbound-attribute] "Attribute `x` on type `Literal[C1, C2, C3]` is possibly unbound"
reveal_type(C.x) # revealed: Unknown | Literal[1, 3]
# error: [invalid-assignment] "Object of type `Literal[100]` is not assignable to attribute `x` on type `<class 'C1'> | <class 'C2'> | <class 'C3'>`"
# error: [invalid-assignment] "Object of type `Literal[100]` is not assignable to attribute `x` on type `Literal[C1, C2, C3]`"
C.x = 100
# error: [possibly-unbound-attribute] "Attribute `x` on type `C1 | C2 | C3` is possibly unbound"
@@ -1034,7 +1034,7 @@ def _(flag: bool, flag1: bool, flag2: bool):
C = C1 if flag1 else C2 if flag2 else C3
# error: [possibly-unbound-attribute] "Attribute `x` on type `<class 'C1'> | <class 'C2'> | <class 'C3'>` is possibly unbound"
# error: [possibly-unbound-attribute] "Attribute `x` on type `Literal[C1, C2, C3]` is possibly unbound"
reveal_type(C.x) # revealed: Unknown | Literal[1, 2, 3]
# error: [possibly-unbound-attribute]
@@ -1165,12 +1165,12 @@ def _(flag: bool):
class C2: ...
C = C1 if flag else C2
# error: [unresolved-attribute] "Type `<class 'C1'> | <class 'C2'>` has no attribute `x`"
# error: [unresolved-attribute] "Type `Literal[C1, C2]` has no attribute `x`"
reveal_type(C.x) # revealed: Unknown
# TODO: This should ideally be a `unresolved-attribute` error. We need better union
# handling in `validate_attribute_assignment` for this.
# error: [invalid-assignment] "Object of type `Literal[1]` is not assignable to attribute `x` on type `<class 'C1'> | <class 'C2'>`"
# error: [invalid-assignment] "Object of type `Literal[1]` is not assignable to attribute `x` on type `Literal[C1, C2]`"
C.x = 1
```
@@ -1206,7 +1206,7 @@ class C(D, F): ...
class B(E, D): ...
class A(B, C): ...
# revealed: tuple[<class 'A'>, <class 'B'>, <class 'E'>, <class 'C'>, <class 'D'>, <class 'F'>, <class 'O'>, <class 'object'>]
# revealed: tuple[Literal[A], Literal[B], Literal[E], Literal[C], Literal[D], Literal[F], Literal[O], Literal[object]]
reveal_type(A.__mro__)
# `E` is earlier in the MRO than `F`, so we should use the type of `E.X`
@@ -1399,7 +1399,7 @@ class A:
class B(Any): ...
class C(B, A): ...
reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, <class 'B'>, Any, <class 'A'>, <class 'object'>]
reveal_type(C.__mro__) # revealed: tuple[Literal[C], Literal[B], Any, Literal[A], Literal[object]]
reveal_type(C.x) # revealed: Literal[1] & Any
```
@@ -1556,31 +1556,31 @@ The type of `x.__class__` is the same as `x`'s meta-type. `x.__class__` is alway
```py
import typing_extensions
reveal_type(typing_extensions.__class__) # revealed: <class 'ModuleType'>
reveal_type(type(typing_extensions)) # revealed: <class 'ModuleType'>
reveal_type(typing_extensions.__class__) # revealed: Literal[ModuleType]
reveal_type(type(typing_extensions)) # revealed: Literal[ModuleType]
a = 42
reveal_type(a.__class__) # revealed: <class 'int'>
reveal_type(type(a)) # revealed: <class 'int'>
reveal_type(a.__class__) # revealed: Literal[int]
reveal_type(type(a)) # revealed: Literal[int]
b = "42"
reveal_type(b.__class__) # revealed: <class 'str'>
reveal_type(b.__class__) # revealed: Literal[str]
c = b"42"
reveal_type(c.__class__) # revealed: <class 'bytes'>
reveal_type(c.__class__) # revealed: Literal[bytes]
d = True
reveal_type(d.__class__) # revealed: <class 'bool'>
reveal_type(d.__class__) # revealed: Literal[bool]
e = (42, 42)
reveal_type(e.__class__) # revealed: <class 'tuple'>
reveal_type(e.__class__) # revealed: Literal[tuple]
def f(a: int, b: typing_extensions.LiteralString, c: int | str, d: type[str]):
reveal_type(a.__class__) # revealed: type[int]
reveal_type(type(a)) # revealed: type[int]
reveal_type(b.__class__) # revealed: <class 'str'>
reveal_type(type(b)) # revealed: <class 'str'>
reveal_type(b.__class__) # revealed: Literal[str]
reveal_type(type(b)) # revealed: Literal[str]
reveal_type(c.__class__) # revealed: type[int] | type[str]
reveal_type(type(c)) # revealed: type[int] | type[str]
@@ -1591,11 +1591,11 @@ def f(a: int, b: typing_extensions.LiteralString, c: int | str, d: type[str]):
# All we know is that the metaclass must be a (non-strict) subclass of `type`.
reveal_type(d.__class__) # revealed: type[type]
reveal_type(f.__class__) # revealed: <class 'FunctionType'>
reveal_type(f.__class__) # revealed: Literal[FunctionType]
class Foo: ...
reveal_type(Foo.__class__) # revealed: <class 'type'>
reveal_type(Foo.__class__) # revealed: Literal[type]
```
## Module attributes

View File

@@ -22,6 +22,6 @@ reveal_type(A | B) # revealed: UnionType
class A: ...
class B: ...
# error: "Operator `|` is unsupported between objects of type `<class 'A'>` and `<class 'B'>`"
# error: "Operator `|` is unsupported between objects of type `Literal[A]` and `Literal[B]`"
reveal_type(A | B) # revealed: Unknown
```

View File

@@ -307,11 +307,11 @@ class Yes:
class Sub(Yes): ...
class No: ...
# error: [unsupported-operator] "Operator `+` is unsupported between objects of type `<class 'Yes'>` and `<class 'Yes'>`"
# error: [unsupported-operator] "Operator `+` is unsupported between objects of type `Literal[Yes]` and `Literal[Yes]`"
reveal_type(Yes + Yes) # revealed: Unknown
# error: [unsupported-operator] "Operator `+` is unsupported between objects of type `<class 'Sub'>` and `<class 'Sub'>`"
# error: [unsupported-operator] "Operator `+` is unsupported between objects of type `Literal[Sub]` and `Literal[Sub]`"
reveal_type(Sub + Sub) # revealed: Unknown
# error: [unsupported-operator] "Operator `+` is unsupported between objects of type `<class 'No'>` and `<class 'No'>`"
# error: [unsupported-operator] "Operator `+` is unsupported between objects of type `Literal[No]` and `Literal[No]`"
reveal_type(No + No) # revealed: Unknown
```

View File

@@ -388,13 +388,13 @@ class A(metaclass=Meta): ...
class B(metaclass=Meta): ...
reveal_type(A + B) # revealed: int
# error: [unsupported-operator] "Operator `-` is unsupported between objects of type `<class 'A'>` and `<class 'B'>`"
# error: [unsupported-operator] "Operator `-` is unsupported between objects of type `Literal[A]` and `Literal[B]`"
reveal_type(A - B) # revealed: Unknown
reveal_type(A < B) # revealed: bool
reveal_type(A > B) # revealed: bool
# error: [unsupported-operator] "Operator `<=` is not supported for types `<class 'A'>` and `<class 'B'>`"
# error: [unsupported-operator] "Operator `<=` is not supported for types `Literal[A]` and `Literal[B]`"
reveal_type(A <= B) # revealed: Unknown
reveal_type(A[0]) # revealed: str

View File

@@ -312,8 +312,8 @@ class C:
```py
from mod import MyInt, C
reveal_type(MyInt) # revealed: <class 'int'>
reveal_type(C.MyStr) # revealed: <class 'str'>
reveal_type(MyInt) # revealed: Literal[int]
reveal_type(C.MyStr) # revealed: Literal[str]
```
### Undeclared and possibly unbound
@@ -336,8 +336,8 @@ if flag():
# error: [possibly-unbound-import]
from mod import MyInt, C
reveal_type(MyInt) # revealed: <class 'int'>
reveal_type(C.MyStr) # revealed: <class 'str'>
reveal_type(MyInt) # revealed: Literal[int]
reveal_type(C.MyStr) # revealed: Literal[str]
```
### Undeclared and unbound

View File

@@ -20,7 +20,7 @@ tested more extensively in `crates/ty_python_semantic/resources/mdtest/attribute
tests for the `__class__` attribute.)
```py
reveal_type(type(1)) # revealed: <class 'int'>
reveal_type(type(1)) # revealed: Literal[int]
```
But a three-argument call to type creates a dynamic instance of the `type` class:

View File

@@ -75,8 +75,7 @@ constructor from it.
from typing_extensions import Self
class Base:
def __new__(cls, x: int) -> Self:
return cls()
def __new__(cls, x: int) -> Self: ...
class Foo(Base): ...

View File

@@ -270,7 +270,7 @@ class Meta(type):
class C(metaclass=Meta):
pass
reveal_type(C.f) # revealed: bound method <class 'C'>.f(arg: int) -> str
reveal_type(C.f) # revealed: bound method Literal[C].f(arg: int) -> str
reveal_type(C.f(1)) # revealed: str
```
@@ -322,7 +322,7 @@ class C:
def f(cls: type[C], x: int) -> str:
return "a"
reveal_type(C.f) # revealed: bound method <class 'C'>.f(x: int) -> str
reveal_type(C.f) # revealed: bound method Literal[C].f(x: int) -> str
reveal_type(C().f) # revealed: bound method type[C].f(x: int) -> str
```
@@ -350,7 +350,7 @@ class D:
# This function is wrongly annotated, it should be `type[D]` instead of `D`
pass
# error: [invalid-argument-type] "Argument to this function is incorrect: Expected `D`, found `<class 'D'>`"
# error: [invalid-argument-type] "Argument to this function is incorrect: Expected `D`, found `Literal[D]`"
D.f()
```
@@ -360,7 +360,7 @@ When a class method is accessed on a derived class, it is bound to that derived
class Derived(C):
pass
reveal_type(Derived.f) # revealed: bound method <class 'Derived'>.f(x: int) -> str
reveal_type(Derived.f) # revealed: bound method Literal[Derived].f(x: int) -> str
reveal_type(Derived().f) # revealed: bound method type[Derived].f(x: int) -> str
reveal_type(Derived.f(1)) # revealed: str
@@ -386,15 +386,15 @@ reveal_type(getattr_static(C, "f").__get__) # revealed: <method-wrapper `__get_
But we correctly model how the `classmethod` descriptor works:
```py
reveal_type(getattr_static(C, "f").__get__(None, C)) # revealed: bound method <class 'C'>.f() -> Unknown
reveal_type(getattr_static(C, "f").__get__(C(), C)) # revealed: bound method <class 'C'>.f() -> Unknown
reveal_type(getattr_static(C, "f").__get__(None, C)) # revealed: bound method Literal[C].f() -> Unknown
reveal_type(getattr_static(C, "f").__get__(C(), C)) # revealed: bound method Literal[C].f() -> Unknown
reveal_type(getattr_static(C, "f").__get__(C())) # revealed: bound method type[C].f() -> Unknown
```
The `owner` argument takes precedence over the `instance` argument:
```py
reveal_type(getattr_static(C, "f").__get__("dummy", C)) # revealed: bound method <class 'C'>.f() -> Unknown
reveal_type(getattr_static(C, "f").__get__("dummy", C)) # revealed: bound method Literal[C].f() -> Unknown
```
### Classmethods mixed with other decorators

View File

@@ -1,6 +1,6 @@
# Super
Python defines the terms _bound super object_ and _unbound super object_.
Python defines the terms *bound super object* and *unbound super object*.
An **unbound super object** is created when `super` is called with only one argument. (e.g.
`super(A)`). This object may later be bound using the `super.__get__` method. However, this form is
@@ -30,24 +30,24 @@ class C(B):
def c(self): ...
cc: int = 3
reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, <class 'B'>, <class 'A'>, <class 'object'>]
reveal_type(C.__mro__) # revealed: tuple[Literal[C], Literal[B], Literal[A], Literal[object]]
super(C, C()).a
super(C, C()).b
# error: [unresolved-attribute] "Type `<super: <class 'C'>, C>` has no attribute `c`"
# error: [unresolved-attribute] "Type `<super: Literal[C], C>` has no attribute `c`"
super(C, C()).c
super(B, C()).a
# error: [unresolved-attribute] "Type `<super: <class 'B'>, C>` has no attribute `b`"
# error: [unresolved-attribute] "Type `<super: Literal[B], C>` has no attribute `b`"
super(B, C()).b
# error: [unresolved-attribute] "Type `<super: <class 'B'>, C>` has no attribute `c`"
# error: [unresolved-attribute] "Type `<super: Literal[B], C>` has no attribute `c`"
super(B, C()).c
# error: [unresolved-attribute] "Type `<super: <class 'A'>, C>` has no attribute `a`"
# error: [unresolved-attribute] "Type `<super: Literal[A], C>` has no attribute `a`"
super(A, C()).a
# error: [unresolved-attribute] "Type `<super: <class 'A'>, C>` has no attribute `b`"
# error: [unresolved-attribute] "Type `<super: Literal[A], C>` has no attribute `b`"
super(A, C()).b
# error: [unresolved-attribute] "Type `<super: <class 'A'>, C>` has no attribute `c`"
# error: [unresolved-attribute] "Type `<super: Literal[A], C>` has no attribute `c`"
super(A, C()).c
reveal_type(super(C, C()).a) # revealed: bound method C.a() -> Unknown
@@ -72,14 +72,14 @@ class A:
class B(A):
def __init__(self, a: int):
# TODO: Once `Self` is supported, this should be `<super: <class 'B'>, B>`
reveal_type(super()) # revealed: <super: <class 'B'>, Unknown>
# TODO: Once `Self` is supported, this should be `<super: Literal[B], B>`
reveal_type(super()) # revealed: <super: Literal[B], Unknown>
super().__init__(a)
@classmethod
def f(cls):
# TODO: Once `Self` is supported, this should be `<super: <class 'B'>, <class 'B'>>`
reveal_type(super()) # revealed: <super: <class 'B'>, Unknown>
# TODO: Once `Self` is supported, this should be `<super: Literal[B], Literal[B]>`
reveal_type(super()) # revealed: <super: Literal[B], Unknown>
super().f()
super(B, B(42)).__init__(42)
@@ -88,7 +88,7 @@ super(B, B).f()
### Unbound Super Object
Calling `super(cls)` without a second argument returns an _unbound super object_. This is treated as
Calling `super(cls)` without a second argument returns an *unbound super object*. This is treated as
a plain `super` instance and does not support name lookup via the MRO.
```py
@@ -115,7 +115,7 @@ class A:
class B(A): ...
reveal_type(super(B, B()).a) # revealed: int
# error: [invalid-assignment] "Cannot assign to attribute `a` on type `<super: <class 'B'>, B>`"
# error: [invalid-assignment] "Cannot assign to attribute `a` on type `<super: Literal[B], B>`"
super(B, B()).a = 3
# error: [invalid-assignment] "Cannot assign to attribute `a` on type `super`"
super(B).a = 5
@@ -134,7 +134,7 @@ def f(x):
reveal_type(x) # revealed: Unknown
reveal_type(super(x, x)) # revealed: <super: Unknown, Unknown>
reveal_type(super(A, x)) # revealed: <super: <class 'A'>, Unknown>
reveal_type(super(A, x)) # revealed: <super: Literal[A], Unknown>
reveal_type(super(x, A())) # revealed: <super: Unknown, A>
reveal_type(super(x, x).a) # revealed: Unknown
@@ -149,29 +149,29 @@ from __future__ import annotations
class A:
def test(self):
reveal_type(super()) # revealed: <super: <class 'A'>, Unknown>
reveal_type(super()) # revealed: <super: Literal[A], Unknown>
class B:
def test(self):
reveal_type(super()) # revealed: <super: <class 'B'>, Unknown>
reveal_type(super()) # revealed: <super: Literal[B], Unknown>
class C(A.B):
def test(self):
reveal_type(super()) # revealed: <super: <class 'C'>, Unknown>
reveal_type(super()) # revealed: <super: Literal[C], Unknown>
def inner(t: C):
reveal_type(super()) # revealed: <super: <class 'B'>, C>
lambda x: reveal_type(super()) # revealed: <super: <class 'B'>, Unknown>
reveal_type(super()) # revealed: <super: Literal[B], C>
lambda x: reveal_type(super()) # revealed: <super: Literal[B], Unknown>
```
## Built-ins and Literals
```py
reveal_type(super(bool, True)) # revealed: <super: <class 'bool'>, bool>
reveal_type(super(bool, bool())) # revealed: <super: <class 'bool'>, bool>
reveal_type(super(int, bool())) # revealed: <super: <class 'int'>, bool>
reveal_type(super(int, 3)) # revealed: <super: <class 'int'>, int>
reveal_type(super(str, "")) # revealed: <super: <class 'str'>, str>
reveal_type(super(bool, True)) # revealed: <super: Literal[bool], bool>
reveal_type(super(bool, bool())) # revealed: <super: Literal[bool], bool>
reveal_type(super(int, bool())) # revealed: <super: Literal[int], bool>
reveal_type(super(int, 3)) # revealed: <super: Literal[int], int>
reveal_type(super(str, "")) # revealed: <super: Literal[str], str>
```
## Descriptor Behavior with Super
@@ -195,7 +195,7 @@ reveal_type(super(B, B()).a2) # revealed: bound method type[B].a2() -> Unknown
# A.__dict__["a1"].__get__(None, B)
reveal_type(super(B, B).a1) # revealed: def a1(self) -> Unknown
# A.__dict__["a2"].__get__(None, B)
reveal_type(super(B, B).a2) # revealed: bound method <class 'B'>.a2() -> Unknown
reveal_type(super(B, B).a2) # revealed: bound method Literal[B].a2() -> Unknown
```
## Union of Supers
@@ -213,22 +213,22 @@ class C(A, B): ...
class D(B, A): ...
def f(x: C | D):
reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, <class 'A'>, <class 'B'>, <class 'object'>]
reveal_type(D.__mro__) # revealed: tuple[<class 'D'>, <class 'B'>, <class 'A'>, <class 'object'>]
reveal_type(C.__mro__) # revealed: tuple[Literal[C], Literal[A], Literal[B], Literal[object]]
reveal_type(D.__mro__) # revealed: tuple[Literal[D], Literal[B], Literal[A], Literal[object]]
s = super(A, x)
reveal_type(s) # revealed: <super: <class 'A'>, C> | <super: <class 'A'>, D>
reveal_type(s) # revealed: <super: Literal[A], C> | <super: Literal[A], D>
# error: [possibly-unbound-attribute] "Attribute `b` on type `<super: <class 'A'>, C> | <super: <class 'A'>, D>` is possibly unbound"
# error: [possibly-unbound-attribute] "Attribute `b` on type `<super: Literal[A], C> | <super: Literal[A], D>` is possibly unbound"
s.b
def f(flag: bool):
x = str() if flag else str("hello")
reveal_type(x) # revealed: Literal["", "hello"]
reveal_type(super(str, x)) # revealed: <super: <class 'str'>, str>
reveal_type(super(str, x)) # revealed: <super: Literal[str], str>
def f(x: int | str):
# error: [invalid-super-argument] "`str` is not an instance or subclass of `<class 'int'>` in `super(<class 'int'>, str)` call"
# error: [invalid-super-argument] "`str` is not an instance or subclass of `Literal[int]` in `super(Literal[int], str)` call"
super(int, x)
```
@@ -254,12 +254,12 @@ def f(flag: bool):
class D(C): ...
s = super(D, D())
reveal_type(s) # revealed: <super: <class 'B'>, B> | <super: <class 'D'>, D>
reveal_type(s) # revealed: <super: Literal[B], B> | <super: Literal[D], D>
reveal_type(s.x) # revealed: Unknown | Literal[1, 2]
reveal_type(s.y) # revealed: int | str
# error: [possibly-unbound-attribute] "Attribute `a` on type `<super: <class 'B'>, B> | <super: <class 'D'>, D>` is possibly unbound"
# error: [possibly-unbound-attribute] "Attribute `a` on type `<super: Literal[B], B> | <super: Literal[D], D>` is possibly unbound"
reveal_type(s.a) # revealed: str
```
@@ -339,30 +339,30 @@ def f(x: int):
# error: [invalid-super-argument] "`typing.TypeAliasType` is not a valid class"
super(IntAlias, 0)
# error: [invalid-super-argument] "`Literal[""]` is not an instance or subclass of `<class 'int'>` in `super(<class 'int'>, Literal[""])` call"
# error: [invalid-super-argument] "`Literal[""]` is not an instance or subclass of `Literal[int]` in `super(Literal[int], Literal[""])` call"
# revealed: Unknown
reveal_type(super(int, str()))
# error: [invalid-super-argument] "`<class 'str'>` is not an instance or subclass of `<class 'int'>` in `super(<class 'int'>, <class 'str'>)` call"
# error: [invalid-super-argument] "`Literal[str]` is not an instance or subclass of `Literal[int]` in `super(Literal[int], Literal[str])` call"
# revealed: Unknown
reveal_type(super(int, str))
class A: ...
class B(A): ...
# error: [invalid-super-argument] "`A` is not an instance or subclass of `<class 'B'>` in `super(<class 'B'>, A)` call"
# error: [invalid-super-argument] "`A` is not an instance or subclass of `Literal[B]` in `super(Literal[B], A)` call"
# revealed: Unknown
reveal_type(super(B, A()))
# error: [invalid-super-argument] "`object` is not an instance or subclass of `<class 'B'>` in `super(<class 'B'>, object)` call"
# error: [invalid-super-argument] "`object` is not an instance or subclass of `Literal[B]` in `super(Literal[B], object)` call"
# revealed: Unknown
reveal_type(super(B, object()))
# error: [invalid-super-argument] "`<class 'A'>` is not an instance or subclass of `<class 'B'>` in `super(<class 'B'>, <class 'A'>)` call"
# error: [invalid-super-argument] "`Literal[A]` is not an instance or subclass of `Literal[B]` in `super(Literal[B], Literal[A])` call"
# revealed: Unknown
reveal_type(super(B, A))
# error: [invalid-super-argument] "`<class 'object'>` is not an instance or subclass of `<class 'B'>` in `super(<class 'B'>, <class 'object'>)` call"
# error: [invalid-super-argument] "`Literal[object]` is not an instance or subclass of `Literal[B]` in `super(Literal[B], Literal[object])` call"
# revealed: Unknown
reveal_type(super(B, object))
@@ -386,7 +386,7 @@ class B(A):
# TODO: Once `Self` is supported, this should raise `unresolved-attribute` error
super().a
# error: [unresolved-attribute] "Type `<super: <class 'B'>, B>` has no attribute `a`"
# error: [unresolved-attribute] "Type `<super: Literal[B], B>` has no attribute `a`"
super(B, B(42)).a
```
@@ -405,6 +405,6 @@ class B(A): ...
reveal_type(A()[0]) # revealed: int
reveal_type(super(B, B()).__getitem__) # revealed: bound method B.__getitem__(key: int) -> int
# error: [non-subscriptable] "Cannot subscript object of type `<super: <class 'B'>, B>` with no `__getitem__` method"
# error: [non-subscriptable] "Cannot subscript object of type `<super: Literal[B], B>` with no `__getitem__` method"
super(B, B())[0]
```

View File

@@ -281,11 +281,13 @@ class D1:
class D2:
x: str
D1("a")
D2("a")
# TODO: these should not be errors
D1("a") # error: [too-many-positional-arguments]
D2("a") # error: [too-many-positional-arguments]
D1(1.2) # error: [invalid-argument-type]
D2(1.2) # error: [invalid-argument-type]
# TODO: these should be invalid-argument-type errors
D1(1.2) # error: [too-many-positional-arguments]
D2(1.2) # error: [too-many-positional-arguments]
```
[`typing.dataclass_transform`]: https://docs.python.org/3/library/typing.html#typing.dataclass_transform

View File

@@ -181,7 +181,7 @@ class D:
class_literal: TypeOf[SomeClass]
class_subtype_of: type[SomeClass]
# revealed: (function_literal: def some_function() -> None, class_literal: <class 'SomeClass'>, class_subtype_of: type[SomeClass]) -> None
# revealed: (function_literal: def some_function() -> None, class_literal: Literal[SomeClass], class_subtype_of: type[SomeClass]) -> None
reveal_type(D.__init__)
```
@@ -489,7 +489,7 @@ class DataWithDescription[T]:
data: T
description: str
reveal_type(DataWithDescription[int]) # revealed: <class 'DataWithDescription[int]'>
reveal_type(DataWithDescription[int]) # revealed: Literal[DataWithDescription[int]]
d_int = DataWithDescription[int](1, "description") # OK
reveal_type(d_int.data) # revealed: int
@@ -675,7 +675,7 @@ from dataclasses import dataclass
class C:
x: int
# error: [unresolved-attribute] "Attribute `x` can only be accessed on instances, not on the class object `<class 'C'>` itself."
# error: [unresolved-attribute] "Attribute `x` can only be accessed on instances, not on the class object `Literal[C]` itself."
C.x
```
@@ -715,8 +715,8 @@ class Person:
name: str
age: int | None = None
reveal_type(type(Person)) # revealed: <class 'type'>
reveal_type(Person.__mro__) # revealed: tuple[<class 'Person'>, <class 'object'>]
reveal_type(type(Person)) # revealed: Literal[type]
reveal_type(Person.__mro__) # revealed: tuple[Literal[Person], Literal[object]]
```
The generated methods have the following signatures:

View File

@@ -269,7 +269,7 @@ on the metaclass:
```py
C1.meta_data_descriptor = 1
# error: [invalid-assignment] "Invalid assignment to data descriptor attribute `meta_data_descriptor` on type `<class 'C1'>` with custom `__set__` method"
# error: [invalid-assignment] "Invalid assignment to data descriptor attribute `meta_data_descriptor` on type `Literal[C1]` with custom `__set__` method"
C1.meta_data_descriptor = "invalid"
```
@@ -371,7 +371,7 @@ def _(flag: bool):
# TODO: We currently emit two diagnostics here, corresponding to the two states of `flag`. The diagnostics are not
# wrong, but they could be subsumed under a higher-level diagnostic.
# error: [invalid-assignment] "Invalid assignment to data descriptor attribute `meta_data_descriptor1` on type `<class 'C5'>` with custom `__set__` method"
# error: [invalid-assignment] "Invalid assignment to data descriptor attribute `meta_data_descriptor1` on type `Literal[C5]` with custom `__set__` method"
# error: [invalid-assignment] "Object of type `None` is not assignable to attribute `meta_data_descriptor1` of type `Literal["value on class"]`"
C5.meta_data_descriptor1 = None
@@ -724,13 +724,13 @@ def _(flag: bool):
non_data: NonDataDescriptor = NonDataDescriptor()
data: DataDescriptor = DataDescriptor()
# error: [possibly-unbound-attribute] "Attribute `non_data` on type `<class 'PossiblyUnbound'>` is possibly unbound"
# error: [possibly-unbound-attribute] "Attribute `non_data` on type `Literal[PossiblyUnbound]` is possibly unbound"
reveal_type(PossiblyUnbound.non_data) # revealed: int
# error: [possibly-unbound-attribute] "Attribute `non_data` on type `PossiblyUnbound` is possibly unbound"
reveal_type(PossiblyUnbound().non_data) # revealed: int
# error: [possibly-unbound-attribute] "Attribute `data` on type `<class 'PossiblyUnbound'>` is possibly unbound"
# error: [possibly-unbound-attribute] "Attribute `data` on type `Literal[PossiblyUnbound]` is possibly unbound"
reveal_type(PossiblyUnbound.data) # revealed: int
# error: [possibly-unbound-attribute] "Attribute `data` on type `PossiblyUnbound` is possibly unbound"

View File

@@ -600,12 +600,12 @@ except:
reveal_type(x) # revealed: E
x = Bar
reveal_type(x) # revealed: <class 'Bar'>
reveal_type(x) # revealed: Literal[Bar]
finally:
# TODO: should be `Literal[1] | <class 'foo'> | <class 'Bar'>`
reveal_type(x) # revealed: (def foo(param=A) -> Unknown) | <class 'Bar'>
# TODO: should be `Literal[1] | Literal[foo] | Literal[Bar]`
reveal_type(x) # revealed: (def foo(param=A) -> Unknown) | Literal[Bar]
reveal_type(x) # revealed: (def foo(param=A) -> Unknown) | <class 'Bar'>
reveal_type(x) # revealed: (def foo(param=A) -> Unknown) | Literal[Bar]
```
[1]: https://astral-sh.notion.site/Exception-handler-control-flow-11348797e1ca80bb8ce1e9aedbbe439d

View File

@@ -26,9 +26,9 @@ def _(flag: bool):
reveal_type(A.union_declared) # revealed: int | str
# error: [possibly-unbound-attribute] "Attribute `possibly_unbound` on type `<class 'A'>` is possibly unbound"
# error: [possibly-unbound-attribute] "Attribute `possibly_unbound` on type `Literal[A]` is possibly unbound"
reveal_type(A.possibly_unbound) # revealed: str
# error: [unresolved-attribute] "Type `<class 'A'>` has no attribute `non_existent`"
# error: [unresolved-attribute] "Type `Literal[A]` has no attribute `non_existent`"
reveal_type(A.non_existent) # revealed: Unknown
```

View File

@@ -345,7 +345,7 @@ def f(cond: bool) -> str:
<!-- snapshot-diagnostics -->
A function with a `yield` or `yield from` expression anywhere in its body is a
A function with a `yield` statement anywhere in its body is a
[generator function](https://docs.python.org/3/glossary.html#term-generator). A generator function
implicitly returns an instance of `types.GeneratorType` even if it does not contain any `return`
statements.
@@ -366,9 +366,6 @@ def h() -> typing.Iterator:
def i() -> typing.Iterable:
yield 42
def i2() -> typing.Generator:
yield from i()
def j() -> str: # error: [invalid-return-type]
yield 42
```

View File

@@ -29,7 +29,7 @@ class RepeatedTypevar(Generic[T, T]): ...
You can only specialize `typing.Generic` with typevars (TODO: or param specs or typevar tuples).
```py
# error: [invalid-argument-type] "`<class 'int'>` is not a valid argument to `typing.Generic`"
# error: [invalid-argument-type] "`Literal[int]` is not a valid argument to `typing.Generic`"
class GenericOfType(Generic[int]): ...
```
@@ -436,7 +436,7 @@ T = TypeVar("T")
class Base(Generic[T]): ...
class Sub(Base[Sub]): ...
reveal_type(Sub) # revealed: <class 'Sub'>
reveal_type(Sub) # revealed: Literal[Sub]
```
#### With string forward references
@@ -451,7 +451,7 @@ T = TypeVar("T")
class Base(Generic[T]): ...
class Sub(Base["Sub"]): ...
reveal_type(Sub) # revealed: <class 'Sub'>
reveal_type(Sub) # revealed: Literal[Sub]
```
#### Without string forward references

View File

@@ -19,7 +19,7 @@ in newer Python releases.
from typing import TypeVar
T = TypeVar("T")
reveal_type(type(T)) # revealed: <class 'TypeVar'>
reveal_type(type(T)) # revealed: Literal[TypeVar]
reveal_type(T) # revealed: typing.TypeVar
reveal_type(T.__name__) # revealed: Literal["T"]
```
@@ -123,32 +123,4 @@ from typing import TypeVar
T = TypeVar("T", int)
```
### Cannot be both covariant and contravariant
> To facilitate the declaration of container types where covariant or contravariant type checking is
> acceptable, type variables accept keyword arguments `covariant=True` or `contravariant=True`. At
> most one of these may be passed.
```py
from typing import TypeVar
# error: [invalid-legacy-type-variable]
T = TypeVar("T", covariant=True, contravariant=True)
```
### Variance parameters must be unambiguous
```py
from typing import TypeVar
def cond() -> bool:
return True
# error: [invalid-legacy-type-variable]
T = TypeVar("T", covariant=cond())
# error: [invalid-legacy-type-variable]
U = TypeVar("U", contravariant=cond())
```
[generics]: https://typing.python.org/en/latest/spec/generics.html

View File

@@ -1,321 +0,0 @@
# Variance: Legacy syntax
Type variables have a property called _variance_ that affects the subtyping and assignability
relations. Much more detail can be found in the [spec]. To summarize, each typevar is either
**covariant**, **contravariant**, **invariant**, or **bivariant**. (Note that bivariance is not
currently mentioned in the typing spec, but is a fourth case that we must consider.)
For all of the examples below, we will consider typevars `T` and `U`, two generic classes using
those typevars `C[T]` and `D[U]`, and two types `A` and `B`.
(Note that dynamic types like `Any` never participate in subtyping, so `C[Any]` is neither a subtype
nor supertype of any other specialization of `C`, regardless of `T`'s variance. It is, however,
assignable to any specialization of `C`, regardless of variance, via materialization.)
## Covariance
With a covariant typevar, subtyping and assignability are in "alignment": if `A <: B` and `C <: D`,
then `C[A] <: C[B]` and `C[A] <: D[B]`.
Types that "produce" data on demand are covariant in their typevar. If you expect a sequence of
`int`s, someone can safely provide a sequence of `bool`s, since each `bool` element that you would
get from the sequence is a valid `int`.
```py
from ty_extensions import is_assignable_to, is_equivalent_to, is_gradual_equivalent_to, is_subtype_of, static_assert, Unknown
from typing import Any, Generic, TypeVar
class A: ...
class B(A): ...
T = TypeVar("T", covariant=True)
U = TypeVar("U", covariant=True)
class C(Generic[T]):
def receive(self) -> T:
raise ValueError
class D(C[U]):
pass
static_assert(is_assignable_to(C[B], C[A]))
static_assert(not is_assignable_to(C[A], C[B]))
static_assert(is_assignable_to(C[A], C[Any]))
static_assert(is_assignable_to(C[B], C[Any]))
static_assert(is_assignable_to(C[Any], C[A]))
static_assert(is_assignable_to(C[Any], C[B]))
static_assert(is_assignable_to(D[B], C[A]))
static_assert(not is_assignable_to(D[A], C[B]))
static_assert(is_assignable_to(D[A], C[Any]))
static_assert(is_assignable_to(D[B], C[Any]))
static_assert(is_assignable_to(D[Any], C[A]))
static_assert(is_assignable_to(D[Any], C[B]))
static_assert(is_subtype_of(C[B], C[A]))
static_assert(not is_subtype_of(C[A], C[B]))
static_assert(not is_subtype_of(C[A], C[Any]))
static_assert(not is_subtype_of(C[B], C[Any]))
static_assert(not is_subtype_of(C[Any], C[A]))
static_assert(not is_subtype_of(C[Any], C[B]))
static_assert(is_subtype_of(D[B], C[A]))
static_assert(not is_subtype_of(D[A], C[B]))
static_assert(not is_subtype_of(D[A], C[Any]))
static_assert(not is_subtype_of(D[B], C[Any]))
static_assert(not is_subtype_of(D[Any], C[A]))
static_assert(not is_subtype_of(D[Any], C[B]))
static_assert(is_equivalent_to(C[A], C[A]))
static_assert(is_equivalent_to(C[B], C[B]))
static_assert(not is_equivalent_to(C[B], C[A]))
static_assert(not is_equivalent_to(C[A], C[B]))
static_assert(not is_equivalent_to(C[A], C[Any]))
static_assert(not is_equivalent_to(C[B], C[Any]))
static_assert(not is_equivalent_to(C[Any], C[A]))
static_assert(not is_equivalent_to(C[Any], C[B]))
static_assert(not is_equivalent_to(D[A], C[A]))
static_assert(not is_equivalent_to(D[B], C[B]))
static_assert(not is_equivalent_to(D[B], C[A]))
static_assert(not is_equivalent_to(D[A], C[B]))
static_assert(not is_equivalent_to(D[A], C[Any]))
static_assert(not is_equivalent_to(D[B], C[Any]))
static_assert(not is_equivalent_to(D[Any], C[A]))
static_assert(not is_equivalent_to(D[Any], C[B]))
static_assert(is_gradual_equivalent_to(C[A], C[A]))
static_assert(is_gradual_equivalent_to(C[B], C[B]))
static_assert(is_gradual_equivalent_to(C[Any], C[Any]))
static_assert(is_gradual_equivalent_to(C[Any], C[Unknown]))
static_assert(not is_gradual_equivalent_to(C[B], C[A]))
static_assert(not is_gradual_equivalent_to(C[A], C[B]))
static_assert(not is_gradual_equivalent_to(C[A], C[Any]))
static_assert(not is_gradual_equivalent_to(C[B], C[Any]))
static_assert(not is_gradual_equivalent_to(C[Any], C[A]))
static_assert(not is_gradual_equivalent_to(C[Any], C[B]))
static_assert(not is_gradual_equivalent_to(D[A], C[A]))
static_assert(not is_gradual_equivalent_to(D[B], C[B]))
static_assert(not is_gradual_equivalent_to(D[Any], C[Any]))
static_assert(not is_gradual_equivalent_to(D[Any], C[Unknown]))
static_assert(not is_gradual_equivalent_to(D[B], C[A]))
static_assert(not is_gradual_equivalent_to(D[A], C[B]))
static_assert(not is_gradual_equivalent_to(D[A], C[Any]))
static_assert(not is_gradual_equivalent_to(D[B], C[Any]))
static_assert(not is_gradual_equivalent_to(D[Any], C[A]))
static_assert(not is_gradual_equivalent_to(D[Any], C[B]))
```
## Contravariance
With a contravariant typevar, subtyping and assignability are in "opposition": if `A <: B` and
`C <: D`, then `C[B] <: C[A]` and `D[B] <: C[A]`.
Types that "consume" data are contravariant in their typevar. If you expect a consumer that receives
`bool`s, someone can safely provide a consumer that expects to receive `int`s, since each `bool`
that you pass into the consumer is a valid `int`.
```py
from ty_extensions import is_assignable_to, is_equivalent_to, is_gradual_equivalent_to, is_subtype_of, static_assert, Unknown
from typing import Any, Generic, TypeVar
class A: ...
class B(A): ...
T = TypeVar("T", contravariant=True)
U = TypeVar("U", contravariant=True)
class C(Generic[T]):
def send(self, value: T): ...
class D(C[U]):
pass
static_assert(not is_assignable_to(C[B], C[A]))
static_assert(is_assignable_to(C[A], C[B]))
static_assert(is_assignable_to(C[A], C[Any]))
static_assert(is_assignable_to(C[B], C[Any]))
static_assert(is_assignable_to(C[Any], C[A]))
static_assert(is_assignable_to(C[Any], C[B]))
static_assert(not is_assignable_to(D[B], C[A]))
static_assert(is_assignable_to(D[A], C[B]))
static_assert(is_assignable_to(D[A], C[Any]))
static_assert(is_assignable_to(D[B], C[Any]))
static_assert(is_assignable_to(D[Any], C[A]))
static_assert(is_assignable_to(D[Any], C[B]))
static_assert(not is_subtype_of(C[B], C[A]))
static_assert(is_subtype_of(C[A], C[B]))
static_assert(not is_subtype_of(C[A], C[Any]))
static_assert(not is_subtype_of(C[B], C[Any]))
static_assert(not is_subtype_of(C[Any], C[A]))
static_assert(not is_subtype_of(C[Any], C[B]))
static_assert(not is_subtype_of(D[B], C[A]))
static_assert(is_subtype_of(D[A], C[B]))
static_assert(not is_subtype_of(D[A], C[Any]))
static_assert(not is_subtype_of(D[B], C[Any]))
static_assert(not is_subtype_of(D[Any], C[A]))
static_assert(not is_subtype_of(D[Any], C[B]))
static_assert(is_equivalent_to(C[A], C[A]))
static_assert(is_equivalent_to(C[B], C[B]))
static_assert(not is_equivalent_to(C[B], C[A]))
static_assert(not is_equivalent_to(C[A], C[B]))
static_assert(not is_equivalent_to(C[A], C[Any]))
static_assert(not is_equivalent_to(C[B], C[Any]))
static_assert(not is_equivalent_to(C[Any], C[A]))
static_assert(not is_equivalent_to(C[Any], C[B]))
static_assert(not is_equivalent_to(D[A], C[A]))
static_assert(not is_equivalent_to(D[B], C[B]))
static_assert(not is_equivalent_to(D[B], C[A]))
static_assert(not is_equivalent_to(D[A], C[B]))
static_assert(not is_equivalent_to(D[A], C[Any]))
static_assert(not is_equivalent_to(D[B], C[Any]))
static_assert(not is_equivalent_to(D[Any], C[A]))
static_assert(not is_equivalent_to(D[Any], C[B]))
static_assert(is_gradual_equivalent_to(C[A], C[A]))
static_assert(is_gradual_equivalent_to(C[B], C[B]))
static_assert(is_gradual_equivalent_to(C[Any], C[Any]))
static_assert(is_gradual_equivalent_to(C[Any], C[Unknown]))
static_assert(not is_gradual_equivalent_to(C[B], C[A]))
static_assert(not is_gradual_equivalent_to(C[A], C[B]))
static_assert(not is_gradual_equivalent_to(C[A], C[Any]))
static_assert(not is_gradual_equivalent_to(C[B], C[Any]))
static_assert(not is_gradual_equivalent_to(C[Any], C[A]))
static_assert(not is_gradual_equivalent_to(C[Any], C[B]))
static_assert(not is_gradual_equivalent_to(D[A], C[A]))
static_assert(not is_gradual_equivalent_to(D[B], C[B]))
static_assert(not is_gradual_equivalent_to(D[Any], C[Any]))
static_assert(not is_gradual_equivalent_to(D[Any], C[Unknown]))
static_assert(not is_gradual_equivalent_to(D[B], C[A]))
static_assert(not is_gradual_equivalent_to(D[A], C[B]))
static_assert(not is_gradual_equivalent_to(D[A], C[Any]))
static_assert(not is_gradual_equivalent_to(D[B], C[Any]))
static_assert(not is_gradual_equivalent_to(D[Any], C[A]))
static_assert(not is_gradual_equivalent_to(D[Any], C[B]))
```
## Invariance
With an invariant typevar, only equivalent specializations of the generic class are subtypes of or
assignable to each other.
This often occurs for types that are both producers _and_ consumers, like a mutable `list`.
Iterating over the elements in a list would work with a covariant typevar, just like with the
"producer" type above. Appending elements to a list would work with a contravariant typevar, just
like with the "consumer" type above. However, a typevar cannot be both covariant and contravariant
at the same time!
If you expect a mutable list of `int`s, it's not safe for someone to provide you with a mutable list
of `bool`s, since you might try to add an element to the list: if you try to add an `int`, the list
would no longer only contain elements that are subtypes of `bool`.
Conversely, if you expect a mutable list of `bool`s, it's not safe for someone to provide you with a
mutable list of `int`s, since you might try to extract elements from the list: you expect every
element that you extract to be a subtype of `bool`, but the list can contain any `int`.
In the end, if you expect a mutable list, you must always be given a list of exactly that type,
since we can't know in advance which of the allowed methods you'll want to use.
```py
from ty_extensions import is_assignable_to, is_equivalent_to, is_gradual_equivalent_to, is_subtype_of, static_assert, Unknown
from typing import Any, Generic, TypeVar
class A: ...
class B(A): ...
T = TypeVar("T")
U = TypeVar("U")
class C(Generic[T]):
def send(self, value: T): ...
def receive(self) -> T:
raise ValueError
class D(C[U]):
pass
static_assert(not is_assignable_to(C[B], C[A]))
static_assert(not is_assignable_to(C[A], C[B]))
static_assert(is_assignable_to(C[A], C[Any]))
static_assert(is_assignable_to(C[B], C[Any]))
static_assert(is_assignable_to(C[Any], C[A]))
static_assert(is_assignable_to(C[Any], C[B]))
static_assert(not is_assignable_to(D[B], C[A]))
static_assert(not is_assignable_to(D[A], C[B]))
static_assert(is_assignable_to(D[A], C[Any]))
static_assert(is_assignable_to(D[B], C[Any]))
static_assert(is_assignable_to(D[Any], C[A]))
static_assert(is_assignable_to(D[Any], C[B]))
static_assert(not is_subtype_of(C[B], C[A]))
static_assert(not is_subtype_of(C[A], C[B]))
static_assert(not is_subtype_of(C[A], C[Any]))
static_assert(not is_subtype_of(C[B], C[Any]))
static_assert(not is_subtype_of(C[Any], C[A]))
static_assert(not is_subtype_of(C[Any], C[B]))
static_assert(not is_subtype_of(D[B], C[A]))
static_assert(not is_subtype_of(D[A], C[B]))
static_assert(not is_subtype_of(D[A], C[Any]))
static_assert(not is_subtype_of(D[B], C[Any]))
static_assert(not is_subtype_of(D[Any], C[A]))
static_assert(not is_subtype_of(D[Any], C[B]))
static_assert(is_equivalent_to(C[A], C[A]))
static_assert(is_equivalent_to(C[B], C[B]))
static_assert(not is_equivalent_to(C[B], C[A]))
static_assert(not is_equivalent_to(C[A], C[B]))
static_assert(not is_equivalent_to(C[A], C[Any]))
static_assert(not is_equivalent_to(C[B], C[Any]))
static_assert(not is_equivalent_to(C[Any], C[A]))
static_assert(not is_equivalent_to(C[Any], C[B]))
static_assert(not is_equivalent_to(D[A], C[A]))
static_assert(not is_equivalent_to(D[B], C[B]))
static_assert(not is_equivalent_to(D[B], C[A]))
static_assert(not is_equivalent_to(D[A], C[B]))
static_assert(not is_equivalent_to(D[A], C[Any]))
static_assert(not is_equivalent_to(D[B], C[Any]))
static_assert(not is_equivalent_to(D[Any], C[A]))
static_assert(not is_equivalent_to(D[Any], C[B]))
static_assert(is_gradual_equivalent_to(C[A], C[A]))
static_assert(is_gradual_equivalent_to(C[B], C[B]))
static_assert(is_gradual_equivalent_to(C[Any], C[Any]))
static_assert(is_gradual_equivalent_to(C[Any], C[Unknown]))
static_assert(not is_gradual_equivalent_to(C[B], C[A]))
static_assert(not is_gradual_equivalent_to(C[A], C[B]))
static_assert(not is_gradual_equivalent_to(C[A], C[Any]))
static_assert(not is_gradual_equivalent_to(C[B], C[Any]))
static_assert(not is_gradual_equivalent_to(C[Any], C[A]))
static_assert(not is_gradual_equivalent_to(C[Any], C[B]))
static_assert(not is_gradual_equivalent_to(D[A], C[A]))
static_assert(not is_gradual_equivalent_to(D[B], C[B]))
static_assert(not is_gradual_equivalent_to(D[Any], C[Any]))
static_assert(not is_gradual_equivalent_to(D[Any], C[Unknown]))
static_assert(not is_gradual_equivalent_to(D[B], C[A]))
static_assert(not is_gradual_equivalent_to(D[A], C[B]))
static_assert(not is_gradual_equivalent_to(D[A], C[Any]))
static_assert(not is_gradual_equivalent_to(D[B], C[Any]))
static_assert(not is_gradual_equivalent_to(D[Any], C[A]))
static_assert(not is_gradual_equivalent_to(D[Any], C[B]))
```
## Bivariance
With a bivariant typevar, _all_ specializations of the generic class are assignable to (and in fact,
gradually equivalent to) each other, and all fully static specializations are subtypes of (and
equivalent to) each other.
It is not possible to construct a legacy typevar that is explicitly bivariant.
[spec]: https://typing.python.org/en/latest/spec/generics.html#variance

View File

@@ -362,7 +362,7 @@ Here, `Sub` is not a generic class, since it fills its superclass's type paramet
class Base[T]: ...
class Sub(Base[Sub]): ...
reveal_type(Sub) # revealed: <class 'Sub'>
reveal_type(Sub) # revealed: Literal[Sub]
```
#### With string forward references
@@ -373,7 +373,7 @@ A similar case can work in a non-stub file, if forward references are stringifie
class Base[T]: ...
class Sub(Base["Sub"]): ...
reveal_type(Sub) # revealed: <class 'Sub'>
reveal_type(Sub) # revealed: Literal[Sub]
```
#### Without string forward references

View File

@@ -16,7 +16,7 @@ instances of `typing.TypeVar`, just like legacy type variables.
```py
def f[T]():
reveal_type(type(T)) # revealed: <class 'TypeVar'>
reveal_type(type(T)) # revealed: Literal[TypeVar]
reveal_type(T) # revealed: typing.TypeVar
reveal_type(T.__name__) # revealed: Literal["T"]
```
@@ -331,95 +331,6 @@ def union[T: Base, U: (Base, Unrelated)](t: T, u: U) -> None:
static_assert(is_subtype_of(U, U | None))
```
And an intersection of a typevar with another type is always a subtype of the TypeVar:
```py
from ty_extensions import Intersection, Not, is_disjoint_from
class A: ...
def inter[T: Base, U: (Base, Unrelated)](t: T, u: U) -> None:
static_assert(is_assignable_to(Intersection[T, Unrelated], T))
static_assert(is_subtype_of(Intersection[T, Unrelated], T))
static_assert(is_assignable_to(Intersection[U, A], U))
static_assert(is_subtype_of(Intersection[U, A], U))
static_assert(is_disjoint_from(Not[T], T))
static_assert(is_disjoint_from(T, Not[T]))
static_assert(is_disjoint_from(Not[U], U))
static_assert(is_disjoint_from(U, Not[U]))
```
## Equivalence
A fully static `TypeVar` is always equivalent to itself, but never to another `TypeVar`, since there
is no guarantee that they will be specialized to the same type. (This is true even if both typevars
are bounded by the same final class, since you can specialize the typevars to `Never` in addition to
that final class.)
```py
from typing import final
from ty_extensions import is_equivalent_to, static_assert, is_gradual_equivalent_to
@final
class FinalClass: ...
@final
class SecondFinalClass: ...
def f[A, B, C: FinalClass, D: FinalClass, E: (FinalClass, SecondFinalClass), F: (FinalClass, SecondFinalClass)]():
static_assert(is_equivalent_to(A, A))
static_assert(is_equivalent_to(B, B))
static_assert(is_equivalent_to(C, C))
static_assert(is_equivalent_to(D, D))
static_assert(is_equivalent_to(E, E))
static_assert(is_equivalent_to(F, F))
static_assert(is_gradual_equivalent_to(A, A))
static_assert(is_gradual_equivalent_to(B, B))
static_assert(is_gradual_equivalent_to(C, C))
static_assert(is_gradual_equivalent_to(D, D))
static_assert(is_gradual_equivalent_to(E, E))
static_assert(is_gradual_equivalent_to(F, F))
static_assert(not is_equivalent_to(A, B))
static_assert(not is_equivalent_to(C, D))
static_assert(not is_equivalent_to(E, F))
static_assert(not is_gradual_equivalent_to(A, B))
static_assert(not is_gradual_equivalent_to(C, D))
static_assert(not is_gradual_equivalent_to(E, F))
```
TypeVars which have non-fully-static bounds or constraints do not participate in equivalence
relations, but do participate in gradual equivalence relations.
```py
from typing import final, Any
from ty_extensions import is_equivalent_to, static_assert, is_gradual_equivalent_to
# fmt: off
def f[
A: tuple[Any],
B: tuple[Any],
C: (tuple[Any], tuple[Any, Any]),
D: (tuple[Any], tuple[Any, Any])
]():
static_assert(not is_equivalent_to(A, A))
static_assert(not is_equivalent_to(B, B))
static_assert(not is_equivalent_to(C, C))
static_assert(not is_equivalent_to(D, D))
static_assert(is_gradual_equivalent_to(A, A))
static_assert(is_gradual_equivalent_to(B, B))
static_assert(is_gradual_equivalent_to(C, C))
static_assert(is_gradual_equivalent_to(D, D))
# fmt: on
```
## Singletons and single-valued types
(Note: for simplicity, all of the prose in this section refers to _singleton_ types, but all of the

View File

@@ -10,17 +10,12 @@ relations. Much more detail can be found in the [spec]. To summarize, each typev
**covariant**, **contravariant**, **invariant**, or **bivariant**. (Note that bivariance is not
currently mentioned in the typing spec, but is a fourth case that we must consider.)
For all of the examples below, we will consider typevars `T` and `U`, two generic classes using
those typevars `C[T]` and `D[U]`, and two types `A` and `B`.
(Note that dynamic types like `Any` never participate in subtyping, so `C[Any]` is neither a subtype
nor supertype of any other specialization of `C`, regardless of `T`'s variance. It is, however,
assignable to any specialization of `C`, regardless of variance, via materialization.)
For all of the examples below, we will consider a typevar `T`, a generic class using that typevar
`C[T]`, and two types `A` and `B`.
## Covariance
With a covariant typevar, subtyping and assignability are in "alignment": if `A <: B` and `C <: D`,
then `C[A] <: C[B]` and `C[A] <: D[B]`.
With a covariant typevar, subtyping is in "alignment": if `A <: B`, then `C[A] <: C[B]`.
Types that "produce" data on demand are covariant in their typevar. If you expect a sequence of
`int`s, someone can safely provide a sequence of `bool`s, since each `bool` element that you would
@@ -37,9 +32,6 @@ class C[T]:
def receive(self) -> T:
raise ValueError
class D[U](C[U]):
pass
# TODO: no error
# error: [static-assert-error]
static_assert(is_assignable_to(C[B], C[A]))
@@ -49,15 +41,6 @@ static_assert(is_assignable_to(C[B], C[Any]))
static_assert(is_assignable_to(C[Any], C[A]))
static_assert(is_assignable_to(C[Any], C[B]))
# TODO: no error
# error: [static-assert-error]
static_assert(is_assignable_to(D[B], C[A]))
static_assert(not is_assignable_to(D[A], C[B]))
static_assert(is_assignable_to(D[A], C[Any]))
static_assert(is_assignable_to(D[B], C[Any]))
static_assert(is_assignable_to(D[Any], C[A]))
static_assert(is_assignable_to(D[Any], C[B]))
# TODO: no error
# error: [static-assert-error]
static_assert(is_subtype_of(C[B], C[A]))
@@ -67,15 +50,6 @@ static_assert(not is_subtype_of(C[B], C[Any]))
static_assert(not is_subtype_of(C[Any], C[A]))
static_assert(not is_subtype_of(C[Any], C[B]))
# TODO: no error
# error: [static-assert-error]
static_assert(is_subtype_of(D[B], C[A]))
static_assert(not is_subtype_of(D[A], C[B]))
static_assert(not is_subtype_of(D[A], C[Any]))
static_assert(not is_subtype_of(D[B], C[Any]))
static_assert(not is_subtype_of(D[Any], C[A]))
static_assert(not is_subtype_of(D[Any], C[B]))
static_assert(is_equivalent_to(C[A], C[A]))
static_assert(is_equivalent_to(C[B], C[B]))
static_assert(not is_equivalent_to(C[B], C[A]))
@@ -85,15 +59,6 @@ static_assert(not is_equivalent_to(C[B], C[Any]))
static_assert(not is_equivalent_to(C[Any], C[A]))
static_assert(not is_equivalent_to(C[Any], C[B]))
static_assert(not is_equivalent_to(D[A], C[A]))
static_assert(not is_equivalent_to(D[B], C[B]))
static_assert(not is_equivalent_to(D[B], C[A]))
static_assert(not is_equivalent_to(D[A], C[B]))
static_assert(not is_equivalent_to(D[A], C[Any]))
static_assert(not is_equivalent_to(D[B], C[Any]))
static_assert(not is_equivalent_to(D[Any], C[A]))
static_assert(not is_equivalent_to(D[Any], C[B]))
static_assert(is_gradual_equivalent_to(C[A], C[A]))
static_assert(is_gradual_equivalent_to(C[B], C[B]))
static_assert(is_gradual_equivalent_to(C[Any], C[Any]))
@@ -104,23 +69,11 @@ static_assert(not is_gradual_equivalent_to(C[A], C[Any]))
static_assert(not is_gradual_equivalent_to(C[B], C[Any]))
static_assert(not is_gradual_equivalent_to(C[Any], C[A]))
static_assert(not is_gradual_equivalent_to(C[Any], C[B]))
static_assert(not is_gradual_equivalent_to(D[A], C[A]))
static_assert(not is_gradual_equivalent_to(D[B], C[B]))
static_assert(not is_gradual_equivalent_to(D[Any], C[Any]))
static_assert(not is_gradual_equivalent_to(D[Any], C[Unknown]))
static_assert(not is_gradual_equivalent_to(D[B], C[A]))
static_assert(not is_gradual_equivalent_to(D[A], C[B]))
static_assert(not is_gradual_equivalent_to(D[A], C[Any]))
static_assert(not is_gradual_equivalent_to(D[B], C[Any]))
static_assert(not is_gradual_equivalent_to(D[Any], C[A]))
static_assert(not is_gradual_equivalent_to(D[Any], C[B]))
```
## Contravariance
With a contravariant typevar, subtyping and assignability are in "opposition": if `A <: B` and
`C <: D`, then `C[B] <: C[A]` and `D[B] <: C[A]`.
With a contravariant typevar, subtyping is in "opposition": if `A <: B`, then `C[B] <: C[A]`.
Types that "consume" data are contravariant in their typevar. If you expect a consumer that receives
`bool`s, someone can safely provide a consumer that expects to receive `int`s, since each `bool`
@@ -136,9 +89,6 @@ class B(A): ...
class C[T]:
def send(self, value: T): ...
class D[U](C[U]):
pass
static_assert(not is_assignable_to(C[B], C[A]))
# TODO: no error
# error: [static-assert-error]
@@ -148,15 +98,6 @@ static_assert(is_assignable_to(C[B], C[Any]))
static_assert(is_assignable_to(C[Any], C[A]))
static_assert(is_assignable_to(C[Any], C[B]))
static_assert(not is_assignable_to(D[B], C[A]))
# TODO: no error
# error: [static-assert-error]
static_assert(is_assignable_to(D[A], C[B]))
static_assert(is_assignable_to(D[A], C[Any]))
static_assert(is_assignable_to(D[B], C[Any]))
static_assert(is_assignable_to(D[Any], C[A]))
static_assert(is_assignable_to(D[Any], C[B]))
static_assert(not is_subtype_of(C[B], C[A]))
# TODO: no error
# error: [static-assert-error]
@@ -166,15 +107,6 @@ static_assert(not is_subtype_of(C[B], C[Any]))
static_assert(not is_subtype_of(C[Any], C[A]))
static_assert(not is_subtype_of(C[Any], C[B]))
static_assert(not is_subtype_of(D[B], C[A]))
# TODO: no error
# error: [static-assert-error]
static_assert(is_subtype_of(D[A], C[B]))
static_assert(not is_subtype_of(D[A], C[Any]))
static_assert(not is_subtype_of(D[B], C[Any]))
static_assert(not is_subtype_of(D[Any], C[A]))
static_assert(not is_subtype_of(D[Any], C[B]))
static_assert(is_equivalent_to(C[A], C[A]))
static_assert(is_equivalent_to(C[B], C[B]))
static_assert(not is_equivalent_to(C[B], C[A]))
@@ -184,15 +116,6 @@ static_assert(not is_equivalent_to(C[B], C[Any]))
static_assert(not is_equivalent_to(C[Any], C[A]))
static_assert(not is_equivalent_to(C[Any], C[B]))
static_assert(not is_equivalent_to(D[A], C[A]))
static_assert(not is_equivalent_to(D[B], C[B]))
static_assert(not is_equivalent_to(D[B], C[A]))
static_assert(not is_equivalent_to(D[A], C[B]))
static_assert(not is_equivalent_to(D[A], C[Any]))
static_assert(not is_equivalent_to(D[B], C[Any]))
static_assert(not is_equivalent_to(D[Any], C[A]))
static_assert(not is_equivalent_to(D[Any], C[B]))
static_assert(is_gradual_equivalent_to(C[A], C[A]))
static_assert(is_gradual_equivalent_to(C[B], C[B]))
static_assert(is_gradual_equivalent_to(C[Any], C[Any]))
@@ -203,23 +126,11 @@ static_assert(not is_gradual_equivalent_to(C[A], C[Any]))
static_assert(not is_gradual_equivalent_to(C[B], C[Any]))
static_assert(not is_gradual_equivalent_to(C[Any], C[A]))
static_assert(not is_gradual_equivalent_to(C[Any], C[B]))
static_assert(not is_gradual_equivalent_to(D[A], C[A]))
static_assert(not is_gradual_equivalent_to(D[B], C[B]))
static_assert(not is_gradual_equivalent_to(D[Any], C[Any]))
static_assert(not is_gradual_equivalent_to(D[Any], C[Unknown]))
static_assert(not is_gradual_equivalent_to(D[B], C[A]))
static_assert(not is_gradual_equivalent_to(D[A], C[B]))
static_assert(not is_gradual_equivalent_to(D[A], C[Any]))
static_assert(not is_gradual_equivalent_to(D[B], C[Any]))
static_assert(not is_gradual_equivalent_to(D[Any], C[A]))
static_assert(not is_gradual_equivalent_to(D[Any], C[B]))
```
## Invariance
With an invariant typevar, only equivalent specializations of the generic class are subtypes of or
assignable to each other.
With an invariant typevar, _no_ specializations of the generic class are subtypes of each other.
This often occurs for types that are both producers _and_ consumers, like a mutable `list`.
Iterating over the elements in a list would work with a covariant typevar, just like with the
@@ -250,9 +161,6 @@ class C[T]:
def receive(self) -> T:
raise ValueError
class D[U](C[U]):
pass
static_assert(not is_assignable_to(C[B], C[A]))
static_assert(not is_assignable_to(C[A], C[B]))
static_assert(is_assignable_to(C[A], C[Any]))
@@ -260,13 +168,6 @@ static_assert(is_assignable_to(C[B], C[Any]))
static_assert(is_assignable_to(C[Any], C[A]))
static_assert(is_assignable_to(C[Any], C[B]))
static_assert(not is_assignable_to(D[B], C[A]))
static_assert(not is_assignable_to(D[A], C[B]))
static_assert(is_assignable_to(D[A], C[Any]))
static_assert(is_assignable_to(D[B], C[Any]))
static_assert(is_assignable_to(D[Any], C[A]))
static_assert(is_assignable_to(D[Any], C[B]))
static_assert(not is_subtype_of(C[B], C[A]))
static_assert(not is_subtype_of(C[A], C[B]))
static_assert(not is_subtype_of(C[A], C[Any]))
@@ -274,13 +175,6 @@ static_assert(not is_subtype_of(C[B], C[Any]))
static_assert(not is_subtype_of(C[Any], C[A]))
static_assert(not is_subtype_of(C[Any], C[B]))
static_assert(not is_subtype_of(D[B], C[A]))
static_assert(not is_subtype_of(D[A], C[B]))
static_assert(not is_subtype_of(D[A], C[Any]))
static_assert(not is_subtype_of(D[B], C[Any]))
static_assert(not is_subtype_of(D[Any], C[A]))
static_assert(not is_subtype_of(D[Any], C[B]))
static_assert(is_equivalent_to(C[A], C[A]))
static_assert(is_equivalent_to(C[B], C[B]))
static_assert(not is_equivalent_to(C[B], C[A]))
@@ -290,15 +184,6 @@ static_assert(not is_equivalent_to(C[B], C[Any]))
static_assert(not is_equivalent_to(C[Any], C[A]))
static_assert(not is_equivalent_to(C[Any], C[B]))
static_assert(not is_equivalent_to(D[A], C[A]))
static_assert(not is_equivalent_to(D[B], C[B]))
static_assert(not is_equivalent_to(D[B], C[A]))
static_assert(not is_equivalent_to(D[A], C[B]))
static_assert(not is_equivalent_to(D[A], C[Any]))
static_assert(not is_equivalent_to(D[B], C[Any]))
static_assert(not is_equivalent_to(D[Any], C[A]))
static_assert(not is_equivalent_to(D[Any], C[B]))
static_assert(is_gradual_equivalent_to(C[A], C[A]))
static_assert(is_gradual_equivalent_to(C[B], C[B]))
static_assert(is_gradual_equivalent_to(C[Any], C[Any]))
@@ -309,23 +194,11 @@ static_assert(not is_gradual_equivalent_to(C[A], C[Any]))
static_assert(not is_gradual_equivalent_to(C[B], C[Any]))
static_assert(not is_gradual_equivalent_to(C[Any], C[A]))
static_assert(not is_gradual_equivalent_to(C[Any], C[B]))
static_assert(not is_gradual_equivalent_to(D[A], C[A]))
static_assert(not is_gradual_equivalent_to(D[B], C[B]))
static_assert(not is_gradual_equivalent_to(D[Any], C[Any]))
static_assert(not is_gradual_equivalent_to(D[Any], C[Unknown]))
static_assert(not is_gradual_equivalent_to(D[B], C[A]))
static_assert(not is_gradual_equivalent_to(D[A], C[B]))
static_assert(not is_gradual_equivalent_to(D[A], C[Any]))
static_assert(not is_gradual_equivalent_to(D[B], C[Any]))
static_assert(not is_gradual_equivalent_to(D[Any], C[A]))
static_assert(not is_gradual_equivalent_to(D[Any], C[B]))
```
## Bivariance
With a bivariant typevar, _all_ specializations of the generic class are assignable to (and in fact,
gradually equivalent to) each other, and all fully static specializations are subtypes of (and
With a bivariant typevar, _all_ specializations of the generic class are subtypes of (and in fact,
equivalent to) each other.
This is a bit of pathological case, which really only happens when the class doesn't use the typevar
@@ -342,9 +215,6 @@ class B(A): ...
class C[T]:
pass
class D[U](C[U]):
pass
# TODO: no error
# error: [static-assert-error]
static_assert(is_assignable_to(C[B], C[A]))
@@ -356,17 +226,6 @@ static_assert(is_assignable_to(C[B], C[Any]))
static_assert(is_assignable_to(C[Any], C[A]))
static_assert(is_assignable_to(C[Any], C[B]))
# TODO: no error
# error: [static-assert-error]
static_assert(is_assignable_to(D[B], C[A]))
# TODO: no error
# error: [static-assert-error]
static_assert(is_assignable_to(D[A], C[B]))
static_assert(is_assignable_to(D[A], C[Any]))
static_assert(is_assignable_to(D[B], C[Any]))
static_assert(is_assignable_to(D[Any], C[A]))
static_assert(is_assignable_to(D[Any], C[B]))
# TODO: no error
# error: [static-assert-error]
static_assert(is_subtype_of(C[B], C[A]))
@@ -378,17 +237,6 @@ static_assert(not is_subtype_of(C[B], C[Any]))
static_assert(not is_subtype_of(C[Any], C[A]))
static_assert(not is_subtype_of(C[Any], C[B]))
# TODO: no error
# error: [static-assert-error]
static_assert(is_subtype_of(D[B], C[A]))
# TODO: no error
# error: [static-assert-error]
static_assert(is_subtype_of(D[A], C[B]))
static_assert(not is_subtype_of(D[A], C[Any]))
static_assert(not is_subtype_of(D[B], C[Any]))
static_assert(not is_subtype_of(D[Any], C[A]))
static_assert(not is_subtype_of(D[Any], C[B]))
static_assert(is_equivalent_to(C[A], C[A]))
static_assert(is_equivalent_to(C[B], C[B]))
# TODO: no error
@@ -402,15 +250,6 @@ static_assert(not is_equivalent_to(C[B], C[Any]))
static_assert(not is_equivalent_to(C[Any], C[A]))
static_assert(not is_equivalent_to(C[Any], C[B]))
static_assert(not is_equivalent_to(D[A], C[A]))
static_assert(not is_equivalent_to(D[B], C[B]))
static_assert(not is_equivalent_to(D[B], C[A]))
static_assert(not is_equivalent_to(D[A], C[B]))
static_assert(not is_equivalent_to(D[A], C[Any]))
static_assert(not is_equivalent_to(D[B], C[Any]))
static_assert(not is_equivalent_to(D[Any], C[A]))
static_assert(not is_equivalent_to(D[Any], C[B]))
static_assert(is_gradual_equivalent_to(C[A], C[A]))
static_assert(is_gradual_equivalent_to(C[B], C[B]))
static_assert(is_gradual_equivalent_to(C[Any], C[Any]))
@@ -433,17 +272,6 @@ static_assert(is_gradual_equivalent_to(C[Any], C[A]))
# TODO: no error
# error: [static-assert-error]
static_assert(is_gradual_equivalent_to(C[Any], C[B]))
static_assert(not is_gradual_equivalent_to(D[A], C[A]))
static_assert(not is_gradual_equivalent_to(D[B], C[B]))
static_assert(not is_gradual_equivalent_to(D[Any], C[Any]))
static_assert(not is_gradual_equivalent_to(D[Any], C[Unknown]))
static_assert(not is_gradual_equivalent_to(D[B], C[A]))
static_assert(not is_gradual_equivalent_to(D[A], C[B]))
static_assert(not is_gradual_equivalent_to(D[A], C[Any]))
static_assert(not is_gradual_equivalent_to(D[B], C[Any]))
static_assert(not is_gradual_equivalent_to(D[Any], C[A]))
static_assert(not is_gradual_equivalent_to(D[Any], C[B]))
```
[spec]: https://typing.python.org/en/latest/spec/generics.html#variance

View File

@@ -6,7 +6,7 @@
from b import C as D
E = D
reveal_type(E) # revealed: <class 'C'>
reveal_type(E) # revealed: Literal[C]
```
`b.py`:
@@ -21,7 +21,7 @@ class C: ...
import b
D = b.C
reveal_type(D) # revealed: <class 'C'>
reveal_type(D) # revealed: Literal[C]
```
`b.py`:
@@ -35,7 +35,7 @@ class C: ...
```py
import a.b
reveal_type(a.b.C) # revealed: <class 'C'>
reveal_type(a.b.C) # revealed: Literal[C]
```
`a/__init__.py`:
@@ -54,7 +54,7 @@ class C: ...
```py
import a.b.c
reveal_type(a.b.c.C) # revealed: <class 'C'>
reveal_type(a.b.c.C) # revealed: Literal[C]
```
`a/__init__.py`:
@@ -78,7 +78,7 @@ class C: ...
```py
import a.b as b
reveal_type(b.C) # revealed: <class 'C'>
reveal_type(b.C) # revealed: Literal[C]
```
`a/__init__.py`:
@@ -97,7 +97,7 @@ class C: ...
```py
import a.b.c as c
reveal_type(c.C) # revealed: <class 'C'>
reveal_type(c.C) # revealed: Literal[C]
```
`a/__init__.py`:

View File

@@ -16,7 +16,7 @@ Or used implicitly:
```py
reveal_type(chr) # revealed: def chr(i: SupportsIndex, /) -> str
reveal_type(str) # revealed: <class 'str'>
reveal_type(str) # revealed: Literal[str]
```
## Builtin symbol from custom typeshed

View File

@@ -95,7 +95,6 @@ from typing import Any, Literal
`foo.pyi`:
```pyi
```
## Nested non-exports
@@ -188,7 +187,7 @@ reveal_type(Foo) # revealed: Unknown
```pyi
from b import AnyFoo as Foo
reveal_type(Foo) # revealed: <class 'AnyFoo'>
reveal_type(Foo) # revealed: Literal[AnyFoo]
```
`b.pyi`:
@@ -202,9 +201,9 @@ class AnyFoo: ...
Here, the symbol is re-exported using the `__all__` variable.
```py
# TODO: This should *not* be an error but we don't understand `__all__` yet.
# error: "Module `a` has no member `Foo`"
from a import Foo
reveal_type(Foo) # revealed: <class 'Foo'>
```
`a.pyi`:
@@ -221,44 +220,6 @@ __all__ = ['Foo']
class Foo: ...
```
## Re-exports with `__all__`
If a symbol is re-exported via redundant alias but is not included in `__all__`, it shouldn't raise
an error when using named import.
`named_import.py`:
```py
from a import Foo
reveal_type(Foo) # revealed: <class 'Foo'>
```
`a.pyi`:
```pyi
from b import Foo as Foo
__all__ = []
```
`b.pyi`:
```pyi
class Foo: ...
```
However, a star import _would_ raise an error.
`star_import.py`:
```py
from a import *
# error: [unresolved-reference] "Name `Foo` used when not defined"
reveal_type(Foo) # revealed: Unknown
```
## Re-exports in `__init__.pyi`
Similarly, for an `__init__.pyi` (stub) file, importing a non-exported name should raise an error
@@ -290,13 +251,11 @@ class Foo: ...
`a/b/__init__.pyi`:
```pyi
```
`a/b/c.pyi`:
```pyi
```
## Conditional re-export in stub file
@@ -322,7 +281,7 @@ def coinflip() -> bool: ...
if coinflip():
Foo: str = ...
reveal_type(Foo) # revealed: <class 'Foo'> | str
reveal_type(Foo) # revealed: Literal[Foo] | str
```
`b.pyi`:
@@ -340,7 +299,7 @@ the other does not.
# error: "Member `Foo` of module `a` is possibly unbound"
from a import Foo
reveal_type(Foo) # revealed: <class 'Foo'>
reveal_type(Foo) # revealed: Literal[Foo]
```
`a.pyi`:
@@ -353,7 +312,7 @@ if coinflip():
else:
from b import Foo as Foo
reveal_type(Foo) # revealed: <class 'Foo'>
reveal_type(Foo) # revealed: Literal[Foo]
```
`b.pyi`:
@@ -368,7 +327,7 @@ class Foo: ...
# error: "Member `Foo` of module `a` is possibly unbound"
from a import Foo
reveal_type(Foo) # revealed: <class 'Foo'>
reveal_type(Foo) # revealed: Literal[Foo]
```
`a.pyi`:

View File

@@ -1,796 +0,0 @@
# `__all__`
Reference:
<https://typing.python.org/en/latest/spec/distributing.html#library-interface-public-and-private-symbols>
NOTE: This file only includes the usage of `__all__` for named-imports i.e.,
`from module import symbol`. For the usage of `__all__` in wildcard imports, refer to
[star.md](star.md).
## Undefined
`exporter.py`:
```py
class A: ...
class B: ...
```
`importer.py`:
```py
import exporter
from ty_extensions import dunder_all_names
# revealed: None
reveal_type(dunder_all_names(exporter))
```
## Global scope
The `__all__` variable is only recognized from the global scope of the module. It is not recognized
from the local scope of a function or class.
`exporter.py`:
```py
__all__ = ["A"]
def foo():
__all__.append("B")
class Foo:
__all__ += ["C"]
class A: ...
class B: ...
class C: ...
foo()
```
`importer.py`:
```py
import exporter
from ty_extensions import dunder_all_names
# revealed: tuple[Literal["A"]]
reveal_type(dunder_all_names(exporter))
```
## Supported idioms
According to the [specification], the following idioms are supported:
### List assignment
`exporter.py`:
```py
__all__ = ["A", "B"]
class A: ...
class B: ...
```
`exporter_annotated.py`:
```py
__all__: list[str] = ["C", "D"]
class C: ...
class D: ...
```
`importer.py`:
```py
import exporter
import exporter_annotated
from ty_extensions import dunder_all_names
# revealed: tuple[Literal["A"], Literal["B"]]
reveal_type(dunder_all_names(exporter))
# revealed: tuple[Literal["C"], Literal["D"]]
reveal_type(dunder_all_names(exporter_annotated))
```
### List assignment (shadowed)
`exporter.py`:
```py
__all__ = ["A", "B"]
class A: ...
class B: ...
__all__ = ["C", "D"]
class C: ...
class D: ...
```
`exporter_annotated.py`:
```py
__all__ = ["X"]
class X: ...
__all__: list[str] = ["Y", "Z"]
class Y: ...
class Z: ...
```
`importer.py`:
```py
import exporter
import exporter_annotated
from ty_extensions import dunder_all_names
# revealed: tuple[Literal["C"], Literal["D"]]
reveal_type(dunder_all_names(exporter))
# revealed: tuple[Literal["Y"], Literal["Z"]]
reveal_type(dunder_all_names(exporter_annotated))
```
### Tuple assignment
`exporter.py`:
```py
__all__ = ("A", "B")
class A: ...
class B: ...
```
`exporter_annotated.py`:
```py
__all__: tuple[str, ...] = ("C", "D")
class C: ...
class D: ...
```
`importer.py`:
```py
import exporter
import exporter_annotated
from ty_extensions import dunder_all_names
# revealed: tuple[Literal["A"], Literal["B"]]
reveal_type(dunder_all_names(exporter))
# revealed: tuple[Literal["C"], Literal["D"]]
reveal_type(dunder_all_names(exporter_annotated))
```
### Tuple assignment (shadowed)
`exporter.py`:
```py
__all__ = ("A", "B")
class A: ...
class B: ...
__all__ = ("C", "D")
class C: ...
class D: ...
```
`exporter_annotated.py`:
```py
__all__ = ("X",)
class X: ...
__all__: tuple[str, ...] = ("Y", "Z")
class Y: ...
class Z: ...
```
`importer.py`:
```py
import exporter
import exporter_annotated
from ty_extensions import dunder_all_names
# revealed: tuple[Literal["C"], Literal["D"]]
reveal_type(dunder_all_names(exporter))
# revealed: tuple[Literal["Y"], Literal["Z"]]
reveal_type(dunder_all_names(exporter_annotated))
```
### Augmenting list with a list or submodule `__all__`
`subexporter.py`:
```py
__all__ = ["A", "B"]
class A: ...
class B: ...
```
`exporter.py`:
```py
import subexporter
__all__ = []
__all__ += ["C", "D"]
__all__ += subexporter.__all__
class C: ...
class D: ...
```
`importer.py`:
```py
import exporter
from ty_extensions import dunder_all_names
# revealed: tuple[Literal["A"], Literal["B"], Literal["C"], Literal["D"]]
reveal_type(dunder_all_names(exporter))
```
### Extending with a list or submodule `__all__`
`subexporter.py`:
```py
__all__ = ["A", "B"]
class A: ...
class B: ...
```
`exporter.py`:
```py
import subexporter
__all__ = []
__all__.extend(["C", "D"])
__all__.extend(subexporter.__all__)
class C: ...
class D: ...
```
`importer.py`:
```py
import exporter
from ty_extensions import dunder_all_names
# revealed: tuple[Literal["A"], Literal["B"], Literal["C"], Literal["D"]]
reveal_type(dunder_all_names(exporter))
```
### Appending a single symbol
`exporter.py`:
```py
__all__ = ["A"]
__all__.append("B")
class A: ...
class B: ...
```
`importer.py`:
```py
import exporter
from ty_extensions import dunder_all_names
# revealed: tuple[Literal["A"], Literal["B"]]
reveal_type(dunder_all_names(exporter))
```
### Removing a single symbol
`exporter.py`:
```py
__all__ = ["A", "B"]
__all__.remove("A")
# Non-existant symbol in `__all__` at this point
# TODO: This raises `ValueError` at runtime, maybe we should raise a diagnostic as well?
__all__.remove("C")
class A: ...
class B: ...
```
`importer.py`:
```py
import exporter
from ty_extensions import dunder_all_names
# revealed: tuple[Literal["B"]]
reveal_type(dunder_all_names(exporter))
```
### Mixed
`subexporter.py`:
```py
__all__ = []
__all__ = ["A"]
__all__.append("B")
__all__.extend(["C"])
__all__.remove("B")
class A: ...
class B: ...
class C: ...
```
`exporter.py`:
```py
import subexporter
__all__ = []
__all__ += ["D"]
__all__ += subexporter.__all__
class D: ...
```
`importer.py`:
```py
import exporter
from ty_extensions import dunder_all_names
# revealed: tuple[Literal["A"], Literal["C"], Literal["D"]]
reveal_type(dunder_all_names(exporter))
```
## Invalid
### Unsupported idioms
Idioms that are not mentioned in the [specification] are not recognized by `ty` and if they're used,
`__all__` is considered to be undefined for that module. This is to avoid false positives.
`bar.py`:
```py
__all__ = ["A", "B"]
class A: ...
class B: ...
```
`foo.py`:
```py
import bar as bar
```
`exporter.py`:
```py
import foo
from ty_extensions import dunder_all_names
__all__ = []
# revealed: tuple[Literal["A"], Literal["B"]]
reveal_type(dunder_all_names(foo.bar))
# Only direct attribute access of modules are recognized
# TODO: warning diagnostic
__all__.extend(foo.bar.__all__)
# TODO: warning diagnostic
__all__ += foo.bar.__all__
# Augmented assignment is only allowed when the value is a list expression
# TODO: warning diagnostic
__all__ += ("C",)
# Other methods on `list` are not recognized
# TODO: warning diagnostic
__all__.insert(0, "C")
# TODO: warning diagnostic
__all__.clear()
__all__.append("C")
# `pop` is not valid; use `remove` instead
# TODO: warning diagnostic
__all__.pop()
# Sets are not recognized
# TODO: warning diagnostic
__all__ = {"C", "D"}
class C: ...
class D: ...
```
`importer.py`:
```py
import exporter
from ty_extensions import dunder_all_names
# revealed: None
reveal_type(dunder_all_names(exporter))
```
### Non-string elements
Similarly, if `__all__` contains any non-string elements, we will consider `__all__` to not be
defined for that module. This is also to avoid false positives.
`subexporter.py`:
```py
__all__ = ("A", "B")
class A: ...
class B: ...
```
`exporter1.py`:
```py
import subexporter
from ty_extensions import dunder_all_names
# revealed: tuple[Literal["A"], Literal["B"]]
reveal_type(dunder_all_names(subexporter))
# TODO: warning diagnostic
__all__ = ("C", *subexporter.__all__)
class C: ...
```
`importer.py`:
```py
import exporter1
from ty_extensions import dunder_all_names
# revealed: None
reveal_type(dunder_all_names(exporter1))
```
## Statically known branches
### Python 3.10
```toml
[environment]
python-version = "3.10"
```
`exporter.py`:
```py
import sys
__all__ = ["AllVersion"]
if sys.version_info >= (3, 12):
__all__ += ["Python312"]
elif sys.version_info >= (3, 11):
__all__ += ["Python311"]
else:
__all__ += ["Python310"]
class AllVersion: ...
if sys.version_info >= (3, 12):
class Python312: ...
elif sys.version_info >= (3, 11):
class Python311: ...
else:
class Python310: ...
```
`importer.py`:
```py
import exporter
from ty_extensions import dunder_all_names
# revealed: tuple[Literal["AllVersion"], Literal["Python310"]]
reveal_type(dunder_all_names(exporter))
```
### Python 3.11
```toml
[environment]
python-version = "3.11"
```
`exporter.py`:
```py
import sys
__all__ = ["AllVersion"]
if sys.version_info >= (3, 12):
__all__ += ["Python312"]
elif sys.version_info >= (3, 11):
__all__ += ["Python311"]
else:
__all__ += ["Python310"]
class AllVersion: ...
if sys.version_info >= (3, 12):
class Python312: ...
elif sys.version_info >= (3, 11):
class Python311: ...
else:
class Python310: ...
```
`importer.py`:
```py
import exporter
from ty_extensions import dunder_all_names
# revealed: tuple[Literal["AllVersion"], Literal["Python311"]]
reveal_type(dunder_all_names(exporter))
```
### Python 3.12
```toml
[environment]
python-version = "3.12"
```
`exporter.py`:
```py
import sys
__all__ = ["AllVersion"]
if sys.version_info >= (3, 12):
__all__ += ["Python312"]
elif sys.version_info >= (3, 11):
__all__ += ["Python311"]
else:
__all__ += ["Python310"]
class AllVersion: ...
if sys.version_info >= (3, 12):
class Python312: ...
elif sys.version_info >= (3, 11):
class Python311: ...
else:
class Python310: ...
```
`importer.py`:
```py
import exporter
from ty_extensions import dunder_all_names
# revealed: tuple[Literal["AllVersion"], Literal["Python312"]]
reveal_type(dunder_all_names(exporter))
```
### Multiple `if` statements
```toml
[environment]
python-version = "3.11"
```
`exporter.py`:
```py
import sys
__all__ = ["AllVersion"]
if sys.version_info >= (3, 12):
__all__ += ["Python312"]
if sys.version_info >= (3, 11):
__all__ += ["Python311"]
if sys.version_info >= (3, 10):
__all__ += ["Python310"]
class AllVersion: ...
if sys.version_info >= (3, 12):
class Python312: ...
if sys.version_info >= (3, 11):
class Python311: ...
if sys.version_info >= (3, 10):
class Python310: ...
```
`importer.py`:
```py
import exporter
from ty_extensions import dunder_all_names
# revealed: tuple[Literal["AllVersion"], Literal["Python310"], Literal["Python311"]]
reveal_type(dunder_all_names(exporter))
```
## Origin
`__all__` can be defined in a module mainly in the following three ways:
### Directly in the module
`exporter.py`:
```py
__all__ = ["A"]
class A: ...
```
`importer.py`:
```py
import exporter
from ty_extensions import dunder_all_names
# revealed: tuple[Literal["A"]]
reveal_type(dunder_all_names(exporter))
```
### Using named import
`subexporter.py`:
```py
__all__ = ["A"]
class A: ...
```
`exporter.py`:
```py
from subexporter import __all__
__all__.append("B")
class B: ...
```
`importer.py`:
```py
import exporter
import subexporter
from ty_extensions import dunder_all_names
# revealed: tuple[Literal["A"]]
reveal_type(dunder_all_names(subexporter))
# revealed: tuple[Literal["A"], Literal["B"]]
reveal_type(dunder_all_names(exporter))
```
### Using wildcard import (1)
Wildcard import doesn't export `__all__` unless it is explicitly included in the `__all__` of the
module.
`subexporter.py`:
```py
__all__ = ["A", "__all__"]
class A: ...
```
`exporter.py`:
```py
from subexporter import *
# TODO: Should be `list[str]`
# TODO: Should we avoid including `Unknown` for this case?
reveal_type(__all__) # revealed: Unknown | list
__all__.append("B")
class B: ...
```
`importer.py`:
```py
import exporter
import subexporter
from ty_extensions import dunder_all_names
# revealed: tuple[Literal["A"], Literal["__all__"]]
reveal_type(dunder_all_names(subexporter))
# revealed: tuple[Literal["A"], Literal["B"], Literal["__all__"]]
reveal_type(dunder_all_names(exporter))
```
### Using wildcard import (2)
`subexporter.py`:
```py
__all__ = ["A"]
class A: ...
```
`exporter.py`:
```py
from subexporter import *
# error: [unresolved-reference]
reveal_type(__all__) # revealed: Unknown
# error: [unresolved-reference]
__all__.append("B")
class B: ...
```
`importer.py`:
```py
import exporter
import subexporter
from ty_extensions import dunder_all_names
# revealed: tuple[Literal["A"]]
reveal_type(dunder_all_names(subexporter))
# revealed: None
reveal_type(dunder_all_names(exporter))
```
[specification]: https://typing.python.org/en/latest/spec/distributing.html#library-interface-public-and-private-symbols

View File

@@ -69,12 +69,12 @@ x = "foo" # error: [invalid-assignment] "Object of type `Literal["foo"]"
```py
class A: ...
reveal_type(A.__mro__) # revealed: tuple[<class 'A'>, <class 'object'>]
reveal_type(A.__mro__) # revealed: tuple[Literal[A], Literal[object]]
import b
class C(b.B): ...
reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, <class 'B'>, <class 'A'>, <class 'object'>]
reveal_type(C.__mro__) # revealed: tuple[Literal[C], Literal[B], Literal[A], Literal[object]]
```
`b.py`:
@@ -84,5 +84,5 @@ from a import A
class B(A): ...
reveal_type(B.__mro__) # revealed: tuple[<class 'B'>, <class 'A'>, <class 'object'>]
reveal_type(B.__mro__) # revealed: tuple[Literal[B], Literal[A], Literal[object]]
```

View File

@@ -255,7 +255,7 @@ python-version = "3.13"
```py
from foo import A
reveal_type(A) # revealed: <class 'A'>
reveal_type(A) # revealed: Literal[A]
```
`/src/.venv/<path-to-site-packages>/foo/__init__.py`:

View File

@@ -899,8 +899,8 @@ reveal_type(__protected) # revealed: bool
reveal_type(__dunder__) # revealed: bool
reveal_type(___thunder___) # revealed: bool
# error: [unresolved-reference]
reveal_type(Y) # revealed: Unknown
# TODO: should emit [unresolved-reference] diagnostic & reveal `Unknown`
reveal_type(Y) # revealed: bool
```
### Simple list `__all__`
@@ -921,8 +921,8 @@ from exporter import *
reveal_type(X) # revealed: bool
# error: [unresolved-reference]
reveal_type(Y) # revealed: Unknown
# TODO: should emit [unresolved-reference] diagnostic & reveal `Unknown`
reveal_type(Y) # revealed: bool
```
### `__all__` with additions later on in the global scope
@@ -949,13 +949,15 @@ __all__ = ["A"]
__all__ += ["B"]
__all__.append("C")
__all__.extend(["D"])
__all__.extend(("E",))
__all__.extend(a.__all__)
A: bool = True
B: bool = True
C: bool = True
D: bool = True
E: bool = False
E: bool = True
F: bool = False
```
`c.py`:
@@ -967,10 +969,11 @@ reveal_type(A) # revealed: bool
reveal_type(B) # revealed: bool
reveal_type(C) # revealed: bool
reveal_type(D) # revealed: bool
reveal_type(E) # revealed: bool
reveal_type(FOO) # revealed: bool
# error: [unresolved-reference]
reveal_type(E) # revealed: Unknown
# TODO should error with [unresolved-reference] & reveal `Unknown`
reveal_type(F) # revealed: bool
```
### `__all__` with subtractions later on in the global scope
@@ -982,7 +985,7 @@ one way of subtracting from `__all__` that type checkers are required to support
```py
__all__ = ["A", "B"]
__all__.remove("B")
__all__.remove("A")
A: bool = True
B: bool = True
@@ -995,8 +998,8 @@ from exporter import *
reveal_type(A) # revealed: bool
# error: [unresolved-reference]
reveal_type(B) # revealed: Unknown
# TODO should emit an [unresolved-reference] diagnostic & reveal `Unknown`
reveal_type(B) # revealed: bool
```
### Invalid `__all__`
@@ -1122,8 +1125,8 @@ else:
```py
from exporter import *
# error: [unresolved-reference]
reveal_type(X) # revealed: Unknown
# TODO: should reveal `Unknown` and emit `[unresolved-reference]`
reveal_type(X) # revealed: bool
# error: [unresolved-reference]
reveal_type(Y) # revealed: Unknown
@@ -1196,8 +1199,8 @@ else:
```py
from exporter import *
# error: [unresolved-reference]
reveal_type(X) # revealed: Unknown
# TODO: should reveal `Unknown` & emit `[unresolved-reference]
reveal_type(X) # revealed: bool
# error: [unresolved-reference]
reveal_type(Y) # revealed: Unknown
@@ -1232,11 +1235,9 @@ __all__ = []
from a import *
from b import *
# error: [unresolved-reference]
reveal_type(X) # revealed: Unknown
# error: [unresolved-reference]
reveal_type(Y) # revealed: Unknown
# TODO: both of these should have [unresolved-reference] diagnostics and reveal `Unknown`
reveal_type(X) # revealed: bool
reveal_type(Y) # revealed: bool
```
### `__all__` in a stub file
@@ -1256,11 +1257,7 @@ Y: bool = True
```pyi
from a import X, Y
__all__ = ["X", "Z"]
Z: bool = True
Nope: bool = True
__all__ = ["X"]
```
`c.py`:
@@ -1268,21 +1265,18 @@ Nope: bool = True
```py
from b import *
# `X` is re-exported from `b.pyi` due to presence in `__all__`
reveal_type(X) # revealed: bool
# TODO: should not error, should reveal `bool`
# (`X` is re-exported from `b.pyi` due to presence in `__all__`)
# See https://github.com/astral-sh/ruff/issues/16159
#
# error: [unresolved-reference]
reveal_type(X) # revealed: Unknown
# This diagnostic is accurate: `Y` does not use the "redundant alias" convention in `b.pyi`,
# nor is it included in `b.__all__`, so it is not exported from `b.pyi`. It would still be
# an error if it used the "redundant alias" convention as `__all__` would take precedence.
# nor is it included in `b.__all__`, so it is not exported from `b.pyi`
#
# error: [unresolved-reference]
reveal_type(Y) # revealed: Unknown
# `Z` is defined in `b.pyi` and included in `__all__`
reveal_type(Z) # revealed: bool
# error: [unresolved-reference]
reveal_type(Nope) # revealed: Unknown
```
## `global` statements in non-global scopes
@@ -1359,9 +1353,12 @@ are present due to `*` imports.
```py
import collections.abc
reveal_type(collections.abc.Sequence) # revealed: <class 'Sequence'>
reveal_type(collections.abc.Sequence) # revealed: Literal[Sequence]
reveal_type(collections.abc.Callable) # revealed: typing.Callable
reveal_type(collections.abc.Set) # revealed: <class 'AbstractSet'>
# TODO: false positive as it's only re-exported from `_collections.abc` due to presence in `__all__`
# error: [unresolved-attribute]
reveal_type(collections.abc.Set) # revealed: Unknown
```
## Invalid `*` imports

View File

@@ -27,7 +27,7 @@ has been imported.
import a
# Would be an error with flow-sensitive tracking
reveal_type(a.b.C) # revealed: <class 'C'>
reveal_type(a.b.C) # revealed: Literal[C]
import a.b
```
@@ -53,10 +53,10 @@ submodule `b`, even though `a.b` is never imported in the main module.
from q import a, b
reveal_type(b) # revealed: <module 'a.b'>
reveal_type(b.C) # revealed: <class 'C'>
reveal_type(b.C) # revealed: Literal[C]
reveal_type(a.b) # revealed: <module 'a.b'>
reveal_type(a.b.C) # revealed: <class 'C'>
reveal_type(a.b.C) # revealed: Literal[C]
```
`a/__init__.py`:

View File

@@ -3,19 +3,19 @@
```py
class M(type): ...
reveal_type(M.__class__) # revealed: <class 'type'>
reveal_type(M.__class__) # revealed: Literal[type]
```
## `object`
```py
reveal_type(object.__class__) # revealed: <class 'type'>
reveal_type(object.__class__) # revealed: Literal[type]
```
## `type`
```py
reveal_type(type.__class__) # revealed: <class 'type'>
reveal_type(type.__class__) # revealed: Literal[type]
```
## Basic
@@ -24,7 +24,7 @@ reveal_type(type.__class__) # revealed: <class 'type'>
class M(type): ...
class B(metaclass=M): ...
reveal_type(B.__class__) # revealed: <class 'M'>
reveal_type(B.__class__) # revealed: Literal[M]
```
## Invalid metaclass
@@ -37,7 +37,7 @@ class M: ...
class A(metaclass=M): ...
# TODO: emit a diagnostic for the invalid metaclass
reveal_type(A.__class__) # revealed: <class 'M'>
reveal_type(A.__class__) # revealed: Literal[M]
```
## Linear inheritance
@@ -50,7 +50,7 @@ class M(type): ...
class A(metaclass=M): ...
class B(A): ...
reveal_type(B.__class__) # revealed: <class 'M'>
reveal_type(B.__class__) # revealed: Literal[M]
```
## Linear inheritance with PEP 695 generic class
@@ -68,8 +68,8 @@ class A[T](metaclass=M): ...
class B(A): ...
class C(A[int]): ...
reveal_type(B.__class__) # revealed: <class 'M'>
reveal_type(C.__class__) # revealed: <class 'M'>
reveal_type(B.__class__) # revealed: Literal[M]
reveal_type(C.__class__) # revealed: Literal[M]
```
## Conflict (1)
@@ -117,7 +117,7 @@ class A(metaclass=M): ...
class B(metaclass=M): ...
class C(A, B): ...
reveal_type(C.__class__) # revealed: <class 'M'>
reveal_type(C.__class__) # revealed: Literal[M]
```
## Metaclass metaclass
@@ -131,7 +131,7 @@ class M3(M2): ...
class A(metaclass=M3): ...
class B(A): ...
reveal_type(A.__class__) # revealed: <class 'M3'>
reveal_type(A.__class__) # revealed: Literal[M3]
```
## Diamond inheritance
@@ -159,14 +159,14 @@ from nonexistent_module import UnknownClass # error: [unresolved-import]
class C(UnknownClass): ...
# TODO: should be `type[type] & Unknown`
reveal_type(C.__class__) # revealed: <class 'type'>
reveal_type(C.__class__) # revealed: Literal[type]
class M(type): ...
class A(metaclass=M): ...
class B(A, UnknownClass): ...
# TODO: should be `type[M] & Unknown`
reveal_type(B.__class__) # revealed: <class 'M'>
reveal_type(B.__class__) # revealed: Literal[M]
```
## Duplicate
@@ -176,7 +176,7 @@ class M(type): ...
class A(metaclass=M): ...
class B(A, A): ... # error: [duplicate-base] "Duplicate base class `A`"
reveal_type(B.__class__) # revealed: <class 'M'>
reveal_type(B.__class__) # revealed: Literal[M]
```
## Non-class
@@ -191,14 +191,14 @@ def f(*args, **kwargs) -> int:
class A(metaclass=f): ...
# TODO: Should be `int`
reveal_type(A) # revealed: <class 'A'>
reveal_type(A) # revealed: Literal[A]
reveal_type(A.__class__) # revealed: type[int]
def _(n: int):
# error: [invalid-metaclass]
class B(metaclass=n): ...
# TODO: Should be `Unknown`
reveal_type(B) # revealed: <class 'B'>
reveal_type(B) # revealed: Literal[B]
reveal_type(B.__class__) # revealed: type[Unknown]
def _(flag: bool):
@@ -207,7 +207,7 @@ def _(flag: bool):
# error: [invalid-metaclass]
class C(metaclass=m): ...
# TODO: Should be `int | Unknown`
reveal_type(C) # revealed: <class 'C'>
reveal_type(C) # revealed: Literal[C]
reveal_type(C.__class__) # revealed: type[Unknown]
class SignatureMismatch: ...
@@ -216,9 +216,9 @@ class SignatureMismatch: ...
class D(metaclass=SignatureMismatch): ...
# TODO: Should be `Unknown`
reveal_type(D) # revealed: <class 'D'>
reveal_type(D) # revealed: Literal[D]
# TODO: Should be `type[Unknown]`
reveal_type(D.__class__) # revealed: <class 'SignatureMismatch'>
reveal_type(D.__class__) # revealed: Literal[SignatureMismatch]
```
## Cyclic
@@ -244,7 +244,7 @@ python-version = "3.12"
class M(type): ...
class A[T: str](metaclass=M): ...
reveal_type(A.__class__) # revealed: <class 'M'>
reveal_type(A.__class__) # revealed: Literal[M]
```
## Metaclasses of metaclasses
@@ -255,9 +255,9 @@ class Bar(type, metaclass=Foo): ...
class Baz(type, metaclass=Bar): ...
class Spam(metaclass=Baz): ...
reveal_type(Spam.__class__) # revealed: <class 'Baz'>
reveal_type(Spam.__class__.__class__) # revealed: <class 'Bar'>
reveal_type(Spam.__class__.__class__.__class__) # revealed: <class 'Foo'>
reveal_type(Spam.__class__) # revealed: Literal[Baz]
reveal_type(Spam.__class__.__class__) # revealed: Literal[Bar]
reveal_type(Spam.__class__.__class__.__class__) # revealed: Literal[Foo]
def test(x: Spam):
reveal_type(x.__class__) # revealed: type[Spam]

View File

@@ -16,13 +16,13 @@ For documentation on method resolution orders, see:
```py
class C: ...
reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, <class 'object'>]
reveal_type(C.__mro__) # revealed: tuple[Literal[C], Literal[object]]
```
## The special case: `object` itself
```py
reveal_type(object.__mro__) # revealed: tuple[<class 'object'>]
reveal_type(object.__mro__) # revealed: tuple[Literal[object]]
```
## Explicit inheritance from `object`
@@ -30,7 +30,7 @@ reveal_type(object.__mro__) # revealed: tuple[<class 'object'>]
```py
class C(object): ...
reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, <class 'object'>]
reveal_type(C.__mro__) # revealed: tuple[Literal[C], Literal[object]]
```
## Explicit inheritance from non-`object` single base
@@ -39,7 +39,7 @@ reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, <class 'object'>]
class A: ...
class B(A): ...
reveal_type(B.__mro__) # revealed: tuple[<class 'B'>, <class 'A'>, <class 'object'>]
reveal_type(B.__mro__) # revealed: tuple[Literal[B], Literal[A], Literal[object]]
```
## Linearization of multiple bases
@@ -49,7 +49,7 @@ class A: ...
class B: ...
class C(A, B): ...
reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, <class 'A'>, <class 'B'>, <class 'object'>]
reveal_type(C.__mro__) # revealed: tuple[Literal[C], Literal[A], Literal[B], Literal[object]]
```
## Complex diamond inheritance (1)
@@ -63,8 +63,8 @@ class Y(O): ...
class A(X, Y): ...
class B(Y, X): ...
reveal_type(A.__mro__) # revealed: tuple[<class 'A'>, <class 'X'>, <class 'Y'>, <class 'O'>, <class 'object'>]
reveal_type(B.__mro__) # revealed: tuple[<class 'B'>, <class 'Y'>, <class 'X'>, <class 'O'>, <class 'object'>]
reveal_type(A.__mro__) # revealed: tuple[Literal[A], Literal[X], Literal[Y], Literal[O], Literal[object]]
reveal_type(B.__mro__) # revealed: tuple[Literal[B], Literal[Y], Literal[X], Literal[O], Literal[object]]
```
## Complex diamond inheritance (2)
@@ -80,11 +80,11 @@ class C(D, F): ...
class B(D, E): ...
class A(B, C): ...
# revealed: tuple[<class 'C'>, <class 'D'>, <class 'F'>, <class 'O'>, <class 'object'>]
# revealed: tuple[Literal[C], Literal[D], Literal[F], Literal[O], Literal[object]]
reveal_type(C.__mro__)
# revealed: tuple[<class 'B'>, <class 'D'>, <class 'E'>, <class 'O'>, <class 'object'>]
# revealed: tuple[Literal[B], Literal[D], Literal[E], Literal[O], Literal[object]]
reveal_type(B.__mro__)
# revealed: tuple[<class 'A'>, <class 'B'>, <class 'C'>, <class 'D'>, <class 'E'>, <class 'F'>, <class 'O'>, <class 'object'>]
# revealed: tuple[Literal[A], Literal[B], Literal[C], Literal[D], Literal[E], Literal[F], Literal[O], Literal[object]]
reveal_type(A.__mro__)
```
@@ -101,11 +101,11 @@ class C(D, F): ...
class B(E, D): ...
class A(B, C): ...
# revealed: tuple[<class 'C'>, <class 'D'>, <class 'F'>, <class 'O'>, <class 'object'>]
# revealed: tuple[Literal[C], Literal[D], Literal[F], Literal[O], Literal[object]]
reveal_type(C.__mro__)
# revealed: tuple[<class 'B'>, <class 'E'>, <class 'D'>, <class 'O'>, <class 'object'>]
# revealed: tuple[Literal[B], Literal[E], Literal[D], Literal[O], Literal[object]]
reveal_type(B.__mro__)
# revealed: tuple[<class 'A'>, <class 'B'>, <class 'E'>, <class 'C'>, <class 'D'>, <class 'F'>, <class 'O'>, <class 'object'>]
# revealed: tuple[Literal[A], Literal[B], Literal[E], Literal[C], Literal[D], Literal[F], Literal[O], Literal[object]]
reveal_type(A.__mro__)
```
@@ -125,13 +125,13 @@ class K2(D, B, E): ...
class K3(D, A): ...
class Z(K1, K2, K3): ...
# revealed: tuple[<class 'K1'>, <class 'A'>, <class 'B'>, <class 'C'>, <class 'O'>, <class 'object'>]
# revealed: tuple[Literal[K1], Literal[A], Literal[B], Literal[C], Literal[O], Literal[object]]
reveal_type(K1.__mro__)
# revealed: tuple[<class 'K2'>, <class 'D'>, <class 'B'>, <class 'E'>, <class 'O'>, <class 'object'>]
# revealed: tuple[Literal[K2], Literal[D], Literal[B], Literal[E], Literal[O], Literal[object]]
reveal_type(K2.__mro__)
# revealed: tuple[<class 'K3'>, <class 'D'>, <class 'A'>, <class 'O'>, <class 'object'>]
# revealed: tuple[Literal[K3], Literal[D], Literal[A], Literal[O], Literal[object]]
reveal_type(K3.__mro__)
# revealed: tuple[<class 'Z'>, <class 'K1'>, <class 'K2'>, <class 'K3'>, <class 'D'>, <class 'A'>, <class 'B'>, <class 'C'>, <class 'E'>, <class 'O'>, <class 'object'>]
# revealed: tuple[Literal[Z], Literal[K1], Literal[K2], Literal[K3], Literal[D], Literal[A], Literal[B], Literal[C], Literal[E], Literal[O], Literal[object]]
reveal_type(Z.__mro__)
```
@@ -147,11 +147,10 @@ class D(A, B, C): ...
class E(B, C): ...
class F(E, A): ...
reveal_type(A.__mro__) # revealed: tuple[<class 'A'>, Unknown, <class 'object'>]
reveal_type(D.__mro__) # revealed: tuple[<class 'D'>, <class 'A'>, Unknown, <class 'B'>, <class 'C'>, <class 'object'>]
reveal_type(E.__mro__) # revealed: tuple[<class 'E'>, <class 'B'>, <class 'C'>, <class 'object'>]
# revealed: tuple[<class 'F'>, <class 'E'>, <class 'B'>, <class 'C'>, <class 'A'>, Unknown, <class 'object'>]
reveal_type(F.__mro__)
reveal_type(A.__mro__) # revealed: tuple[Literal[A], Unknown, Literal[object]]
reveal_type(D.__mro__) # revealed: tuple[Literal[D], Literal[A], Unknown, Literal[B], Literal[C], Literal[object]]
reveal_type(E.__mro__) # revealed: tuple[Literal[E], Literal[B], Literal[C], Literal[object]]
reveal_type(F.__mro__) # revealed: tuple[Literal[F], Literal[E], Literal[B], Literal[C], Literal[A], Unknown, Literal[object]]
```
## `__bases__` lists that cause errors at runtime
@@ -163,11 +162,11 @@ creation to fail, we infer the class's `__mro__` as being `[<class>, Unknown, ob
# error: [inconsistent-mro] "Cannot create a consistent method resolution order (MRO) for class `Foo` with bases list `[<class 'object'>, <class 'int'>]`"
class Foo(object, int): ...
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
reveal_type(Foo.__mro__) # revealed: tuple[Literal[Foo], Unknown, Literal[object]]
class Bar(Foo): ...
reveal_type(Bar.__mro__) # revealed: tuple[<class 'Bar'>, <class 'Foo'>, Unknown, <class 'object'>]
reveal_type(Bar.__mro__) # revealed: tuple[Literal[Bar], Literal[Foo], Unknown, Literal[object]]
# This is the `TypeError` at the bottom of "ex_2"
# in the examples at <https://docs.python.org/3/howto/mro.html#the-end>
@@ -177,17 +176,17 @@ class Y(O): ...
class A(X, Y): ...
class B(Y, X): ...
reveal_type(A.__mro__) # revealed: tuple[<class 'A'>, <class 'X'>, <class 'Y'>, <class 'O'>, <class 'object'>]
reveal_type(B.__mro__) # revealed: tuple[<class 'B'>, <class 'Y'>, <class 'X'>, <class 'O'>, <class 'object'>]
reveal_type(A.__mro__) # revealed: tuple[Literal[A], Literal[X], Literal[Y], Literal[O], Literal[object]]
reveal_type(B.__mro__) # revealed: tuple[Literal[B], Literal[Y], Literal[X], Literal[O], Literal[object]]
# error: [inconsistent-mro] "Cannot create a consistent method resolution order (MRO) for class `Z` with bases list `[<class 'A'>, <class 'B'>]`"
class Z(A, B): ...
reveal_type(Z.__mro__) # revealed: tuple[<class 'Z'>, Unknown, <class 'object'>]
reveal_type(Z.__mro__) # revealed: tuple[Literal[Z], Unknown, Literal[object]]
class AA(Z): ...
reveal_type(AA.__mro__) # revealed: tuple[<class 'AA'>, <class 'Z'>, Unknown, <class 'object'>]
reveal_type(AA.__mro__) # revealed: tuple[Literal[AA], Literal[Z], Unknown, Literal[object]]
```
## `__bases__` includes a `Union`
@@ -208,12 +207,12 @@ if returns_bool():
else:
x = B
reveal_type(x) # revealed: <class 'A'> | <class 'B'>
reveal_type(x) # revealed: Literal[A, B]
# error: 11 [invalid-base] "Invalid class base with type `<class 'A'> | <class 'B'>` (all bases must be a class, `Any`, `Unknown` or `Todo`)"
# error: 11 [invalid-base] "Invalid class base with type `Literal[A, B]` (all bases must be a class, `Any`, `Unknown` or `Todo`)"
class Foo(x): ...
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
reveal_type(Foo.__mro__) # revealed: tuple[Literal[Foo], Unknown, Literal[object]]
```
## `__bases__` includes multiple `Union`s
@@ -237,14 +236,14 @@ if returns_bool():
else:
y = D
reveal_type(x) # revealed: <class 'A'> | <class 'B'>
reveal_type(y) # revealed: <class 'C'> | <class 'D'>
reveal_type(x) # revealed: Literal[A, B]
reveal_type(y) # revealed: Literal[C, D]
# error: 11 [invalid-base] "Invalid class base with type `<class 'A'> | <class 'B'>` (all bases must be a class, `Any`, `Unknown` or `Todo`)"
# error: 14 [invalid-base] "Invalid class base with type `<class 'C'> | <class 'D'>` (all bases must be a class, `Any`, `Unknown` or `Todo`)"
# error: 11 [invalid-base] "Invalid class base with type `Literal[A, B]` (all bases must be a class, `Any`, `Unknown` or `Todo`)"
# error: 14 [invalid-base] "Invalid class base with type `Literal[C, D]` (all bases must be a class, `Any`, `Unknown` or `Todo`)"
class Foo(x, y): ...
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
reveal_type(Foo.__mro__) # revealed: tuple[Literal[Foo], Unknown, Literal[object]]
```
## `__bases__` lists that cause errors... now with `Union`s
@@ -262,14 +261,14 @@ if returns_bool():
else:
foo = object
# error: 21 [invalid-base] "Invalid class base with type `<class 'Y'> | <class 'object'>` (all bases must be a class, `Any`, `Unknown` or `Todo`)"
# error: 21 [invalid-base] "Invalid class base with type `Literal[Y, object]` (all bases must be a class, `Any`, `Unknown` or `Todo`)"
class PossibleError(foo, X): ...
reveal_type(PossibleError.__mro__) # revealed: tuple[<class 'PossibleError'>, Unknown, <class 'object'>]
reveal_type(PossibleError.__mro__) # revealed: tuple[Literal[PossibleError], Unknown, Literal[object]]
class A(X, Y): ...
reveal_type(A.__mro__) # revealed: tuple[<class 'A'>, <class 'X'>, <class 'Y'>, <class 'O'>, <class 'object'>]
reveal_type(A.__mro__) # revealed: tuple[Literal[A], Literal[X], Literal[Y], Literal[O], Literal[object]]
if returns_bool():
class B(X, Y): ...
@@ -277,116 +276,37 @@ if returns_bool():
else:
class B(Y, X): ...
# revealed: tuple[<class 'B'>, <class 'X'>, <class 'Y'>, <class 'O'>, <class 'object'>] | tuple[<class 'B'>, <class 'Y'>, <class 'X'>, <class 'O'>, <class 'object'>]
# revealed: tuple[Literal[B], Literal[X], Literal[Y], Literal[O], Literal[object]] | tuple[Literal[B], Literal[Y], Literal[X], Literal[O], Literal[object]]
reveal_type(B.__mro__)
# error: 12 [invalid-base] "Invalid class base with type `<class 'B'> | <class 'B'>` (all bases must be a class, `Any`, `Unknown` or `Todo`)"
# error: 12 [invalid-base] "Invalid class base with type `Literal[B, B]` (all bases must be a class, `Any`, `Unknown` or `Todo`)"
class Z(A, B): ...
reveal_type(Z.__mro__) # revealed: tuple[<class 'Z'>, Unknown, <class 'object'>]
reveal_type(Z.__mro__) # revealed: tuple[Literal[Z], Unknown, Literal[object]]
```
## `__bases__` lists with duplicate bases
<!-- snapshot-diagnostics -->
```py
from typing_extensions import reveal_type
class Foo(str, str): ... # error: 16 [duplicate-base] "Duplicate base class `str`"
class Foo(str, str): ... # error: [duplicate-base] "Duplicate base class `str`"
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
reveal_type(Foo.__mro__) # revealed: tuple[Literal[Foo], Unknown, Literal[object]]
class Spam: ...
class Eggs: ...
class Bar: ...
class Baz: ...
# fmt: off
# error: [duplicate-base] "Duplicate base class `Spam`"
# error: [duplicate-base] "Duplicate base class `Eggs`"
class Ham(
Spam,
Eggs,
Bar,
Baz,
Spam,
Eggs,
Spam, # error: [duplicate-base] "Duplicate base class `Spam`"
Eggs, # error: [duplicate-base] "Duplicate base class `Eggs`"
): ...
# fmt: on
reveal_type(Ham.__mro__) # revealed: tuple[<class 'Ham'>, Unknown, <class 'object'>]
reveal_type(Ham.__mro__) # revealed: tuple[Literal[Ham], Unknown, Literal[object]]
class Mushrooms: ...
class Omelette(Spam, Eggs, Mushrooms, Mushrooms): ... # error: [duplicate-base]
reveal_type(Omelette.__mro__) # revealed: tuple[<class 'Omelette'>, Unknown, <class 'object'>]
# fmt: off
# error: [duplicate-base] "Duplicate base class `Eggs`"
class VeryEggyOmelette(
Eggs,
Ham,
Spam,
Eggs,
Mushrooms,
Bar,
Eggs,
Baz,
Eggs,
): ...
# fmt: off
```
A `type: ignore` comment can suppress `duplicate-bases` errors if it is on the first or last line of
the class "header":
```py
# fmt: off
class A: ...
class B( # type: ignore[duplicate-base]
A,
A,
): ...
class C(
A,
A
): # type: ignore[duplicate-base]
x: int
# fmt: on
```
But it will not suppress the error if it occurs in the class body, or on the duplicate base itself.
The justification for this is that it is the class definition as a whole that will raise an
exception at runtime, not a sub-expression in the class's bases list.
```py
# fmt: off
# error: [duplicate-base]
class D(
A,
# error: [unused-ignore-comment]
A, # type: ignore[duplicate-base]
): ...
# error: [duplicate-base]
class E(
A,
A
):
# error: [unused-ignore-comment]
x: int # type: ignore[duplicate-base]
# fmt: on
reveal_type(Omelette.__mro__) # revealed: tuple[Literal[Omelette], Unknown, Literal[object]]
```
## `__bases__` lists with duplicate `Unknown` bases
@@ -410,7 +330,7 @@ reveal_type(unknown_object_2) # revealed: Unknown
# error: [inconsistent-mro] "Cannot create a consistent method resolution order (MRO) for class `Foo` with bases list `[Unknown, Unknown]`"
class Foo(unknown_object_1, unknown_object_2): ...
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
reveal_type(Foo.__mro__) # revealed: tuple[Literal[Foo], Unknown, Literal[object]]
```
## Unrelated objects inferred as `Any`/`Unknown` do not have special `__mro__` attributes
@@ -429,15 +349,15 @@ These are invalid, but we need to be able to handle them gracefully without pani
```pyi
class Foo(Foo): ... # error: [cyclic-class-definition]
reveal_type(Foo) # revealed: <class 'Foo'>
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
reveal_type(Foo) # revealed: Literal[Foo]
reveal_type(Foo.__mro__) # revealed: tuple[Literal[Foo], Unknown, Literal[object]]
class Bar: ...
class Baz: ...
class Boz(Bar, Baz, Boz): ... # error: [cyclic-class-definition]
reveal_type(Boz) # revealed: <class 'Boz'>
reveal_type(Boz.__mro__) # revealed: tuple[<class 'Boz'>, Unknown, <class 'object'>]
reveal_type(Boz) # revealed: Literal[Boz]
reveal_type(Boz.__mro__) # revealed: tuple[Literal[Boz], Unknown, Literal[object]]
```
## Classes with indirect cycles in their MROs
@@ -449,9 +369,9 @@ class Foo(Bar): ... # error: [cyclic-class-definition]
class Bar(Baz): ... # error: [cyclic-class-definition]
class Baz(Foo): ... # error: [cyclic-class-definition]
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
reveal_type(Bar.__mro__) # revealed: tuple[<class 'Bar'>, Unknown, <class 'object'>]
reveal_type(Baz.__mro__) # revealed: tuple[<class 'Baz'>, Unknown, <class 'object'>]
reveal_type(Foo.__mro__) # revealed: tuple[Literal[Foo], Unknown, Literal[object]]
reveal_type(Bar.__mro__) # revealed: tuple[Literal[Bar], Unknown, Literal[object]]
reveal_type(Baz.__mro__) # revealed: tuple[Literal[Baz], Unknown, Literal[object]]
```
## Classes with cycles in their MROs, and multiple inheritance
@@ -462,9 +382,9 @@ class Foo(Bar): ... # error: [cyclic-class-definition]
class Bar(Baz): ... # error: [cyclic-class-definition]
class Baz(Foo, Spam): ... # error: [cyclic-class-definition]
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
reveal_type(Bar.__mro__) # revealed: tuple[<class 'Bar'>, Unknown, <class 'object'>]
reveal_type(Baz.__mro__) # revealed: tuple[<class 'Baz'>, Unknown, <class 'object'>]
reveal_type(Foo.__mro__) # revealed: tuple[Literal[Foo], Unknown, Literal[object]]
reveal_type(Bar.__mro__) # revealed: tuple[Literal[Bar], Unknown, Literal[object]]
reveal_type(Baz.__mro__) # revealed: tuple[Literal[Baz], Unknown, Literal[object]]
```
## Classes with cycles in their MRO, and a sub-graph
@@ -480,8 +400,8 @@ class Bar(Foo): ...
class Baz(Bar, BarCycle): ...
class Spam(Baz): ...
reveal_type(FooCycle.__mro__) # revealed: tuple[<class 'FooCycle'>, Unknown, <class 'object'>]
reveal_type(BarCycle.__mro__) # revealed: tuple[<class 'BarCycle'>, Unknown, <class 'object'>]
reveal_type(Baz.__mro__) # revealed: tuple[<class 'Baz'>, Unknown, <class 'object'>]
reveal_type(Spam.__mro__) # revealed: tuple[<class 'Spam'>, Unknown, <class 'object'>]
reveal_type(FooCycle.__mro__) # revealed: tuple[Literal[FooCycle], Unknown, Literal[object]]
reveal_type(BarCycle.__mro__) # revealed: tuple[Literal[BarCycle], Unknown, Literal[object]]
reveal_type(Baz.__mro__) # revealed: tuple[Literal[Baz], Unknown, Literal[object]]
reveal_type(Spam.__mro__) # revealed: tuple[Literal[Spam], Unknown, Literal[object]]
```

View File

@@ -43,9 +43,9 @@ def _(flag: bool):
C = A if flag else B
if C != A:
reveal_type(C) # revealed: <class 'B'>
reveal_type(C) # revealed: Literal[B]
else:
reveal_type(C) # revealed: <class 'A'>
reveal_type(C) # revealed: Literal[A]
```
## `x != y` where `y` has multiple single-valued options

View File

@@ -14,15 +14,15 @@ def _(flag: bool):
reveal_type(t) # revealed: Never
if issubclass(t, object):
reveal_type(t) # revealed: <class 'int'> | <class 'str'>
reveal_type(t) # revealed: Literal[int, str]
if issubclass(t, int):
reveal_type(t) # revealed: <class 'int'>
reveal_type(t) # revealed: Literal[int]
else:
reveal_type(t) # revealed: <class 'str'>
reveal_type(t) # revealed: Literal[str]
if issubclass(t, str):
reveal_type(t) # revealed: <class 'str'>
reveal_type(t) # revealed: Literal[str]
if issubclass(t, int):
reveal_type(t) # revealed: Never
```
@@ -34,16 +34,16 @@ def _(flag1: bool, flag2: bool):
t = int if flag1 else str if flag2 else bytes
if issubclass(t, int):
reveal_type(t) # revealed: <class 'int'>
reveal_type(t) # revealed: Literal[int]
else:
reveal_type(t) # revealed: <class 'str'> | <class 'bytes'>
reveal_type(t) # revealed: Literal[str, bytes]
if issubclass(t, int):
reveal_type(t) # revealed: <class 'int'>
reveal_type(t) # revealed: Literal[int]
elif issubclass(t, str):
reveal_type(t) # revealed: <class 'str'>
reveal_type(t) # revealed: Literal[str]
else:
reveal_type(t) # revealed: <class 'bytes'>
reveal_type(t) # revealed: Literal[bytes]
```
### Multiple derived classes
@@ -58,24 +58,24 @@ def _(flag1: bool, flag2: bool, flag3: bool):
t1 = Derived1 if flag1 else Derived2
if issubclass(t1, Base):
reveal_type(t1) # revealed: <class 'Derived1'> | <class 'Derived2'>
reveal_type(t1) # revealed: Literal[Derived1, Derived2]
if issubclass(t1, Derived1):
reveal_type(t1) # revealed: <class 'Derived1'>
reveal_type(t1) # revealed: Literal[Derived1]
else:
reveal_type(t1) # revealed: <class 'Derived2'>
reveal_type(t1) # revealed: Literal[Derived2]
t2 = Derived1 if flag2 else Base
if issubclass(t2, Base):
reveal_type(t2) # revealed: <class 'Derived1'> | <class 'Base'>
reveal_type(t2) # revealed: Literal[Derived1, Base]
t3 = Derived1 if flag3 else Unrelated
if issubclass(t3, Base):
reveal_type(t3) # revealed: <class 'Derived1'>
reveal_type(t3) # revealed: Literal[Derived1]
else:
reveal_type(t3) # revealed: <class 'Unrelated'>
reveal_type(t3) # revealed: Literal[Unrelated]
```
### Narrowing for non-literals
@@ -109,10 +109,10 @@ def _(flag: bool):
t = int if flag else NoneType
if issubclass(t, NoneType):
reveal_type(t) # revealed: <class 'NoneType'>
reveal_type(t) # revealed: Literal[NoneType]
if issubclass(t, type(None)):
reveal_type(t) # revealed: <class 'NoneType'>
reveal_type(t) # revealed: Literal[NoneType]
```
## `classinfo` contains multiple types
@@ -126,9 +126,9 @@ def _(flag1: bool, flag2: bool):
t = int if flag1 else str if flag2 else bytes
if issubclass(t, (int, (Unrelated, (bytes,)))):
reveal_type(t) # revealed: <class 'int'> | <class 'bytes'>
reveal_type(t) # revealed: Literal[int, bytes]
else:
reveal_type(t) # revealed: <class 'str'>
reveal_type(t) # revealed: Literal[str]
```
## Special cases
@@ -175,7 +175,7 @@ def flag() -> bool:
t = int if flag() else str
if issubclass(t, int):
reveal_type(t) # revealed: <class 'int'> | <class 'str'>
reveal_type(t) # revealed: Literal[int, str]
```
### Do support narrowing if `issubclass` is aliased
@@ -188,7 +188,7 @@ def flag() -> bool:
t = int if flag() else str
if issubclass_alias(t, int):
reveal_type(t) # revealed: <class 'int'>
reveal_type(t) # revealed: Literal[int]
```
### Do support narrowing if `issubclass` is imported
@@ -201,7 +201,7 @@ def flag() -> bool:
t = int if flag() else str
if imported_issubclass(t, int):
reveal_type(t) # revealed: <class 'int'>
reveal_type(t) # revealed: Literal[int]
```
### Do not narrow if second argument is not a proper `classinfo` argument
@@ -217,17 +217,17 @@ t = int if flag() else str
# TODO: this should cause us to emit a diagnostic during
# type checking
if issubclass(t, "str"):
reveal_type(t) # revealed: <class 'int'> | <class 'str'>
reveal_type(t) # revealed: Literal[int, str]
# TODO: this should cause us to emit a diagnostic during
# type checking
if issubclass(t, (bytes, "str")):
reveal_type(t) # revealed: <class 'int'> | <class 'str'>
reveal_type(t) # revealed: Literal[int, str]
# TODO: this should cause us to emit a diagnostic during
# type checking
if issubclass(t, Any):
reveal_type(t) # revealed: <class 'int'> | <class 'str'>
reveal_type(t) # revealed: Literal[int, str]
```
### Do not narrow if there are keyword arguments
@@ -240,7 +240,7 @@ t = int if flag() else str
# error: [unknown-argument]
if issubclass(t, int, foo="bar"):
reveal_type(t) # revealed: <class 'int'> | <class 'str'>
reveal_type(t) # revealed: Literal[int, str]
```
### `type[]` types are narrowed as well as class-literal types

View File

@@ -108,12 +108,12 @@ def flag() -> bool:
return True
x = int if flag() else str
reveal_type(x) # revealed: <class 'int'> | <class 'str'>
reveal_type(x) # revealed: Literal[int, str]
if x:
reveal_type(x) # revealed: (<class 'int'> & ~AlwaysFalsy) | (<class 'str'> & ~AlwaysFalsy)
reveal_type(x) # revealed: (Literal[int] & ~AlwaysFalsy) | (Literal[str] & ~AlwaysFalsy)
else:
reveal_type(x) # revealed: (<class 'int'> & ~AlwaysTruthy) | (<class 'str'> & ~AlwaysTruthy)
reveal_type(x) # revealed: (Literal[int] & ~AlwaysTruthy) | (Literal[str] & ~AlwaysTruthy)
```
## Determined Truthiness
@@ -276,12 +276,12 @@ def _(
reveal_type(d) # revealed: type[DeferredClass] & ~AlwaysFalsy
tf = TruthyClass if flag else FalsyClass
reveal_type(tf) # revealed: <class 'TruthyClass'> | <class 'FalsyClass'>
reveal_type(tf) # revealed: Literal[TruthyClass, FalsyClass]
if tf:
reveal_type(tf) # revealed: <class 'TruthyClass'>
reveal_type(tf) # revealed: Literal[TruthyClass]
else:
reveal_type(tf) # revealed: <class 'FalsyClass'>
reveal_type(tf) # revealed: Literal[FalsyClass]
```
## Narrowing in chained boolean expressions

View File

@@ -150,7 +150,7 @@ def _(x: Base):
```py
def _(x: object):
if (y := type(x)) is bool:
reveal_type(y) # revealed: <class 'bool'>
reveal_type(y) # revealed: Literal[bool]
if (type(y := x)) is bool:
reveal_type(y) # revealed: bool
```

View File

@@ -28,7 +28,7 @@ from typing import Protocol
class MyProtocol(Protocol): ...
reveal_type(MyProtocol.__mro__) # revealed: tuple[<class 'MyProtocol'>, typing.Protocol, typing.Generic, <class 'object'>]
reveal_type(MyProtocol.__mro__) # revealed: tuple[Literal[MyProtocol], typing.Protocol, typing.Generic, Literal[object]]
```
Just like for any other class base, it is an error for `Protocol` to appear multiple times in a
@@ -37,7 +37,7 @@ class's bases:
```py
class Foo(Protocol, Protocol): ... # error: [inconsistent-mro]
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
reveal_type(Foo.__mro__) # revealed: tuple[Literal[Foo], Unknown, Literal[object]]
```
Protocols can also be generic, either by including `Generic[]` in the bases list, subscripting
@@ -71,7 +71,7 @@ class DuplicateBases(Protocol, Protocol[T]):
x: T
# TODO: should not have `Protocol` multiple times
# revealed: tuple[<class 'DuplicateBases'>, typing.Protocol, @Todo(`Protocol[]` subscript), typing.Generic, <class 'object'>]
# revealed: tuple[Literal[DuplicateBases], typing.Protocol, @Todo(`Protocol[]` subscript), typing.Generic, Literal[object]]
reveal_type(DuplicateBases.__mro__)
```
@@ -107,7 +107,7 @@ it is not sufficient for it to have `Protocol` in its MRO.
```py
class SubclassOfMyProtocol(MyProtocol): ...
# revealed: tuple[<class 'SubclassOfMyProtocol'>, <class 'MyProtocol'>, typing.Protocol, typing.Generic, <class 'object'>]
# revealed: tuple[Literal[SubclassOfMyProtocol], Literal[MyProtocol], typing.Protocol, typing.Generic, Literal[object]]
reveal_type(SubclassOfMyProtocol.__mro__)
reveal_type(is_protocol(SubclassOfMyProtocol)) # revealed: Literal[False]
@@ -126,7 +126,7 @@ class OtherProtocol(Protocol):
class ComplexInheritance(SubProtocol, OtherProtocol, Protocol): ...
# revealed: tuple[<class 'ComplexInheritance'>, <class 'SubProtocol'>, <class 'MyProtocol'>, <class 'OtherProtocol'>, typing.Protocol, typing.Generic, <class 'object'>]
# revealed: tuple[Literal[ComplexInheritance], Literal[SubProtocol], Literal[MyProtocol], Literal[OtherProtocol], typing.Protocol, typing.Generic, Literal[object]]
reveal_type(ComplexInheritance.__mro__)
reveal_type(is_protocol(ComplexInheritance)) # revealed: Literal[True]
@@ -139,13 +139,13 @@ or `TypeError` is raised at runtime when the class is created.
# error: [invalid-protocol] "Protocol class `Invalid` cannot inherit from non-protocol class `NotAProtocol`"
class Invalid(NotAProtocol, Protocol): ...
# revealed: tuple[<class 'Invalid'>, <class 'NotAProtocol'>, typing.Protocol, typing.Generic, <class 'object'>]
# revealed: tuple[Literal[Invalid], Literal[NotAProtocol], typing.Protocol, typing.Generic, Literal[object]]
reveal_type(Invalid.__mro__)
# error: [invalid-protocol] "Protocol class `AlsoInvalid` cannot inherit from non-protocol class `NotAProtocol`"
class AlsoInvalid(MyProtocol, OtherProtocol, NotAProtocol, Protocol): ...
# revealed: tuple[<class 'AlsoInvalid'>, <class 'MyProtocol'>, <class 'OtherProtocol'>, <class 'NotAProtocol'>, typing.Protocol, typing.Generic, <class 'object'>]
# revealed: tuple[Literal[AlsoInvalid], Literal[MyProtocol], Literal[OtherProtocol], Literal[NotAProtocol], typing.Protocol, typing.Generic, Literal[object]]
reveal_type(AlsoInvalid.__mro__)
```
@@ -163,7 +163,7 @@ T = TypeVar("T")
# type checkers.
class Fine(Protocol, object): ...
reveal_type(Fine.__mro__) # revealed: tuple[<class 'Fine'>, typing.Protocol, typing.Generic, <class 'object'>]
reveal_type(Fine.__mro__) # revealed: tuple[Literal[Fine], typing.Protocol, typing.Generic, Literal[object]]
class StillFine(Protocol, Generic[T], object): ...
class EvenThis[T](Protocol, object): ...
@@ -177,7 +177,7 @@ And multiple inheritance from a mix of protocol and non-protocol classes is fine
```py
class FineAndDandy(MyProtocol, OtherProtocol, NotAProtocol): ...
# revealed: tuple[<class 'FineAndDandy'>, <class 'MyProtocol'>, <class 'OtherProtocol'>, typing.Protocol, typing.Generic, <class 'NotAProtocol'>, <class 'object'>]
# revealed: tuple[Literal[FineAndDandy], Literal[MyProtocol], Literal[OtherProtocol], typing.Protocol, typing.Generic, Literal[NotAProtocol], Literal[object]]
reveal_type(FineAndDandy.__mro__)
```
@@ -1558,43 +1558,7 @@ def two(some_list: list, some_tuple: tuple[int, str], some_sized: Sized):
c: Sized = some_sized
```
## Recursive protocols
### Properties
```py
from __future__ import annotations
from typing import Protocol, Any
from ty_extensions import is_fully_static, static_assert, is_assignable_to, is_subtype_of, is_equivalent_to
class RecursiveFullyStatic(Protocol):
parent: RecursiveFullyStatic | None
x: int
class RecursiveNonFullyStatic(Protocol):
parent: RecursiveNonFullyStatic | None
x: Any
static_assert(is_fully_static(RecursiveFullyStatic))
static_assert(not is_fully_static(RecursiveNonFullyStatic))
static_assert(not is_subtype_of(RecursiveFullyStatic, RecursiveNonFullyStatic))
static_assert(not is_subtype_of(RecursiveNonFullyStatic, RecursiveFullyStatic))
# TODO: currently leads to a stack overflow
# static_assert(is_assignable_to(RecursiveFullyStatic, RecursiveNonFullyStatic))
# static_assert(is_assignable_to(RecursiveNonFullyStatic, RecursiveFullyStatic))
class AlsoRecursiveFullyStatic(Protocol):
parent: AlsoRecursiveFullyStatic | None
x: int
# TODO: currently leads to a stack overflow
# static_assert(is_equivalent_to(AlsoRecursiveFullyStatic, RecursiveFullyStatic))
```
### Regression test: narrowing with self-referential protocols
## Regression test: narrowing with self-referential protocols
This snippet caused us to panic on an early version of the implementation for protocols.

View File

@@ -56,7 +56,7 @@ reveal_type(typing.__init__) # revealed: bound method ModuleType.__init__(name:
# These come from `builtins.object`, not `types.ModuleType`:
reveal_type(typing.__eq__) # revealed: bound method ModuleType.__eq__(value: object, /) -> bool
reveal_type(typing.__class__) # revealed: <class 'ModuleType'>
reveal_type(typing.__class__) # revealed: Literal[ModuleType]
reveal_type(typing.__dict__) # revealed: dict[str, Any]
```

View File

@@ -16,7 +16,7 @@ class C:
if flag:
x = 2
# error: [possibly-unbound-attribute] "Attribute `x` on type `<class 'C'>` is possibly unbound"
# error: [possibly-unbound-attribute] "Attribute `x` on type `Literal[C]` is possibly unbound"
reveal_type(C.x) # revealed: Unknown | Literal[2]
reveal_type(C.y) # revealed: Unknown | Literal[1]
```

View File

@@ -26,7 +26,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/attribute_as
# Diagnostics
```
warning: lint:possibly-unbound-attribute: Attribute `attr` on type `<class 'C'>` is possibly unbound
warning: lint:possibly-unbound-attribute: Attribute `attr` on type `Literal[C]` is possibly unbound
--> src/mdtest_snippet.py:6:5
|
4 | attr: int = 0

View File

@@ -41,7 +41,7 @@ info: `lint:invalid-assignment` is enabled by default
```
```
error: lint:invalid-attribute-access: Cannot assign to instance attribute `attr` from the class object `<class 'C'>`
error: lint:invalid-attribute-access: Cannot assign to instance attribute `attr` from the class object `Literal[C]`
--> src/mdtest_snippet.py:9:1
|
7 | instance.attr = "wrong" # error: [invalid-assignment]

View File

@@ -37,7 +37,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/attribute_as
# Diagnostics
```
error: lint:invalid-assignment: Object of type `Literal[1]` is not assignable to attribute `attr` on type `<class 'C1'> | <class 'C1'>`
error: lint:invalid-assignment: Object of type `Literal[1]` is not assignable to attribute `attr` on type `Literal[C1, C1]`
--> src/mdtest_snippet.py:11:5
|
10 | # TODO: The error message here could be improved to explain why the assignment fails.

View File

@@ -23,7 +23,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/attribute_as
# Diagnostics
```
error: lint:unresolved-attribute: Unresolved attribute `non_existent` on type `<class 'C'>`.
error: lint:unresolved-attribute: Unresolved attribute `non_existent` on type `Literal[C]`.
--> src/mdtest_snippet.py:3:1
|
1 | class C: ...

View File

@@ -46,18 +46,6 @@ info: `lint:not-iterable` is enabled by default
```
```
info: revealed-type: Revealed type
--> src/mdtest_snippet.py:16:5
|
14 | # revealed: Unknown
15 | # error: [possibly-unresolved-reference]
16 | reveal_type(x)
| ^^^^^^^^^^^^^^ `Unknown`
|
```
```
warning: lint:possibly-unresolved-reference: Name `x` used when possibly not defined
--> src/mdtest_snippet.py:16:17
@@ -70,3 +58,15 @@ warning: lint:possibly-unresolved-reference: Name `x` used when possibly not def
info: `lint:possibly-unresolved-reference` is enabled by default
```
```
info: revealed-type: Revealed type
--> src/mdtest_snippet.py:16:5
|
14 | # revealed: Unknown
15 | # error: [possibly-unresolved-reference]
16 | reveal_type(x)
| ^^^^^^^^^^^^^^ `Unknown`
|
```

View File

@@ -55,18 +55,6 @@ info: revealed-type: Revealed type
```
```
info: revealed-type: Revealed type
--> src/mdtest_snippet.py:12:1
|
10 | reveal_type(f(True)) # revealed: Literal[True]
11 | # error: [invalid-argument-type]
12 | reveal_type(f("string")) # revealed: Unknown
| ^^^^^^^^^^^^^^^^^^^^^^^^ `Unknown`
|
```
```
error: lint:invalid-argument-type: Argument to this function is incorrect
--> src/mdtest_snippet.py:12:15
@@ -89,3 +77,15 @@ info: Type variable defined here
info: `lint:invalid-argument-type` is enabled by default
```
```
info: revealed-type: Revealed type
--> src/mdtest_snippet.py:12:1
|
10 | reveal_type(f(True)) # revealed: Literal[True]
11 | # error: [invalid-argument-type]
12 | reveal_type(f("string")) # revealed: Unknown
| ^^^^^^^^^^^^^^^^^^^^^^^^ `Unknown`
|
```

View File

@@ -70,18 +70,6 @@ info: revealed-type: Revealed type
```
```
info: revealed-type: Revealed type
--> src/mdtest_snippet.py:13:1
|
11 | reveal_type(f(None)) # revealed: None
12 | # error: [invalid-argument-type]
13 | reveal_type(f("string")) # revealed: Unknown
| ^^^^^^^^^^^^^^^^^^^^^^^^ `Unknown`
|
```
```
error: lint:invalid-argument-type: Argument to this function is incorrect
--> src/mdtest_snippet.py:13:15
@@ -104,3 +92,15 @@ info: Type variable defined here
info: `lint:invalid-argument-type` is enabled by default
```
```
info: revealed-type: Revealed type
--> src/mdtest_snippet.py:13:1
|
11 | reveal_type(f(None)) # revealed: None
12 | # error: [invalid-argument-type]
13 | reveal_type(f("string")) # revealed: Unknown
| ^^^^^^^^^^^^^^^^^^^^^^^^ `Unknown`
|
```

View File

@@ -52,18 +52,6 @@ info: revealed-type: Revealed type
```
```
info: revealed-type: Revealed type
--> src/mdtest_snippet.py:9:1
|
7 | reveal_type(f(True)) # revealed: Literal[True]
8 | # error: [invalid-argument-type]
9 | reveal_type(f("string")) # revealed: Unknown
| ^^^^^^^^^^^^^^^^^^^^^^^^ `Unknown`
|
```
```
error: lint:invalid-argument-type: Argument to this function is incorrect
--> src/mdtest_snippet.py:9:15
@@ -85,3 +73,15 @@ info: Type variable defined here
info: `lint:invalid-argument-type` is enabled by default
```
```
info: revealed-type: Revealed type
--> src/mdtest_snippet.py:9:1
|
7 | reveal_type(f(True)) # revealed: Literal[True]
8 | # error: [invalid-argument-type]
9 | reveal_type(f("string")) # revealed: Unknown
| ^^^^^^^^^^^^^^^^^^^^^^^^ `Unknown`
|
```

View File

@@ -67,18 +67,6 @@ info: revealed-type: Revealed type
```
```
info: revealed-type: Revealed type
--> src/mdtest_snippet.py:10:1
|
8 | reveal_type(f(None)) # revealed: None
9 | # error: [invalid-argument-type]
10 | reveal_type(f("string")) # revealed: Unknown
| ^^^^^^^^^^^^^^^^^^^^^^^^ `Unknown`
|
```
```
error: lint:invalid-argument-type: Argument to this function is incorrect
--> src/mdtest_snippet.py:10:15
@@ -100,3 +88,15 @@ info: Type variable defined here
info: `lint:invalid-argument-type` is enabled by default
```
```
info: revealed-type: Revealed type
--> src/mdtest_snippet.py:10:1
|
8 | reveal_type(f(None)) # revealed: None
9 | # error: [invalid-argument-type]
10 | reveal_type(f("string")) # revealed: Unknown
| ^^^^^^^^^^^^^^^^^^^^^^^^ `Unknown`
|
```

View File

@@ -1,402 +0,0 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: mro.md - Method Resolution Order tests - `__bases__` lists with duplicate bases
mdtest path: crates/ty_python_semantic/resources/mdtest/mro.md
---
# Python source files
## mdtest_snippet.py
```
1 | from typing_extensions import reveal_type
2 |
3 | class Foo(str, str): ... # error: [duplicate-base] "Duplicate base class `str`"
4 |
5 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
6 |
7 | class Spam: ...
8 | class Eggs: ...
9 | class Bar: ...
10 | class Baz: ...
11 |
12 | # fmt: off
13 |
14 | # error: [duplicate-base] "Duplicate base class `Spam`"
15 | # error: [duplicate-base] "Duplicate base class `Eggs`"
16 | class Ham(
17 | Spam,
18 | Eggs,
19 | Bar,
20 | Baz,
21 | Spam,
22 | Eggs,
23 | ): ...
24 |
25 | # fmt: on
26 |
27 | reveal_type(Ham.__mro__) # revealed: tuple[<class 'Ham'>, Unknown, <class 'object'>]
28 |
29 | class Mushrooms: ...
30 | class Omelette(Spam, Eggs, Mushrooms, Mushrooms): ... # error: [duplicate-base]
31 |
32 | reveal_type(Omelette.__mro__) # revealed: tuple[<class 'Omelette'>, Unknown, <class 'object'>]
33 |
34 | # fmt: off
35 |
36 | # error: [duplicate-base] "Duplicate base class `Eggs`"
37 | class VeryEggyOmelette(
38 | Eggs,
39 | Ham,
40 | Spam,
41 | Eggs,
42 | Mushrooms,
43 | Bar,
44 | Eggs,
45 | Baz,
46 | Eggs,
47 | ): ...
48 |
49 | # fmt: off
50 | # fmt: off
51 |
52 | class A: ...
53 |
54 | class B( # type: ignore[duplicate-base]
55 | A,
56 | A,
57 | ): ...
58 |
59 | class C(
60 | A,
61 | A
62 | ): # type: ignore[duplicate-base]
63 | x: int
64 |
65 | # fmt: on
66 | # fmt: off
67 |
68 | # error: [duplicate-base]
69 | class D(
70 | A,
71 | # error: [unused-ignore-comment]
72 | A, # type: ignore[duplicate-base]
73 | ): ...
74 |
75 | # error: [duplicate-base]
76 | class E(
77 | A,
78 | A
79 | ):
80 | # error: [unused-ignore-comment]
81 | x: int # type: ignore[duplicate-base]
82 |
83 | # fmt: on
```
# Diagnostics
```
error: lint:duplicate-base: Duplicate base class `str`
--> src/mdtest_snippet.py:3:7
|
1 | from typing_extensions import reveal_type
2 |
3 | class Foo(str, str): ... # error: [duplicate-base] "Duplicate base class `str`"
| ^^^^^^^^^^^^^
4 |
5 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
info: The definition of class `Foo` will raise `TypeError` at runtime
--> src/mdtest_snippet.py:3:11
|
1 | from typing_extensions import reveal_type
2 |
3 | class Foo(str, str): ... # error: [duplicate-base] "Duplicate base class `str`"
| --- ^^^ Class `str` later repeated here
| |
| Class `str` first included in bases list here
4 |
5 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
info: `lint:duplicate-base` is enabled by default
```
```
info: revealed-type: Revealed type
--> src/mdtest_snippet.py:5:1
|
3 | class Foo(str, str): ... # error: [duplicate-base] "Duplicate base class `str`"
4 |
5 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
| ^^^^^^^^^^^^^^^^^^^^^^^^ `tuple[<class 'Foo'>, Unknown, <class 'object'>]`
6 |
7 | class Spam: ...
|
```
```
error: lint:duplicate-base: Duplicate base class `Spam`
--> src/mdtest_snippet.py:16:7
|
14 | # error: [duplicate-base] "Duplicate base class `Spam`"
15 | # error: [duplicate-base] "Duplicate base class `Eggs`"
16 | class Ham(
| _______^
17 | | Spam,
18 | | Eggs,
19 | | Bar,
20 | | Baz,
21 | | Spam,
22 | | Eggs,
23 | | ): ...
| |_^
24 |
25 | # fmt: on
|
info: The definition of class `Ham` will raise `TypeError` at runtime
--> src/mdtest_snippet.py:17:5
|
15 | # error: [duplicate-base] "Duplicate base class `Eggs`"
16 | class Ham(
17 | Spam,
| ---- Class `Spam` first included in bases list here
18 | Eggs,
19 | Bar,
20 | Baz,
21 | Spam,
| ^^^^ Class `Spam` later repeated here
22 | Eggs,
23 | ): ...
|
info: `lint:duplicate-base` is enabled by default
```
```
error: lint:duplicate-base: Duplicate base class `Eggs`
--> src/mdtest_snippet.py:16:7
|
14 | # error: [duplicate-base] "Duplicate base class `Spam`"
15 | # error: [duplicate-base] "Duplicate base class `Eggs`"
16 | class Ham(
| _______^
17 | | Spam,
18 | | Eggs,
19 | | Bar,
20 | | Baz,
21 | | Spam,
22 | | Eggs,
23 | | ): ...
| |_^
24 |
25 | # fmt: on
|
info: The definition of class `Ham` will raise `TypeError` at runtime
--> src/mdtest_snippet.py:18:5
|
16 | class Ham(
17 | Spam,
18 | Eggs,
| ---- Class `Eggs` first included in bases list here
19 | Bar,
20 | Baz,
21 | Spam,
22 | Eggs,
| ^^^^ Class `Eggs` later repeated here
23 | ): ...
|
info: `lint:duplicate-base` is enabled by default
```
```
info: revealed-type: Revealed type
--> src/mdtest_snippet.py:27:1
|
25 | # fmt: on
26 |
27 | reveal_type(Ham.__mro__) # revealed: tuple[<class 'Ham'>, Unknown, <class 'object'>]
| ^^^^^^^^^^^^^^^^^^^^^^^^ `tuple[<class 'Ham'>, Unknown, <class 'object'>]`
28 |
29 | class Mushrooms: ...
|
```
```
error: lint:duplicate-base: Duplicate base class `Mushrooms`
--> src/mdtest_snippet.py:30:7
|
29 | class Mushrooms: ...
30 | class Omelette(Spam, Eggs, Mushrooms, Mushrooms): ... # error: [duplicate-base]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
31 |
32 | reveal_type(Omelette.__mro__) # revealed: tuple[<class 'Omelette'>, Unknown, <class 'object'>]
|
info: The definition of class `Omelette` will raise `TypeError` at runtime
--> src/mdtest_snippet.py:30:28
|
29 | class Mushrooms: ...
30 | class Omelette(Spam, Eggs, Mushrooms, Mushrooms): ... # error: [duplicate-base]
| --------- ^^^^^^^^^ Class `Mushrooms` later repeated here
| |
| Class `Mushrooms` first included in bases list here
31 |
32 | reveal_type(Omelette.__mro__) # revealed: tuple[<class 'Omelette'>, Unknown, <class 'object'>]
|
info: `lint:duplicate-base` is enabled by default
```
```
info: revealed-type: Revealed type
--> src/mdtest_snippet.py:32:1
|
30 | class Omelette(Spam, Eggs, Mushrooms, Mushrooms): ... # error: [duplicate-base]
31 |
32 | reveal_type(Omelette.__mro__) # revealed: tuple[<class 'Omelette'>, Unknown, <class 'object'>]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `tuple[<class 'Omelette'>, Unknown, <class 'object'>]`
33 |
34 | # fmt: off
|
```
```
error: lint:duplicate-base: Duplicate base class `Eggs`
--> src/mdtest_snippet.py:37:7
|
36 | # error: [duplicate-base] "Duplicate base class `Eggs`"
37 | class VeryEggyOmelette(
| _______^
38 | | Eggs,
39 | | Ham,
40 | | Spam,
41 | | Eggs,
42 | | Mushrooms,
43 | | Bar,
44 | | Eggs,
45 | | Baz,
46 | | Eggs,
47 | | ): ...
| |_^
48 |
49 | # fmt: off
|
info: The definition of class `VeryEggyOmelette` will raise `TypeError` at runtime
--> src/mdtest_snippet.py:38:5
|
36 | # error: [duplicate-base] "Duplicate base class `Eggs`"
37 | class VeryEggyOmelette(
38 | Eggs,
| ---- Class `Eggs` first included in bases list here
39 | Ham,
40 | Spam,
41 | Eggs,
| ^^^^ Class `Eggs` later repeated here
42 | Mushrooms,
43 | Bar,
44 | Eggs,
| ^^^^ Class `Eggs` later repeated here
45 | Baz,
46 | Eggs,
| ^^^^ Class `Eggs` later repeated here
47 | ): ...
|
info: `lint:duplicate-base` is enabled by default
```
```
error: lint:duplicate-base: Duplicate base class `A`
--> src/mdtest_snippet.py:69:7
|
68 | # error: [duplicate-base]
69 | class D(
| _______^
70 | | A,
71 | | # error: [unused-ignore-comment]
72 | | A, # type: ignore[duplicate-base]
73 | | ): ...
| |_^
74 |
75 | # error: [duplicate-base]
|
info: The definition of class `D` will raise `TypeError` at runtime
--> src/mdtest_snippet.py:70:5
|
68 | # error: [duplicate-base]
69 | class D(
70 | A,
| - Class `A` first included in bases list here
71 | # error: [unused-ignore-comment]
72 | A, # type: ignore[duplicate-base]
| ^ Class `A` later repeated here
73 | ): ...
|
info: `lint:duplicate-base` is enabled by default
```
```
warning: lint:unused-ignore-comment
--> src/mdtest_snippet.py:72:9
|
70 | A,
71 | # error: [unused-ignore-comment]
72 | A, # type: ignore[duplicate-base]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Unused blanket `type: ignore` directive
73 | ): ...
|
```
```
error: lint:duplicate-base: Duplicate base class `A`
--> src/mdtest_snippet.py:76:7
|
75 | # error: [duplicate-base]
76 | class E(
| _______^
77 | | A,
78 | | A
79 | | ):
| |_^
80 | # error: [unused-ignore-comment]
81 | x: int # type: ignore[duplicate-base]
|
info: The definition of class `E` will raise `TypeError` at runtime
--> src/mdtest_snippet.py:77:5
|
75 | # error: [duplicate-base]
76 | class E(
77 | A,
| - Class `A` first included in bases list here
78 | A
| ^ Class `A` later repeated here
79 | ):
80 | # error: [unused-ignore-comment]
|
info: `lint:duplicate-base` is enabled by default
```
```
warning: lint:unused-ignore-comment
--> src/mdtest_snippet.py:81:13
|
79 | ):
80 | # error: [unused-ignore-comment]
81 | x: int # type: ignore[duplicate-base]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Unused blanket `type: ignore` directive
82 |
83 | # fmt: on
|
```

View File

@@ -71,6 +71,23 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/overloads.md
# Diagnostics
```
error: lint:invalid-overload: Overloaded function `try_from3` does not use the `@classmethod` decorator consistently
--> src/mdtest_snippet.py:40:9
|
38 | def try_from3(cls, x: str) -> None: ...
39 | # error: [invalid-overload]
40 | def try_from3(cls, x: int | str) -> CheckClassMethod | None:
| ---------
| |
| Missing here
41 | if isinstance(x, int):
42 | return cls(x)
|
info: `lint:invalid-overload` is enabled by default
```
```
error: lint:invalid-overload: Overloaded function `try_from1` does not use the `@classmethod` decorator consistently
--> src/mdtest_snippet.py:13:9
@@ -112,20 +129,3 @@ error: lint:invalid-overload: Overloaded function `try_from2` does not use the `
info: `lint:invalid-overload` is enabled by default
```
```
error: lint:invalid-overload: Overloaded function `try_from3` does not use the `@classmethod` decorator consistently
--> src/mdtest_snippet.py:40:9
|
38 | def try_from3(cls, x: str) -> None: ...
39 | # error: [invalid-overload]
40 | def try_from3(cls, x: int | str) -> CheckClassMethod | None:
| ---------
| |
| Missing here
41 | if isinstance(x, int):
42 | return cls(x)
|
info: `lint:invalid-overload` is enabled by default
```

View File

@@ -64,22 +64,6 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/overloads.md
# Diagnostics
```
error: lint:invalid-overload: `@final` decorator should be applied only to the overload implementation
--> src/mdtest_snippet.py:18:9
|
16 | def method2(self, x: str) -> str: ...
17 | # error: [invalid-overload]
18 | def method2(self, x: int | str) -> int | str:
| -------
| |
| Implementation defined here
19 | return x
|
info: `lint:invalid-overload` is enabled by default
```
```
error: lint:invalid-overload: `@final` decorator should be applied only to the overload implementation
--> src/mdtest_snippet.py:27:9
@@ -96,6 +80,22 @@ info: `lint:invalid-overload` is enabled by default
```
```
error: lint:invalid-overload: `@final` decorator should be applied only to the overload implementation
--> src/mdtest_snippet.py:18:9
|
16 | def method2(self, x: str) -> str: ...
17 | # error: [invalid-overload]
18 | def method2(self, x: int | str) -> int | str:
| -------
| |
| Implementation defined here
19 | return x
|
info: `lint:invalid-overload` is enabled by default
```
```
error: lint:invalid-overload: `@final` decorator should be applied only to the first overload
--> src/mdtest_snippet.pyi:11:9

View File

@@ -41,19 +41,6 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/protocols.md
# Diagnostics
```
info: revealed-type: Revealed type
--> src/mdtest_snippet.py:4:1
|
3 | # error: [call-non-callable]
4 | reveal_type(Protocol()) # revealed: Unknown
| ^^^^^^^^^^^^^^^^^^^^^^^ `Unknown`
5 |
6 | class MyProtocol(Protocol):
|
```
```
error: lint:call-non-callable: Object of type `typing.Protocol` is not callable
--> src/mdtest_snippet.py:4:13
@@ -70,14 +57,14 @@ info: `lint:call-non-callable` is enabled by default
```
info: revealed-type: Revealed type
--> src/mdtest_snippet.py:10:1
|
9 | # error: [call-non-callable] "Cannot instantiate class `MyProtocol`"
10 | reveal_type(MyProtocol()) # revealed: MyProtocol
| ^^^^^^^^^^^^^^^^^^^^^^^^^ `MyProtocol`
11 |
12 | class GenericProtocol[T](Protocol):
|
--> src/mdtest_snippet.py:4:1
|
3 | # error: [call-non-callable]
4 | reveal_type(Protocol()) # revealed: Unknown
| ^^^^^^^^^^^^^^^^^^^^^^^ `Unknown`
5 |
6 | class MyProtocol(Protocol):
|
```
@@ -106,12 +93,13 @@ info: `lint:call-non-callable` is enabled by default
```
info: revealed-type: Revealed type
--> src/mdtest_snippet.py:16:1
--> src/mdtest_snippet.py:10:1
|
15 | # error: [call-non-callable] "Cannot instantiate class `GenericProtocol`"
16 | reveal_type(GenericProtocol[int]()) # revealed: GenericProtocol[int]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `GenericProtocol[int]`
17 | class SubclassOfMyProtocol(MyProtocol): ...
9 | # error: [call-non-callable] "Cannot instantiate class `MyProtocol`"
10 | reveal_type(MyProtocol()) # revealed: MyProtocol
| ^^^^^^^^^^^^^^^^^^^^^^^^^ `MyProtocol`
11 |
12 | class GenericProtocol[T](Protocol):
|
```
@@ -138,6 +126,18 @@ info: `lint:call-non-callable` is enabled by default
```
```
info: revealed-type: Revealed type
--> src/mdtest_snippet.py:16:1
|
15 | # error: [call-non-callable] "Cannot instantiate class `GenericProtocol`"
16 | reveal_type(GenericProtocol[int]()) # revealed: GenericProtocol[int]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `GenericProtocol[int]`
17 | class SubclassOfMyProtocol(MyProtocol): ...
|
```
```
info: revealed-type: Revealed type
--> src/mdtest_snippet.py:19:1

View File

@@ -27,42 +27,39 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/function/return_type.md
13 | def i() -> typing.Iterable:
14 | yield 42
15 |
16 | def i2() -> typing.Generator:
17 | yield from i()
18 |
19 | def j() -> str: # error: [invalid-return-type]
20 | yield 42
21 | import types
22 | import typing
16 | def j() -> str: # error: [invalid-return-type]
17 | yield 42
18 | import types
19 | import typing
20 |
21 | async def f() -> types.AsyncGeneratorType:
22 | yield 42
23 |
24 | async def f() -> types.AsyncGeneratorType:
24 | async def g() -> typing.AsyncGenerator:
25 | yield 42
26 |
27 | async def g() -> typing.AsyncGenerator:
27 | async def h() -> typing.AsyncIterator:
28 | yield 42
29 |
30 | async def h() -> typing.AsyncIterator:
30 | async def i() -> typing.AsyncIterable:
31 | yield 42
32 |
33 | async def i() -> typing.AsyncIterable:
33 | async def j() -> str: # error: [invalid-return-type]
34 | yield 42
35 |
36 | async def j() -> str: # error: [invalid-return-type]
37 | yield 42
```
# Diagnostics
```
error: lint:invalid-return-type: Return type does not match returned value
--> src/mdtest_snippet.py:19:12
--> src/mdtest_snippet.py:16:12
|
17 | yield from i()
18 |
19 | def j() -> str: # error: [invalid-return-type]
14 | yield 42
15 |
16 | def j() -> str: # error: [invalid-return-type]
| ^^^ Expected `str`, found `types.GeneratorType`
20 | yield 42
21 | import types
17 | yield 42
18 | import types
|
info: Function is inferred as returning `types.GeneratorType` because it is a generator function
info: See https://docs.python.org/3/glossary.html#term-generator for more details
@@ -72,13 +69,13 @@ info: `lint:invalid-return-type` is enabled by default
```
error: lint:invalid-return-type: Return type does not match returned value
--> src/mdtest_snippet.py:36:18
--> src/mdtest_snippet.py:33:18
|
34 | yield 42
35 |
36 | async def j() -> str: # error: [invalid-return-type]
31 | yield 42
32 |
33 | async def j() -> str: # error: [invalid-return-type]
| ^^^ Expected `str`, found `types.AsyncGeneratorType`
37 | yield 42
34 | yield 42
|
info: Function is inferred as returning `types.AsyncGeneratorType` because it is an async generator function
info: See https://docs.python.org/3/glossary.html#term-asynchronous-generator for more details

View File

@@ -15,8 +15,8 @@ class Foo[T]: ...
class Bar(Foo[Bar]): ...
reveal_type(Bar) # revealed: <class 'Bar'>
reveal_type(Bar.__mro__) # revealed: tuple[<class 'Bar'>, <class 'Foo[Bar]'>, <class 'object'>]
reveal_type(Bar) # revealed: Literal[Bar]
reveal_type(Bar.__mro__) # revealed: tuple[Literal[Bar], Literal[Foo[Bar]], Literal[object]]
```
## Access to attributes declared in stubs

View File

@@ -5,8 +5,7 @@
```py
class NotSubscriptable: ...
# error: "Cannot subscript object of type `<class 'NotSubscriptable'>` with no `__class_getitem__` method"
a = NotSubscriptable[0]
a = NotSubscriptable[0] # error: "Cannot subscript object of type `Literal[NotSubscriptable]` with no `__class_getitem__` method"
```
## Class getitem
@@ -48,7 +47,7 @@ def _(flag: bool):
x = A if flag else B
reveal_type(x) # revealed: <class 'A'> | <class 'B'>
reveal_type(x) # revealed: Literal[A, B]
reveal_type(x[0]) # revealed: str | int
```
@@ -63,7 +62,7 @@ def _(flag: bool):
else:
class Spam: ...
# error: [call-possibly-unbound-method] "Method `__class_getitem__` of type `<class 'Spam'> | <class 'Spam'>` is possibly unbound"
# error: [call-possibly-unbound-method] "Method `__class_getitem__` of type `Literal[Spam, Spam]` is possibly unbound"
# revealed: str
reveal_type(Spam[42])
```
@@ -80,7 +79,7 @@ def _(flag: bool):
else:
Eggs = 1
a = Eggs[42] # error: "Cannot subscript object of type `<class 'Eggs'> | Literal[1]` with no `__getitem__` method"
a = Eggs[42] # error: "Cannot subscript object of type `Literal[Eggs] | Literal[1]` with no `__getitem__` method"
# TODO: should _probably_ emit `str | Unknown`
reveal_type(a) # revealed: Unknown

View File

@@ -85,7 +85,7 @@ class A(tuple[int, str]): ...
# Runtime value: `(A, tuple, object)`
# TODO: Generics
reveal_type(A.__mro__) # revealed: tuple[<class 'A'>, @Todo(GenericAlias instance), <class 'object'>]
reveal_type(A.__mro__) # revealed: tuple[Literal[A], @Todo(GenericAlias instance), Literal[object]]
```
## `typing.Tuple`
@@ -116,6 +116,6 @@ from typing import Tuple
class C(Tuple): ...
# TODO: generic protocols
# revealed: tuple[<class 'C'>, <class 'tuple'>, <class 'Sequence'>, <class 'Reversible'>, <class 'Collection'>, <class 'Iterable'>, <class 'Container'>, @Todo(`Protocol[]` subscript), typing.Generic, <class 'object'>]
# revealed: tuple[Literal[C], Literal[tuple], Literal[Sequence], Literal[Reversible], Literal[Collection], Literal[Iterable], Literal[Container], @Todo(`Protocol[]` subscript), typing.Generic, Literal[object]]
reveal_type(C.__mro__)
```

View File

@@ -105,7 +105,7 @@ def explicit_unknown(x: Unknown, y: tuple[str, Unknown], z: Unknown = 1) -> None
```py
class C(Unknown): ...
# revealed: tuple[<class 'C'>, Unknown, <class 'object'>]
# revealed: tuple[Literal[C], Unknown, Literal[object]]
reveal_type(C.__mro__)
# error: "Special form `ty_extensions.Unknown` expected no type parameter"

View File

@@ -147,8 +147,8 @@ _: type[A, B]
```py
class Foo(type[int]): ...
# TODO: should be `tuple[<class 'Foo'>, <class 'type'>, <class 'object'>]
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, @Todo(GenericAlias instance), <class 'object'>]
# TODO: should be `tuple[Literal[Foo], Literal[type], Literal[object]]
reveal_type(Foo.__mro__) # revealed: tuple[Literal[Foo], @Todo(GenericAlias instance), Literal[object]]
```
## `@final` classes
@@ -169,6 +169,6 @@ from typing import final
class Foo: ...
def _(x: type[Foo], y: type[EllipsisType]):
reveal_type(x) # revealed: <class 'Foo'>
reveal_type(y) # revealed: <class 'EllipsisType'>
reveal_type(x) # revealed: Literal[Foo]
reveal_type(y) # revealed: Literal[EllipsisType]
```

View File

@@ -28,5 +28,5 @@ class C(Type): ...
# Runtime value: `(C, type, typing.Generic, object)`
# TODO: Add `Generic` to the MRO
reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, <class 'type'>, <class 'object'>]
reveal_type(C.__mro__) # revealed: tuple[Literal[C], Literal[type], Literal[object]]
```

View File

@@ -334,19 +334,10 @@ static_assert(is_subtype_of(TypeOf[typing], ModuleType))
### Slice literals
The type of a slice literal is currently inferred as `slice`, which is a generic type whose default
specialization includes `Any`. Slice literals therefore do not participate in the subtyping
relationship.
TODO: Infer a specialized type for the slice literal
```py
from ty_extensions import TypeOf, is_subtype_of, static_assert
static_assert(not is_subtype_of(TypeOf[1:2:3], slice))
# TODO: no error
# error: [static-assert-error]
static_assert(is_subtype_of(TypeOf[1:2:3], slice[int]))
static_assert(is_subtype_of(TypeOf[1:2:3], slice))
```
### Special forms

View File

@@ -52,25 +52,25 @@ class Yes:
class Sub(Yes): ...
class No: ...
# error: [unsupported-operator] "Unary operator `+` is unsupported for type `<class 'Yes'>`"
# error: [unsupported-operator] "Unary operator `+` is unsupported for type `Literal[Yes]`"
reveal_type(+Yes) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `-` is unsupported for type `<class 'Yes'>`"
# error: [unsupported-operator] "Unary operator `-` is unsupported for type `Literal[Yes]`"
reveal_type(-Yes) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `~` is unsupported for type `<class 'Yes'>`"
# error: [unsupported-operator] "Unary operator `~` is unsupported for type `Literal[Yes]`"
reveal_type(~Yes) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `+` is unsupported for type `<class 'Sub'>`"
# error: [unsupported-operator] "Unary operator `+` is unsupported for type `Literal[Sub]`"
reveal_type(+Sub) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `-` is unsupported for type `<class 'Sub'>`"
# error: [unsupported-operator] "Unary operator `-` is unsupported for type `Literal[Sub]`"
reveal_type(-Sub) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `~` is unsupported for type `<class 'Sub'>`"
# error: [unsupported-operator] "Unary operator `~` is unsupported for type `Literal[Sub]`"
reveal_type(~Sub) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `+` is unsupported for type `<class 'No'>`"
# error: [unsupported-operator] "Unary operator `+` is unsupported for type `Literal[No]`"
reveal_type(+No) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `-` is unsupported for type `<class 'No'>`"
# error: [unsupported-operator] "Unary operator `-` is unsupported for type `Literal[No]`"
reveal_type(-No) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `~` is unsupported for type `<class 'No'>`"
# error: [unsupported-operator] "Unary operator `~` is unsupported for type `Literal[No]`"
reveal_type(~No) # revealed: Unknown
```
@@ -160,10 +160,10 @@ reveal_type(+Sub) # revealed: bool
reveal_type(-Sub) # revealed: str
reveal_type(~Sub) # revealed: int
# error: [unsupported-operator] "Unary operator `+` is unsupported for type `<class 'No'>`"
# error: [unsupported-operator] "Unary operator `+` is unsupported for type `Literal[No]`"
reveal_type(+No) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `-` is unsupported for type `<class 'No'>`"
# error: [unsupported-operator] "Unary operator `-` is unsupported for type `Literal[No]`"
reveal_type(-No) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `~` is unsupported for type `<class 'No'>`"
# error: [unsupported-operator] "Unary operator `~` is unsupported for type `Literal[No]`"
reveal_type(~No) # revealed: Unknown
```

View File

@@ -9,6 +9,7 @@ discord.py # some kind of hang, only when multi-threaded?
freqtrade # cycle panics (try_metaclass_)
hydpy # cycle panics (try_metaclass_)
ibis # cycle panics (try_metaclass_)
manticore # stack overflow, see https://github.com/astral-sh/ruff/issues/17863
pandas # slow
pandas-stubs # cycle panics (try_metaclass_)
pandera # cycle panics (try_metaclass_)

View File

@@ -50,7 +50,6 @@ jinja
koda-validate
kopf
kornia
manticore
materialize
meson
mitmproxy

View File

@@ -16,7 +16,7 @@ pub trait Db: SourceDb + Upcast<dyn SourceDb> {
#[cfg(test)]
pub(crate) mod tests {
use std::sync::{Arc, Mutex};
use std::sync::Arc;
use crate::program::{Program, SearchPathSettings};
use crate::{default_lint_registry, ProgramSettings, PythonPlatform};
@@ -32,8 +32,6 @@ pub(crate) mod tests {
use ruff_db::{Db as SourceDb, Upcast};
use ruff_python_ast::PythonVersion;
type Events = Arc<Mutex<Vec<salsa::Event>>>;
#[salsa::db]
#[derive(Clone)]
pub(crate) struct TestDb {
@@ -41,34 +39,30 @@ pub(crate) mod tests {
files: Files,
system: TestSystem,
vendored: VendoredFileSystem,
events: Events,
events: Arc<std::sync::Mutex<Vec<salsa::Event>>>,
rule_selection: Arc<RuleSelection>,
}
impl TestDb {
pub(crate) fn new() -> Self {
let events = Events::default();
Self {
storage: salsa::Storage::new(Some(Box::new({
let events = events.clone();
move |event| {
tracing::trace!("event: {event:?}");
let mut events = events.lock().unwrap();
events.push(event);
}
}))),
storage: salsa::Storage::default(),
system: TestSystem::default(),
vendored: ty_vendored::file_system().clone(),
events,
events: Arc::default(),
files: Files::default(),
rule_selection: Arc::new(RuleSelection::from_registry(default_lint_registry())),
}
}
/// Takes the salsa events.
///
/// ## Panics
/// If there are any pending salsa snapshots.
pub(crate) fn take_salsa_events(&mut self) -> Vec<salsa::Event> {
let mut events = self.events.lock().unwrap();
let inner = Arc::get_mut(&mut self.events).expect("no pending salsa snapshots");
let events = inner.get_mut().unwrap();
std::mem::take(&mut *events)
}
@@ -135,7 +129,14 @@ pub(crate) mod tests {
}
#[salsa::db]
impl salsa::Database for TestDb {}
impl salsa::Database for TestDb {
fn salsa_event(&self, event: &dyn Fn() -> salsa::Event) {
let event = event();
tracing::trace!("event: {event:?}");
let mut events = self.events.lock().unwrap();
events.push(event);
}
}
pub(crate) struct TestDbBuilder<'a> {
/// Target Python version

View File

@@ -1,470 +0,0 @@
use rustc_hash::FxHashSet;
use ruff_db::files::File;
use ruff_db::parsed::parsed_module;
use ruff_python_ast::name::Name;
use ruff_python_ast::statement_visitor::{walk_stmt, StatementVisitor};
use ruff_python_ast::{self as ast};
use crate::semantic_index::ast_ids::{HasScopedExpressionId, HasScopedUseId};
use crate::semantic_index::symbol::ScopeId;
use crate::semantic_index::{global_scope, semantic_index, SemanticIndex};
use crate::symbol::{symbol_from_bindings, Boundness, Symbol};
use crate::types::{infer_expression_types, Truthiness};
use crate::{resolve_module, Db, ModuleName};
#[allow(clippy::ref_option)]
fn dunder_all_names_cycle_recover(
_db: &dyn Db,
_value: &Option<FxHashSet<Name>>,
_count: u32,
_file: File,
) -> salsa::CycleRecoveryAction<Option<FxHashSet<Name>>> {
salsa::CycleRecoveryAction::Iterate
}
fn dunder_all_names_cycle_initial(_db: &dyn Db, _file: File) -> Option<FxHashSet<Name>> {
None
}
/// Returns a set of names in the `__all__` variable for `file`, [`None`] if it is not defined or
/// if it contains invalid elements.
pub(crate) fn dunder_all_names(db: &dyn Db, file: File) -> Option<&FxHashSet<Name>> {
#[allow(clippy::ref_option)]
#[salsa::tracked(return_ref, cycle_fn=dunder_all_names_cycle_recover, cycle_initial=dunder_all_names_cycle_initial)]
fn dunder_all_names_impl(db: &dyn Db, file: File) -> Option<FxHashSet<Name>> {
let _span = tracing::trace_span!("dunder_all_names", file=?file.path(db)).entered();
let module = parsed_module(db.upcast(), file);
let index = semantic_index(db, file);
let mut collector = DunderAllNamesCollector::new(db, file, index);
collector.visit_body(module.suite());
collector.into_names()
}
dunder_all_names_impl(db, file).as_ref()
}
/// A visitor that collects the names in the `__all__` variable of a module.
struct DunderAllNamesCollector<'db> {
db: &'db dyn Db,
file: File,
/// The scope in which the `__all__` names are being collected from.
///
/// This is always going to be the global scope of the module.
scope: ScopeId<'db>,
/// The semantic index for the module.
index: &'db SemanticIndex<'db>,
/// The origin of the `__all__` variable in the current module, [`None`] if it is not defined.
origin: Option<DunderAllOrigin>,
/// A flag indicating whether the module uses unrecognized `__all__` idioms or there are any
/// invalid elements in `__all__`.
invalid: bool,
/// A set of names found in `__all__` for the current module.
names: FxHashSet<Name>,
}
impl<'db> DunderAllNamesCollector<'db> {
fn new(db: &'db dyn Db, file: File, index: &'db SemanticIndex<'db>) -> Self {
Self {
db,
file,
scope: global_scope(db, file),
index,
origin: None,
invalid: false,
names: FxHashSet::default(),
}
}
/// Updates the origin of `__all__` in the current module.
///
/// This will clear existing names if the origin is changed to mimic the behavior of overriding
/// `__all__` in the current module.
fn update_origin(&mut self, origin: DunderAllOrigin) {
if self.origin.is_some() {
self.names.clear();
}
self.origin = Some(origin);
}
/// Extends the current set of names with the names from the given expression which can be
/// either a list of names or a module's `__all__` variable.
///
/// Returns `true` if the expression is a valid list or module `__all__`, `false` otherwise.
fn extend_from_list_or_module(&mut self, expr: &ast::Expr) -> bool {
match expr {
// `__all__ += [...]`
// `__all__.extend([...])`
ast::Expr::List(ast::ExprList { elts, .. }) => self.add_names(elts),
// `__all__ += module.__all__`
// `__all__.extend(module.__all__)`
ast::Expr::Attribute(ast::ExprAttribute { value, attr, .. }) => {
if attr != "__all__" {
return false;
}
let Some(name_node) = value.as_name_expr() else {
return false;
};
let Symbol::Type(ty, Boundness::Bound) = symbol_from_bindings(
self.db,
self.index
.use_def_map(self.scope.file_scope_id(self.db))
.bindings_at_use(name_node.scoped_use_id(self.db, self.scope)),
) else {
return false;
};
let Some(module_literal) = ty.into_module_literal() else {
return false;
};
let Some(module_dunder_all_names) =
dunder_all_names(self.db, module_literal.module(self.db).file())
else {
// The module either does not have a `__all__` variable or it is invalid.
return false;
};
self.names.extend(module_dunder_all_names.iter().cloned());
true
}
_ => false,
}
}
/// Processes a call idiom for `__all__` and updates the set of names accordingly.
///
/// Returns `true` if the call idiom is recognized and valid, `false` otherwise.
fn process_call_idiom(
&mut self,
function_name: &ast::Identifier,
arguments: &ast::Arguments,
) -> bool {
if arguments.len() != 1 {
return false;
}
let Some(argument) = arguments.find_positional(0) else {
return false;
};
match function_name.as_str() {
// `__all__.extend([...])`
// `__all__.extend(module.__all__)`
"extend" => {
if !self.extend_from_list_or_module(argument) {
return false;
}
}
// `__all__.append(...)`
"append" => {
let Some(name) = create_name(argument) else {
return false;
};
self.names.insert(name);
}
// `__all__.remove(...)`
"remove" => {
let Some(name) = create_name(argument) else {
return false;
};
self.names.remove(&name);
}
_ => return false,
}
true
}
/// Returns the names in `__all__` from the module imported from the given `import_from`
/// statement.
///
/// Returns [`None`] if module resolution fails, invalid syntax, or if the module does not have
/// a `__all__` variable.
fn dunder_all_names_for_import_from(
&self,
import_from: &ast::StmtImportFrom,
) -> Option<&'db FxHashSet<Name>> {
let module_name =
ModuleName::from_import_statement(self.db, self.file, import_from).ok()?;
let module = resolve_module(self.db, &module_name)?;
dunder_all_names(self.db, module.file())
}
/// Evaluate the given expression and return its truthiness.
///
/// Returns [`None`] if the expression type doesn't implement `__bool__` correctly.
fn evaluate_test_expr(&self, expr: &ast::Expr) -> Option<Truthiness> {
infer_expression_types(self.db, self.index.expression(expr))
.expression_type(expr.scoped_expression_id(self.db, self.scope))
.try_bool(self.db)
.ok()
}
/// Add valid names to the set.
///
/// Returns `false` if any of the names are invalid.
fn add_names(&mut self, exprs: &[ast::Expr]) -> bool {
for expr in exprs {
let Some(name) = create_name(expr) else {
return false;
};
self.names.insert(name);
}
true
}
/// Consumes `self` and returns the collected set of names.
///
/// Returns [`None`] if `__all__` is not defined in the current module or if it contains
/// invalid elements.
fn into_names(self) -> Option<FxHashSet<Name>> {
if self.origin.is_none() {
None
} else if self.invalid {
tracing::debug!("Invalid `__all__` in `{}`", self.file.path(self.db));
None
} else {
Some(self.names)
}
}
}
impl<'db> StatementVisitor<'db> for DunderAllNamesCollector<'db> {
fn visit_stmt(&mut self, stmt: &'db ast::Stmt) {
if self.invalid {
return;
}
match stmt {
ast::Stmt::ImportFrom(import_from @ ast::StmtImportFrom { names, .. }) => {
for ast::Alias { name, asname, .. } in names {
// `from module import *` where `module` is a module with a top-level `__all__`
// variable that contains the "__all__" element.
if name == "*" {
// Here, we need to use the `dunder_all_names` query instead of the
// `exported_names` query because a `*`-import does not import the
// `__all__` attribute unless it is explicitly included in the `__all__` of
// the module.
let Some(all_names) = self.dunder_all_names_for_import_from(import_from)
else {
self.invalid = true;
continue;
};
if all_names.contains(&Name::new_static("__all__")) {
self.update_origin(DunderAllOrigin::StarImport);
self.names.extend(all_names.iter().cloned());
}
} else {
// `from module import __all__`
// `from module import __all__ as __all__`
if name != "__all__"
|| asname.as_ref().is_some_and(|asname| asname != "__all__")
{
continue;
}
// We could do the `__all__` lookup lazily in case it's not needed. This would
// happen if a `__all__` is imported from another module but then the module
// redefines it. For example:
//
// ```python
// from module import __all__ as __all__
//
// __all__ = ["a", "b"]
// ```
//
// I'm avoiding this for now because it doesn't seem likely to happen in
// practice.
let Some(all_names) = self.dunder_all_names_for_import_from(import_from)
else {
self.invalid = true;
continue;
};
self.update_origin(DunderAllOrigin::ExternalModule);
self.names.extend(all_names.iter().cloned());
}
}
}
ast::Stmt::Assign(ast::StmtAssign { targets, value, .. }) => {
let [target] = targets.as_slice() else {
return;
};
if !is_dunder_all(target) {
return;
}
match &**value {
// `__all__ = [...]`
// `__all__ = (...)`
ast::Expr::List(ast::ExprList { elts, .. })
| ast::Expr::Tuple(ast::ExprTuple { elts, .. }) => {
self.update_origin(DunderAllOrigin::CurrentModule);
if !self.add_names(elts) {
self.invalid = true;
}
}
_ => {
self.invalid = true;
}
}
}
ast::Stmt::AugAssign(ast::StmtAugAssign {
target,
op: ast::Operator::Add,
value,
..
}) => {
if self.origin.is_none() {
// We can't update `__all__` if it doesn't already exist.
return;
}
if !is_dunder_all(target) {
return;
}
if !self.extend_from_list_or_module(value) {
self.invalid = true;
}
}
ast::Stmt::AnnAssign(ast::StmtAnnAssign {
target,
value: Some(value),
..
}) => {
if !is_dunder_all(target) {
return;
}
match &**value {
// `__all__: list[str] = [...]`
// `__all__: tuple[str, ...] = (...)`
ast::Expr::List(ast::ExprList { elts, .. })
| ast::Expr::Tuple(ast::ExprTuple { elts, .. }) => {
self.update_origin(DunderAllOrigin::CurrentModule);
if !self.add_names(elts) {
self.invalid = true;
}
}
_ => {
self.invalid = true;
}
}
}
ast::Stmt::Expr(ast::StmtExpr { value: expr, .. }) => {
if self.origin.is_none() {
// We can't update `__all__` if it doesn't already exist.
return;
}
let Some(ast::ExprCall {
func, arguments, ..
}) = expr.as_call_expr()
else {
return;
};
let Some(ast::ExprAttribute {
value,
attr,
ctx: ast::ExprContext::Load,
..
}) = func.as_attribute_expr()
else {
return;
};
if !is_dunder_all(value) {
return;
}
if !self.process_call_idiom(attr, arguments) {
self.invalid = true;
}
}
ast::Stmt::If(ast::StmtIf {
test,
body,
elif_else_clauses,
..
}) => match self.evaluate_test_expr(test) {
Some(Truthiness::AlwaysTrue) => self.visit_body(body),
Some(Truthiness::AlwaysFalse) => {
for ast::ElifElseClause { test, body, .. } in elif_else_clauses {
if let Some(test) = test {
match self.evaluate_test_expr(test) {
Some(Truthiness::AlwaysTrue) => {
self.visit_body(body);
break;
}
Some(Truthiness::AlwaysFalse) => {}
Some(Truthiness::Ambiguous) | None => {
break;
}
}
} else {
self.visit_body(body);
}
}
}
Some(Truthiness::Ambiguous) | None => {}
},
ast::Stmt::For(..)
| ast::Stmt::While(..)
| ast::Stmt::With(..)
| ast::Stmt::Match(..)
| ast::Stmt::Try(..) => {
walk_stmt(self, stmt);
}
ast::Stmt::FunctionDef(..) | ast::Stmt::ClassDef(..) => {
// Avoid recursing into any nested scopes as `__all__` is only valid at the module
// level.
}
ast::Stmt::AugAssign(..)
| ast::Stmt::AnnAssign(..)
| ast::Stmt::Delete(..)
| ast::Stmt::Return(..)
| ast::Stmt::Raise(..)
| ast::Stmt::Assert(..)
| ast::Stmt::Import(..)
| ast::Stmt::Global(..)
| ast::Stmt::Nonlocal(..)
| ast::Stmt::TypeAlias(..)
| ast::Stmt::Pass(..)
| ast::Stmt::Break(..)
| ast::Stmt::Continue(..)
| ast::Stmt::IpyEscapeCommand(..) => {}
}
}
}
#[derive(Debug, Clone)]
enum DunderAllOrigin {
/// The `__all__` variable is defined in the current module.
CurrentModule,
/// The `__all__` variable is imported from another module.
ExternalModule,
/// The `__all__` variable is imported from a module via a `*`-import.
StarImport,
}
/// Checks if the given expression is a name expression for `__all__`.
fn is_dunder_all(expr: &ast::Expr) -> bool {
matches!(expr, ast::Expr::Name(ast::ExprName { id, .. }) if id == "__all__")
}
/// Create and return a [`Name`] from the given expression, [`None`] if it is an invalid expression
/// for a `__all__` element.
fn create_name(expr: &ast::Expr) -> Option<Name> {
Some(Name::new(expr.as_string_literal_expr()?.value.to_str()))
}

View File

@@ -14,7 +14,6 @@ pub use site_packages::SysPrefixPathOrigin;
pub mod ast_node_ref;
mod db;
mod dunder_all;
pub mod lint;
pub(crate) mod list;
mod module_name;

View File

@@ -2310,7 +2310,7 @@ where
walk_expr(self, expr);
}
ast::Expr::Yield(_) | ast::Expr::YieldFrom(_) => {
ast::Expr::Yield(_) => {
let scope = self.current_scope();
if self.scopes[scope].kind() == ScopeKind::Function {
self.generator_functions.insert(scope);

View File

@@ -178,13 +178,12 @@ use std::cmp::Ordering;
use ruff_index::{Idx, IndexVec};
use rustc_hash::FxHashMap;
use crate::dunder_all::dunder_all_names;
use crate::semantic_index::expression::Expression;
use crate::semantic_index::predicate::{
PatternPredicate, PatternPredicateKind, Predicate, PredicateNode, Predicates, ScopedPredicateId,
};
use crate::semantic_index::symbol_table;
use crate::symbol::{imported_symbol, RequiresExplicitReExport};
use crate::symbol::imported_symbol;
use crate::types::{infer_expression_type, Truthiness, Type};
use crate::Db;
@@ -656,27 +655,7 @@ impl VisibilityConstraints {
PredicateNode::StarImportPlaceholder(star_import) => {
let symbol_table = symbol_table(db, star_import.scope(db));
let symbol_name = symbol_table.symbol(star_import.symbol_id(db)).name();
let referenced_file = star_import.referenced_file(db);
let requires_explicit_reexport = match dunder_all_names(db, referenced_file) {
Some(all_names) => {
if all_names.contains(symbol_name) {
Some(RequiresExplicitReExport::No)
} else {
tracing::debug!(
"Symbol `{}` (via star import) not found in `__all__` of `{}`",
symbol_name,
referenced_file.path(db)
);
return Truthiness::AlwaysFalse;
}
}
None => None,
};
match imported_symbol(db, referenced_file, symbol_name, requires_explicit_reexport)
.symbol
{
match imported_symbol(db, star_import.referenced_file(db), symbol_name).symbol {
crate::symbol::Symbol::Type(_, crate::symbol::Boundness::Bound) => {
Truthiness::AlwaysTrue
}

View File

@@ -1,6 +1,5 @@
use ruff_db::files::File;
use crate::dunder_all::dunder_all_names;
use crate::module_resolver::file_to_module;
use crate::semantic_index::definition::Definition;
use crate::semantic_index::symbol::{ScopeId, ScopedSymbolId};
@@ -286,23 +285,11 @@ pub(crate) fn global_symbol<'db>(
}
/// Infers the public type of an imported symbol.
///
/// If `requires_explicit_reexport` is [`None`], it will be inferred from the file's source type.
/// For stub files, explicit re-export will be required, while for non-stub files, it will not.
pub(crate) fn imported_symbol<'db>(
db: &'db dyn Db,
file: File,
name: &str,
requires_explicit_reexport: Option<RequiresExplicitReExport>,
) -> SymbolAndQualifiers<'db> {
let requires_explicit_reexport = requires_explicit_reexport.unwrap_or_else(|| {
if file.is_stub(db.upcast()) {
RequiresExplicitReExport::Yes
} else {
RequiresExplicitReExport::No
}
});
// If it's not found in the global scope, check if it's present as an instance on
// `types.ModuleType` or `builtins.object`.
//
@@ -318,16 +305,13 @@ pub(crate) fn imported_symbol<'db>(
// ignore `__getattr__`. Typeshed has a fake `__getattr__` on `types.ModuleType` to help out with
// dynamic imports; we shouldn't use it for `ModuleLiteral` types where we know exactly which
// module we're dealing with.
symbol_impl(db, global_scope(db, file), name, requires_explicit_reexport).or_fall_back_to(
db,
|| {
if name == "__getattr__" {
Symbol::Unbound.into()
} else {
KnownClass::ModuleType.to_instance(db).member(db, name)
}
},
)
external_symbol_impl(db, file, name).or_fall_back_to(db, || {
if name == "__getattr__" {
Symbol::Unbound.into()
} else {
KnownClass::ModuleType.to_instance(db).member(db, name)
}
})
}
/// Lookup the type of `symbol` in the builtins namespace.
@@ -340,13 +324,7 @@ pub(crate) fn imported_symbol<'db>(
pub(crate) fn builtins_symbol<'db>(db: &'db dyn Db, symbol: &str) -> SymbolAndQualifiers<'db> {
resolve_module(db, &KnownModule::Builtins.name())
.map(|module| {
symbol_impl(
db,
global_scope(db, module.file()),
symbol,
RequiresExplicitReExport::Yes,
)
.or_fall_back_to(db, || {
external_symbol_impl(db, module.file(), symbol).or_fall_back_to(db, || {
// We're looking up in the builtins namespace and not the module, so we should
// do the normal lookup in `types.ModuleType` and not the special one as in
// `imported_symbol`.
@@ -365,7 +343,7 @@ pub(crate) fn known_module_symbol<'db>(
symbol: &str,
) -> SymbolAndQualifiers<'db> {
resolve_module(db, &known_module.name())
.map(|module| imported_symbol(db, module.file(), symbol, None))
.map(|module| imported_symbol(db, module.file(), symbol))
.unwrap_or_default()
}
@@ -724,7 +702,7 @@ fn symbol_from_bindings_impl<'db>(
let mut bindings_with_constraints = bindings_with_constraints.peekable();
let is_non_exported = |binding: Definition<'db>| {
requires_explicit_reexport.is_yes() && !is_reexported(db, binding)
requires_explicit_reexport.is_yes() && !binding.is_reexported(db)
};
let unbound_visibility_constraint = match bindings_with_constraints.peek() {
@@ -855,7 +833,7 @@ fn symbol_from_declarations_impl<'db>(
let mut declarations = declarations.peekable();
let is_non_exported = |declaration: Definition<'db>| {
requires_explicit_reexport.is_yes() && !is_reexported(db, declaration)
requires_explicit_reexport.is_yes() && !declaration.is_reexported(db)
};
let undeclared_visibility = match declarations.peek() {
@@ -933,27 +911,6 @@ fn symbol_from_declarations_impl<'db>(
}
}
// Returns `true` if the `definition` is re-exported.
//
// This will first check if the definition is using the "redundant alias" pattern like `import foo
// as foo` or `from foo import bar as bar`. If it's not, it will check whether the symbol is being
// exported via `__all__`.
fn is_reexported(db: &dyn Db, definition: Definition<'_>) -> bool {
// This information is computed by the semantic index builder.
if definition.is_reexported(db) {
return true;
}
// At this point, the definition should either be an `import` or `from ... import` statement.
// This is because the default value of `is_reexported` is `true` for any other kind of
// definition.
let Some(all_names) = dunder_all_names(db, definition.file(db)) else {
return false;
};
let table = symbol_table(db, definition.scope(db));
let symbol_name = table.symbol(definition.symbol(db)).name();
all_names.contains(symbol_name)
}
mod implicit_globals {
use ruff_python_ast as ast;
@@ -1058,8 +1015,26 @@ mod implicit_globals {
}
}
/// Implementation of looking up a module-global symbol as seen from outside the file (e.g. via
/// imports).
///
/// This will take into account whether the definition of the symbol is being explicitly
/// re-exported from a stub file or not.
fn external_symbol_impl<'db>(db: &'db dyn Db, file: File, name: &str) -> SymbolAndQualifiers<'db> {
symbol_impl(
db,
global_scope(db, file),
name,
if file.is_stub(db.upcast()) {
RequiresExplicitReExport::Yes
} else {
RequiresExplicitReExport::No
},
)
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub(crate) enum RequiresExplicitReExport {
enum RequiresExplicitReExport {
Yes,
No,
}

View File

@@ -1,4 +1,3 @@
use infer::enclosing_class_symbol;
use itertools::Either;
use std::slice::Iter;
@@ -851,6 +850,13 @@ impl<'db> Type<'db> {
}
}
pub const fn into_typevar(self) -> Option<TypeVarInstance<'db>> {
match self {
Type::TypeVar(typevar) => Some(typevar),
_ => None,
}
}
pub const fn is_boolean_literal(&self) -> bool {
matches!(self, Type::BooleanLiteral(..))
}
@@ -933,7 +939,6 @@ impl<'db> Type<'db> {
typevar.name(db).clone(),
typevar.definition(db),
Some(TypeVarBoundOrConstraints::UpperBound(bound.normalized(db))),
typevar.variance(db),
typevar.default_ty(db),
typevar.kind(db),
))
@@ -944,7 +949,6 @@ impl<'db> Type<'db> {
typevar.name(db).clone(),
typevar.definition(db),
Some(TypeVarBoundOrConstraints::Constraints(union.normalized(db))),
typevar.variance(db),
typevar.default_ty(db),
typevar.kind(db),
))
@@ -983,7 +987,7 @@ impl<'db> Type<'db> {
return false;
}
match (self, target) {
let result = match (self, target) {
// We should have handled these immediately above.
(Type::Dynamic(_), _) | (_, Type::Dynamic(_)) => {
unreachable!("Non-fully-static types do not participate in subtyping!")
@@ -998,41 +1002,15 @@ impl<'db> Type<'db> {
// Everything is a subtype of `object`.
(_, Type::NominalInstance(instance)) if instance.class().is_object(db) => true,
// In general, a TypeVar `T` is not a subtype of a type `S` unless one of the two conditions is satisfied:
// 1. `T` is a bound TypeVar and `T`'s upper bound is a subtype of `S`.
// TypeVars without an explicit upper bound are treated as having an implicit upper bound of `object`.
// 2. `T` is a constrained TypeVar and all of `T`'s constraints are subtypes of `S`.
//
// However, there is one exception to this general rule: for any given typevar `T`,
// `T` will always be a subtype of any union containing `T`.
// A similar rule applies in reverse to intersection types.
(Type::TypeVar(_), Type::Union(union)) if union.elements(db).contains(&self) => true,
(Type::Intersection(intersection), Type::TypeVar(_))
if intersection.positive(db).contains(&target) =>
{
true
}
(Type::Intersection(intersection), Type::TypeVar(_))
if intersection.negative(db).contains(&target) =>
{
false
}
(Type::Union(union), _) => union
.elements(db)
.iter()
.all(|&elem_ty| elem_ty.is_subtype_of(db, target)),
// A fully static typevar is a subtype of its upper bound, and to something similar to
// the union of its constraints. An unbound, unconstrained, fully static typevar has an
// implicit upper bound of `object` (which is handled above).
(Type::TypeVar(typevar), _) if typevar.bound_or_constraints(db).is_some() => {
match typevar.bound_or_constraints(db) {
None => unreachable!(),
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
bound.is_subtype_of(db, target)
}
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => constraints
.elements(db)
.iter()
.all(|constraint| constraint.is_subtype_of(db, target)),
}
}
(_, Type::Union(union)) => union
.elements(db)
.iter()
.any(|&elem_ty| self.is_subtype_of(db, elem_ty)),
// If the typevar is constrained, there must be multiple constraints, and the typevar
// might be specialized to any one of them. However, the constraints do not have to be
@@ -1047,16 +1025,6 @@ impl<'db> Type<'db> {
true
}
(Type::Union(union), _) => union
.elements(db)
.iter()
.all(|&elem_ty| elem_ty.is_subtype_of(db, target)),
(_, Type::Union(union)) => union
.elements(db)
.iter()
.any(|&elem_ty| self.is_subtype_of(db, elem_ty)),
// If both sides are intersections we need to handle the right side first
// (A & B & C) is a subtype of (A & B) because the left is a subtype of both A and B,
// but none of A, B, or C is a subtype of (A & B).
@@ -1299,7 +1267,33 @@ impl<'db> Type<'db> {
// Other than the special cases enumerated above, `Instance` types and typevars are
// never subtypes of any other variants
(Type::NominalInstance(_) | Type::TypeVar(_), _) => false,
};
// A fully static typevar is a subtype of its upper bound, and to something similar to
// the union of its constraints. An unbound, unconstrained, fully static typevar has an
// implicit upper bound of `object` (which is handled above).
//
// This must be handled here, only if the above failed, because a typevar `T: int` needs to
// both a subtype of `int` and a subtype of e.g. `T | None`, so we need to also try
// treating it as its bound, if treating it as itself failed.
if !result {
if let Some(typevar) = self.into_typevar() {
match typevar.bound_or_constraints(db) {
None => {}
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
return bound.is_subtype_of(db, target);
}
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => {
return constraints
.elements(db)
.iter()
.all(|constraint| constraint.is_subtype_of(db, target));
}
}
}
}
result
}
/// Return true if this type is [assignable to] type `target`.
@@ -1310,7 +1304,7 @@ impl<'db> Type<'db> {
return true;
}
match (self, target) {
let result = match (self, target) {
// Never can be assigned to any type.
(Type::Never, _) => true,
@@ -1322,41 +1316,17 @@ impl<'db> Type<'db> {
// TODO this special case might be removable once the below cases are comprehensive
(_, Type::NominalInstance(instance)) if instance.class().is_object(db) => true,
// In general, a TypeVar `T` is not assignable to a type `S` unless one of the two conditions is satisfied:
// 1. `T` is a bound TypeVar and `T`'s upper bound is assignable to `S`.
// TypeVars without an explicit upper bound are treated as having an implicit upper bound of `object`.
// 2. `T` is a constrained TypeVar and all of `T`'s constraints are assignable to `S`.
//
// However, there is one exception to this general rule: for any given typevar `T`,
// `T` will always be assignable to any union containing `T`.
// A similar rule applies in reverse to intersection types.
(Type::TypeVar(_), Type::Union(union)) if union.elements(db).contains(&self) => true,
(Type::Intersection(intersection), Type::TypeVar(_))
if intersection.positive(db).contains(&target) =>
{
true
}
(Type::Intersection(intersection), Type::TypeVar(_))
if intersection.negative(db).contains(&target) =>
{
false
}
// A union is assignable to a type T iff every element of the union is assignable to T.
(Type::Union(union), ty) => union
.elements(db)
.iter()
.all(|&elem_ty| elem_ty.is_assignable_to(db, ty)),
// A typevar is assignable to its upper bound, and to something similar to the union of
// its constraints. An unbound, unconstrained typevar has an implicit upper bound of
// `object` (which is handled above).
(Type::TypeVar(typevar), _) if typevar.bound_or_constraints(db).is_some() => {
match typevar.bound_or_constraints(db) {
None => unreachable!(),
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
bound.is_assignable_to(db, target)
}
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => constraints
.elements(db)
.iter()
.all(|constraint| constraint.is_assignable_to(db, target)),
}
}
// A type T is assignable to a union iff T is assignable to any element of the union.
(ty, Type::Union(union)) => union
.elements(db)
.iter()
.any(|&elem_ty| ty.is_assignable_to(db, elem_ty)),
// If the typevar is constrained, there must be multiple constraints, and the typevar
// might be specialized to any one of them. However, the constraints do not have to be
@@ -1371,18 +1341,6 @@ impl<'db> Type<'db> {
true
}
// A union is assignable to a type T iff every element of the union is assignable to T.
(Type::Union(union), ty) => union
.elements(db)
.iter()
.all(|&elem_ty| elem_ty.is_assignable_to(db, ty)),
// A type T is assignable to a union iff T is assignable to any element of the union.
(ty, Type::Union(union)) => union
.elements(db)
.iter()
.any(|&elem_ty| ty.is_assignable_to(db, elem_ty)),
// If both sides are intersections we need to handle the right side first
// (A & B & C) is assignable to (A & B) because the left is assignable to both A and B,
// but none of A, B, or C is assignable to (A & B).
@@ -1516,10 +1474,6 @@ impl<'db> Type<'db> {
false
}
(Type::SliceLiteral(_), _) => KnownClass::Slice
.to_instance(db)
.is_assignable_to(db, target),
(Type::FunctionLiteral(self_function_literal), Type::Callable(_)) => {
self_function_literal
.into_callable_type(db)
@@ -1541,7 +1495,33 @@ impl<'db> Type<'db> {
// TODO other types containing gradual forms
_ => self.is_subtype_of(db, target),
};
// A typevar is assignable to its upper bound, and to something similar to the union of
// its constraints. An unbound, unconstrained typevar has an implicit upper bound of
// `object` (which is handled above).
//
// This must be handled here, only if the above failed, because a typevar `T: int` needs to
// both assignable to `int` and to e.g. `T | None`, so we need to also try treating it as
// its bound, if treating it as itself failed.
if !result {
if let Some(typevar) = self.into_typevar() {
match typevar.bound_or_constraints(db) {
None => {}
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
return bound.is_assignable_to(db, target);
}
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => {
return constraints
.elements(db)
.iter()
.all(|constraint| constraint.is_assignable_to(db, target));
}
}
}
}
result
}
/// Return true if this type is [equivalent to] type `other`.
@@ -1559,6 +1539,11 @@ impl<'db> Type<'db> {
}
(Type::Tuple(left), Type::Tuple(right)) => left.is_equivalent_to(db, right),
(Type::Callable(left), Type::Callable(right)) => left.is_equivalent_to(db, right),
// A fully static typevar is always equivalent to itself, and not any other typevar,
// since there is no guarantee that they will be specialized to the same type. (This is
// true even if both typevars are bounded by the same final class, since you can
// specialize the typevars to `Never` in addition to that final class.)
(Type::TypeVar(first), Type::TypeVar(second)) => first == second,
(Type::NominalInstance(left), Type::NominalInstance(right)) => {
left.is_equivalent_to(db, right)
}
@@ -1601,6 +1586,12 @@ impl<'db> Type<'db> {
}
}
// A typevar is always gradually equivalent to itself, and not to any other typevar,
// since there is no guarantee that they will be specialized to the same type. (This is
// true even if both typevars are bounded by the same final class, since you can
// specialize the typevars to `Never` in addition to that final class.)
(Type::TypeVar(first), Type::TypeVar(second)) => first == second,
(Type::NominalInstance(first), Type::NominalInstance(second)) => {
first.is_gradual_equivalent_to(db, second)
}
@@ -1648,13 +1639,6 @@ impl<'db> Type<'db> {
false
}
(tvar @ Type::TypeVar(_), Type::Intersection(intersection))
| (Type::Intersection(intersection), tvar @ Type::TypeVar(_))
if intersection.negative(db).contains(&tvar) =>
{
true
}
// An unbounded typevar is never disjoint from any other type, since it might be
// specialized to any type. A bounded typevar is not disjoint from its bound, and is
// only disjoint from other types if its bound is. A constrained typevar is disjoint
@@ -4679,7 +4663,6 @@ impl<'db> Type<'db> {
pub fn in_type_expression(
&self,
db: &'db dyn Db,
scope_id: ScopeId,
) -> Result<Type<'db>, InvalidTypeExpressionError<'db>> {
match self {
// Special cases for `float` and `complex`
@@ -4764,40 +4747,7 @@ impl<'db> Type<'db> {
// TODO: Use an opt-in rule for a bare `Callable`
KnownInstanceType::Callable => Ok(Type::Callable(CallableType::unknown(db))),
KnownInstanceType::TypingSelf => {
let index = semantic_index(db, scope_id.file(db));
let Some(class_ty) = enclosing_class_symbol(db, index, scope_id) else {
return Err(InvalidTypeExpressionError {
fallback_type: Type::unknown(),
invalid_expressions: smallvec::smallvec![
InvalidTypeExpression::InvalidType(*self)
],
});
};
let Some(TypeDefinition::Class(class_def)) = class_ty.definition(db) else {
debug_assert!(
false,
"enclosing_class_symbol must return a type with class definition"
);
return Ok(Type::unknown());
};
let Some(instance) = class_ty.to_instance(db) else {
debug_assert!(
false,
"enclosing_class_symbol must return type that can be instantiated"
);
return Ok(Type::unknown());
};
Ok(Type::TypeVar(TypeVarInstance::new(
db,
ast::name::Name::new("Self"),
class_def,
Some(TypeVarBoundOrConstraints::UpperBound(instance)),
TypeVarVariance::Invariant,
None,
TypeVarKind::Legacy,
)))
}
KnownInstanceType::TypingSelf => Ok(todo_type!("Support for `typing.Self`")),
KnownInstanceType::TypeAlias => Ok(todo_type!("Support for `typing.TypeAlias`")),
KnownInstanceType::TypedDict => Ok(todo_type!("Support for `typing.TypedDict`")),
@@ -4864,7 +4814,7 @@ impl<'db> Type<'db> {
let mut builder = UnionBuilder::new(db);
let mut invalid_expressions = smallvec::SmallVec::default();
for element in union.elements(db) {
match element.in_type_expression(db, scope_id) {
match element.in_type_expression(db) {
Ok(type_expr) => builder = builder.add(type_expr),
Err(InvalidTypeExpressionError {
fallback_type,
@@ -5688,9 +5638,6 @@ pub struct TypeVarInstance<'db> {
/// The upper bound or constraint on the type of this TypeVar
bound_or_constraints: Option<TypeVarBoundOrConstraints<'db>>,
/// The variance of the TypeVar
variance: TypeVarVariance,
/// The default type for this TypeVar
default_ty: Option<Type<'db>>,
@@ -5719,15 +5666,7 @@ impl<'db> TypeVarInstance<'db> {
}
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, salsa::Update)]
pub enum TypeVarVariance {
Invariant,
Covariant,
Contravariant,
Bivariant,
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, salsa::Update)]
#[derive(Clone, Debug, Hash, PartialEq, Eq, salsa::Update)]
pub enum TypeVarBoundOrConstraints<'db> {
UpperBound(Type<'db>),
Constraints(UnionType<'db>),
@@ -7038,8 +6977,6 @@ pub enum KnownFunction {
IsSingleValued,
/// `ty_extensions.generic_context`
GenericContext,
/// `ty_extensions.dunder_all_names`
DunderAllNames,
}
impl KnownFunction {
@@ -7096,7 +7033,6 @@ impl KnownFunction {
| Self::IsSingleton
| Self::IsSubtypeOf
| Self::GenericContext
| Self::DunderAllNames
| Self::StaticAssert => module.is_ty_extensions(),
}
}
@@ -7404,7 +7340,7 @@ impl<'db> ModuleLiteralType<'db> {
}
}
imported_symbol(db, self.module(db).file(), name, None).symbol
imported_symbol(db, self.module(db).file(), name).symbol
}
}
@@ -8493,7 +8429,6 @@ pub(crate) mod tests {
KnownFunction::IsSingleton
| KnownFunction::IsSubtypeOf
| KnownFunction::GenericContext
| KnownFunction::DunderAllNames
| KnownFunction::StaticAssert
| KnownFunction::IsFullyStatic
| KnownFunction::IsDisjointFrom

View File

@@ -10,7 +10,6 @@ use super::{
InferContext, Signature, Signatures, Type,
};
use crate::db::Db;
use crate::dunder_all::dunder_all_names;
use crate::symbol::{Boundness, Symbol};
use crate::types::diagnostic::{
CALL_NON_CALLABLE, CONFLICTING_ARGUMENT_FORMS, INVALID_ARGUMENT_TYPE, MISSING_ARGUMENT,
@@ -21,8 +20,8 @@ use crate::types::generics::{Specialization, SpecializationBuilder, Specializati
use crate::types::signatures::{Parameter, ParameterForm};
use crate::types::{
todo_type, BoundMethodType, DataclassParams, DataclassTransformerParams, FunctionDecorators,
FunctionType, KnownClass, KnownFunction, KnownInstanceType, MethodWrapperKind,
PropertyInstanceType, TupleType, UnionType, WrapperDescriptorKind,
KnownClass, KnownFunction, KnownInstanceType, MethodWrapperKind, PropertyInstanceType,
TupleType, UnionType, WrapperDescriptorKind,
};
use ruff_db::diagnostic::{Annotation, Severity, SubDiagnostic};
use ruff_python_ast as ast;
@@ -586,30 +585,6 @@ impl<'db> Bindings<'db> {
}
}
Some(KnownFunction::DunderAllNames) => {
if let [Some(ty)] = overload.parameter_types() {
overload.set_return_type(match ty {
Type::ModuleLiteral(module_literal) => {
match dunder_all_names(db, module_literal.module(db).file())
{
Some(names) => {
let mut names = names.iter().collect::<Vec<_>>();
names.sort();
TupleType::from_elements(
db,
names.iter().map(|name| {
Type::string_literal(db, name.as_str())
}),
)
}
None => Type::none(db),
}
}
_ => Type::none(db),
});
}
}
Some(KnownFunction::Len) => {
if let [Some(first_arg)] = overload.parameter_types() {
if let Some(len_ty) = first_arg.len(db) {
@@ -647,7 +622,7 @@ impl<'db> Bindings<'db> {
db,
protocol_class
.interface(db)
.members(db)
.members()
.map(|member| Type::string_literal(db, member.name()))
.collect::<Box<[Type<'db>]>>(),
)));
@@ -795,50 +770,29 @@ impl<'db> Bindings<'db> {
}
_ => {
let mut handle_dataclass_transformer_params =
|function_type: &FunctionType| {
if let Some(params) =
function_type.dataclass_transformer_params(db)
{
// This is a call to a custom function that was decorated with `@dataclass_transformer`.
// If this function was called with a keyword argument like `order=False`, we extract
// the argument type and overwrite the corresponding flag in `dataclass_params` after
// constructing them from the `dataclass_transformer`-parameter defaults.
if let Some(params) = function_type.dataclass_transformer_params(db) {
// This is a call to a custom function that was decorated with `@dataclass_transformer`.
// If this function was called with a keyword argument like `order=False`, we extract
// the argument type and overwrite the corresponding flag in `dataclass_params` after
// constructing them from the `dataclass_transformer`-parameter defaults.
let mut dataclass_params = DataclassParams::from(params);
let mut dataclass_params = DataclassParams::from(params);
if let Some(Some(Type::BooleanLiteral(order))) =
callable_signature.iter().nth(overload_index).and_then(
|signature| {
let (idx, _) = signature
.parameters()
.keyword_by_name("order")?;
overload.parameter_types().get(idx)
},
)
{
dataclass_params.set(DataclassParams::ORDER, *order);
}
overload.set_return_type(Type::DataclassDecorator(
dataclass_params,
));
}
};
// Ideally, either the implementation, or exactly one of the overloads
// of the function can have the dataclass_transform decorator applied.
// However, we do not yet enforce this, and in the case of multiple
// applications of the decorator, we will only consider the last one
// for the return value, since the prior ones will be over-written.
if let Some(overloaded) = function_type.to_overloaded(db) {
overloaded
.overloads
if let Some(Some(Type::BooleanLiteral(order))) = callable_signature
.iter()
.for_each(&mut handle_dataclass_transformer_params);
}
.nth(overload_index)
.and_then(|signature| {
let (idx, _) =
signature.parameters().keyword_by_name("order")?;
overload.parameter_types().get(idx)
})
{
dataclass_params.set(DataclassParams::ORDER, *order);
}
handle_dataclass_transformer_params(&function_type);
overload
.set_return_type(Type::DataclassDecorator(dataclass_params));
}
}
},

View File

@@ -290,82 +290,109 @@ impl<'db> ClassType<'db> {
})
}
/// If `self` and `other` are generic aliases of the same generic class, returns their
/// corresponding specializations.
fn compatible_specializations(
self,
db: &'db dyn Db,
other: ClassType<'db>,
) -> Option<(Specialization<'db>, Specialization<'db>)> {
match (self, other) {
(ClassType::Generic(self_generic), ClassType::Generic(other_generic)) => {
if self_generic.origin(db) == other_generic.origin(db) {
Some((
self_generic.specialization(db),
other_generic.specialization(db),
))
} else {
None
}
}
_ => None,
}
}
/// Return `true` if `other` is present in this class's MRO.
pub(super) fn is_subclass_of(self, db: &'db dyn Db, other: ClassType<'db>) -> bool {
self.iter_mro(db).any(|base| {
match base {
// `is_subclass_of` is checking the subtype relation, in which gradual types do not
// participate.
ClassBase::Dynamic(_) => false,
// `is_subclass_of` is checking the subtype relation, in which gradual types do not
// participate, so we should not return `True` if we find `Any/Unknown` in the MRO.
if self.iter_mro(db).contains(&ClassBase::Class(other)) {
return true;
}
// Protocol and Generic are not represented by a ClassType.
ClassBase::Protocol | ClassBase::Generic(_) => false,
ClassBase::Class(base) => match (base, other) {
(ClassType::NonGeneric(base), ClassType::NonGeneric(other)) => base == other,
(ClassType::Generic(base), ClassType::Generic(other)) => {
base.origin(db) == other.origin(db)
&& base
.specialization(db)
.is_subtype_of(db, other.specialization(db))
}
(ClassType::Generic(_), ClassType::NonGeneric(_))
| (ClassType::NonGeneric(_), ClassType::Generic(_)) => false,
},
// `self` is a subclass of `other` if they are both generic aliases of the same generic
// class, and their specializations are compatible, taking into account the variance of the
// class's typevars.
if let Some((self_specialization, other_specialization)) =
self.compatible_specializations(db, other)
{
if self_specialization.is_subtype_of(db, other_specialization) {
return true;
}
})
}
false
}
pub(super) fn is_equivalent_to(self, db: &'db dyn Db, other: ClassType<'db>) -> bool {
match (self, other) {
(ClassType::NonGeneric(this), ClassType::NonGeneric(other)) => this == other,
(ClassType::NonGeneric(_), _) | (_, ClassType::NonGeneric(_)) => false,
if self == other {
return true;
}
(ClassType::Generic(this), ClassType::Generic(other)) => {
this.origin(db) == other.origin(db)
&& this
.specialization(db)
.is_equivalent_to(db, other.specialization(db))
// `self` is equivalent to `other` if they are both generic aliases of the same generic
// class, and their specializations are compatible, taking into account the variance of the
// class's typevars.
if let Some((self_specialization, other_specialization)) =
self.compatible_specializations(db, other)
{
if self_specialization.is_equivalent_to(db, other_specialization) {
return true;
}
}
false
}
pub(super) fn is_assignable_to(self, db: &'db dyn Db, other: ClassType<'db>) -> bool {
self.iter_mro(db).any(|base| {
match base {
ClassBase::Dynamic(DynamicType::Any | DynamicType::Unknown) => !other.is_final(db),
ClassBase::Dynamic(_) => false,
if self.is_subclass_of(db, other) {
return true;
}
// Protocol and Generic are not represented by a ClassType.
ClassBase::Protocol | ClassBase::Generic(_) => false,
ClassBase::Class(base) => match (base, other) {
(ClassType::NonGeneric(base), ClassType::NonGeneric(other)) => base == other,
(ClassType::Generic(base), ClassType::Generic(other)) => {
base.origin(db) == other.origin(db)
&& base
.specialization(db)
.is_assignable_to(db, other.specialization(db))
}
(ClassType::Generic(_), ClassType::NonGeneric(_))
| (ClassType::NonGeneric(_), ClassType::Generic(_)) => false,
},
// `self` is assignable to `other` if they are both generic aliases of the same generic
// class, and their specializations are compatible, taking into account the variance of the
// class's typevars.
if let Some((self_specialization, other_specialization)) =
self.compatible_specializations(db, other)
{
if self_specialization.is_assignable_to(db, other_specialization) {
return true;
}
})
}
if self.is_subclass_of_any_or_unknown(db) && !other.is_final(db) {
return true;
}
false
}
pub(super) fn is_gradual_equivalent_to(self, db: &'db dyn Db, other: ClassType<'db>) -> bool {
match (self, other) {
(ClassType::NonGeneric(this), ClassType::NonGeneric(other)) => this == other,
(ClassType::NonGeneric(_), _) | (_, ClassType::NonGeneric(_)) => false,
if self == other {
return true;
}
(ClassType::Generic(this), ClassType::Generic(other)) => {
this.origin(db) == other.origin(db)
&& this
.specialization(db)
.is_gradual_equivalent_to(db, other.specialization(db))
// `self` is equivalent to `other` if they are both generic aliases of the same generic
// class, and their specializations are compatible, taking into account the variance of the
// class's typevars.
if let Some((self_specialization, other_specialization)) =
self.compatible_specializations(db, other)
{
if self_specialization.is_gradual_equivalent_to(db, other_specialization) {
return true;
}
}
false
}
/// Return the metaclass of this class, or `type[Unknown]` if the metaclass cannot be inferred.
@@ -1772,32 +1799,26 @@ impl<'db> ClassLiteral<'db> {
}
}
/// Returns a [`Span`] with the range of the class's header.
///
/// See [`Self::header_range`] for more details.
pub(super) fn header_span(self, db: &'db dyn Db) -> Span {
Span::from(self.file(db)).with_range(self.header_range(db))
}
/// Returns the range of the class's "header": the class name
/// Returns the [`Span`] of the class's "header": the class name
/// and any arguments passed to the `class` statement. E.g.
///
/// ```ignore
/// class Foo(Bar, metaclass=Baz): ...
/// ^^^^^^^^^^^^^^^^^^^^^^^
/// ```
pub(super) fn header_range(self, db: &'db dyn Db) -> TextRange {
pub(super) fn header_span(self, db: &'db dyn Db) -> Span {
let class_scope = self.body_scope(db);
let class_node = class_scope.node(db).expect_class();
let class_name = &class_node.name;
TextRange::new(
let header_range = TextRange::new(
class_name.start(),
class_node
.arguments
.as_deref()
.map(Ranged::end)
.unwrap_or_else(|| class_name.end()),
)
);
Span::from(class_scope.file(db)).with_range(header_range)
}
}

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