Skip to content

Commit f3b6c67

Browse files
committed
use visitor.visit(...) in Type methods instead of NewType methods
1 parent 07f188b commit f3b6c67

File tree

3 files changed

+24
-80
lines changed

3 files changed

+24
-80
lines changed

crates/ty_python_semantic/resources/mdtest/annotations/new_types.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,7 @@ reveal_type(d) # revealed: D
130130
```
131131

132132
Normal classes can't inherit from newtypes, but generic classes can be parametrized with them, so we
133-
also need to detect "ordinary" type cycles that happen to involve a newtype. (This turns out to be
134-
tricky in the implementation, see comments around `NewType::normalized_impl` and
135-
`NewType::apply_type_mapping_impl`.)
133+
also need to detect "ordinary" type cycles that happen to involve a newtype.
136134

137135
```py
138136
E = NewType("E", list["E"])

crates/ty_python_semantic/src/types.rs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1379,7 +1379,11 @@ impl<'db> Type<'db> {
13791379
}
13801380
Type::TypeAlias(alias) => alias.value_type(db).normalized_impl(db, visitor),
13811381
Type::NewTypeInstance(newtype) => {
1382-
Type::NewTypeInstance(newtype.normalized_impl(db, visitor))
1382+
visitor.visit(self, || {
1383+
Type::NewTypeInstance(newtype.map_base_class_type(db, |class_type| {
1384+
class_type.normalized_impl(db, visitor)
1385+
}))
1386+
})
13831387
}
13841388
Type::LiteralString
13851389
| Type::AlwaysFalsy
@@ -6892,9 +6896,11 @@ impl<'db> Type<'db> {
68926896
instance.apply_type_mapping_impl(db, type_mapping, tcx, visitor)
68936897
},
68946898

6895-
Type::NewTypeInstance(newtype) => {
6896-
Type::NewTypeInstance(newtype.apply_type_mapping_impl(db, type_mapping, tcx, visitor))
6897-
},
6899+
Type::NewTypeInstance(newtype) => visitor.visit(self, || {
6900+
Type::NewTypeInstance(newtype.map_base_class_type(db, |class_type| {
6901+
class_type.apply_type_mapping_impl(db, type_mapping, tcx, visitor)
6902+
}))
6903+
}),
68986904

68996905
Type::ProtocolInstance(instance) => {
69006906
// TODO: Add tests for materialization once subtyping/assignability is implemented for
@@ -7754,7 +7760,10 @@ impl<'db> KnownInstanceType<'db> {
77547760
// Nothing to normalize
77557761
Self::ConstraintSet(set)
77567762
}
7757-
Self::NewType(newtype) => Self::NewType(newtype.normalized_impl(db, visitor)),
7763+
Self::NewType(newtype) => Self::NewType(
7764+
newtype
7765+
.map_base_class_type(db, |class_type| class_type.normalized_impl(db, visitor)),
7766+
),
77587767
}
77597768
}
77607769

crates/ty_python_semantic/src/types/newtype.rs

Lines changed: 9 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,7 @@ use std::collections::BTreeSet;
33
use crate::Db;
44
use crate::semantic_index::definition::{Definition, DefinitionKind};
55
use crate::types::constraints::ConstraintSet;
6-
use crate::types::{
7-
ApplyTypeMappingVisitor, ClassType, NormalizedVisitor, Type, TypeContext, TypeMapping,
8-
definition_expression_type,
9-
};
6+
use crate::types::{ClassType, Type, definition_expression_type};
107
use ruff_db::parsed::parsed_module;
118
use ruff_python_ast as ast;
129

@@ -133,7 +130,10 @@ impl<'db> NewType<'db> {
133130
ConstraintSet::from(!self.is_subtype_of(db, other) && !other.is_subtype_of(db, self))
134131
}
135132

136-
fn map_base_class_type(
133+
/// Create a new `NewType` by mapping the underlying `ClassType`. This descends through any
134+
/// number of nested `NewType` layers and rebuilds the whole chain. In the rare case of cyclic
135+
/// `NewType`s with no underlying `ClassType`, this has no effect and does not call `f`.
136+
pub(crate) fn map_base_class_type(
137137
self,
138138
db: &'db dyn Db,
139139
f: impl FnOnce(ClassType<'db>) -> ClassType<'db>,
@@ -149,10 +149,11 @@ impl<'db> NewType<'db> {
149149
for base in self.iter_bases(db) {
150150
match base {
151151
// Build up the stack of intermediate newtypes that we'll need to re-wrap after
152-
// we've mapped the base class.
152+
// we've mapped the `ClassType`.
153153
NewTypeBase::NewType(base_newtype) => inner_newtype_stack.push(base_newtype),
154+
// We've reached the `ClassType`.
154155
NewTypeBase::ClassType(base_class_type) => {
155-
// We've reached the base class. Call `f`.
156+
// Call `f`.
156157
let mut mapped_base = NewTypeBase::ClassType(f(base_class_type));
157158
// Re-wrap the mapped base class in however many newtypes we unwrapped.
158159
for inner_newtype in inner_newtype_stack.into_iter().rev() {
@@ -172,74 +173,10 @@ impl<'db> NewType<'db> {
172173
}
173174
}
174175
}
175-
// If we get here, there is no base class (because this newtype is cyclic), and we don't
176+
// If we get here, there is no `ClassType` (because this newtype is cyclic), and we don't
176177
// call `f` at all.
177178
self
178179
}
179-
180-
pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &NormalizedVisitor<'db>) -> Self {
181-
self.map_base_class_type(db, |base_class_type| {
182-
// We don't really want to work with `Type` directly (`NewTypeBase` is much more
183-
// restricted), but we need to let the `NormalizedVisitor` see the `Type` of the base
184-
// class, because this is its only chance to detect an indirect cycle like this one:
185-
//
186-
// ```py
187-
// Foo = NewType("Foo", list["Foo"])
188-
// ```
189-
//
190-
// Note that NewTypeBaseIter does *not* catch this kind of cycle, because it stops
191-
// after returning `NewTypeBase::ClassType` for `list`.
192-
let mut is_first_encounter = false;
193-
let base_type = Type::instance(db, base_class_type);
194-
_ = visitor.visit(base_type, || {
195-
// If we hit in a cycle, this closure won't run.
196-
is_first_encounter = true;
197-
base_type
198-
});
199-
if is_first_encounter {
200-
base_class_type.normalized_impl(db, visitor)
201-
} else {
202-
// Cycle detected. Fall back to `object`.
203-
// REVIEWERS: Is this correct?
204-
ClassType::object(db)
205-
}
206-
})
207-
}
208-
209-
pub(crate) fn apply_type_mapping_impl<'a>(
210-
self,
211-
db: &'db dyn Db,
212-
type_mapping: &TypeMapping<'a, 'db>,
213-
tcx: TypeContext<'db>,
214-
visitor: &ApplyTypeMappingVisitor<'db>,
215-
) -> Self {
216-
self.map_base_class_type(db, |base_class_type| {
217-
// We don't really want to work with `Type` directly (`NewTypeBase` is much more
218-
// restricted), but we need to let the `ApplyTypeMappingVisitor` see the `Type` of the
219-
// base class, because this is its only chance to detect an indirect cycle like:
220-
//
221-
// ```py
222-
// Foo = NewType("Foo", list["Foo"])
223-
// ```
224-
//
225-
// Note that NewTypeBaseIter does *not* catch this kind of cycle, because it stops
226-
// after returning `NewTypeBase::ClassType` for `list`.
227-
let mut is_first_encounter = false;
228-
let base_type = Type::instance(db, base_class_type);
229-
_ = visitor.visit(base_type, || {
230-
// If we hit in a cycle, this closure won't run.
231-
is_first_encounter = true;
232-
base_type
233-
});
234-
if is_first_encounter {
235-
base_class_type.apply_type_mapping_impl(db, type_mapping, tcx, visitor)
236-
} else {
237-
// Cycle detected. Fall back to `object`.
238-
// REVIEWERS: Is this correct?
239-
ClassType::object(db)
240-
}
241-
})
242-
}
243180
}
244181

245182
/// `typing.NewType` typically wraps a class type, but it can also wrap another newtype.

0 commit comments

Comments
 (0)