Skip to content

Commit b8334c7

Browse files
committed
Review feedback
1 parent 02911a4 commit b8334c7

8 files changed

Lines changed: 71 additions & 15 deletions

File tree

crates/ty_python_semantic/resources/mdtest/directives/cast.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,22 @@ def f(x: Any, y: Unknown, z: Any | str | int):
8181
e = cast(str | int | Any, z) # error: [redundant-cast]
8282
```
8383

84+
Recursive aliases that fall back to `Divergent` should not trigger `redundant-cast`.
85+
86+
```toml
87+
[environment]
88+
python-version = "3.12"
89+
```
90+
91+
```py
92+
from typing import cast
93+
94+
RecursiveAlias = list["RecursiveAlias | None"]
95+
96+
def f(x: RecursiveAlias):
97+
cast(RecursiveAlias, x)
98+
```
99+
84100
## Diagnostic snapshots
85101

86102
<!-- snapshot-diagnostics -->

crates/ty_python_semantic/resources/mdtest/ide_support/all_members.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,29 @@ static_assert(has_member(C(), "static_method"))
108108
static_assert(not has_member(C(), "non_existent"))
109109
```
110110

111+
Recursive attribute inference can fall back to `Divergent`, but should still preserve members that
112+
were available before the cycle was introduced:
113+
114+
```py
115+
from ty_extensions import has_member, static_assert
116+
117+
class Base:
118+
def flip(self) -> "Base":
119+
return Base()
120+
121+
class Sub(Base):
122+
pass
123+
124+
class C:
125+
def __init__(self, x: Sub):
126+
self.x = [x]
127+
128+
def replace_with(self, other: "C"):
129+
self.x = [self.x[0].flip()]
130+
131+
static_assert(has_member(C(Sub()).x[0], "flip"))
132+
```
133+
111134
### Class objects
112135

113136
```toml

crates/ty_python_semantic/resources/mdtest/typed_dict.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1508,17 +1508,24 @@ _ = cast(Bar2, foo) # error: [redundant-cast]
15081508
```py
15091509
from typing import TypedDict, Final, Literal, Any
15101510

1511+
RecursiveKey = list["RecursiveKey | None"]
1512+
15111513
class Person(TypedDict):
15121514
name: str
15131515
age: int | None
15141516

15151517
class Animal(TypedDict):
15161518
name: str
15171519

1520+
class Movie(TypedDict):
1521+
name: str
1522+
15181523
NAME_FINAL: Final = "name"
15191524
AGE_FINAL: Final[Literal["age"]] = "age"
15201525

15211526
def _(
1527+
recursive_key: RecursiveKey,
1528+
movie: Movie,
15221529
person: Person,
15231530
animal: Animal,
15241531
being: Person | Animal,
@@ -1546,6 +1553,8 @@ def _(
15461553
# No error here:
15471554
reveal_type(person[unknown_key]) # revealed: Unknown
15481555

1556+
reveal_type(movie[recursive_key[0]]) # revealed: Unknown
1557+
15491558
# error: [invalid-key] "Unknown key "anything" for TypedDict `Animal`"
15501559
reveal_type(animal["anything"]) # revealed: Unknown
15511560

crates/ty_python_semantic/src/types.rs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6461,10 +6461,6 @@ pub enum DynamicType<'db> {
64616461
TodoStarredExpression,
64626462
/// A special Todo-variant for `TypeVarTuple` instances encountered in type expressions
64636463
TodoTypeVarTuple,
6464-
/// A special Todo-variant for functional `TypedDict`s.
6465-
TodoFunctionalTypedDict,
6466-
/// A type that is determined to be divergent during recursive type inference.
6467-
Divergent(DivergentType),
64686464
}
64696465

64706466
impl DynamicType<'_> {
@@ -6489,8 +6485,6 @@ impl std::fmt::Display for DynamicType<'_> {
64896485
DynamicType::TodoUnpack => f.write_str("@Todo(typing.Unpack)"),
64906486
DynamicType::TodoStarredExpression => f.write_str("@Todo(StarredExpression)"),
64916487
DynamicType::TodoTypeVarTuple => f.write_str("@Todo(TypeVarTuple)"),
6492-
DynamicType::TodoFunctionalTypedDict => f.write_str("@Todo(Functional TypedDicts)"),
6493-
DynamicType::Divergent(_) => f.write_str("Divergent"),
64946488
}
64956489
}
64966490
}

crates/ty_python_semantic/src/types/function.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2043,11 +2043,23 @@ impl KnownFunction {
20432043
let [Some(casted_type), Some(source_type)] = parameter_types else {
20442044
return;
20452045
};
2046-
let contains_unknown_or_todo =
2047-
|ty| matches!(ty, Type::Dynamic(dynamic) if dynamic != DynamicType::Any);
2046+
let contains_unknown_or_todo_or_divergent = |ty: Type<'_>| {
2047+
matches!(ty, Type::Divergent(_))
2048+
|| (ty.is_dynamic() && !matches!(ty, Type::Dynamic(DynamicType::Any)))
2049+
};
20482050
if source_type.is_equivalent_to(db, *casted_type)
2049-
&& !any_over_type(db, *source_type, true, contains_unknown_or_todo)
2050-
&& !any_over_type(db, *casted_type, true, contains_unknown_or_todo)
2051+
&& !any_over_type(
2052+
db,
2053+
*source_type,
2054+
true,
2055+
contains_unknown_or_todo_or_divergent,
2056+
)
2057+
&& !any_over_type(
2058+
db,
2059+
*casted_type,
2060+
true,
2061+
contains_unknown_or_todo_or_divergent,
2062+
)
20512063
{
20522064
if let Some(builder) = context.report_lint(&REDUNDANT_CAST, call_expression) {
20532065
let source_display = source_type.display(db).to_string();

crates/ty_python_semantic/src/types/infer/builder/subscript.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1213,7 +1213,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
12131213
// types like `LiteralString & Any` to pass, but it does not need to be perfect. We would just
12141214
// fail to provide the "can only be subscripted with a string literal key" hint in that case.
12151215

1216-
if slice_ty.is_dynamic() {
1216+
if slice_ty.is_dynamic() || slice_ty.is_divergent() {
12171217
return true;
12181218
}
12191219

crates/ty_python_semantic/src/types/list_members.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -170,10 +170,12 @@ impl<'db> AllMembers<'db> {
170170
// `Type` guarantees that unions/intersections
171171
// are kept in DNF (i.e., they are flattened).
172172
ty.is_dynamic()
173+
|| ty.is_divergent()
173174
|| match ty {
174-
Type::Intersection(intersection) => {
175-
intersection.positive(db).iter().any(Type::is_dynamic)
176-
}
175+
Type::Intersection(intersection) => intersection
176+
.positive(db)
177+
.iter()
178+
.any(|ty| ty.is_dynamic() || ty.is_divergent()),
177179
_ => false,
178180
}
179181
}

crates/ty_python_semantic/src/types/subscript.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -439,7 +439,7 @@ fn typed_dict_subscript<'db>(
439439
typed_dict: TypedDictType<'db>,
440440
slice_ty: Type<'db>,
441441
) -> Result<Type<'db>, SubscriptError<'db>> {
442-
if slice_ty.is_dynamic() {
442+
if slice_ty.is_dynamic() || slice_ty.is_divergent() {
443443
return Ok(Type::unknown());
444444
}
445445

0 commit comments

Comments
 (0)