Skip to content

Commit 1a3b737

Browse files
authored
[red-knot] Silence errors in unreachable type annotations / class bases (#17342)
## Summary For silencing `invalid-type-form` diagnostics in unreachable code, we use the same approach that we use before and check the reachability that we already record. For silencing `invalid-bases`, we simply check if the type of the base is `Never`. If so, we silence the diagnostic with the argument that the class construction would never happen. ## Test Plan Updated Markdown tests.
1 parent 8b2727c commit 1a3b737

File tree

3 files changed

+83
-51
lines changed

3 files changed

+83
-51
lines changed

crates/red_knot_python_semantic/resources/mdtest/unreachable.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -447,15 +447,16 @@ We should not show any diagnostics in type annotations inside unreachable sectio
447447

448448
```py
449449
def _():
450-
class C: ...
450+
class C:
451+
class Inner: ...
452+
451453
return
452454

453-
# TODO
454-
# error: [invalid-type-form] "Variable of type `Never` is not allowed in a type expression"
455-
c: C = C()
455+
c1: C = C()
456+
c2: C.Inner = C.Inner()
457+
c3: tuple[C, C] = (C(), C())
458+
c4: tuple[C.Inner, C.Inner] = (C.Inner(), C.Inner())
456459

457-
# TODO
458-
# error: [invalid-base] "Invalid class base with type `Never` (all bases must be a class, `Any`, `Unknown` or `Todo`)"
459460
class Sub(C): ...
460461
```
461462

crates/red_knot_python_semantic/src/types.rs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4475,17 +4475,24 @@ pub struct InvalidTypeExpressionError<'db> {
44754475
}
44764476

44774477
impl<'db> InvalidTypeExpressionError<'db> {
4478-
fn into_fallback_type(self, context: &InferContext, node: &ast::Expr) -> Type<'db> {
4478+
fn into_fallback_type(
4479+
self,
4480+
context: &InferContext,
4481+
node: &ast::Expr,
4482+
is_reachable: bool,
4483+
) -> Type<'db> {
44794484
let InvalidTypeExpressionError {
44804485
fallback_type,
44814486
invalid_expressions,
44824487
} = self;
4483-
for error in invalid_expressions {
4484-
context.report_lint_old(
4485-
&INVALID_TYPE_FORM,
4486-
node,
4487-
format_args!("{}", error.reason(context.db())),
4488-
);
4488+
if is_reachable {
4489+
for error in invalid_expressions {
4490+
context.report_lint_old(
4491+
&INVALID_TYPE_FORM,
4492+
node,
4493+
format_args!("{}", error.reason(context.db())),
4494+
);
4495+
}
44894496
}
44904497
fallback_type
44914498
}

crates/red_knot_python_semantic/src/types/infer.rs

Lines changed: 62 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,22 @@ impl<'db> TypeInferenceBuilder<'db> {
600600
}
601601
}
602602

603+
/// Check if a given AST node is reachable.
604+
///
605+
/// Note that this only works if reachability is explicitly tracked for this specific
606+
/// type of node (see `node_reachability` in the use-def map).
607+
fn is_reachable<'a, N>(&self, node: N) -> bool
608+
where
609+
N: Into<AnyNodeRef<'a>>,
610+
{
611+
let file_scope_id = self.scope().file_scope_id(self.db());
612+
self.index.is_node_reachable(
613+
self.db(),
614+
file_scope_id,
615+
self.enclosing_node_key(node.into()),
616+
)
617+
}
618+
603619
fn in_stub(&self) -> bool {
604620
self.context.in_stub()
605621
}
@@ -781,6 +797,12 @@ impl<'db> TypeInferenceBuilder<'db> {
781797
MroErrorKind::InvalidBases(bases) => {
782798
let base_nodes = class_node.bases();
783799
for (index, base_ty) in bases {
800+
if base_ty.is_never() {
801+
// A class base of type `Never` can appear in unreachable code. It
802+
// does not indicate a problem, since the actual construction of the
803+
// class will never happen.
804+
continue;
805+
}
784806
self.context.report_lint_old(
785807
&INVALID_BASE,
786808
&base_nodes[*index],
@@ -802,7 +824,7 @@ impl<'db> TypeInferenceBuilder<'db> {
802824
)
803825
}
804826
}
805-
Ok(_) => check_class_slots(&self.context, class, class_node)
827+
Ok(_) => check_class_slots(&self.context, class, class_node),
806828
}
807829

808830
// (4) Check that the class's metaclass can be determined without error.
@@ -3120,15 +3142,12 @@ impl<'db> TypeInferenceBuilder<'db> {
31203142

31213143
fn report_unresolved_import(
31223144
&self,
3123-
import_node: NodeKey,
3145+
import_node: AnyNodeRef<'_>,
31243146
range: TextRange,
31253147
level: u32,
31263148
module: Option<&str>,
31273149
) {
3128-
let file_scope_id = self.scope().file_scope_id(self.db());
3129-
let is_import_reachable =
3130-
self.index
3131-
.is_node_reachable(self.db(), file_scope_id, import_node);
3150+
let is_import_reachable = self.is_reachable(import_node);
31323151

31333152
if !is_import_reachable {
31343153
return;
@@ -3166,7 +3185,7 @@ impl<'db> TypeInferenceBuilder<'db> {
31663185

31673186
// Resolve the module being imported.
31683187
let Some(full_module_ty) = self.module_type_from_name(&full_module_name) else {
3169-
self.report_unresolved_import(NodeKey::from_node(node), alias.range(), 0, Some(name));
3188+
self.report_unresolved_import(node.into(), alias.range(), 0, Some(name));
31703189
self.add_unknown_declaration_with_binding(alias.into(), definition);
31713190
return;
31723191
};
@@ -3280,8 +3299,6 @@ impl<'db> TypeInferenceBuilder<'db> {
32803299
) -> Option<(ModuleName, Type<'db>)> {
32813300
let ast::StmtImportFrom { module, level, .. } = import_from;
32823301

3283-
let node_key = NodeKey::from_node(import_from);
3284-
32853302
// For diagnostics, we want to highlight the unresolvable
32863303
// module and not the entire `from ... import ...` statement.
32873304
let module_ref = module
@@ -3310,7 +3327,12 @@ impl<'db> TypeInferenceBuilder<'db> {
33103327
"Relative module resolution `{}` failed: too many leading dots",
33113328
format_import_from_module(*level, module),
33123329
);
3313-
self.report_unresolved_import(node_key, module_ref.range(), *level, module);
3330+
self.report_unresolved_import(
3331+
import_from.into(),
3332+
module_ref.range(),
3333+
*level,
3334+
module,
3335+
);
33143336
return None;
33153337
}
33163338
Err(ModuleNameResolutionError::UnknownCurrentModule) => {
@@ -3319,13 +3341,18 @@ impl<'db> TypeInferenceBuilder<'db> {
33193341
format_import_from_module(*level, module),
33203342
self.file().path(self.db())
33213343
);
3322-
self.report_unresolved_import(node_key, module_ref.range(), *level, module);
3344+
self.report_unresolved_import(
3345+
import_from.into(),
3346+
module_ref.range(),
3347+
*level,
3348+
module,
3349+
);
33233350
return None;
33243351
}
33253352
};
33263353

33273354
let Some(module_ty) = self.module_type_from_name(&module_name) else {
3328-
self.report_unresolved_import(node_key, module_ref.range(), *level, module);
3355+
self.report_unresolved_import(import_from.into(), module_ref.range(), *level, module);
33293356
return None;
33303357
};
33313358

@@ -3410,12 +3437,7 @@ impl<'db> TypeInferenceBuilder<'db> {
34103437
}
34113438

34123439
if &alias.name != "*" {
3413-
let file_scope_id = self.scope().file_scope_id(self.db());
3414-
let is_import_reachable = self.index.is_node_reachable(
3415-
self.db(),
3416-
file_scope_id,
3417-
NodeKey::from_node(import_from),
3418-
);
3440+
let is_import_reachable = self.is_reachable(import_from);
34193441

34203442
if is_import_reachable {
34213443
self.context.report_lint_old(
@@ -4503,24 +4525,16 @@ impl<'db> TypeInferenceBuilder<'db> {
45034525
})
45044526
});
45054527

4506-
let report_unresolved_usage = || {
4507-
self.index.is_node_reachable(
4508-
db,
4509-
file_scope_id,
4510-
self.enclosing_node_key(name_node.into()),
4511-
)
4512-
};
4513-
45144528
symbol
45154529
.unwrap_with_diagnostic(|lookup_error| match lookup_error {
45164530
LookupError::Unbound(qualifiers) => {
4517-
if report_unresolved_usage() {
4531+
if self.is_reachable(name_node) {
45184532
report_unresolved_reference(&self.context, name_node);
45194533
}
45204534
TypeAndQualifiers::new(Type::unknown(), qualifiers)
45214535
}
45224536
LookupError::PossiblyUnbound(type_when_bound) => {
4523-
if report_unresolved_usage() {
4537+
if self.is_reachable(name_node) {
45244538
report_possibly_unresolved_reference(&self.context, name_node);
45254539
}
45264540
type_when_bound
@@ -4553,13 +4567,7 @@ impl<'db> TypeInferenceBuilder<'db> {
45534567
.member(db, &attr.id)
45544568
.unwrap_with_diagnostic(|lookup_error| match lookup_error {
45554569
LookupError::Unbound(_) => {
4556-
let report_unresolved_attribute = self
4557-
.index
4558-
.is_node_reachable(
4559-
db,
4560-
self.scope().file_scope_id(db),
4561-
self.enclosing_node_key(attribute.into()),
4562-
);
4570+
let report_unresolved_attribute = self.is_reachable(attribute);
45634571

45644572
if report_unresolved_attribute {
45654573
let bound_on_instance = match value_type {
@@ -6334,7 +6342,11 @@ impl<'db> TypeInferenceBuilder<'db> {
63346342
_ => name_expr_ty
63356343
.in_type_expression(self.db())
63366344
.unwrap_or_else(|error| {
6337-
error.into_fallback_type(&self.context, annotation)
6345+
error.into_fallback_type(
6346+
&self.context,
6347+
annotation,
6348+
self.is_reachable(annotation),
6349+
)
63386350
})
63396351
.into(),
63406352
}
@@ -6499,7 +6511,13 @@ impl<'db> TypeInferenceBuilder<'db> {
64996511
ast::ExprContext::Load => self
65006512
.infer_name_expression(name)
65016513
.in_type_expression(self.db())
6502-
.unwrap_or_else(|error| error.into_fallback_type(&self.context, expression)),
6514+
.unwrap_or_else(|error| {
6515+
error.into_fallback_type(
6516+
&self.context,
6517+
expression,
6518+
self.is_reachable(expression),
6519+
)
6520+
}),
65036521
ast::ExprContext::Invalid => Type::unknown(),
65046522
ast::ExprContext::Store | ast::ExprContext::Del => {
65056523
todo_type!("Name expression annotation in Store/Del context")
@@ -6510,7 +6528,13 @@ impl<'db> TypeInferenceBuilder<'db> {
65106528
ast::ExprContext::Load => self
65116529
.infer_attribute_expression(attribute_expression)
65126530
.in_type_expression(self.db())
6513-
.unwrap_or_else(|error| error.into_fallback_type(&self.context, expression)),
6531+
.unwrap_or_else(|error| {
6532+
error.into_fallback_type(
6533+
&self.context,
6534+
expression,
6535+
self.is_reachable(expression),
6536+
)
6537+
}),
65146538
ast::ExprContext::Invalid => Type::unknown(),
65156539
ast::ExprContext::Store | ast::ExprContext::Del => {
65166540
todo_type!("Attribute expression annotation in Store/Del context")

0 commit comments

Comments
 (0)