[ty] Improve invalid-type-form diagnostic where a module-literal type is used in a type expression and the module has a member which would be valid in a type expression (#18244)

This commit is contained in:
Alex Waygood 2025-05-21 15:38:56 -04:00 committed by GitHub
parent 41463396cf
commit 02394b8049
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 136 additions and 7 deletions

View file

@ -4821,7 +4821,7 @@ impl<'db> Type<'db> {
pub fn in_type_expression(
&self,
db: &'db dyn Db,
scope_id: ScopeId,
scope_id: ScopeId<'db>,
) -> Result<Type<'db>, InvalidTypeExpressionError<'db>> {
match self {
// Special cases for `float` and `complex`
@ -4872,7 +4872,9 @@ impl<'db> Type<'db> {
| Type::BoundSuper(_)
| Type::ProtocolInstance(_)
| Type::PropertyInstance(_) => Err(InvalidTypeExpressionError {
invalid_expressions: smallvec::smallvec![InvalidTypeExpression::InvalidType(*self)],
invalid_expressions: smallvec::smallvec![InvalidTypeExpression::InvalidType(
*self, scope_id
)],
fallback_type: Type::unknown(),
}),
@ -4910,7 +4912,7 @@ impl<'db> Type<'db> {
return Err(InvalidTypeExpressionError {
fallback_type: Type::unknown(),
invalid_expressions: smallvec::smallvec![
InvalidTypeExpression::InvalidType(*self)
InvalidTypeExpression::InvalidType(*self, scope_id)
],
});
};
@ -5037,7 +5039,7 @@ impl<'db> Type<'db> {
)),
_ => Err(InvalidTypeExpressionError {
invalid_expressions: smallvec::smallvec![InvalidTypeExpression::InvalidType(
*self
*self, scope_id
)],
fallback_type: Type::unknown(),
}),
@ -5751,7 +5753,8 @@ impl<'db> InvalidTypeExpressionError<'db> {
let Some(builder) = context.report_lint(&INVALID_TYPE_FORM, node) else {
continue;
};
builder.into_diagnostic(error.reason(context.db()));
let diagnostic = builder.into_diagnostic(error.reason(context.db()));
error.add_subdiagnostics(context.db(), diagnostic);
}
}
fallback_type
@ -5778,7 +5781,7 @@ enum InvalidTypeExpression<'db> {
/// and which would require exactly one argument even if they appeared in an annotation expression
TypeQualifierRequiresOneArgument(KnownInstanceType<'db>),
/// Some types are always invalid in type expressions
InvalidType(Type<'db>),
InvalidType(Type<'db>, ScopeId<'db>),
}
impl<'db> InvalidTypeExpression<'db> {
@ -5822,7 +5825,7 @@ impl<'db> InvalidTypeExpression<'db> {
"Type qualifier `{q}` is not allowed in type expressions (only in annotation expressions, and only with exactly one argument)",
q = qualifier.repr(self.db)
),
InvalidTypeExpression::InvalidType(ty) => write!(
InvalidTypeExpression::InvalidType(ty, _) => write!(
f,
"Variable of type `{ty}` is not allowed in a type expression",
ty = ty.display(self.db)
@ -5833,6 +5836,38 @@ impl<'db> InvalidTypeExpression<'db> {
Display { error: self, db }
}
fn add_subdiagnostics(self, db: &'db dyn Db, mut diagnostic: LintDiagnosticGuard) {
let InvalidTypeExpression::InvalidType(ty, scope) = self else {
return;
};
let Type::ModuleLiteral(module_type) = ty else {
return;
};
let module = module_type.module(db);
let Some(module_name_final_part) = module.name().components().next_back() else {
return;
};
let Some(module_member_with_same_name) = ty
.member(db, module_name_final_part)
.symbol
.ignore_possibly_unbound()
else {
return;
};
if module_member_with_same_name
.in_type_expression(db, scope)
.is_err()
{
return;
}
// TODO: showing a diff (and even having an autofix) would be even better
diagnostic.info(format_args!(
"Did you mean to use the module's member \
`{module_name_final_part}.{module_name_final_part}` instead?"
));
}
}
/// Whether this typecar was created via the legacy `TypeVar` constructor, or using PEP 695 syntax.