Skip to content

Commit 114abc7

Browse files
authored
[red-knot] support empty TypeInference with fallback type (#16510)
This is split out of #14029, to reduce the size of that PR, and to validate that this "fallback type" support in `TypeInference` doesn't come with a performance cost. It also improves the reliability and debuggability of our current (temporary) cycle handling. In order to recover from a cycle, we have to be able to construct a "default" `TypeInference` where all expressions and definitions have some "default" type. In our current cycle handling, this "default" type is just unknown or a todo type. With fixpoint iteration, the "default" type will be `Type::Never`, which is the "bottom" type that fixpoint iteration starts from. Since it would be costly (both in space and time) to actually enumerate all expressions and definitions in a scope, just to insert the same default type for all of them, instead we add an optional "missing type" fallback to `TypeInference`, which (if set) is the fallback type for any expression or definition which doesn't have an explicit type set. With this change, cycles can no longer result in the dreaded "Missing key" errors looking up the type of some expression.
1 parent 318f503 commit 114abc7

File tree

1 file changed

+45
-20
lines changed
  • crates/red_knot_python_semantic/src/types

1 file changed

+45
-20
lines changed

crates/red_knot_python_semantic/src/types/infer.rs

+45-20
Original file line numberDiff line numberDiff line change
@@ -111,27 +111,14 @@ pub(crate) fn infer_scope_types<'db>(db: &'db dyn Db, scope: ScopeId<'db>) -> Ty
111111
TypeInferenceBuilder::new(db, InferenceRegion::Scope(scope), index).finish()
112112
}
113113

114-
/// Cycle recovery for [`infer_definition_types()`]: for now, just [`Type::unknown`]
115-
/// TODO fixpoint iteration
114+
/// TODO temporary cycle recovery for [`infer_definition_types()`], pending fixpoint iteration
116115
fn infer_definition_types_cycle_recovery<'db>(
117116
db: &'db dyn Db,
118117
_cycle: &salsa::Cycle,
119118
input: Definition<'db>,
120119
) -> TypeInference<'db> {
121120
tracing::trace!("infer_definition_types_cycle_recovery");
122-
let mut inference = TypeInference::empty(input.scope(db));
123-
let category = input.kind(db).category(input.file(db).is_stub(db.upcast()));
124-
if category.is_declaration() {
125-
inference
126-
.declarations
127-
.insert(input, TypeAndQualifiers::unknown());
128-
}
129-
if category.is_binding() {
130-
inference.bindings.insert(input, Type::unknown());
131-
}
132-
// TODO we don't fill in expression types for the cycle-participant definitions, which can
133-
// later cause a panic when looking up an expression type.
134-
inference
121+
TypeInference::cycle_fallback(input.scope(db), todo_type!("cycle recovery"))
135122
}
136123

137124
/// Infer all types for a [`Definition`] (including sub-expressions).
@@ -291,8 +278,13 @@ pub(crate) struct TypeInference<'db> {
291278
/// The diagnostics for this region.
292279
diagnostics: TypeCheckDiagnostics,
293280

294-
/// The scope belong to this region.
281+
/// The scope this region is part of.
295282
scope: ScopeId<'db>,
283+
284+
/// The fallback type for missing expressions/bindings/declarations.
285+
///
286+
/// This is used only when constructing a cycle-recovery `TypeInference`.
287+
cycle_fallback_type: Option<Type<'db>>,
296288
}
297289

298290
impl<'db> TypeInference<'db> {
@@ -304,26 +296,59 @@ impl<'db> TypeInference<'db> {
304296
deferred: FxHashSet::default(),
305297
diagnostics: TypeCheckDiagnostics::default(),
306298
scope,
299+
cycle_fallback_type: None,
300+
}
301+
}
302+
303+
fn cycle_fallback(scope: ScopeId<'db>, cycle_fallback_type: Type<'db>) -> Self {
304+
Self {
305+
expressions: FxHashMap::default(),
306+
bindings: FxHashMap::default(),
307+
declarations: FxHashMap::default(),
308+
deferred: FxHashSet::default(),
309+
diagnostics: TypeCheckDiagnostics::default(),
310+
scope,
311+
cycle_fallback_type: Some(cycle_fallback_type),
307312
}
308313
}
309314

310315
#[track_caller]
311316
pub(crate) fn expression_type(&self, expression: ScopedExpressionId) -> Type<'db> {
312-
self.expressions[&expression]
317+
self.try_expression_type(expression).expect(
318+
"expression should belong to this TypeInference region and
319+
TypeInferenceBuilder should have inferred a type for it",
320+
)
313321
}
314322

315323
pub(crate) fn try_expression_type(&self, expression: ScopedExpressionId) -> Option<Type<'db>> {
316-
self.expressions.get(&expression).copied()
324+
self.expressions
325+
.get(&expression)
326+
.copied()
327+
.or(self.cycle_fallback_type)
317328
}
318329

319330
#[track_caller]
320331
pub(crate) fn binding_type(&self, definition: Definition<'db>) -> Type<'db> {
321-
self.bindings[&definition]
332+
self.bindings
333+
.get(&definition)
334+
.copied()
335+
.or(self.cycle_fallback_type)
336+
.expect(
337+
"definition should belong to this TypeInference region and
338+
TypeInferenceBuilder should have inferred a type for it",
339+
)
322340
}
323341

324342
#[track_caller]
325343
pub(crate) fn declaration_type(&self, definition: Definition<'db>) -> TypeAndQualifiers<'db> {
326-
self.declarations[&definition]
344+
self.declarations
345+
.get(&definition)
346+
.copied()
347+
.or(self.cycle_fallback_type.map(Into::into))
348+
.expect(
349+
"definition should belong to this TypeInference region and
350+
TypeInferenceBuilder should have inferred a type for it",
351+
)
327352
}
328353

329354
pub(crate) fn diagnostics(&self) -> &TypeCheckDiagnostics {

0 commit comments

Comments
 (0)