Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions crates/ruff_db/src/files.rs
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,10 @@ impl File {
.map_or(PySourceType::Python, PySourceType::from_extension),
}
}

pub fn structural_ordering(self, db: &dyn Db, other: Self) -> std::cmp::Ordering {
self.path(db).cmp(other.path(db))
}
}

impl fmt::Debug for File {
Expand Down
2 changes: 1 addition & 1 deletion crates/ruff_db/src/files/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use std::fmt::{Display, Formatter};
/// * a file stored on the [host system](crate::system::System).
/// * a virtual file stored on the [host system](crate::system::System).
/// * a vendored file stored in the [vendored file system](crate::vendored::VendoredFileSystem).
#[derive(Clone, Debug, Eq, PartialEq, Hash, get_size2::GetSize)]
#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, get_size2::GetSize)]
pub enum FilePath {
/// Path to a file on the [host system](crate::system::System).
System(SystemPathBuf),
Expand Down
2 changes: 1 addition & 1 deletion crates/ruff_db/src/vendored/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ impl ToOwned for VendoredPath {
}

#[repr(transparent)]
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
#[derive(Debug, Eq, PartialEq, Clone, Hash, PartialOrd, Ord)]
pub struct VendoredPathBuf(Utf8PathBuf);

impl get_size2::GetSize for VendoredPathBuf {
Expand Down
46 changes: 23 additions & 23 deletions crates/ty_python_semantic/resources/mdtest/attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -2325,7 +2325,7 @@ class C:
def copy(self, other: "C"):
self.x = other.x

reveal_type(C().x) # revealed: Unknown | Literal[1]
reveal_type(C().x) # revealed: Literal[1] | Unknown
```

If the only assignment to a name is cyclic, we just infer `Unknown` for that attribute:
Expand Down Expand Up @@ -2381,8 +2381,8 @@ class B:
def copy(self, other: "A"):
self.x = other.x

reveal_type(B().x) # revealed: Unknown | Literal[1]
reveal_type(A().x) # revealed: Unknown | Literal[1]
reveal_type(B().x) # revealed: Literal[1] | Unknown
reveal_type(A().x) # revealed: Literal[1] | Unknown

class Base:
def flip(self) -> "Sub":
Expand All @@ -2400,7 +2400,7 @@ class C2:
def replace_with(self, other: "C2"):
self.x = other.x.flip()

reveal_type(C2(Sub()).x) # revealed: Unknown | Base
reveal_type(C2(Sub()).x) # revealed: Base | Unknown

class C3:
def __init__(self, x: Sub):
Expand All @@ -2409,8 +2409,8 @@ class C3:
def replace_with(self, other: "C3"):
self.x = [self.x[0].flip()]

# TODO: should be `Unknown | list[Unknown | Sub] | list[Unknown | Base]`
reveal_type(C3(Sub()).x) # revealed: Unknown | list[Unknown | Sub] | list[Divergent]
# TODO: should be `list[Unknown | Sub] | list[Unknown | Base] | Unknown`
reveal_type(C3(Sub()).x) # revealed: list[Divergent] | list[Unknown | Sub] | Unknown
```

And cycles between many attributes:
Expand Down Expand Up @@ -2453,13 +2453,13 @@ class ManyCycles:
self.x6 = self.x1 + self.x2 + self.x3 + self.x4 + self.x5 + self.x7
self.x7 = self.x1 + self.x2 + self.x3 + self.x4 + self.x5 + self.x6

reveal_type(self.x1) # revealed: Unknown | int
reveal_type(self.x2) # revealed: Unknown | int
reveal_type(self.x3) # revealed: Unknown | int
reveal_type(self.x4) # revealed: Unknown | int
reveal_type(self.x5) # revealed: Unknown | int
reveal_type(self.x6) # revealed: Unknown | int
reveal_type(self.x7) # revealed: Unknown | int
reveal_type(self.x1) # revealed: int | Unknown
reveal_type(self.x2) # revealed: int | Unknown
reveal_type(self.x3) # revealed: int | Unknown
reveal_type(self.x4) # revealed: int | Unknown
reveal_type(self.x5) # revealed: int | Unknown
reveal_type(self.x6) # revealed: int | Unknown
reveal_type(self.x7) # revealed: int | Unknown

class ManyCycles2:
def __init__(self: "ManyCycles2"):
Expand All @@ -2468,8 +2468,8 @@ class ManyCycles2:
self.x3 = [1]

def f1(self: "ManyCycles2"):
# TODO: should be Unknown | list[Unknown | int] | list[Divergent]
reveal_type(self.x3) # revealed: Unknown | list[Unknown | int] | list[Divergent] | list[Divergent]
# TODO: should be list[Unknown | int] | list[Divergent] | Unknown
reveal_type(self.x3) # revealed: list[Divergent] | list[Divergent] | list[Unknown | int] | Unknown

self.x1 = [self.x2] + [self.x3]
self.x2 = [self.x1] + [self.x3]
Expand Down Expand Up @@ -2528,7 +2528,7 @@ class Counter:
def increment(self: "Counter"):
self.count = self.count + 1

reveal_type(Counter().count) # revealed: Unknown | int
reveal_type(Counter().count) # revealed: int | Unknown
```

We also handle infinitely nested generics:
Expand All @@ -2541,7 +2541,7 @@ class NestedLists:
def f(self: "NestedLists"):
self.x = [self.x]

reveal_type(NestedLists().x) # revealed: Unknown | Literal[1] | list[Divergent]
reveal_type(NestedLists().x) # revealed: Literal[1] | list[Divergent] | Unknown

class NestedMixed:
def f(self: "NestedMixed"):
Expand All @@ -2550,7 +2550,7 @@ class NestedMixed:
def g(self: "NestedMixed"):
self.x = {self.x}

reveal_type(NestedMixed().x) # revealed: Unknown | list[Divergent] | set[Divergent]
reveal_type(NestedMixed().x) # revealed: list[Divergent] | set[Divergent] | Unknown
```

And cases where the types originate from annotations:
Expand All @@ -2567,7 +2567,7 @@ class NestedLists2:
def f(self: "NestedLists2"):
self.x = make_list(self.x)

reveal_type(NestedLists2().x) # revealed: Unknown | list[Divergent]
reveal_type(NestedLists2().x) # revealed: list[Divergent] | Unknown
```

### Builtin types attributes
Expand Down Expand Up @@ -2673,7 +2673,7 @@ class C:
def f(self, other: "C"):
self.x = (other.x, 1)

reveal_type(C().x) # revealed: Unknown | tuple[Divergent, Literal[1]]
reveal_type(C().x) # revealed: tuple[Divergent, Literal[1]] | Unknown
reveal_type(C().x[0]) # revealed: Unknown | Divergent
```

Expand All @@ -2691,7 +2691,7 @@ class D:
def f(self, other: "D"):
self.x = make_tuple(other.x)

reveal_type(D().x) # revealed: Unknown | tuple[Divergent, Literal[1]]
reveal_type(D().x) # revealed: tuple[Divergent, Literal[1]] | Unknown
```

The tuple type may also expand exponentially "in breadth":
Expand All @@ -2704,7 +2704,7 @@ class E:
def f(self: "E"):
self.x = duplicate(self.x)

reveal_type(E().x) # revealed: Unknown | tuple[Divergent, Divergent]
reveal_type(E().x) # revealed: tuple[Divergent, Divergent] | Unknown
```

And it also works for homogeneous tuples:
Expand All @@ -2717,7 +2717,7 @@ class F:
def f(self, other: "F"):
self.x = make_homogeneous_tuple(other.x)

reveal_type(F().x) # revealed: Unknown | tuple[Divergent, ...]
reveal_type(F().x) # revealed: tuple[Divergent, ...] | Unknown
```

## Attributes of standard library modules that aren't yet defined
Expand Down
4 changes: 2 additions & 2 deletions crates/ty_python_semantic/resources/mdtest/cycle.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ class Point:
self.x, self.y = other.x, other.y

p = Point()
reveal_type(p.x) # revealed: Unknown | int
reveal_type(p.y) # revealed: Unknown | int
reveal_type(p.x) # revealed: int | Unknown
reveal_type(p.y) # revealed: int | Unknown
```

## Self-referential bare type alias
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,9 +248,9 @@ IntOrStr = TypeAliasType(get_name(), int | str)
type OptNestedInt = int | tuple[OptNestedInt, ...] | None

def f(x: OptNestedInt) -> None:
reveal_type(x) # revealed: int | tuple[OptNestedInt, ...] | None
reveal_type(x) # revealed: tuple[OptNestedInt, ...] | None | int
if x is not None:
reveal_type(x) # revealed: int | tuple[OptNestedInt, ...]
reveal_type(x) # revealed: tuple[OptNestedInt, ...] | int
```

### Invalid self-referential
Expand Down Expand Up @@ -327,7 +327,7 @@ class B(A[Alias]):

def f(b: B):
reveal_type(b) # revealed: B
reveal_type(b.attr) # revealed: list[Alias] | int
reveal_type(b.attr) # revealed: int | list[Alias]
```

### Mutually recursive
Expand Down Expand Up @@ -450,5 +450,5 @@ type Y = X | str | dict[str, Y]

def _(y: Y):
if isinstance(y, dict):
reveal_type(y) # revealed: dict[str, X] | dict[str, Y]
reveal_type(y) # revealed: dict[str, Y] | dict[str, X]
```
2 changes: 1 addition & 1 deletion crates/ty_python_semantic/resources/mdtest/protocols.md
Original file line number Diff line number Diff line change
Expand Up @@ -3029,7 +3029,7 @@ class B(A[P[int]]):
def f(b: B):
reveal_type(b) # revealed: B
reveal_type(b.attr) # revealed: P[int]
reveal_type(b.attr.attr) # revealed: P[int] | int
reveal_type(b.attr.attr) # revealed: int | P[int]
```

### Recursive generic protocols with property members
Expand Down
19 changes: 19 additions & 0 deletions crates/ty_python_semantic/src/module_resolver/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,17 @@ impl<'db> Module<'db> {
.as_deref()
.unwrap_or_default()
}

pub(crate) fn structural_ordering(self, db: &'db dyn Db, other: Self) -> std::cmp::Ordering {
match (self, other) {
(Module::File(left), Module::File(right)) => left.structural_ordering(db, right),
(Module::Namespace(left), Module::Namespace(right)) => {
left.name(db).cmp(right.name(db))
}
(Module::File(_), Module::Namespace(_)) => std::cmp::Ordering::Less,
(Module::Namespace(_), Module::File(_)) => std::cmp::Ordering::Greater,
}
}
}

impl std::fmt::Debug for Module<'_> {
Expand Down Expand Up @@ -274,6 +285,14 @@ pub struct FileModule<'db> {
pub(super) known: Option<KnownModule>,
}

impl FileModule<'_> {
pub(crate) fn structural_ordering(self, db: &dyn Db, other: Self) -> std::cmp::Ordering {
self.name(db)
.cmp(other.name(db))
.then_with(|| self.file(db).structural_ordering(db, other.file(db)))
}
}

/// A namespace package.
///
/// Namespace packages are special because there are
Expand Down
11 changes: 11 additions & 0 deletions crates/ty_python_semantic/src/semantic_index/definition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,17 @@ impl<'db> Definition<'db> {
_ => None,
}
}

pub(crate) fn structural_ordering(
self,
db: &'db dyn Db,
other: Definition<'db>,
) -> std::cmp::Ordering {
self.file(db)
.cmp(&other.file(db))
.then_with(|| self.file_scope(db).cmp(&other.file_scope(db)))
.then_with(|| self.place(db).cmp(&other.place(db)))
}
}

/// Get the module-level docstring for the given file
Expand Down
2 changes: 1 addition & 1 deletion crates/ty_python_semantic/src/semantic_index/member.rs
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ impl Hash for MemberExprRef<'_> {

/// Uniquely identifies a member in a scope.
#[newtype_index]
#[derive(get_size2::GetSize, salsa::Update)]
#[derive(PartialOrd, Ord, get_size2::GetSize, salsa::Update)]
pub struct ScopedMemberId;

/// The members of a scope. Allows lookup by member path and [`ScopedMemberId`].
Expand Down
4 changes: 3 additions & 1 deletion crates/ty_python_semantic/src/semantic_index/place.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,9 @@ impl std::fmt::Display for PlaceExprRef<'_> {
}

/// ID that uniquely identifies a place inside a [`Scope`](super::FileScopeId).
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, get_size2::GetSize, salsa::Update)]
#[derive(
Debug, Copy, Clone, Eq, PartialEq, Hash, PartialOrd, Ord, get_size2::GetSize, salsa::Update,
)]
pub enum ScopedPlaceId {
Symbol(ScopedSymbolId),
Member(ScopedMemberId),
Expand Down
12 changes: 11 additions & 1 deletion crates/ty_python_semantic/src/semantic_index/scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,21 @@ impl<'db> ScopeId<'db> {
NodeWithScopeKind::GeneratorExpression(_) => "<generator>",
}
}

pub(crate) fn structural_ordering(
self,
db: &'db dyn Db,
other: ScopeId<'db>,
) -> std::cmp::Ordering {
self.file(db)
.cmp(&other.file(db))
.then_with(|| self.file_scope_id(db).cmp(&other.file_scope_id(db)))
}
}

/// ID that uniquely identifies a scope inside of a module.
#[newtype_index]
#[derive(salsa::Update, get_size2::GetSize)]
#[derive(salsa::Update, get_size2::GetSize, PartialOrd, Ord)]
pub struct FileScopeId;

impl FileScopeId {
Expand Down
2 changes: 1 addition & 1 deletion crates/ty_python_semantic/src/semantic_index/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use std::ops::{Deref, DerefMut};

/// Uniquely identifies a symbol in a given scope.
#[newtype_index]
#[derive(get_size2::GetSize)]
#[derive(PartialOrd, Ord, get_size2::GetSize)]
pub struct ScopedSymbolId;

/// A symbol in a given scope.
Expand Down
Loading
Loading