Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
c9cc000
[ty] [WIP] handle recursive type inference properly
mtshiba Sep 24, 2025
2cfdf1d
Merge branch 'main' into recursive-inference
mtshiba Oct 24, 2025
041bb24
use the last provisional value of `cycle_fn`
mtshiba Oct 27, 2025
074f18f
`Divergent` has `salsa::Id`
mtshiba Oct 27, 2025
c71fd0b
handle cycles in more query functions
mtshiba Oct 27, 2025
de28c13
Merge branch 'main' into recursive-inference
mtshiba Oct 27, 2025
a1e3d93
Update 1377_iteration_count_mismatch.md
mtshiba Oct 27, 2025
e63e3bd
Merge branch 'main' into recursive-inference
mtshiba Oct 30, 2025
b852be7
update salsa
mtshiba Oct 30, 2025
b45ebbd
refactor
mtshiba Oct 31, 2025
d8cab29
refactor
mtshiba Oct 31, 2025
9115e40
simplify type joining performed within `cycle_recovery`
mtshiba Oct 31, 2025
d68e5c0
revert unnecessary changes
mtshiba Oct 31, 2025
b0a7804
Add examples of self-referential types
mtshiba Oct 31, 2025
0caa4b1
Merge branch 'main' into recursive-inference
mtshiba Nov 3, 2025
36c5257
refactor
mtshiba Nov 3, 2025
6cc30e4
implement `ProtocolInstanceType::recursive_type_normalized_impl`
mtshiba Nov 3, 2025
1a9d899
Merge branch 'main' into recursive-inference
mtshiba Nov 3, 2025
eda8017
the code in `pr_20962_comprehension_panics.md` no longer panics
mtshiba Nov 3, 2025
fc63d5f
Update attributes.md
mtshiba Nov 5, 2025
8c06a05
follow the changes in salsa-rs/salsa#1021
mtshiba Nov 5, 2025
3653427
Merge branch 'main' into recursive-inference
mtshiba Nov 5, 2025
ab84bdf
Do not use `UnionBuilder` to union the last provisional type and the …
mtshiba Nov 6, 2025
fa88ca3
Merge branch 'main' into recursive-inference
mtshiba Nov 6, 2025
d0145c6
Revert "Do not use `UnionBuilder` to union the last provisional type …
mtshiba Nov 6, 2025
682a2e8
don't create a query cycle in the cycle recovery function
mtshiba Nov 6, 2025
d0b68f9
use `CycleHeads` to remove `Divergent` types
mtshiba Nov 6, 2025
55f9ae9
Merge branch 'main' into recursive-inference
mtshiba Nov 6, 2025
65bb223
Update builder.rs
mtshiba Nov 6, 2025
0cf4eaf
revert unnecessary changes
mtshiba Nov 6, 2025
91167f9
Merge branch 'main' into recursive-inference
mtshiba Nov 7, 2025
2393f6a
fix fuzzer hang
mtshiba Nov 7, 2025
02705c4
the second parameter `Id` of `cycle_fn` is not necessary
mtshiba Nov 10, 2025
664b8d8
Revert "fix fuzzer hang"
mtshiba Nov 10, 2025
4aad144
specify cycle_fn for `PEP695TypeAliasType::raw_value_type`
mtshiba Nov 10, 2025
74a3758
Merge branch 'main' into recursive-inference
mtshiba Nov 11, 2025
e7b0dc5
revert unnecessary changes
mtshiba Nov 11, 2025
e879f8b
update salsa
mtshiba Nov 11, 2025
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
5 changes: 3 additions & 2 deletions crates/ruff_benchmark/benches/ty_walltime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ static PANDAS: std::sync::LazyLock<Benchmark<'static>> = std::sync::LazyLock::ne
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312,
},
3000,
3300,
)
});

Expand Down Expand Up @@ -199,7 +199,8 @@ static SYMPY: std::sync::LazyLock<Benchmark<'static>> = std::sync::LazyLock::new
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312,
},
13000,
// TODO: With better decorator support, `__slots__` support, etc., it should be possible to reduce the number of errors considerably.
70000,
)
});

Expand Down
17 changes: 17 additions & 0 deletions crates/ty_python_semantic/resources/corpus/cycle_into_callable.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Regression test for https://github.com/astral-sh/ruff/issues/17371
# panicked in commit d1088545a08aeb57b67ec1e3a7f5141159efefa5
# error message:
# dependency graph cycle when querying ClassType < 'db >::into_callable_(Id(1c00))

try:
class foo[T: bar](object):
pass
bar = foo
except Exception:
bar = lambda: 0
def bar():
pass

@bar()
class bar:
pass
69 changes: 69 additions & 0 deletions crates/ty_python_semantic/resources/corpus/divergent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
def f(cond: bool):
if cond:
result = ()
result += (f(cond),)
return result

return None

reveal_type(f(True))

def f(cond: bool):
if cond:
result = ()
result += (f(cond),)
return result

return None

def f(cond: bool):
result = None
if cond:
result = ()
result += (f(cond),)

return result

reveal_type(f(True))

def f(cond: bool):
result = None
if cond:
result = [f(cond) for _ in range(1)]

return result

reveal_type(f(True))

class Foo:
def value(self):
return 1

def unwrap(value):
if isinstance(value, Foo):
foo = value
return foo.value()
elif type(value) is tuple:
length = len(value)
if length == 0:
return ()
elif length == 1:
return (unwrap(value[0]),)
else:
result = []
for item in value:
result.append(unwrap(item))
return tuple(result)
else:
raise TypeError()

def descent(x: int, y: int):
if x > y:
y, x = descent(y, x)
return x, y
if x == 1:
return (1, 0)
if y == 1:
return (0, 1)
else:
return descent(x-1, y-1)
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@ def outer_sync(): # `yield` from is only valid syntax inside a synchronous func
a: (yield from [1]), # error: [invalid-type-form] "`yield from` expressions are not allowed in type expressions"
): ...

async def baz(): ...
async def baz():
yield

async def outer_async(): # avoid unrelated syntax errors on `yield` and `await`
def _(
a: 1, # error: [invalid-type-form] "Int literals are not allowed in this context in a type expression"
Expand Down
18 changes: 18 additions & 0 deletions crates/ty_python_semantic/resources/mdtest/attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -2281,6 +2281,24 @@ class B:

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

class Base:
def flip(self) -> "Sub":
return Sub()

class Sub(Base):
# TODO invalid override error
def flip(self) -> "Base":
return Base()

class C2:
def __init__(self, x: Sub):
self.x = x

def replace_with(self, other: "C2"):
self.x = other.x.flip()

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

This case additionally tests our union/intersection simplification logic:
Expand Down
6 changes: 3 additions & 3 deletions crates/ty_python_semantic/resources/mdtest/call/union.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ def _(flag: bool):

# error: [call-non-callable] "Object of type `Literal["This is a string literal"]` is not callable"
x = f(3)
reveal_type(x) # revealed: Unknown
reveal_type(x) # revealed: None | Unknown
```

## Union of binding errors
Expand All @@ -128,7 +128,7 @@ def _(flag: bool):
# error: [too-many-positional-arguments] "Too many positional arguments to function `f1`: expected 0, got 1"
# error: [too-many-positional-arguments] "Too many positional arguments to function `f2`: expected 0, got 1"
x = f(3)
reveal_type(x) # revealed: Unknown
reveal_type(x) # revealed: None
```

## One not-callable, one wrong argument
Expand All @@ -146,7 +146,7 @@ def _(flag: bool):
# error: [too-many-positional-arguments] "Too many positional arguments to function `f1`: expected 0, got 1"
# error: [call-non-callable] "Object of type `C` is not callable"
x = f(3)
reveal_type(x) # revealed: Unknown
reveal_type(x) # revealed: None | Unknown
```

## Union including a special-cased function
Expand Down
25 changes: 25 additions & 0 deletions crates/ty_python_semantic/resources/mdtest/cycle.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,28 @@ p = Point()
reveal_type(p.x) # revealed: Unknown | int
reveal_type(p.y) # revealed: Unknown | int
```

## Self-referential bare type alias

```py
A = list["A" | None]

def f(x: A):
# TODO: should be `list[A | None]`?
reveal_type(x) # revealed: list[Divergent]
# TODO: should be `A | None`?
reveal_type(x[0]) # revealed: Divergent
```

## Self-referential type variables

```py
from typing import Generic, TypeVar

B = TypeVar("B", bound="Base")

# TODO: no error
# error: [invalid-argument-type] "`typing.TypeVar | typing.TypeVar` is not a valid argument to `Generic`"
class Base(Generic[B]):
pass
```
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,8 @@ match obj:

```py
class C:
def __await__(self): ...
def __await__(self):
yield

# error: [invalid-syntax] "`return` statement outside of a function"
return
Expand Down
Loading