Compare commits
5 Commits
david/excl
...
alex/submo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c86b40ee1 | ||
|
|
0c6d652b5f | ||
|
|
03fe560164 | ||
|
|
68343e7edf | ||
|
|
a57e291311 |
@@ -37,7 +37,8 @@ fn config_override_python_version() -> anyhow::Result<()> {
|
||||
5 | print(sys.last_exc)
|
||||
| ^^^^^^^^^^^^
|
||||
|
|
||||
info: Python 3.11 was assumed when accessing `last_exc`
|
||||
info: The member may be available on other Python versions or platforms
|
||||
info: Python 3.11 was assumed when resolving the `last_exc` attribute
|
||||
--> pyproject.toml:3:18
|
||||
|
|
||||
2 | [tool.ty.environment]
|
||||
@@ -1179,6 +1180,8 @@ fn defaults_to_a_new_python_version() -> anyhow::Result<()> {
|
||||
import os
|
||||
|
||||
os.grantpt(1) # only available on unix, Python 3.13 or newer
|
||||
|
||||
from typing import LiteralString # added in Python 3.11
|
||||
"#,
|
||||
),
|
||||
])?;
|
||||
@@ -1194,8 +1197,11 @@ fn defaults_to_a_new_python_version() -> anyhow::Result<()> {
|
||||
3 |
|
||||
4 | os.grantpt(1) # only available on unix, Python 3.13 or newer
|
||||
| ^^^^^^^^^^
|
||||
5 |
|
||||
6 | from typing import LiteralString # added in Python 3.11
|
||||
|
|
||||
info: Python 3.10 was assumed when accessing `grantpt`
|
||||
info: The member may be available on other Python versions or platforms
|
||||
info: Python 3.10 was assumed when resolving the `grantpt` attribute
|
||||
--> ty.toml:3:18
|
||||
|
|
||||
2 | [environment]
|
||||
@@ -1205,7 +1211,26 @@ fn defaults_to_a_new_python_version() -> anyhow::Result<()> {
|
||||
|
|
||||
info: rule `unresolved-attribute` is enabled by default
|
||||
|
||||
Found 1 diagnostic
|
||||
error[unresolved-import]: Module `typing` has no member `LiteralString`
|
||||
--> main.py:6:20
|
||||
|
|
||||
4 | os.grantpt(1) # only available on unix, Python 3.13 or newer
|
||||
5 |
|
||||
6 | from typing import LiteralString # added in Python 3.11
|
||||
| ^^^^^^^^^^^^^
|
||||
|
|
||||
info: The member may be available on other Python versions or platforms
|
||||
info: Python 3.10 was assumed when resolving imports
|
||||
--> ty.toml:3:18
|
||||
|
|
||||
2 | [environment]
|
||||
3 | python-version = "3.10"
|
||||
| ^^^^^^ Python 3.10 assumed due to this configuration setting
|
||||
4 | python-platform = "linux"
|
||||
|
|
||||
info: rule `unresolved-import` is enabled by default
|
||||
|
||||
Found 2 diagnostics
|
||||
|
||||
----- stderr -----
|
||||
"#);
|
||||
@@ -1225,6 +1250,8 @@ fn defaults_to_a_new_python_version() -> anyhow::Result<()> {
|
||||
import os
|
||||
|
||||
os.grantpt(1) # only available on unix, Python 3.13 or newer
|
||||
|
||||
from typing import LiteralString # added in Python 3.11
|
||||
"#,
|
||||
),
|
||||
])?;
|
||||
|
||||
@@ -18,11 +18,11 @@ numpy-array,main.py,1,1
|
||||
object-attr-instance-methods,main.py,0,1
|
||||
object-attr-instance-methods,main.py,1,1
|
||||
pass-keyword-completion,main.py,0,1
|
||||
raise-uses-base-exception,main.py,0,2
|
||||
raise-uses-base-exception,main.py,0,1
|
||||
scope-existing-over-new-import,main.py,0,1
|
||||
scope-prioritize-closer,main.py,0,2
|
||||
scope-simple-long-identifier,main.py,0,1
|
||||
tstring-completions,main.py,0,1
|
||||
ty-extensions-lower-stdlib,main.py,0,8
|
||||
type-var-typing-over-ast,main.py,0,3
|
||||
type-var-typing-over-ast,main.py,1,279
|
||||
type-var-typing-over-ast,main.py,1,278
|
||||
|
||||
|
@@ -9,9 +9,10 @@ use ruff_python_ast::name::Name;
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_parser::{Token, TokenAt, TokenKind, Tokens};
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
||||
use ty_python_semantic::types::UnionType;
|
||||
use ty_python_semantic::{
|
||||
Completion as SemanticCompletion, ModuleName, NameKind, SemanticModel,
|
||||
types::{CycleDetector, Type},
|
||||
Completion as SemanticCompletion, KnownModule, ModuleName, NameKind, SemanticModel,
|
||||
types::{CycleDetector, KnownClass, Type},
|
||||
};
|
||||
|
||||
use crate::docstring::Docstring;
|
||||
@@ -82,6 +83,31 @@ impl<'db> Completions<'db> {
|
||||
fn force_add(&mut self, completion: Completion<'db>) {
|
||||
self.items.push(completion);
|
||||
}
|
||||
|
||||
/// Tags completions with whether they are known to be usable in
|
||||
/// a `raise` context.
|
||||
///
|
||||
/// It's possible that some completions are usable in a `raise`
|
||||
/// but aren't marked by this method. That is, false negatives are
|
||||
/// possible but false positives are not.
|
||||
fn tag_raisable(&mut self) {
|
||||
let raisable_type = UnionType::from_elements(
|
||||
self.db,
|
||||
[
|
||||
KnownClass::BaseException.to_subclass_of(self.db),
|
||||
KnownClass::BaseException.to_instance(self.db),
|
||||
],
|
||||
);
|
||||
for item in &mut self.items {
|
||||
let Some(ty) = item.ty else { continue };
|
||||
item.is_definitively_raisable = ty.is_assignable_to(self.db, raisable_type);
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes any completion that doesn't satisfy the given predicate.
|
||||
fn retain(&mut self, predicate: impl FnMut(&Completion<'_>) -> bool) {
|
||||
self.items.retain(predicate);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> Extend<SemanticCompletion<'db>> for Completions<'db> {
|
||||
@@ -153,6 +179,13 @@ pub struct Completion<'db> {
|
||||
/// Whether this item only exists for type checking purposes and
|
||||
/// will be missing at runtime
|
||||
pub is_type_check_only: bool,
|
||||
/// Whether this item can definitively be used in a `raise` context.
|
||||
///
|
||||
/// Note that this may not always be computed. (i.e., Only computed
|
||||
/// when we are in a `raise` context.) And also note that if this
|
||||
/// is `true`, then it's definitively usable in `raise`, but if
|
||||
/// it's `false`, it _may_ still be usable in `raise`.
|
||||
pub is_definitively_raisable: bool,
|
||||
/// The documentation associated with this item, if
|
||||
/// available.
|
||||
pub documentation: Option<Docstring>,
|
||||
@@ -177,6 +210,7 @@ impl<'db> Completion<'db> {
|
||||
import: None,
|
||||
builtin: semantic.builtin,
|
||||
is_type_check_only,
|
||||
is_definitively_raisable: false,
|
||||
documentation,
|
||||
}
|
||||
}
|
||||
@@ -257,6 +291,7 @@ impl<'db> Completion<'db> {
|
||||
import: None,
|
||||
builtin: false,
|
||||
is_type_check_only: false,
|
||||
is_definitively_raisable: false,
|
||||
documentation: None,
|
||||
}
|
||||
}
|
||||
@@ -271,6 +306,7 @@ impl<'db> Completion<'db> {
|
||||
import: None,
|
||||
builtin: true,
|
||||
is_type_check_only: false,
|
||||
is_definitively_raisable: false,
|
||||
documentation: None,
|
||||
}
|
||||
}
|
||||
@@ -364,6 +400,20 @@ pub fn completion<'db>(
|
||||
}
|
||||
}
|
||||
|
||||
if is_raising_exception(tokens) {
|
||||
completions.tag_raisable();
|
||||
|
||||
// As a special case, and because it's a common footgun, we
|
||||
// specifically disallow `NotImplemented` in this context.
|
||||
// `NotImplementedError` should be used instead. So if we can
|
||||
// definitively detect `NotImplemented`, then we can safely
|
||||
// omit it from suggestions.
|
||||
completions.retain(|item| {
|
||||
let Some(ty) = item.ty else { return true };
|
||||
!ty.is_notimplemented(db)
|
||||
});
|
||||
}
|
||||
|
||||
completions.into_completions()
|
||||
}
|
||||
|
||||
@@ -427,7 +477,8 @@ fn add_unimported_completions<'db>(
|
||||
let members = importer.members_in_scope_at(scoped.node, scoped.node.start());
|
||||
|
||||
for symbol in all_symbols(db, &completions.query) {
|
||||
if symbol.module.file(db) == Some(file) {
|
||||
if symbol.module.file(db) == Some(file) || symbol.module.is_known(db, KnownModule::Builtins)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -450,6 +501,7 @@ fn add_unimported_completions<'db>(
|
||||
builtin: false,
|
||||
// TODO: `is_type_check_only` requires inferring the type of the symbol
|
||||
is_type_check_only: false,
|
||||
is_definitively_raisable: false,
|
||||
documentation: None,
|
||||
});
|
||||
}
|
||||
@@ -1358,6 +1410,30 @@ fn is_in_variable_binding(parsed: &ParsedModuleRef, offset: TextSize, typed: Opt
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns true when the cursor is after a `raise` keyword.
|
||||
fn is_raising_exception(tokens: &[Token]) -> bool {
|
||||
/// The maximum number of tokens we're willing to
|
||||
/// look-behind to find a `raise` keyword.
|
||||
const LIMIT: usize = 10;
|
||||
|
||||
// This only looks for things like `raise foo.bar.baz.qu<CURSOR>`.
|
||||
// Technically, any kind of expression is allowed after `raise`.
|
||||
// But we may not always want to treat it specially. So we're
|
||||
// rather conservative about what we consider "raising an
|
||||
// exception" to be for the purposes of completions. The failure
|
||||
// mode here is that we may wind up suggesting things that
|
||||
// shouldn't be raised. The benefit is that when this heuristic
|
||||
// does work, we won't suggest things that shouldn't be raised.
|
||||
for token in tokens.iter().rev().take(LIMIT) {
|
||||
match token.kind() {
|
||||
TokenKind::Name | TokenKind::Dot => continue,
|
||||
TokenKind::Raise => return true,
|
||||
_ => return false,
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Order completions according to the following rules:
|
||||
///
|
||||
/// 1) Names with no underscore prefix
|
||||
@@ -1370,8 +1446,16 @@ fn is_in_variable_binding(parsed: &ParsedModuleRef, offset: TextSize, typed: Opt
|
||||
/// This has the effect of putting all dunder attributes after "normal"
|
||||
/// attributes, and all single-underscore attributes after dunder attributes.
|
||||
fn compare_suggestions(c1: &Completion, c2: &Completion) -> Ordering {
|
||||
fn key<'a>(completion: &'a Completion) -> (bool, bool, bool, NameKind, bool, &'a Name) {
|
||||
fn key<'a>(completion: &'a Completion) -> (bool, bool, bool, bool, NameKind, bool, &'a Name) {
|
||||
(
|
||||
// This is only true when we are both in a `raise` context
|
||||
// *and* we know this suggestion is definitively usable
|
||||
// in a `raise` context. So we should sort these before
|
||||
// anything else.
|
||||
!completion.is_definitively_raisable,
|
||||
// When `None`, a completion is for something in the
|
||||
// current module, which we should generally prefer over
|
||||
// something from outside the module.
|
||||
completion.module_name.is_some(),
|
||||
// At time of writing (2025-11-11), keyword completions
|
||||
// are classified as builtins, which makes them sort after
|
||||
|
||||
@@ -2711,10 +2711,10 @@ We give special diagnostics for this common case too:
|
||||
import foo
|
||||
import baz
|
||||
|
||||
# error: [unresolved-attribute]
|
||||
reveal_type(foo.bar) # revealed: Unknown
|
||||
# error: [unresolved-attribute]
|
||||
reveal_type(baz.bar) # revealed: Unknown
|
||||
# error: [possibly-missing-attribute]
|
||||
reveal_type(foo.bar) # revealed: <module 'foo.bar'>
|
||||
# error: [possibly-missing-attribute]
|
||||
reveal_type(baz.bar) # revealed: <module 'baz.bar'>
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
@@ -124,6 +124,10 @@ def match_singletons_error(obj: Literal[1, "a"] | None):
|
||||
case None:
|
||||
pass
|
||||
case _ as obj:
|
||||
# TODO: We should emit an error here: `Literal["a"]` is not `Never`.
|
||||
# TODO: We should emit an error here, but the message should
|
||||
# show the type `Literal["a"]` instead of `@Todo(…)`. We only
|
||||
# assert on the first part of the message because the `@Todo`
|
||||
# message is not available in release mode builds.
|
||||
# error: [type-assertion-failure] "Type `@Todo"
|
||||
assert_never(obj)
|
||||
```
|
||||
|
||||
@@ -60,8 +60,8 @@ Y: int = 47
|
||||
import mypackage
|
||||
|
||||
reveal_type(mypackage.imported.X) # revealed: int
|
||||
# error: [unresolved-attribute] "Submodule `fails` may not be available"
|
||||
reveal_type(mypackage.fails.Y) # revealed: Unknown
|
||||
# error: [possibly-missing-attribute] "Submodule `fails` may not be available"
|
||||
reveal_type(mypackage.fails.Y) # revealed: int
|
||||
```
|
||||
|
||||
### In Non-Stub
|
||||
@@ -90,8 +90,8 @@ Y: int = 47
|
||||
import mypackage
|
||||
|
||||
reveal_type(mypackage.imported.X) # revealed: int
|
||||
# error: [unresolved-attribute] "Submodule `fails` may not be available"
|
||||
reveal_type(mypackage.fails.Y) # revealed: Unknown
|
||||
# error: [possibly-missing-attribute] "Submodule `fails` may not be available"
|
||||
reveal_type(mypackage.fails.Y) # revealed: int
|
||||
```
|
||||
|
||||
## Absolute `from` Import of Direct Submodule in `__init__`
|
||||
@@ -125,8 +125,8 @@ Y: int = 47
|
||||
import mypackage
|
||||
|
||||
reveal_type(mypackage.imported.X) # revealed: int
|
||||
# error: [unresolved-attribute] "Submodule `fails` may not be available"
|
||||
reveal_type(mypackage.fails.Y) # revealed: Unknown
|
||||
# error: [possibly-missing-attribute] "Submodule `fails` may not be available"
|
||||
reveal_type(mypackage.fails.Y) # revealed: int
|
||||
```
|
||||
|
||||
### In Non-Stub
|
||||
@@ -155,8 +155,8 @@ Y: int = 47
|
||||
import mypackage
|
||||
|
||||
reveal_type(mypackage.imported.X) # revealed: int
|
||||
# error: [unresolved-attribute] "Submodule `fails` may not be available"
|
||||
reveal_type(mypackage.fails.Y) # revealed: Unknown
|
||||
# error: [possibly-missing-attribute] "Submodule `fails` may not be available"
|
||||
reveal_type(mypackage.fails.Y) # revealed: int
|
||||
```
|
||||
|
||||
## Import of Direct Submodule in `__init__`
|
||||
@@ -184,8 +184,8 @@ X: int = 42
|
||||
import mypackage
|
||||
|
||||
# TODO: this could work and would be nice to have?
|
||||
# error: [unresolved-attribute] "Submodule `imported` may not be available"
|
||||
reveal_type(mypackage.imported.X) # revealed: Unknown
|
||||
# error: [possibly-missing-attribute] "Submodule `imported` may not be available"
|
||||
reveal_type(mypackage.imported.X) # revealed: int
|
||||
```
|
||||
|
||||
### In Non-Stub
|
||||
@@ -208,8 +208,8 @@ X: int = 42
|
||||
import mypackage
|
||||
|
||||
# TODO: this could work and would be nice to have
|
||||
# error: [unresolved-attribute] "Submodule `imported` may not be available"
|
||||
reveal_type(mypackage.imported.X) # revealed: Unknown
|
||||
# error: [possibly-missing-attribute] "Submodule `imported` may not be available"
|
||||
reveal_type(mypackage.imported.X) # revealed: int
|
||||
```
|
||||
|
||||
## Relative `from` Import of Nested Submodule in `__init__`
|
||||
@@ -242,10 +242,10 @@ X: int = 42
|
||||
import mypackage
|
||||
|
||||
reveal_type(mypackage.submodule) # revealed: <module 'mypackage.submodule'>
|
||||
# error: [unresolved-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested) # revealed: Unknown
|
||||
# error: [unresolved-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: Unknown
|
||||
# error: [possibly-missing-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested) # revealed: <module 'mypackage.submodule.nested'>
|
||||
# error: [possibly-missing-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: int
|
||||
# error: [unresolved-attribute] "has no member `nested`"
|
||||
reveal_type(mypackage.nested) # revealed: Unknown
|
||||
# error: [unresolved-attribute] "has no member `nested`"
|
||||
@@ -280,10 +280,10 @@ import mypackage
|
||||
|
||||
reveal_type(mypackage.submodule) # revealed: <module 'mypackage.submodule'>
|
||||
# TODO: this would be nice to support
|
||||
# error: [unresolved-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested) # revealed: Unknown
|
||||
# error: [unresolved-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: Unknown
|
||||
# error: [possibly-missing-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested) # revealed: <module 'mypackage.submodule.nested'>
|
||||
# error: [possibly-missing-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: int
|
||||
reveal_type(mypackage.nested) # revealed: <module 'mypackage.submodule.nested'>
|
||||
reveal_type(mypackage.nested.X) # revealed: int
|
||||
```
|
||||
@@ -318,10 +318,10 @@ X: int = 42
|
||||
import mypackage
|
||||
|
||||
reveal_type(mypackage.submodule) # revealed: <module 'mypackage.submodule'>
|
||||
# error: [unresolved-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested) # revealed: Unknown
|
||||
# error: [unresolved-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: Unknown
|
||||
# error: [possibly-missing-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested) # revealed: <module 'mypackage.submodule.nested'>
|
||||
# error: [possibly-missing-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: int
|
||||
# error: [unresolved-attribute] "has no member `nested`"
|
||||
reveal_type(mypackage.nested) # revealed: Unknown
|
||||
# error: [unresolved-attribute] "has no member `nested`"
|
||||
@@ -356,10 +356,10 @@ import mypackage
|
||||
|
||||
reveal_type(mypackage.submodule) # revealed: <module 'mypackage.submodule'>
|
||||
# TODO: this would be nice to support
|
||||
# error: [unresolved-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested) # revealed: Unknown
|
||||
# error: [unresolved-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: Unknown
|
||||
# error: [possibly-missing-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested) # revealed: <module 'mypackage.submodule.nested'>
|
||||
# error: [possibly-missing-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: int
|
||||
reveal_type(mypackage.nested) # revealed: <module 'mypackage.submodule.nested'>
|
||||
reveal_type(mypackage.nested.X) # revealed: int
|
||||
```
|
||||
@@ -393,12 +393,14 @@ X: int = 42
|
||||
```py
|
||||
import mypackage
|
||||
|
||||
# error: [unresolved-attribute] "Submodule `submodule` may not be available"
|
||||
reveal_type(mypackage.submodule) # revealed: Unknown
|
||||
# error: [unresolved-attribute] "Submodule `submodule` may not be available"
|
||||
reveal_type(mypackage.submodule.nested) # revealed: Unknown
|
||||
# error: [unresolved-attribute] "Submodule `submodule` may not be available"
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: Unknown
|
||||
# error: [possibly-missing-attribute] "Submodule `submodule` may not be available"
|
||||
reveal_type(mypackage.submodule) # revealed: <module 'mypackage.submodule'>
|
||||
# error: [possibly-missing-attribute] "Submodule `submodule` may not be available"
|
||||
# error: [possibly-missing-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested) # revealed: <module 'mypackage.submodule.nested'>
|
||||
# error: [possibly-missing-attribute] "Submodule `submodule` may not be available"
|
||||
# error: [possibly-missing-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: int
|
||||
```
|
||||
|
||||
### In Non-Stub
|
||||
@@ -429,12 +431,14 @@ X: int = 42
|
||||
import mypackage
|
||||
|
||||
# TODO: this would be nice to support
|
||||
# error: [unresolved-attribute] "Submodule `submodule` may not be available"
|
||||
reveal_type(mypackage.submodule) # revealed: Unknown
|
||||
# error: [unresolved-attribute] "Submodule `submodule` may not be available"
|
||||
reveal_type(mypackage.submodule.nested) # revealed: Unknown
|
||||
# error: [unresolved-attribute] "Submodule `submodule` may not be available"
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: Unknown
|
||||
# error: [possibly-missing-attribute] "Submodule `submodule` may not be available"
|
||||
reveal_type(mypackage.submodule) # revealed: <module 'mypackage.submodule'>
|
||||
# error: [possibly-missing-attribute] "Submodule `submodule` may not be available"
|
||||
# error: [possibly-missing-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested) # revealed: <module 'mypackage.submodule.nested'>
|
||||
# error: [possibly-missing-attribute] "Submodule `submodule` may not be available"
|
||||
# error: [possibly-missing-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: int
|
||||
```
|
||||
|
||||
## Relative `from` Import of Direct Submodule in `__init__`, Mismatched Alias
|
||||
@@ -460,8 +464,8 @@ X: int = 42
|
||||
```py
|
||||
import mypackage
|
||||
|
||||
# error: [unresolved-attribute] "Submodule `imported` may not be available"
|
||||
reveal_type(mypackage.imported.X) # revealed: Unknown
|
||||
# error: [possibly-missing-attribute] "Submodule `imported` may not be available"
|
||||
reveal_type(mypackage.imported.X) # revealed: int
|
||||
# error: [unresolved-attribute] "has no member `imported_m`"
|
||||
reveal_type(mypackage.imported_m.X) # revealed: Unknown
|
||||
```
|
||||
@@ -486,8 +490,8 @@ X: int = 42
|
||||
import mypackage
|
||||
|
||||
# TODO: this would be nice to support, as it works at runtime
|
||||
# error: [unresolved-attribute] "Submodule `imported` may not be available"
|
||||
reveal_type(mypackage.imported.X) # revealed: Unknown
|
||||
# error: [possibly-missing-attribute] "Submodule `imported` may not be available"
|
||||
reveal_type(mypackage.imported.X) # revealed: int
|
||||
reveal_type(mypackage.imported_m.X) # revealed: int
|
||||
```
|
||||
|
||||
@@ -673,8 +677,8 @@ reveal_type(imported.X) # revealed: int
|
||||
|
||||
# TODO: this would be nice to support, but it's dangerous with available_submodule_attributes
|
||||
# for details, see: https://github.com/astral-sh/ty/issues/1488
|
||||
# error: [unresolved-attribute] "Submodule `imported` may not be available"
|
||||
reveal_type(mypackage.imported.X) # revealed: Unknown
|
||||
# error: [possibly-missing-attribute] "Submodule `imported` may not be available"
|
||||
reveal_type(mypackage.imported.X) # revealed: int
|
||||
```
|
||||
|
||||
### In Non-Stub
|
||||
@@ -699,8 +703,8 @@ from mypackage import imported
|
||||
reveal_type(imported.X) # revealed: int
|
||||
|
||||
# TODO: this would be nice to support, as it works at runtime
|
||||
# error: [unresolved-attribute] "Submodule `imported` may not be available"
|
||||
reveal_type(mypackage.imported.X) # revealed: Unknown
|
||||
# error: [possibly-missing-attribute] "Submodule `imported` may not be available"
|
||||
reveal_type(mypackage.imported.X) # revealed: int
|
||||
```
|
||||
|
||||
## `from` Import of Sibling Module
|
||||
@@ -737,8 +741,8 @@ from mypackage import imported
|
||||
reveal_type(imported.X) # revealed: int
|
||||
# error: [unresolved-attribute] "has no member `fails`"
|
||||
reveal_type(imported.fails.Y) # revealed: Unknown
|
||||
# error: [unresolved-attribute] "Submodule `fails` may not be available"
|
||||
reveal_type(mypackage.fails.Y) # revealed: Unknown
|
||||
# error: [possibly-missing-attribute] "Submodule `fails` may not be available"
|
||||
reveal_type(mypackage.fails.Y) # revealed: int
|
||||
```
|
||||
|
||||
### In Non-Stub
|
||||
@@ -770,8 +774,8 @@ from mypackage import imported
|
||||
|
||||
reveal_type(imported.X) # revealed: int
|
||||
reveal_type(imported.fails.Y) # revealed: int
|
||||
# error: [unresolved-attribute] "Submodule `fails`"
|
||||
reveal_type(mypackage.fails.Y) # revealed: Unknown
|
||||
# error: [possibly-missing-attribute] "Submodule `fails`"
|
||||
reveal_type(mypackage.fails.Y) # revealed: int
|
||||
```
|
||||
|
||||
## Fractal Re-export Nameclash Problems
|
||||
|
||||
@@ -247,8 +247,8 @@ X: int = 42
|
||||
from . import foo
|
||||
import package
|
||||
|
||||
# error: [unresolved-attribute]
|
||||
reveal_type(package.foo.X) # revealed: Unknown
|
||||
# error: [possibly-missing-attribute]
|
||||
reveal_type(package.foo.X) # revealed: int
|
||||
```
|
||||
|
||||
## Relative imports at the top of a search path
|
||||
|
||||
@@ -2003,6 +2003,7 @@ python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing import final
|
||||
from typing_extensions import TypeVar, Self, Protocol
|
||||
from ty_extensions import is_equivalent_to, static_assert, is_assignable_to, is_subtype_of
|
||||
|
||||
@@ -2094,6 +2095,13 @@ class NominalReturningSelfNotGeneric:
|
||||
def g(self) -> "NominalReturningSelfNotGeneric":
|
||||
return self
|
||||
|
||||
@final
|
||||
class Other: ...
|
||||
|
||||
class NominalReturningOtherClass:
|
||||
def g(self) -> Other:
|
||||
raise NotImplementedError
|
||||
|
||||
# TODO: should pass
|
||||
static_assert(is_equivalent_to(LegacyFunctionScoped, NewStyleFunctionScoped)) # error: [static-assert-error]
|
||||
|
||||
@@ -2112,8 +2120,7 @@ static_assert(not is_assignable_to(NominalLegacy, UsesSelf))
|
||||
static_assert(not is_assignable_to(NominalWithSelf, NewStyleFunctionScoped))
|
||||
static_assert(not is_assignable_to(NominalWithSelf, LegacyFunctionScoped))
|
||||
static_assert(is_assignable_to(NominalWithSelf, UsesSelf))
|
||||
# TODO: should pass
|
||||
static_assert(is_subtype_of(NominalWithSelf, UsesSelf)) # error: [static-assert-error]
|
||||
static_assert(is_subtype_of(NominalWithSelf, UsesSelf))
|
||||
|
||||
# TODO: these should pass
|
||||
static_assert(not is_assignable_to(NominalNotGeneric, NewStyleFunctionScoped)) # error: [static-assert-error]
|
||||
@@ -2126,6 +2133,8 @@ static_assert(not is_assignable_to(NominalReturningSelfNotGeneric, LegacyFunctio
|
||||
# TODO: should pass
|
||||
static_assert(not is_assignable_to(NominalReturningSelfNotGeneric, UsesSelf)) # error: [static-assert-error]
|
||||
|
||||
static_assert(not is_assignable_to(NominalReturningOtherClass, UsesSelf))
|
||||
|
||||
# These test cases are taken from the typing conformance suite:
|
||||
class ShapeProtocolImplicitSelf(Protocol):
|
||||
def set_scale(self, scale: float) -> Self: ...
|
||||
|
||||
@@ -32,7 +32,8 @@ error[unresolved-attribute]: Module `datetime` has no member `UTC`
|
||||
5 | # error: [unresolved-attribute]
|
||||
6 | reveal_type(datetime.fakenotreal) # revealed: Unknown
|
||||
|
|
||||
info: Python 3.10 was assumed when accessing `UTC` because it was specified on the command line
|
||||
info: The member may be available on other Python versions or platforms
|
||||
info: Python 3.10 was assumed when resolving the `UTC` attribute because it was specified on the command line
|
||||
info: rule `unresolved-attribute` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
@@ -30,39 +30,39 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/attributes.md
|
||||
1 | import foo
|
||||
2 | import baz
|
||||
3 |
|
||||
4 | # error: [unresolved-attribute]
|
||||
5 | reveal_type(foo.bar) # revealed: Unknown
|
||||
6 | # error: [unresolved-attribute]
|
||||
7 | reveal_type(baz.bar) # revealed: Unknown
|
||||
4 | # error: [possibly-missing-attribute]
|
||||
5 | reveal_type(foo.bar) # revealed: <module 'foo.bar'>
|
||||
6 | # error: [possibly-missing-attribute]
|
||||
7 | reveal_type(baz.bar) # revealed: <module 'baz.bar'>
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[unresolved-attribute]: Submodule `bar` may not be available as an attribute on module `foo`
|
||||
warning[possibly-missing-attribute]: Submodule `bar` may not be available as an attribute on module `foo`
|
||||
--> src/main.py:5:13
|
||||
|
|
||||
4 | # error: [unresolved-attribute]
|
||||
5 | reveal_type(foo.bar) # revealed: Unknown
|
||||
4 | # error: [possibly-missing-attribute]
|
||||
5 | reveal_type(foo.bar) # revealed: <module 'foo.bar'>
|
||||
| ^^^^^^^
|
||||
6 | # error: [unresolved-attribute]
|
||||
7 | reveal_type(baz.bar) # revealed: Unknown
|
||||
6 | # error: [possibly-missing-attribute]
|
||||
7 | reveal_type(baz.bar) # revealed: <module 'baz.bar'>
|
||||
|
|
||||
help: Consider explicitly importing `foo.bar`
|
||||
info: rule `unresolved-attribute` is enabled by default
|
||||
info: rule `possibly-missing-attribute` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[unresolved-attribute]: Submodule `bar` may not be available as an attribute on module `baz`
|
||||
warning[possibly-missing-attribute]: Submodule `bar` may not be available as an attribute on module `baz`
|
||||
--> src/main.py:7:13
|
||||
|
|
||||
5 | reveal_type(foo.bar) # revealed: Unknown
|
||||
6 | # error: [unresolved-attribute]
|
||||
7 | reveal_type(baz.bar) # revealed: Unknown
|
||||
5 | reveal_type(foo.bar) # revealed: <module 'foo.bar'>
|
||||
6 | # error: [possibly-missing-attribute]
|
||||
7 | reveal_type(baz.bar) # revealed: <module 'baz.bar'>
|
||||
| ^^^^^^^
|
||||
|
|
||||
help: Consider explicitly importing `baz.bar`
|
||||
info: rule `unresolved-attribute` is enabled by default
|
||||
info: rule `possibly-missing-attribute` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
@@ -628,8 +628,8 @@ import imported
|
||||
from module2 import imported as other_imported
|
||||
from ty_extensions import TypeOf, static_assert, is_equivalent_to
|
||||
|
||||
# error: [unresolved-attribute]
|
||||
reveal_type(imported.abc) # revealed: Unknown
|
||||
# error: [possibly-missing-attribute]
|
||||
reveal_type(imported.abc) # revealed: <module 'imported.abc'>
|
||||
|
||||
reveal_type(other_imported.abc) # revealed: <module 'imported.abc'>
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@ pub use db::Db;
|
||||
pub use diagnostic::add_inferred_python_version_hint_to_diagnostic;
|
||||
pub use module_name::{ModuleName, ModuleNameResolutionError};
|
||||
pub use module_resolver::{
|
||||
Module, SearchPath, SearchPathValidationError, SearchPaths, all_modules, list_modules,
|
||||
resolve_module, resolve_real_module, system_module_search_paths,
|
||||
KnownModule, Module, SearchPath, SearchPathValidationError, SearchPaths, all_modules,
|
||||
list_modules, resolve_module, resolve_real_module, system_module_search_paths,
|
||||
};
|
||||
pub use program::{
|
||||
Program, ProgramSettings, PythonVersionFileSource, PythonVersionSource,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::iter::FusedIterator;
|
||||
|
||||
pub use list::{all_modules, list_modules};
|
||||
pub(crate) use module::KnownModule;
|
||||
pub use module::KnownModule;
|
||||
pub use module::Module;
|
||||
pub use path::{SearchPath, SearchPathValidationError};
|
||||
pub use resolver::SearchPaths;
|
||||
|
||||
@@ -67,7 +67,7 @@ impl<'db> Module<'db> {
|
||||
}
|
||||
|
||||
/// Does this module represent the given known module?
|
||||
pub(crate) fn is_known(self, db: &'db dyn Database, known_module: KnownModule) -> bool {
|
||||
pub fn is_known(self, db: &'db dyn Database, known_module: KnownModule) -> bool {
|
||||
self.known(db) == Some(known_module)
|
||||
}
|
||||
|
||||
|
||||
@@ -1376,9 +1376,7 @@ mod implicit_globals {
|
||||
use crate::place::{Definedness, PlaceAndQualifiers, TypeOrigin};
|
||||
use crate::semantic_index::symbol::Symbol;
|
||||
use crate::semantic_index::{place_table, use_def_map};
|
||||
use crate::types::{
|
||||
CallableType, KnownClass, MemberLookupPolicy, Parameter, Parameters, Signature, Type,
|
||||
};
|
||||
use crate::types::{KnownClass, MemberLookupPolicy, Parameter, Parameters, Signature, Type};
|
||||
use ruff_python_ast::PythonVersion;
|
||||
|
||||
use super::{Place, place_from_declarations};
|
||||
@@ -1461,7 +1459,7 @@ mod implicit_globals {
|
||||
)),
|
||||
);
|
||||
Place::Defined(
|
||||
CallableType::function_like(db, signature),
|
||||
Type::function_like_callable(db, signature),
|
||||
TypeOrigin::Inferred,
|
||||
Definedness::PossiblyUndefined,
|
||||
)
|
||||
|
||||
@@ -208,7 +208,7 @@ use crate::semantic_index::predicate::{
|
||||
Predicates, ScopedPredicateId,
|
||||
};
|
||||
use crate::types::{
|
||||
IntersectionBuilder, Truthiness, Type, TypeContext, UnionBuilder, UnionType,
|
||||
CallableTypes, IntersectionBuilder, Truthiness, Type, TypeContext, UnionBuilder, UnionType,
|
||||
infer_expression_type, static_expression_truthiness,
|
||||
};
|
||||
|
||||
@@ -871,12 +871,14 @@ impl ReachabilityConstraints {
|
||||
return Truthiness::AlwaysFalse.negate_if(!predicate.is_positive);
|
||||
}
|
||||
|
||||
let overloads_iterator =
|
||||
if let Some(Type::Callable(callable)) = ty.try_upcast_to_callable(db) {
|
||||
callable.signatures(db).overloads.iter()
|
||||
} else {
|
||||
return Truthiness::AlwaysFalse.negate_if(!predicate.is_positive);
|
||||
};
|
||||
let overloads_iterator = if let Some(callable) = ty
|
||||
.try_upcast_to_callable(db)
|
||||
.and_then(CallableTypes::exactly_one)
|
||||
{
|
||||
callable.signatures(db).overloads.iter()
|
||||
} else {
|
||||
return Truthiness::AlwaysFalse.negate_if(!predicate.is_positive);
|
||||
};
|
||||
|
||||
let (no_overloads_return_never, all_overloads_return_never) = overloads_iterator
|
||||
.fold((true, true), |(none, all), overload| {
|
||||
|
||||
@@ -16,6 +16,7 @@ use ruff_db::files::File;
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::name::Name;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
use smallvec::{SmallVec, smallvec};
|
||||
|
||||
use type_ordering::union_or_intersection_elements_ordering;
|
||||
|
||||
@@ -74,7 +75,8 @@ use crate::types::variance::VarianceInferable;
|
||||
use crate::types::visitor::{any_over_type, exceeds_max_specialization_depth};
|
||||
use crate::unpack::EvaluationMode;
|
||||
use crate::{Db, FxOrderSet, Module, Program};
|
||||
pub(crate) use class::{ClassLiteral, ClassType, GenericAlias, KnownClass};
|
||||
pub use class::KnownClass;
|
||||
pub(crate) use class::{ClassLiteral, ClassType, GenericAlias};
|
||||
use instance::Protocol;
|
||||
pub use instance::{NominalInstanceType, ProtocolInstanceType};
|
||||
pub use special_form::SpecialFormType;
|
||||
@@ -892,7 +894,7 @@ impl<'db> Type<'db> {
|
||||
!(check_dunder("__eq__", true) && check_dunder("__ne__", false))
|
||||
}
|
||||
|
||||
pub(crate) fn is_notimplemented(&self, db: &'db dyn Db) -> bool {
|
||||
pub fn is_notimplemented(&self, db: &'db dyn Db) -> bool {
|
||||
self.is_instance_of(db, KnownClass::NotImplementedType)
|
||||
}
|
||||
|
||||
@@ -1532,17 +1534,20 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn try_upcast_to_callable(self, db: &'db dyn Db) -> Option<Type<'db>> {
|
||||
pub(crate) fn try_upcast_to_callable(self, db: &'db dyn Db) -> Option<CallableTypes<'db>> {
|
||||
match self {
|
||||
Type::Callable(_) => Some(self),
|
||||
Type::Callable(callable) => Some(CallableTypes::one(callable)),
|
||||
|
||||
Type::Dynamic(_) => Some(CallableType::function_like(db, Signature::dynamic(self))),
|
||||
Type::Dynamic(_) => Some(CallableTypes::one(CallableType::function_like(
|
||||
db,
|
||||
Signature::dynamic(self),
|
||||
))),
|
||||
|
||||
Type::FunctionLiteral(function_literal) => {
|
||||
Some(Type::Callable(function_literal.into_callable_type(db)))
|
||||
Some(CallableTypes::one(function_literal.into_callable_type(db)))
|
||||
}
|
||||
Type::BoundMethod(bound_method) => {
|
||||
Some(Type::Callable(bound_method.into_callable_type(db)))
|
||||
Some(CallableTypes::one(bound_method.into_callable_type(db)))
|
||||
}
|
||||
|
||||
Type::NominalInstance(_) | Type::ProtocolInstance(_) => {
|
||||
@@ -1573,13 +1578,22 @@ impl<'db> Type<'db> {
|
||||
// TODO: This is unsound so in future we can consider an opt-in option to disable it.
|
||||
Type::SubclassOf(subclass_of_ty) => match subclass_of_ty.subclass_of() {
|
||||
SubclassOfInner::Class(class) => Some(class.into_callable(db)),
|
||||
SubclassOfInner::Dynamic(dynamic) => Some(CallableType::single(
|
||||
db,
|
||||
Signature::new(Parameters::unknown(), Some(Type::Dynamic(dynamic))),
|
||||
)),
|
||||
SubclassOfInner::Dynamic(dynamic) => {
|
||||
Some(CallableTypes::one(CallableType::single(
|
||||
db,
|
||||
Signature::new(Parameters::unknown(), Some(Type::Dynamic(dynamic))),
|
||||
)))
|
||||
}
|
||||
},
|
||||
|
||||
Type::Union(union) => union.try_map(db, |element| element.try_upcast_to_callable(db)),
|
||||
Type::Union(union) => {
|
||||
let mut callables = SmallVec::new();
|
||||
for element in union.elements(db) {
|
||||
let element_callable = element.try_upcast_to_callable(db)?;
|
||||
callables.extend(element_callable.into_inner());
|
||||
}
|
||||
Some(CallableTypes(callables))
|
||||
}
|
||||
|
||||
Type::EnumLiteral(enum_literal) => enum_literal
|
||||
.enum_class_instance(db)
|
||||
@@ -1587,26 +1601,30 @@ impl<'db> Type<'db> {
|
||||
|
||||
Type::TypeAlias(alias) => alias.value_type(db).try_upcast_to_callable(db),
|
||||
|
||||
Type::KnownBoundMethod(method) => Some(Type::Callable(CallableType::new(
|
||||
Type::KnownBoundMethod(method) => Some(CallableTypes::one(CallableType::new(
|
||||
db,
|
||||
CallableSignature::from_overloads(method.signatures(db)),
|
||||
false,
|
||||
))),
|
||||
|
||||
Type::WrapperDescriptor(wrapper_descriptor) => Some(Type::Callable(CallableType::new(
|
||||
db,
|
||||
CallableSignature::from_overloads(wrapper_descriptor.signatures(db)),
|
||||
false,
|
||||
))),
|
||||
Type::WrapperDescriptor(wrapper_descriptor) => {
|
||||
Some(CallableTypes::one(CallableType::new(
|
||||
db,
|
||||
CallableSignature::from_overloads(wrapper_descriptor.signatures(db)),
|
||||
false,
|
||||
)))
|
||||
}
|
||||
|
||||
Type::KnownInstance(KnownInstanceType::NewType(newtype)) => Some(CallableType::single(
|
||||
db,
|
||||
Signature::new(
|
||||
Parameters::new([Parameter::positional_only(None)
|
||||
.with_annotated_type(newtype.base(db).instance_type(db))]),
|
||||
Some(Type::NewTypeInstance(newtype)),
|
||||
),
|
||||
)),
|
||||
Type::KnownInstance(KnownInstanceType::NewType(newtype)) => {
|
||||
Some(CallableTypes::one(CallableType::single(
|
||||
db,
|
||||
Signature::new(
|
||||
Parameters::new([Parameter::positional_only(None)
|
||||
.with_annotated_type(newtype.base(db).instance_type(db))]),
|
||||
Some(Type::NewTypeInstance(newtype)),
|
||||
),
|
||||
)))
|
||||
}
|
||||
|
||||
Type::Never
|
||||
| Type::DataclassTransformer(_)
|
||||
@@ -1670,8 +1688,8 @@ impl<'db> Type<'db> {
|
||||
|
||||
/// Return true if this type is assignable to type `target`.
|
||||
///
|
||||
/// See [`TypeRelation::Assignability`] for more details.
|
||||
pub(crate) fn is_assignable_to(self, db: &'db dyn Db, target: Type<'db>) -> bool {
|
||||
/// See `TypeRelation::Assignability` for more details.
|
||||
pub fn is_assignable_to(self, db: &'db dyn Db, target: Type<'db>) -> bool {
|
||||
self.when_assignable_to(db, target, InferableTypeVars::None)
|
||||
.is_always_satisfied(db)
|
||||
}
|
||||
@@ -2182,18 +2200,20 @@ impl<'db> Type<'db> {
|
||||
)
|
||||
}),
|
||||
|
||||
(_, Type::Callable(_)) => relation_visitor.visit((self, target, relation), || {
|
||||
self.try_upcast_to_callable(db).when_some_and(|callable| {
|
||||
callable.has_relation_to_impl(
|
||||
db,
|
||||
target,
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
(_, Type::Callable(other_callable)) => {
|
||||
relation_visitor.visit((self, target, relation), || {
|
||||
self.try_upcast_to_callable(db).when_some_and(|callables| {
|
||||
callables.has_relation_to_impl(
|
||||
db,
|
||||
other_callable,
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
})
|
||||
})
|
||||
}),
|
||||
}
|
||||
|
||||
(_, Type::ProtocolInstance(protocol)) => {
|
||||
relation_visitor.visit((self, target, relation), || {
|
||||
@@ -4092,7 +4112,7 @@ impl<'db> Type<'db> {
|
||||
Some((self, AttributeKind::NormalOrNonDataDescriptor))
|
||||
} else {
|
||||
Some((
|
||||
Type::Callable(callable.bind_self(db)),
|
||||
Type::Callable(callable.bind_self(db, None)),
|
||||
AttributeKind::NormalOrNonDataDescriptor,
|
||||
))
|
||||
};
|
||||
@@ -5626,7 +5646,7 @@ impl<'db> Type<'db> {
|
||||
.with_annotated_type(UnionType::from_elements(
|
||||
db,
|
||||
[
|
||||
CallableType::single(db, getter_signature),
|
||||
Type::single_callable(db, getter_signature),
|
||||
Type::none(db),
|
||||
],
|
||||
))
|
||||
@@ -5635,7 +5655,7 @@ impl<'db> Type<'db> {
|
||||
.with_annotated_type(UnionType::from_elements(
|
||||
db,
|
||||
[
|
||||
CallableType::single(db, setter_signature),
|
||||
Type::single_callable(db, setter_signature),
|
||||
Type::none(db),
|
||||
],
|
||||
))
|
||||
@@ -5644,7 +5664,7 @@ impl<'db> Type<'db> {
|
||||
.with_annotated_type(UnionType::from_elements(
|
||||
db,
|
||||
[
|
||||
CallableType::single(db, deleter_signature),
|
||||
Type::single_callable(db, deleter_signature),
|
||||
Type::none(db),
|
||||
],
|
||||
))
|
||||
@@ -7138,6 +7158,15 @@ impl<'db> Type<'db> {
|
||||
tcx: TypeContext<'db>,
|
||||
visitor: &ApplyTypeMappingVisitor<'db>,
|
||||
) -> Type<'db> {
|
||||
// If we are binding `typing.Self`, and this type is what we are binding `Self` to, return
|
||||
// early. This is not just an optimization, it also prevents us from infinitely expanding
|
||||
// the type, if it's something that can contain a `Self` reference.
|
||||
if let TypeMapping::BindSelf(self_type) = type_mapping
|
||||
&& self == *self_type
|
||||
{
|
||||
return self;
|
||||
}
|
||||
|
||||
match self {
|
||||
Type::TypeVar(bound_typevar) => match type_mapping {
|
||||
TypeMapping::Specialization(specialization) => {
|
||||
@@ -10956,34 +10985,44 @@ pub(super) fn walk_callable_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
|
||||
// The Salsa heap is tracked separately.
|
||||
impl get_size2::GetSize for CallableType<'_> {}
|
||||
|
||||
impl<'db> CallableType<'db> {
|
||||
impl<'db> Type<'db> {
|
||||
/// Create a callable type with a single non-overloaded signature.
|
||||
pub(crate) fn single(db: &'db dyn Db, signature: Signature<'db>) -> Type<'db> {
|
||||
Type::Callable(CallableType::new(
|
||||
db,
|
||||
CallableSignature::single(signature),
|
||||
false,
|
||||
))
|
||||
pub(crate) fn single_callable(db: &'db dyn Db, signature: Signature<'db>) -> Type<'db> {
|
||||
Type::Callable(CallableType::single(db, signature))
|
||||
}
|
||||
|
||||
/// Create a non-overloaded, function-like callable type with a single signature.
|
||||
///
|
||||
/// A function-like callable will bind `self` when accessed as an attribute on an instance.
|
||||
pub(crate) fn function_like(db: &'db dyn Db, signature: Signature<'db>) -> Type<'db> {
|
||||
Type::Callable(CallableType::new(
|
||||
db,
|
||||
CallableSignature::single(signature),
|
||||
true,
|
||||
))
|
||||
pub(crate) fn function_like_callable(db: &'db dyn Db, signature: Signature<'db>) -> Type<'db> {
|
||||
Type::Callable(CallableType::function_like(db, signature))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> CallableType<'db> {
|
||||
pub(crate) fn single(db: &'db dyn Db, signature: Signature<'db>) -> CallableType<'db> {
|
||||
CallableType::new(db, CallableSignature::single(signature), false)
|
||||
}
|
||||
|
||||
pub(crate) fn function_like(db: &'db dyn Db, signature: Signature<'db>) -> CallableType<'db> {
|
||||
CallableType::new(db, CallableSignature::single(signature), true)
|
||||
}
|
||||
|
||||
/// Create a callable type which accepts any parameters and returns an `Unknown` type.
|
||||
pub(crate) fn unknown(db: &'db dyn Db) -> Type<'db> {
|
||||
Self::single(db, Signature::unknown())
|
||||
Type::Callable(Self::single(db, Signature::unknown()))
|
||||
}
|
||||
|
||||
pub(crate) fn bind_self(self, db: &'db dyn Db) -> CallableType<'db> {
|
||||
CallableType::new(db, self.signatures(db).bind_self(db, None), false)
|
||||
pub(crate) fn bind_self(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
self_type: Option<Type<'db>>,
|
||||
) -> CallableType<'db> {
|
||||
CallableType::new(db, self.signatures(db).bind_self(db, self_type), false)
|
||||
}
|
||||
|
||||
pub(crate) fn apply_self(self, db: &'db dyn Db, self_type: Type<'db>) -> CallableType<'db> {
|
||||
CallableType::new(db, self.signatures(db).apply_self(db, self_type), false)
|
||||
}
|
||||
|
||||
/// Create a callable type which represents a fully-static "bottom" callable.
|
||||
@@ -11077,6 +11116,72 @@ impl<'db> CallableType<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Converting a type "into a callable" can possibly return a _union_ of callables. Eventually,
|
||||
/// when coercing that result to a single type, you'll get a `UnionType`. But this lets you handle
|
||||
/// that result as a list of `CallableType`s before merging them into a `UnionType` should that be
|
||||
/// helpful.
|
||||
///
|
||||
/// Note that this type is guaranteed to contain at least one callable. If you need to support "no
|
||||
/// callables" as a possibility, use `Option<CallableTypes>`.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, get_size2::GetSize, salsa::Update)]
|
||||
pub(crate) struct CallableTypes<'db>(SmallVec<[CallableType<'db>; 1]>);
|
||||
|
||||
impl<'db> CallableTypes<'db> {
|
||||
pub(crate) fn one(callable: CallableType<'db>) -> Self {
|
||||
CallableTypes(smallvec![callable])
|
||||
}
|
||||
|
||||
pub(crate) fn from_elements(callables: impl IntoIterator<Item = CallableType<'db>>) -> Self {
|
||||
let callables: SmallVec<_> = callables.into_iter().collect();
|
||||
assert!(!callables.is_empty(), "CallableTypes should not be empty");
|
||||
CallableTypes(callables)
|
||||
}
|
||||
|
||||
pub(crate) fn exactly_one(self) -> Option<CallableType<'db>> {
|
||||
match self.0.as_slice() {
|
||||
[single] => Some(*single),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn into_inner(self) -> SmallVec<[CallableType<'db>; 1]> {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub(crate) fn into_type(self, db: &'db dyn Db) -> Type<'db> {
|
||||
match self.0.as_slice() {
|
||||
[] => unreachable!("CallableTypes should not be empty"),
|
||||
[single] => Type::Callable(*single),
|
||||
slice => UnionType::from_elements(db, slice.iter().copied().map(Type::Callable)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn map(self, mut f: impl FnMut(CallableType<'db>) -> CallableType<'db>) -> Self {
|
||||
Self::from_elements(self.0.iter().map(|element| f(*element)))
|
||||
}
|
||||
|
||||
pub(crate) fn has_relation_to_impl(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
other: CallableType<'db>,
|
||||
inferable: InferableTypeVars<'_, 'db>,
|
||||
relation: TypeRelation<'db>,
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
self.0.iter().when_all(db, |element| {
|
||||
element.has_relation_to_impl(
|
||||
db,
|
||||
other,
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a specific instance of a bound method type for a builtin class.
|
||||
///
|
||||
/// Unlike bound methods of user-defined classes, these are not generally instances
|
||||
@@ -12108,7 +12213,7 @@ impl get_size2::GetSize for UnionType<'_> {}
|
||||
impl<'db> UnionType<'db> {
|
||||
/// Create a union from a list of elements
|
||||
/// (which may be eagerly simplified into a different variant of [`Type`] altogether).
|
||||
pub(crate) fn from_elements<I, T>(db: &'db dyn Db, elements: I) -> Type<'db>
|
||||
pub fn from_elements<I, T>(db: &'db dyn Db, elements: I) -> Type<'db>
|
||||
where
|
||||
I: IntoIterator<Item = T>,
|
||||
T: Into<Type<'db>>,
|
||||
|
||||
@@ -32,7 +32,7 @@ use crate::types::tuple::{TupleSpec, TupleType};
|
||||
use crate::types::typed_dict::typed_dict_params_from_class_def;
|
||||
use crate::types::visitor::{TypeCollector, TypeVisitor, walk_type_with_recursion_guard};
|
||||
use crate::types::{
|
||||
ApplyTypeMappingVisitor, Binding, BoundSuperType, CallableType, DATACLASS_FLAGS,
|
||||
ApplyTypeMappingVisitor, Binding, BoundSuperType, CallableType, CallableTypes, DATACLASS_FLAGS,
|
||||
DataclassFlags, DataclassParams, DeprecatedInstance, FindLegacyTypeVarsVisitor,
|
||||
HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, KnownInstanceType,
|
||||
ManualPEP695TypeAliasType, MaterializationKind, NormalizedVisitor, PropertyInstanceType,
|
||||
@@ -791,7 +791,7 @@ impl<'db> ClassType<'db> {
|
||||
.with_annotated_type(Type::instance(db, self))]);
|
||||
|
||||
let synthesized_dunder_method =
|
||||
CallableType::function_like(db, Signature::new(parameters, Some(return_type)));
|
||||
Type::function_like_callable(db, Signature::new(parameters, Some(return_type)));
|
||||
|
||||
Member::definitely_declared(synthesized_dunder_method)
|
||||
}
|
||||
@@ -1013,7 +1013,7 @@ impl<'db> ClassType<'db> {
|
||||
iterable_parameter,
|
||||
]);
|
||||
|
||||
let synthesized_dunder = CallableType::function_like(
|
||||
let synthesized_dunder = Type::function_like_callable(
|
||||
db,
|
||||
Signature::new_generic(inherited_generic_context, parameters, None),
|
||||
);
|
||||
@@ -1052,7 +1052,7 @@ impl<'db> ClassType<'db> {
|
||||
/// Return a callable type (or union of callable types) that represents the callable
|
||||
/// constructor signature of this class.
|
||||
#[salsa::tracked(cycle_initial=into_callable_cycle_initial, heap_size=ruff_memory_usage::heap_size)]
|
||||
pub(super) fn into_callable(self, db: &'db dyn Db) -> Type<'db> {
|
||||
pub(super) fn into_callable(self, db: &'db dyn Db) -> CallableTypes<'db> {
|
||||
let self_ty = Type::from(self);
|
||||
let metaclass_dunder_call_function_symbol = self_ty
|
||||
.member_lookup_with_policy(
|
||||
@@ -1070,7 +1070,7 @@ impl<'db> ClassType<'db> {
|
||||
// https://typing.python.org/en/latest/spec/constructors.html#converting-a-constructor-to-callable
|
||||
// by always respecting the signature of the metaclass `__call__`, rather than
|
||||
// using a heuristic which makes unwarranted assumptions to sometimes ignore it.
|
||||
return Type::Callable(metaclass_dunder_call_function.into_callable_type(db));
|
||||
return CallableTypes::one(metaclass_dunder_call_function.into_callable_type(db));
|
||||
}
|
||||
|
||||
let dunder_new_function_symbol = self_ty.lookup_dunder_new(db);
|
||||
@@ -1098,14 +1098,14 @@ impl<'db> ClassType<'db> {
|
||||
});
|
||||
|
||||
let instance_ty = Type::instance(db, self);
|
||||
let dunder_new_bound_method = Type::Callable(CallableType::new(
|
||||
let dunder_new_bound_method = CallableType::new(
|
||||
db,
|
||||
dunder_new_signature.bind_self(db, Some(instance_ty)),
|
||||
true,
|
||||
));
|
||||
);
|
||||
|
||||
if returns_non_subclass {
|
||||
return dunder_new_bound_method;
|
||||
return CallableTypes::one(dunder_new_bound_method);
|
||||
}
|
||||
Some(dunder_new_bound_method)
|
||||
} else {
|
||||
@@ -1148,11 +1148,11 @@ impl<'db> ClassType<'db> {
|
||||
signature.overloads.iter().map(synthesized_signature),
|
||||
);
|
||||
|
||||
Some(Type::Callable(CallableType::new(
|
||||
Some(CallableType::new(
|
||||
db,
|
||||
synthesized_dunder_init_signature,
|
||||
true,
|
||||
)))
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -1162,12 +1162,14 @@ impl<'db> ClassType<'db> {
|
||||
|
||||
match (dunder_new_function, synthesized_dunder_init_callable) {
|
||||
(Some(dunder_new_function), Some(synthesized_dunder_init_callable)) => {
|
||||
UnionType::from_elements(
|
||||
db,
|
||||
vec![dunder_new_function, synthesized_dunder_init_callable],
|
||||
)
|
||||
CallableTypes::from_elements([
|
||||
dunder_new_function,
|
||||
synthesized_dunder_init_callable,
|
||||
])
|
||||
}
|
||||
(Some(constructor), None) | (None, Some(constructor)) => {
|
||||
CallableTypes::one(constructor)
|
||||
}
|
||||
(Some(constructor), None) | (None, Some(constructor)) => constructor,
|
||||
(None, None) => {
|
||||
// If no `__new__` or `__init__` method is found, then we fall back to looking for
|
||||
// an `object.__new__` method.
|
||||
@@ -1182,17 +1184,17 @@ impl<'db> ClassType<'db> {
|
||||
if let Place::Defined(Type::FunctionLiteral(new_function), _, _) =
|
||||
new_function_symbol
|
||||
{
|
||||
Type::Callable(
|
||||
CallableTypes::one(
|
||||
new_function
|
||||
.into_bound_method_type(db, correct_return_type)
|
||||
.into_callable_type(db),
|
||||
)
|
||||
} else {
|
||||
// Fallback if no `object.__new__` is found.
|
||||
CallableType::single(
|
||||
CallableTypes::one(CallableType::single(
|
||||
db,
|
||||
Signature::new(Parameters::empty(), Some(correct_return_type)),
|
||||
)
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1208,11 +1210,11 @@ impl<'db> ClassType<'db> {
|
||||
}
|
||||
|
||||
fn into_callable_cycle_initial<'db>(
|
||||
_db: &'db dyn Db,
|
||||
db: &'db dyn Db,
|
||||
_id: salsa::Id,
|
||||
_self: ClassType<'db>,
|
||||
) -> Type<'db> {
|
||||
Type::Never
|
||||
) -> CallableTypes<'db> {
|
||||
CallableTypes::one(CallableType::bottom(db))
|
||||
}
|
||||
|
||||
impl<'db> From<GenericAlias<'db>> for ClassType<'db> {
|
||||
@@ -2156,7 +2158,7 @@ impl<'db> ClassLiteral<'db> {
|
||||
Parameters::new([Parameter::positional_only(Some(Name::new_static("self")))]),
|
||||
Some(field.declared_ty),
|
||||
);
|
||||
let property_getter = CallableType::single(db, property_getter_signature);
|
||||
let property_getter = Type::single_callable(db, property_getter_signature);
|
||||
let property = PropertyInstanceType::new(db, Some(property_getter), None);
|
||||
return Member::definitely_declared(Type::PropertyInstance(property));
|
||||
}
|
||||
@@ -2370,7 +2372,7 @@ impl<'db> ClassLiteral<'db> {
|
||||
),
|
||||
_ => Signature::new(Parameters::new(parameters), return_ty),
|
||||
};
|
||||
Some(CallableType::function_like(db, signature))
|
||||
Some(Type::function_like_callable(db, signature))
|
||||
};
|
||||
|
||||
match (field_policy, name) {
|
||||
@@ -2406,7 +2408,7 @@ impl<'db> ClassLiteral<'db> {
|
||||
Some(KnownClass::Bool.to_instance(db)),
|
||||
);
|
||||
|
||||
Some(CallableType::function_like(db, signature))
|
||||
Some(Type::function_like_callable(db, signature))
|
||||
}
|
||||
(CodeGeneratorKind::DataclassLike(_), "__hash__") => {
|
||||
let unsafe_hash = has_dataclass_param(DataclassFlags::UNSAFE_HASH);
|
||||
@@ -2422,7 +2424,7 @@ impl<'db> ClassLiteral<'db> {
|
||||
Some(KnownClass::Int.to_instance(db)),
|
||||
);
|
||||
|
||||
Some(CallableType::function_like(db, signature))
|
||||
Some(Type::function_like_callable(db, signature))
|
||||
} else if eq && !frozen {
|
||||
Some(Type::none(db))
|
||||
} else {
|
||||
@@ -2509,7 +2511,7 @@ impl<'db> ClassLiteral<'db> {
|
||||
Some(Type::Never),
|
||||
);
|
||||
|
||||
return Some(CallableType::function_like(db, signature));
|
||||
return Some(Type::function_like_callable(db, signature));
|
||||
}
|
||||
None
|
||||
}
|
||||
@@ -2787,7 +2789,7 @@ impl<'db> ClassLiteral<'db> {
|
||||
Some(Type::none(db)),
|
||||
);
|
||||
|
||||
Some(CallableType::function_like(db, signature))
|
||||
Some(Type::function_like_callable(db, signature))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
@@ -4743,7 +4745,7 @@ impl KnownClass {
|
||||
///
|
||||
/// If the class cannot be found in typeshed, a debug-level log message will be emitted stating this.
|
||||
#[track_caller]
|
||||
pub(crate) fn to_instance(self, db: &dyn Db) -> Type<'_> {
|
||||
pub fn to_instance(self, db: &dyn Db) -> Type<'_> {
|
||||
debug_assert_ne!(
|
||||
self,
|
||||
KnownClass::Tuple,
|
||||
@@ -4896,7 +4898,7 @@ impl KnownClass {
|
||||
/// representing that class and all possible subclasses of the class.
|
||||
///
|
||||
/// If the class cannot be found in typeshed, a debug-level log message will be emitted stating this.
|
||||
pub(crate) fn to_subclass_of(self, db: &dyn Db) -> Type<'_> {
|
||||
pub fn to_subclass_of(self, db: &dyn Db) -> Type<'_> {
|
||||
self.to_class_literal(db)
|
||||
.to_class_type(db)
|
||||
.map(|class| SubclassOfType::from(db, class))
|
||||
|
||||
@@ -1790,7 +1790,11 @@ impl<'db> InteriorNode<'db> {
|
||||
|
||||
/// Returns a sequent map for this BDD, which records the relationships between the constraints
|
||||
/// that appear in the BDD.
|
||||
#[salsa::tracked(returns(ref), heap_size=ruff_memory_usage::heap_size)]
|
||||
#[salsa::tracked(
|
||||
returns(ref),
|
||||
cycle_initial=sequent_map_cycle_initial,
|
||||
heap_size=ruff_memory_usage::heap_size,
|
||||
)]
|
||||
fn sequent_map(self, db: &'db dyn Db) -> SequentMap<'db> {
|
||||
let mut map = SequentMap::default();
|
||||
Node::Interior(self).for_each_constraint(db, &mut |constraint| {
|
||||
@@ -2109,6 +2113,14 @@ impl<'db> InteriorNode<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
fn sequent_map_cycle_initial<'db>(
|
||||
_db: &'db dyn Db,
|
||||
_id: salsa::Id,
|
||||
_self: InteriorNode<'db>,
|
||||
) -> SequentMap<'db> {
|
||||
SequentMap::default()
|
||||
}
|
||||
|
||||
/// An assignment of one BDD variable to either `true` or `false`. (When evaluating a BDD, we
|
||||
/// must provide an assignment for each variable present in the BDD.)
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
|
||||
@@ -3630,30 +3630,32 @@ pub(super) fn report_invalid_method_override<'db>(
|
||||
/// *does* exist as a submodule in the standard library on *other* Python
|
||||
/// versions, we add a hint to the diagnostic that the user may have
|
||||
/// misconfigured their Python version.
|
||||
///
|
||||
/// The function returns `true` if a hint was added, `false` otherwise.
|
||||
pub(super) fn hint_if_stdlib_submodule_exists_on_other_versions(
|
||||
db: &dyn Db,
|
||||
mut diagnostic: LintDiagnosticGuard,
|
||||
diagnostic: &mut Diagnostic,
|
||||
full_submodule_name: &ModuleName,
|
||||
parent_module: Module,
|
||||
) {
|
||||
) -> bool {
|
||||
let Some(search_path) = parent_module.search_path(db) else {
|
||||
return;
|
||||
return false;
|
||||
};
|
||||
|
||||
if !search_path.is_standard_library() {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
let program = Program::get(db);
|
||||
let typeshed_versions = program.search_paths(db).typeshed_versions();
|
||||
|
||||
let Some(version_range) = typeshed_versions.exact(full_submodule_name) else {
|
||||
return;
|
||||
return false;
|
||||
};
|
||||
|
||||
let python_version = program.python_version(db);
|
||||
if version_range.contains(python_version) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
diagnostic.info(format_args!(
|
||||
@@ -3667,7 +3669,9 @@ pub(super) fn hint_if_stdlib_submodule_exists_on_other_versions(
|
||||
version_range = version_range.diagnostic_display(),
|
||||
));
|
||||
|
||||
add_inferred_python_version_hint_to_diagnostic(db, &mut diagnostic, "resolving modules");
|
||||
add_inferred_python_version_hint_to_diagnostic(db, diagnostic, "resolving modules");
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// This function receives an unresolved `foo.bar` attribute access,
|
||||
@@ -3681,8 +3685,9 @@ pub(super) fn hint_if_stdlib_submodule_exists_on_other_versions(
|
||||
pub(super) fn hint_if_stdlib_attribute_exists_on_other_versions(
|
||||
db: &dyn Db,
|
||||
mut diagnostic: LintDiagnosticGuard,
|
||||
value_type: &Type,
|
||||
value_type: Type,
|
||||
attr: &str,
|
||||
action: &str,
|
||||
) {
|
||||
// Currently we limit this analysis to attributes of stdlib modules,
|
||||
// as this covers the most important cases while not being too noisy
|
||||
@@ -3705,17 +3710,19 @@ pub(super) fn hint_if_stdlib_attribute_exists_on_other_versions(
|
||||
// so if this lookup succeeds then we know that this lookup *could* succeed with possible
|
||||
// configuration changes.
|
||||
let symbol_table = place_table(db, global_scope(db, file));
|
||||
if symbol_table.symbol_by_name(attr).is_none() {
|
||||
let Some(symbol) = symbol_table.symbol_by_name(attr) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if !symbol.is_bound() {
|
||||
return;
|
||||
}
|
||||
|
||||
diagnostic.info("The member may be available on other Python versions or platforms");
|
||||
|
||||
// For now, we just mention the current version they're on, and hope that's enough of a nudge.
|
||||
// TODO: determine what version they need to be on
|
||||
// TODO: also mention the platform we're assuming
|
||||
// TODO: determine what platform they need to be on
|
||||
add_inferred_python_version_hint_to_diagnostic(
|
||||
db,
|
||||
&mut diagnostic,
|
||||
&format!("accessing `{attr}`"),
|
||||
);
|
||||
add_inferred_python_version_hint_to_diagnostic(db, &mut diagnostic, action);
|
||||
}
|
||||
|
||||
@@ -1358,8 +1358,6 @@ impl KnownFunction {
|
||||
let db = context.db();
|
||||
let parameter_types = overload.parameter_types();
|
||||
|
||||
let is_todo_type = |ty: Type<'db>| ty.is_todo();
|
||||
|
||||
match self {
|
||||
KnownFunction::RevealType => {
|
||||
let revealed_type = overload
|
||||
@@ -1393,10 +1391,7 @@ impl KnownFunction {
|
||||
let [Some(actual_ty), Some(asserted_ty)] = parameter_types else {
|
||||
return;
|
||||
};
|
||||
if actual_ty.is_equivalent_to(db, *asserted_ty)
|
||||
|| any_over_type(db, *actual_ty, &is_todo_type, true)
|
||||
|| any_over_type(db, *asserted_ty, &is_todo_type, true)
|
||||
{
|
||||
if actual_ty.is_equivalent_to(db, *asserted_ty) {
|
||||
return;
|
||||
}
|
||||
if let Some(builder) = context.report_lint(&TYPE_ASSERTION_FAILURE, call_expression)
|
||||
@@ -1432,9 +1427,7 @@ impl KnownFunction {
|
||||
let [Some(actual_ty)] = parameter_types else {
|
||||
return;
|
||||
};
|
||||
if actual_ty.is_equivalent_to(db, Type::Never)
|
||||
|| any_over_type(db, *actual_ty, &is_todo_type, true)
|
||||
{
|
||||
if actual_ty.is_equivalent_to(db, Type::Never) {
|
||||
return;
|
||||
}
|
||||
if let Some(builder) = context.report_lint(&TYPE_ASSERTION_FAILURE, call_expression)
|
||||
|
||||
@@ -15,7 +15,7 @@ use crate::types::generics::Specialization;
|
||||
use crate::types::signatures::Signature;
|
||||
use crate::types::{CallDunderError, UnionType};
|
||||
use crate::types::{
|
||||
ClassBase, ClassLiteral, KnownClass, KnownInstanceType, Type, TypeContext,
|
||||
CallableTypes, ClassBase, ClassLiteral, KnownClass, KnownInstanceType, Type, TypeContext,
|
||||
TypeVarBoundOrConstraints, class::CodeGeneratorKind,
|
||||
};
|
||||
use crate::{Db, DisplaySettings, HasType, NameKind, SemanticModel};
|
||||
@@ -876,7 +876,10 @@ pub fn definitions_for_keyword_argument<'db>(
|
||||
|
||||
let mut resolved_definitions = Vec::new();
|
||||
|
||||
if let Some(Type::Callable(callable_type)) = func_type.try_upcast_to_callable(db) {
|
||||
if let Some(callable_type) = func_type
|
||||
.try_upcast_to_callable(db)
|
||||
.and_then(CallableTypes::exactly_one)
|
||||
{
|
||||
let signatures = callable_type.signatures(db);
|
||||
|
||||
// For each signature, find the parameter with the matching name
|
||||
@@ -987,7 +990,10 @@ pub fn call_signature_details<'db>(
|
||||
let func_type = call_expr.func.inferred_type(model);
|
||||
|
||||
// Use into_callable to handle all the complex type conversions
|
||||
if let Some(callable_type) = func_type.try_upcast_to_callable(db) {
|
||||
if let Some(callable_type) = func_type
|
||||
.try_upcast_to_callable(db)
|
||||
.map(|callables| callables.into_type(db))
|
||||
{
|
||||
let call_arguments =
|
||||
CallArguments::from_arguments(&call_expr.arguments, |_, splatted_value| {
|
||||
splatted_value.inferred_type(model)
|
||||
@@ -1042,7 +1048,7 @@ pub fn call_type_simplified_by_overloads<'db>(
|
||||
let func_type = call_expr.func.inferred_type(model);
|
||||
|
||||
// Use into_callable to handle all the complex type conversions
|
||||
let callable_type = func_type.try_upcast_to_callable(db)?;
|
||||
let callable_type = func_type.try_upcast_to_callable(db)?.into_type(db);
|
||||
let bindings = callable_type.bindings(db);
|
||||
|
||||
// If the callable is trivial this analysis is useless, bail out
|
||||
|
||||
@@ -62,9 +62,9 @@ use crate::types::diagnostic::{
|
||||
INVALID_NAMED_TUPLE, INVALID_NEWTYPE, INVALID_OVERLOAD, INVALID_PARAMETER_DEFAULT,
|
||||
INVALID_PARAMSPEC, INVALID_PROTOCOL, INVALID_TYPE_FORM, INVALID_TYPE_GUARD_CALL,
|
||||
INVALID_TYPE_VARIABLE_CONSTRAINTS, IncompatibleBases, NON_SUBSCRIPTABLE,
|
||||
POSSIBLY_MISSING_IMPLICIT_CALL, POSSIBLY_MISSING_IMPORT, SUBCLASS_OF_FINAL_CLASS,
|
||||
UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE, UNRESOLVED_GLOBAL, UNRESOLVED_IMPORT,
|
||||
UNRESOLVED_REFERENCE, UNSUPPORTED_OPERATOR, USELESS_OVERLOAD_BODY,
|
||||
POSSIBLY_MISSING_ATTRIBUTE, POSSIBLY_MISSING_IMPLICIT_CALL, POSSIBLY_MISSING_IMPORT,
|
||||
SUBCLASS_OF_FINAL_CLASS, UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE, UNRESOLVED_GLOBAL,
|
||||
UNRESOLVED_IMPORT, UNRESOLVED_REFERENCE, UNSUPPORTED_OPERATOR, USELESS_OVERLOAD_BODY,
|
||||
hint_if_stdlib_attribute_exists_on_other_versions,
|
||||
hint_if_stdlib_submodule_exists_on_other_versions, report_attempted_protocol_instantiation,
|
||||
report_bad_dunder_set_call, report_cannot_pop_required_field_on_typed_dict,
|
||||
@@ -101,8 +101,8 @@ use crate::types::typed_dict::{
|
||||
};
|
||||
use crate::types::visitor::any_over_type;
|
||||
use crate::types::{
|
||||
CallDunderError, CallableBinding, CallableType, ClassLiteral, ClassType, DataclassParams,
|
||||
DynamicType, InternedType, IntersectionBuilder, IntersectionType, KnownClass,
|
||||
CallDunderError, CallableBinding, CallableType, CallableTypes, ClassLiteral, ClassType,
|
||||
DataclassParams, DynamicType, InternedType, IntersectionBuilder, IntersectionType, KnownClass,
|
||||
KnownInstanceType, LintDiagnosticGuard, MemberLookupPolicy, MetaclassCandidate,
|
||||
PEP695TypeAliasType, Parameter, ParameterForm, Parameters, SpecialFormType, SubclassOfType,
|
||||
TrackedConstraintSet, Truthiness, Type, TypeAliasType, TypeAndQualifiers, TypeContext,
|
||||
@@ -2291,7 +2291,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
|
||||
let is_input_function_like = inferred_ty
|
||||
.try_upcast_to_callable(self.db())
|
||||
.and_then(Type::as_callable)
|
||||
.and_then(CallableTypes::exactly_one)
|
||||
.is_some_and(|callable| callable.is_function_like(self.db()));
|
||||
|
||||
if is_input_function_like
|
||||
@@ -3284,7 +3284,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
// specified in this context.
|
||||
match default {
|
||||
ast::Expr::EllipsisLiteral(_) => {
|
||||
CallableType::single(self.db(), Signature::new(Parameters::gradual_form(), None))
|
||||
Type::single_callable(self.db(), Signature::new(Parameters::gradual_form(), None))
|
||||
}
|
||||
ast::Expr::List(ast::ExprList { elts, .. }) => {
|
||||
let mut parameter_types = Vec::with_capacity(elts.len());
|
||||
@@ -3312,7 +3312,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
}))
|
||||
};
|
||||
|
||||
CallableType::single(self.db(), Signature::new(parameters, None))
|
||||
Type::single_callable(self.db(), Signature::new(parameters, None))
|
||||
}
|
||||
ast::Expr::Name(name) => {
|
||||
let name_ty = self.infer_name_load(name);
|
||||
@@ -6240,18 +6240,30 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
return;
|
||||
};
|
||||
|
||||
let diagnostic = builder.into_diagnostic(format_args!(
|
||||
let mut diagnostic = builder.into_diagnostic(format_args!(
|
||||
"Module `{module_name}` has no member `{name}`"
|
||||
));
|
||||
|
||||
let mut submodule_hint_added = false;
|
||||
|
||||
if let Some(full_submodule_name) = full_submodule_name {
|
||||
hint_if_stdlib_submodule_exists_on_other_versions(
|
||||
submodule_hint_added = hint_if_stdlib_submodule_exists_on_other_versions(
|
||||
self.db(),
|
||||
diagnostic,
|
||||
&mut diagnostic,
|
||||
&full_submodule_name,
|
||||
module,
|
||||
);
|
||||
}
|
||||
|
||||
if !submodule_hint_added {
|
||||
hint_if_stdlib_attribute_exists_on_other_versions(
|
||||
self.db(),
|
||||
diagnostic,
|
||||
module_ty,
|
||||
name,
|
||||
"resolving imports",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Infer the implicit local definition `x = <module 'whatever.thispackage.x'>` that
|
||||
@@ -6335,13 +6347,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
return;
|
||||
};
|
||||
|
||||
let diagnostic = builder.into_diagnostic(format_args!(
|
||||
let mut diagnostic = builder.into_diagnostic(format_args!(
|
||||
"Module `{thispackage_name}` has no submodule `{final_part}`"
|
||||
));
|
||||
|
||||
hint_if_stdlib_submodule_exists_on_other_versions(
|
||||
self.db(),
|
||||
diagnostic,
|
||||
&mut diagnostic,
|
||||
&full_submodule_name,
|
||||
module,
|
||||
);
|
||||
@@ -7933,7 +7945,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
// TODO: Useful inference of a lambda's return type will require a different approach,
|
||||
// which does the inference of the body expression based on arguments at each call site,
|
||||
// rather than eagerly computing a return type without knowing the argument types.
|
||||
CallableType::function_like(self.db(), Signature::new(parameters, Some(Type::unknown())))
|
||||
Type::function_like_callable(self.db(), Signature::new(parameters, Some(Type::unknown())))
|
||||
}
|
||||
|
||||
fn infer_call_expression(
|
||||
@@ -9071,6 +9083,36 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if let Type::ModuleLiteral(module) = value_type {
|
||||
let module = module.module(db);
|
||||
let module_name = module.name(db);
|
||||
if module.kind(db).is_package()
|
||||
&& let Some(relative_submodule) = ModuleName::new(attr_name)
|
||||
{
|
||||
let mut maybe_submodule_name = module_name.clone();
|
||||
maybe_submodule_name.extend(&relative_submodule);
|
||||
if let Some(submodule) = resolve_module(db, &maybe_submodule_name) {
|
||||
if let Some(builder) = self
|
||||
.context
|
||||
.report_lint(&POSSIBLY_MISSING_ATTRIBUTE, attribute)
|
||||
{
|
||||
let mut diag = builder.into_diagnostic(format_args!(
|
||||
"Submodule `{attr_name}` may not be available as an attribute \
|
||||
on module `{module_name}`"
|
||||
));
|
||||
diag.help(format_args!(
|
||||
"Consider explicitly importing `{maybe_submodule_name}`"
|
||||
));
|
||||
}
|
||||
return TypeAndQualifiers::new(
|
||||
Type::module_literal(db, self.file(), submodule),
|
||||
TypeOrigin::Inferred,
|
||||
TypeQualifiers::empty(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let Some(builder) = self.context.report_lint(&UNRESOLVED_ATTRIBUTE, attribute)
|
||||
else {
|
||||
return fallback();
|
||||
@@ -9086,30 +9128,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
}
|
||||
|
||||
let diagnostic = match value_type {
|
||||
Type::ModuleLiteral(module) => {
|
||||
let module = module.module(db);
|
||||
let module_name = module.name(db);
|
||||
if module.kind(db).is_package()
|
||||
&& let Some(relative_submodule) = ModuleName::new(attr_name)
|
||||
{
|
||||
let mut maybe_submodule_name = module_name.clone();
|
||||
maybe_submodule_name.extend(&relative_submodule);
|
||||
if resolve_module(db, &maybe_submodule_name).is_some() {
|
||||
let mut diag = builder.into_diagnostic(format_args!(
|
||||
"Submodule `{attr_name}` may not be available as an attribute \
|
||||
on module `{module_name}`"
|
||||
));
|
||||
diag.help(format_args!(
|
||||
"Consider explicitly importing `{maybe_submodule_name}`"
|
||||
));
|
||||
return fallback();
|
||||
}
|
||||
}
|
||||
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Module `{module_name}` has no member `{attr_name}`",
|
||||
))
|
||||
}
|
||||
Type::ModuleLiteral(module) => builder.into_diagnostic(format_args!(
|
||||
"Module `{module_name}` has no member `{attr_name}`",
|
||||
module_name = module.module(db).name(db),
|
||||
)),
|
||||
Type::ClassLiteral(class) => builder.into_diagnostic(format_args!(
|
||||
"Class `{}` has no attribute `{attr_name}`",
|
||||
class.name(db),
|
||||
@@ -9131,8 +9153,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
hint_if_stdlib_attribute_exists_on_other_versions(
|
||||
db,
|
||||
diagnostic,
|
||||
&value_type,
|
||||
value_type,
|
||||
attr_name,
|
||||
&format!("resolving the `{attr_name}` attribute"),
|
||||
);
|
||||
|
||||
fallback()
|
||||
|
||||
@@ -1011,7 +1011,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||
let callable_type = if let (Some(parameters), Some(return_type), true) =
|
||||
(parameters, return_type, correct_argument_number)
|
||||
{
|
||||
CallableType::single(db, Signature::new(parameters, Some(return_type)))
|
||||
Type::single_callable(db, Signature::new(parameters, Some(return_type)))
|
||||
} else {
|
||||
CallableType::unknown(db)
|
||||
};
|
||||
@@ -1227,7 +1227,10 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||
|
||||
let argument_type = self.infer_expression(&arguments[0], TypeContext::default());
|
||||
|
||||
let Some(callable_type) = argument_type.try_upcast_to_callable(db) else {
|
||||
let Some(callable_type) = argument_type
|
||||
.try_upcast_to_callable(db)
|
||||
.map(|callables| callables.into_type(self.db()))
|
||||
else {
|
||||
if let Some(builder) = self
|
||||
.context
|
||||
.report_lint(&INVALID_TYPE_FORM, arguments_slice)
|
||||
|
||||
@@ -126,7 +126,10 @@ fn check_class_declaration<'db>(
|
||||
break;
|
||||
};
|
||||
|
||||
let Some(superclass_type_as_callable) = superclass_type.try_upcast_to_callable(db) else {
|
||||
let Some(superclass_type_as_callable) = superclass_type
|
||||
.try_upcast_to_callable(db)
|
||||
.map(|callables| callables.into_type(db))
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ use crate::place::{builtins_symbol, known_module_symbol};
|
||||
use crate::types::enums::is_single_member_enum;
|
||||
use crate::types::tuple::TupleType;
|
||||
use crate::types::{
|
||||
BoundMethodType, CallableType, EnumLiteralType, IntersectionBuilder, KnownClass, Parameter,
|
||||
Parameters, Signature, SpecialFormType, SubclassOfType, Type, UnionType,
|
||||
BoundMethodType, EnumLiteralType, IntersectionBuilder, KnownClass, Parameter, Parameters,
|
||||
Signature, SpecialFormType, SubclassOfType, Type, UnionType,
|
||||
};
|
||||
use crate::{Db, module_resolver::KnownModule};
|
||||
use quickcheck::{Arbitrary, Gen};
|
||||
@@ -229,7 +229,7 @@ impl Ty {
|
||||
|
||||
create_bound_method(db, function, builtins_class)
|
||||
}
|
||||
Ty::Callable { params, returns } => CallableType::single(
|
||||
Ty::Callable { params, returns } => Type::single_callable(
|
||||
db,
|
||||
Signature::new(
|
||||
params.into_parameters(db),
|
||||
|
||||
@@ -202,7 +202,7 @@ impl<'db> ProtocolInterface<'db> {
|
||||
Parameters::new([Parameter::positional_only(Some(Name::new_static("self")))]),
|
||||
Some(ty.normalized(db)),
|
||||
);
|
||||
let property_getter = CallableType::single(db, property_getter_signature);
|
||||
let property_getter = Type::single_callable(db, property_getter_signature);
|
||||
let property = PropertyInstanceType::new(db, Some(property_getter), None);
|
||||
(
|
||||
Name::new(name),
|
||||
@@ -300,7 +300,7 @@ impl<'db> ProtocolInterface<'db> {
|
||||
.and(db, || {
|
||||
our_type.has_relation_to_impl(
|
||||
db,
|
||||
Type::Callable(other_type.bind_self(db)),
|
||||
Type::Callable(other_type.bind_self(db, None)),
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
@@ -311,9 +311,9 @@ impl<'db> ProtocolInterface<'db> {
|
||||
(
|
||||
ProtocolMemberKind::Method(our_method),
|
||||
ProtocolMemberKind::Method(other_method),
|
||||
) => our_method.bind_self(db).has_relation_to_impl(
|
||||
) => our_method.bind_self(db, None).has_relation_to_impl(
|
||||
db,
|
||||
other_method.bind_self(db),
|
||||
other_method.bind_self(db, None),
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
@@ -676,10 +676,7 @@ impl<'a, 'db> ProtocolMember<'a, 'db> {
|
||||
// unfortunately not sufficient to obtain the `Callable` supertypes of these types, due to the
|
||||
// complex interaction between `__new__`, `__init__` and metaclass `__call__`.
|
||||
let attribute_type = if self.name == "__call__" {
|
||||
let Some(attribute_type) = other.try_upcast_to_callable(db) else {
|
||||
return ConstraintSet::from(false);
|
||||
};
|
||||
attribute_type
|
||||
other
|
||||
} else {
|
||||
let Place::Defined(attribute_type, _, Definedness::AlwaysDefined) = other
|
||||
.invoke_descriptor_protocol(
|
||||
@@ -696,14 +693,32 @@ impl<'a, 'db> ProtocolMember<'a, 'db> {
|
||||
attribute_type
|
||||
};
|
||||
|
||||
attribute_type.has_relation_to_impl(
|
||||
db,
|
||||
Type::Callable(method.bind_self(db)),
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
// TODO: Instances of `typing.Self` in the protocol member should specialize to the
|
||||
// type that we are checking. Without this, we will treat `Self` as an inferable
|
||||
// typevar, and allow it to match against _any_ type.
|
||||
//
|
||||
// It's not very principled, but we also use the literal fallback type, instead of
|
||||
// `other` directly. This lets us check whether things like `Literal[0]` satisfy a
|
||||
// protocol that includes methods that have `typing.Self` annotations, without
|
||||
// overly constraining `Self` to that specific literal.
|
||||
//
|
||||
// With the new solver, we should be to replace all of this with an additional
|
||||
// constraint that enforces what `Self` can specialize to.
|
||||
let fallback_other = other.literal_fallback_instance(db).unwrap_or(other);
|
||||
attribute_type
|
||||
.try_upcast_to_callable(db)
|
||||
.when_some_and(|callables| {
|
||||
callables
|
||||
.map(|callable| callable.apply_self(db, fallback_other))
|
||||
.has_relation_to_impl(
|
||||
db,
|
||||
method.bind_self(db, Some(fallback_other)),
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
})
|
||||
}
|
||||
// TODO: consider the types of the attribute on `other` for property members
|
||||
ProtocolMemberKind::Property(_) => ConstraintSet::from(matches!(
|
||||
|
||||
@@ -213,6 +213,19 @@ impl<'db> CallableSignature<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Replaces any occurrences of `typing.Self` in the parameter and return annotations with the
|
||||
/// given type. (Does not bind the `self` parameter; to do that, use
|
||||
/// [`bind_self`][Self::bind_self].)
|
||||
pub(crate) fn apply_self(&self, db: &'db dyn Db, self_type: Type<'db>) -> Self {
|
||||
Self {
|
||||
overloads: self
|
||||
.overloads
|
||||
.iter()
|
||||
.map(|signature| signature.apply_self(db, self_type))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_subtype_of_impl(
|
||||
&self,
|
||||
db: &'db dyn Db,
|
||||
@@ -628,6 +641,28 @@ impl<'db> Signature<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn apply_self(&self, db: &'db dyn Db, self_type: Type<'db>) -> Self {
|
||||
let parameters = self.parameters.apply_type_mapping_impl(
|
||||
db,
|
||||
&TypeMapping::BindSelf(self_type),
|
||||
TypeContext::default(),
|
||||
&ApplyTypeMappingVisitor::default(),
|
||||
);
|
||||
let return_ty = self.return_ty.map(|ty| {
|
||||
ty.apply_type_mapping(
|
||||
db,
|
||||
&TypeMapping::BindSelf(self_type),
|
||||
TypeContext::default(),
|
||||
)
|
||||
});
|
||||
Self {
|
||||
generic_context: self.generic_context,
|
||||
definition: self.definition,
|
||||
parameters,
|
||||
return_ty,
|
||||
}
|
||||
}
|
||||
|
||||
fn inferable_typevars(&self, db: &'db dyn Db) -> InferableTypeVars<'db, 'db> {
|
||||
match self.generic_context {
|
||||
Some(generic_context) => generic_context.inferable_typevars(db),
|
||||
|
||||
Reference in New Issue
Block a user