diff --git a/crates/ty_python_semantic/src/types/cyclic.rs b/crates/ty_python_semantic/src/types/cyclic.rs index 801a4e928e1e9..1b3a624e8e635 100644 --- a/crates/ty_python_semantic/src/types/cyclic.rs +++ b/crates/ty_python_semantic/src/types/cyclic.rs @@ -76,7 +76,19 @@ impl<'db> Type<'db> { a.definition(db) == b.definition(db) } (Type::TypeAlias(a), Type::TypeAlias(b)) => a.definition(db) == b.definition(db), - (Type::TypedDict(a), Type::TypedDict(b)) => a.definition(db) == b.definition(db), + ( + Type::TypedDict(TypedDictType::Class(a)), + Type::TypedDict(TypedDictType::Class(b)), + ) => { + // Different specializations of the same recursive TypedDict can form a cycle. + // Each class statement has a unique body scope, so comparing scopes tells us + // whether the types came from the same class, even after decorators. + // Other TypedDicts can only match by exact equality, which we checked above. + matches!( + (a.class_literal(db).as_static(), b.class_literal(db).as_static()), + (Some(a), Some(b)) if a.body_scope(db) == b.body_scope(db) + ) + } (Type::Recursive(a), Type::Recursive(b)) => a.definition(db) == b.definition(db), _ => false, }