mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 05:45:24 +00:00
Migrate some inference tests to mdtests (#14795)
As part of #13696, this PR ports a smallish number of inference tests over to the mdtest framework.
This commit is contained in:
parent
b01a651e69
commit
918358aaa6
8 changed files with 139 additions and 248 deletions
|
@ -18,43 +18,3 @@ Note: in this particular example, one could argue that the most likely error wou
|
||||||
of the `x`/`foo` definitions, and so it could be desirable to infer `Literal[1]` for the type of
|
of the `x`/`foo` definitions, and so it could be desirable to infer `Literal[1]` for the type of
|
||||||
`x`. On the other hand, there might be a variable `fob` a little higher up in this file, and the
|
`x`. On the other hand, there might be a variable `fob` a little higher up in this file, and the
|
||||||
actual error might have been just a typo. Inferring `Unknown` thus seems like the safest option.
|
actual error might have been just a typo. Inferring `Unknown` thus seems like the safest option.
|
||||||
|
|
||||||
## Unbound class variable
|
|
||||||
|
|
||||||
Name lookups within a class scope fall back to globals, but lookups of class attributes don't.
|
|
||||||
|
|
||||||
```py
|
|
||||||
def bool_instance() -> bool:
|
|
||||||
return True
|
|
||||||
|
|
||||||
flag = bool_instance()
|
|
||||||
x = 1
|
|
||||||
|
|
||||||
class C:
|
|
||||||
y = x
|
|
||||||
if flag:
|
|
||||||
x = 2
|
|
||||||
|
|
||||||
# error: [possibly-unbound-attribute] "Attribute `x` on type `Literal[C]` is possibly unbound"
|
|
||||||
reveal_type(C.x) # revealed: Literal[2]
|
|
||||||
reveal_type(C.y) # revealed: Literal[1]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Possibly unbound in class and global scope
|
|
||||||
|
|
||||||
```py
|
|
||||||
def bool_instance() -> bool:
|
|
||||||
return True
|
|
||||||
|
|
||||||
if bool_instance():
|
|
||||||
x = "abc"
|
|
||||||
|
|
||||||
class C:
|
|
||||||
if bool_instance():
|
|
||||||
x = 1
|
|
||||||
|
|
||||||
# error: [possibly-unresolved-reference]
|
|
||||||
y = x
|
|
||||||
|
|
||||||
reveal_type(C.y) # revealed: Literal[1] | Literal["abc"]
|
|
||||||
```
|
|
||||||
|
|
|
@ -55,3 +55,24 @@ from b import x
|
||||||
|
|
||||||
x = "foo" # error: [invalid-assignment] "Object of type `Literal["foo"]"
|
x = "foo" # error: [invalid-assignment] "Object of type `Literal["foo"]"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Import cycle
|
||||||
|
|
||||||
|
```py path=a.py
|
||||||
|
class A: ...
|
||||||
|
|
||||||
|
reveal_type(A.__mro__) # revealed: tuple[Literal[A], Literal[object]]
|
||||||
|
import b
|
||||||
|
|
||||||
|
class C(b.B): ...
|
||||||
|
|
||||||
|
reveal_type(C.__mro__) # revealed: tuple[Literal[C], Literal[B], Literal[A], Literal[object]]
|
||||||
|
```
|
||||||
|
|
||||||
|
```py path=b.py
|
||||||
|
from a import A
|
||||||
|
|
||||||
|
class B(A): ...
|
||||||
|
|
||||||
|
reveal_type(B.__mro__) # revealed: tuple[Literal[B], Literal[A], Literal[object]]
|
||||||
|
```
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
# Invalid syntax
|
||||||
|
|
||||||
|
## Missing module name
|
||||||
|
|
||||||
|
```py
|
||||||
|
from import bar # error: [invalid-syntax]
|
||||||
|
|
||||||
|
reveal_type(bar) # revealed: Unknown
|
||||||
|
```
|
|
@ -0,0 +1,7 @@
|
||||||
|
# Ellipsis literals
|
||||||
|
|
||||||
|
## Simple
|
||||||
|
|
||||||
|
```py
|
||||||
|
reveal_type(...) # revealed: EllipsisType | ellipsis
|
||||||
|
```
|
|
@ -0,0 +1,45 @@
|
||||||
|
# Nonlocal references
|
||||||
|
|
||||||
|
## One level up
|
||||||
|
|
||||||
|
```py
|
||||||
|
def f():
|
||||||
|
x = 1
|
||||||
|
def g():
|
||||||
|
reveal_type(x) # revealed: Literal[1]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Two levels up
|
||||||
|
|
||||||
|
```py
|
||||||
|
def f():
|
||||||
|
x = 1
|
||||||
|
def g():
|
||||||
|
def h():
|
||||||
|
reveal_type(x) # revealed: Literal[1]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Skips class scope
|
||||||
|
|
||||||
|
```py
|
||||||
|
def f():
|
||||||
|
x = 1
|
||||||
|
|
||||||
|
class C:
|
||||||
|
x = 2
|
||||||
|
def g():
|
||||||
|
reveal_type(x) # revealed: Literal[1]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Skips annotation-only assignment
|
||||||
|
|
||||||
|
```py
|
||||||
|
def f():
|
||||||
|
x = 1
|
||||||
|
def g():
|
||||||
|
# it's pretty weird to have an annotated assignment in a function where the
|
||||||
|
# name is otherwise not defined; maybe should be an error?
|
||||||
|
x: int
|
||||||
|
def h():
|
||||||
|
reveal_type(x) # revealed: Literal[1]
|
||||||
|
```
|
|
@ -0,0 +1,57 @@
|
||||||
|
# Unbound
|
||||||
|
|
||||||
|
## Unbound class variable
|
||||||
|
|
||||||
|
Name lookups within a class scope fall back to globals, but lookups of class attributes don't.
|
||||||
|
|
||||||
|
```py
|
||||||
|
def bool_instance() -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
|
flag = bool_instance()
|
||||||
|
x = 1
|
||||||
|
|
||||||
|
class C:
|
||||||
|
y = x
|
||||||
|
if flag:
|
||||||
|
x = 2
|
||||||
|
|
||||||
|
# error: [possibly-unbound-attribute] "Attribute `x` on type `Literal[C]` is possibly unbound"
|
||||||
|
reveal_type(C.x) # revealed: Literal[2]
|
||||||
|
reveal_type(C.y) # revealed: Literal[1]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Possibly unbound in class and global scope
|
||||||
|
|
||||||
|
```py
|
||||||
|
def bool_instance() -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if bool_instance():
|
||||||
|
x = "abc"
|
||||||
|
|
||||||
|
class C:
|
||||||
|
if bool_instance():
|
||||||
|
x = 1
|
||||||
|
|
||||||
|
# error: [possibly-unresolved-reference]
|
||||||
|
y = x
|
||||||
|
|
||||||
|
reveal_type(C.y) # revealed: Literal[1] | Literal["abc"]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Unbound function local
|
||||||
|
|
||||||
|
An unbound function local that has definitions in the scope does not fall back to globals.
|
||||||
|
|
||||||
|
```py
|
||||||
|
x = 1
|
||||||
|
|
||||||
|
def f():
|
||||||
|
# error: [unresolved-reference]
|
||||||
|
# revealed: Unknown
|
||||||
|
reveal_type(x)
|
||||||
|
x = 2
|
||||||
|
# revealed: Literal[2]
|
||||||
|
reveal_type(x)
|
||||||
|
```
|
|
@ -5123,16 +5123,6 @@ mod tests {
|
||||||
assert_diagnostic_messages(diagnostics, expected);
|
assert_diagnostic_messages(diagnostics, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn from_import_with_no_module_name() -> anyhow::Result<()> {
|
|
||||||
// This test checks that invalid syntax in a `StmtImportFrom` node
|
|
||||||
// leads to the type being inferred as `Unknown`
|
|
||||||
let mut db = setup_db();
|
|
||||||
db.write_file("src/foo.py", "from import bar")?;
|
|
||||||
assert_public_ty(&db, "src/foo.py", "bar", "Unknown");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn resolve_method() -> anyhow::Result<()> {
|
fn resolve_method() -> anyhow::Result<()> {
|
||||||
let mut db = setup_db();
|
let mut db = setup_db();
|
||||||
|
@ -5282,112 +5272,6 @@ mod tests {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn bytes_type() -> anyhow::Result<()> {
|
|
||||||
let mut db = setup_db();
|
|
||||||
|
|
||||||
db.write_dedented(
|
|
||||||
"src/a.py",
|
|
||||||
"
|
|
||||||
w = b'red' b'knot'
|
|
||||||
x = b'hello'
|
|
||||||
y = b'world' + b'!'
|
|
||||||
z = b'\\xff\\x00'
|
|
||||||
",
|
|
||||||
)?;
|
|
||||||
|
|
||||||
assert_public_ty(&db, "src/a.py", "w", "Literal[b\"redknot\"]");
|
|
||||||
assert_public_ty(&db, "src/a.py", "x", "Literal[b\"hello\"]");
|
|
||||||
assert_public_ty(&db, "src/a.py", "y", "Literal[b\"world!\"]");
|
|
||||||
assert_public_ty(&db, "src/a.py", "z", "Literal[b\"\\xff\\x00\"]");
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn ellipsis_type() -> anyhow::Result<()> {
|
|
||||||
let mut db = setup_db();
|
|
||||||
|
|
||||||
db.write_dedented(
|
|
||||||
"src/a.py",
|
|
||||||
"
|
|
||||||
x = ...
|
|
||||||
",
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// TODO: sys.version_info
|
|
||||||
assert_public_ty(&db, "src/a.py", "x", "EllipsisType | ellipsis");
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn import_cycle() -> anyhow::Result<()> {
|
|
||||||
let mut db = setup_db();
|
|
||||||
|
|
||||||
db.write_dedented(
|
|
||||||
"src/a.py",
|
|
||||||
"
|
|
||||||
class A: pass
|
|
||||||
import b
|
|
||||||
class C(b.B): pass
|
|
||||||
",
|
|
||||||
)?;
|
|
||||||
db.write_dedented(
|
|
||||||
"src/b.py",
|
|
||||||
"
|
|
||||||
from a import A
|
|
||||||
class B(A): pass
|
|
||||||
",
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let a = system_path_to_file(&db, "src/a.py").expect("file to exist");
|
|
||||||
let c_ty = global_symbol(&db, a, "C").expect_type();
|
|
||||||
let c_class = c_ty.expect_class_literal().class;
|
|
||||||
let mut c_mro = c_class.iter_mro(&db);
|
|
||||||
let b_ty = c_mro.nth(1).unwrap();
|
|
||||||
let b_class = b_ty.expect_class_base();
|
|
||||||
assert_eq!(b_class.name(&db), "B");
|
|
||||||
let mut b_mro = b_class.iter_mro(&db);
|
|
||||||
let a_ty = b_mro.nth(1).unwrap();
|
|
||||||
let a_class = a_ty.expect_class_base();
|
|
||||||
assert_eq!(a_class.name(&db), "A");
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An unbound function local that has definitions in the scope does not fall back to globals.
|
|
||||||
#[test]
|
|
||||||
fn unbound_function_local() -> anyhow::Result<()> {
|
|
||||||
let mut db = setup_db();
|
|
||||||
|
|
||||||
db.write_dedented(
|
|
||||||
"src/a.py",
|
|
||||||
"
|
|
||||||
x = 1
|
|
||||||
def f():
|
|
||||||
y = x
|
|
||||||
x = 2
|
|
||||||
",
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let file = system_path_to_file(&db, "src/a.py").expect("file to exist");
|
|
||||||
let index = semantic_index(&db, file);
|
|
||||||
let function_scope = index
|
|
||||||
.child_scopes(FileScopeId::global())
|
|
||||||
.next()
|
|
||||||
.unwrap()
|
|
||||||
.0
|
|
||||||
.to_scope_id(&db, file);
|
|
||||||
let y_ty = symbol(&db, function_scope, "y").expect_type();
|
|
||||||
let x_ty = symbol(&db, function_scope, "x").expect_type();
|
|
||||||
|
|
||||||
assert_eq!(y_ty.display(&db).to_string(), "Unknown");
|
|
||||||
assert_eq!(x_ty.display(&db).to_string(), "Literal[2]");
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A name reference to a never-defined symbol in a function is implicitly a global lookup.
|
/// A name reference to a never-defined symbol in a function is implicitly a global lookup.
|
||||||
#[test]
|
#[test]
|
||||||
fn implicit_global_in_function() -> anyhow::Result<()> {
|
fn implicit_global_in_function() -> anyhow::Result<()> {
|
||||||
|
@ -5562,89 +5446,6 @@ mod tests {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn nonlocal_name_reference() -> anyhow::Result<()> {
|
|
||||||
let mut db = setup_db();
|
|
||||||
|
|
||||||
db.write_dedented(
|
|
||||||
"/src/a.py",
|
|
||||||
"
|
|
||||||
def f():
|
|
||||||
x = 1
|
|
||||||
def g():
|
|
||||||
y = x
|
|
||||||
",
|
|
||||||
)?;
|
|
||||||
|
|
||||||
assert_scope_ty(&db, "/src/a.py", &["f", "g"], "y", "Literal[1]");
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn nonlocal_name_reference_multi_level() -> anyhow::Result<()> {
|
|
||||||
let mut db = setup_db();
|
|
||||||
|
|
||||||
db.write_dedented(
|
|
||||||
"/src/a.py",
|
|
||||||
"
|
|
||||||
def f():
|
|
||||||
x = 1
|
|
||||||
def g():
|
|
||||||
def h():
|
|
||||||
y = x
|
|
||||||
",
|
|
||||||
)?;
|
|
||||||
|
|
||||||
assert_scope_ty(&db, "/src/a.py", &["f", "g", "h"], "y", "Literal[1]");
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn nonlocal_name_reference_skips_class_scope() -> anyhow::Result<()> {
|
|
||||||
let mut db = setup_db();
|
|
||||||
|
|
||||||
db.write_dedented(
|
|
||||||
"/src/a.py",
|
|
||||||
"
|
|
||||||
def f():
|
|
||||||
x = 1
|
|
||||||
class C:
|
|
||||||
x = 2
|
|
||||||
def g():
|
|
||||||
y = x
|
|
||||||
",
|
|
||||||
)?;
|
|
||||||
|
|
||||||
assert_scope_ty(&db, "/src/a.py", &["f", "C", "g"], "y", "Literal[1]");
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn nonlocal_name_reference_skips_annotation_only_assignment() -> anyhow::Result<()> {
|
|
||||||
let mut db = setup_db();
|
|
||||||
|
|
||||||
db.write_dedented(
|
|
||||||
"/src/a.py",
|
|
||||||
"
|
|
||||||
def f():
|
|
||||||
x = 1
|
|
||||||
def g():
|
|
||||||
// it's pretty weird to have an annotated assignment in a function where the
|
|
||||||
// name is otherwise not defined; maybe should be an error?
|
|
||||||
x: int
|
|
||||||
def h():
|
|
||||||
y = x
|
|
||||||
",
|
|
||||||
)?;
|
|
||||||
|
|
||||||
assert_scope_ty(&db, "/src/a.py", &["f", "g", "h"], "y", "Literal[1]");
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn basic_comprehension() -> anyhow::Result<()> {
|
fn basic_comprehension() -> anyhow::Result<()> {
|
||||||
let mut db = setup_db();
|
let mut db = setup_db();
|
||||||
|
|
|
@ -328,15 +328,6 @@ impl<'db> ClassBase<'db> {
|
||||||
Display { base: self, db }
|
Display { base: self, db }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
#[track_caller]
|
|
||||||
pub(super) fn expect_class_base(self) -> Class<'db> {
|
|
||||||
match self {
|
|
||||||
ClassBase::Class(class) => class,
|
|
||||||
_ => panic!("Expected a `ClassBase::Class()` variant"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return a `ClassBase` representing the class `builtins.object`
|
/// Return a `ClassBase` representing the class `builtins.object`
|
||||||
fn object(db: &'db dyn Db) -> Self {
|
fn object(db: &'db dyn Db) -> Self {
|
||||||
KnownClass::Object
|
KnownClass::Object
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue