[ty] Improve several "Did you mean?" suggestions (#21597)
This commit is contained in:
@@ -8597,25 +8597,23 @@ impl<'db> InvalidTypeExpression<'db> {
|
||||
InvalidTypeExpression::Field => {
|
||||
f.write_str("`dataclasses.Field` is not allowed in type expressions")
|
||||
}
|
||||
InvalidTypeExpression::ConstraintSet => {
|
||||
f.write_str("`ty_extensions.ConstraintSet` is not allowed in type expressions")
|
||||
}
|
||||
InvalidTypeExpression::GenericContext => {
|
||||
f.write_str("`ty_extensions.GenericContext` is not allowed in type expressions")
|
||||
}
|
||||
InvalidTypeExpression::Specialization => {
|
||||
f.write_str("`ty_extensions.GenericContext` is not allowed in type expressions")
|
||||
}
|
||||
InvalidTypeExpression::TypedDict => {
|
||||
f.write_str(
|
||||
"The special form `typing.TypedDict` is not allowed in type expressions. \
|
||||
Did you mean to use a concrete TypedDict or `collections.abc.Mapping[str, object]` instead?")
|
||||
}
|
||||
InvalidTypeExpression::TypeAlias => {
|
||||
f.write_str(
|
||||
"`typing.TypeAlias` is only allowed as the sole annotation on an annotated assignment",
|
||||
)
|
||||
}
|
||||
InvalidTypeExpression::ConstraintSet => f.write_str(
|
||||
"`ty_extensions.ConstraintSet` is not allowed in type expressions",
|
||||
),
|
||||
InvalidTypeExpression::GenericContext => f.write_str(
|
||||
"`ty_extensions.GenericContext` is not allowed in type expressions",
|
||||
),
|
||||
InvalidTypeExpression::Specialization => f.write_str(
|
||||
"`ty_extensions.GenericContext` is not allowed in type expressions",
|
||||
),
|
||||
InvalidTypeExpression::TypedDict => f.write_str(
|
||||
"The special form `typing.TypedDict` \
|
||||
is not allowed in type expressions",
|
||||
),
|
||||
InvalidTypeExpression::TypeAlias => f.write_str(
|
||||
"`typing.TypeAlias` is only allowed \
|
||||
as the sole annotation on an annotated assignment",
|
||||
),
|
||||
InvalidTypeExpression::TypeQualifier(qualifier) => write!(
|
||||
f,
|
||||
"Type qualifier `{qualifier}` is not allowed in type expressions \
|
||||
@@ -8626,6 +8624,11 @@ impl<'db> InvalidTypeExpression<'db> {
|
||||
"Type qualifier `{qualifier}` is not allowed in type expressions \
|
||||
(only in annotation expressions, and only with exactly one argument)",
|
||||
),
|
||||
InvalidTypeExpression::InvalidType(Type::ModuleLiteral(module), _) => write!(
|
||||
f,
|
||||
"Module `{module}` is not valid in a type expression",
|
||||
module = module.module(self.db).name(self.db)
|
||||
),
|
||||
InvalidTypeExpression::InvalidType(ty, _) => write!(
|
||||
f,
|
||||
"Variable of type `{ty}` is not allowed in a type expression",
|
||||
@@ -8639,35 +8642,39 @@ impl<'db> InvalidTypeExpression<'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(db).components().next_back() else {
|
||||
return;
|
||||
};
|
||||
let Some(module_member_with_same_name) = ty
|
||||
.member(db, module_name_final_part)
|
||||
.place
|
||||
.ignore_possibly_undefined()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if module_member_with_same_name
|
||||
.in_type_expression(db, scope, None)
|
||||
.is_err()
|
||||
{
|
||||
return;
|
||||
}
|
||||
if let InvalidTypeExpression::InvalidType(ty, scope) = self {
|
||||
let Type::ModuleLiteral(module_type) = ty else {
|
||||
return;
|
||||
};
|
||||
let module = module_type.module(db);
|
||||
let Some(module_name_final_part) = module.name(db).components().next_back() else {
|
||||
return;
|
||||
};
|
||||
let Some(module_member_with_same_name) = ty
|
||||
.member(db, module_name_final_part)
|
||||
.place
|
||||
.ignore_possibly_undefined()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if module_member_with_same_name
|
||||
.in_type_expression(db, scope, None)
|
||||
.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?"
|
||||
));
|
||||
// TODO: showing a diff (and even having an autofix) would be even better
|
||||
diagnostic.set_primary_message(format_args!(
|
||||
"Did you mean to use the module's member \
|
||||
`{module_name_final_part}.{module_name_final_part}`?"
|
||||
));
|
||||
} else if let InvalidTypeExpression::TypedDict = self {
|
||||
diagnostic.help(
|
||||
"You might have meant to use a concrete TypedDict \
|
||||
or `collections.abc.Mapping[str, object]`",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -148,15 +148,13 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||
// anything else is an invalid annotation:
|
||||
op => {
|
||||
self.infer_binary_expression(binary, TypeContext::default());
|
||||
if let Some(mut diag) = self.report_invalid_type_expression(
|
||||
self.report_invalid_type_expression(
|
||||
expression,
|
||||
format_args!(
|
||||
"Invalid binary operator `{}` in type annotation",
|
||||
op.as_str()
|
||||
),
|
||||
) {
|
||||
diag.info("Did you mean to use `|`?");
|
||||
}
|
||||
);
|
||||
Type::unknown()
|
||||
}
|
||||
}
|
||||
@@ -1446,13 +1444,40 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||
Type::unknown()
|
||||
}
|
||||
SpecialFormType::LiteralString => {
|
||||
self.infer_type_expression(arguments_slice);
|
||||
let arguments = self.infer_expression(arguments_slice, TypeContext::default());
|
||||
|
||||
if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) {
|
||||
let mut diag = builder.into_diagnostic(format_args!(
|
||||
"Type `{special_form}` expected no type parameter",
|
||||
));
|
||||
diag.info("Did you mean to use `Literal[...]` instead?");
|
||||
let mut diag =
|
||||
builder.into_diagnostic("`LiteralString` expects no type parameter");
|
||||
|
||||
let arguments_as_tuple = arguments.exact_tuple_instance_spec(db);
|
||||
|
||||
let mut argument_elements = arguments_as_tuple
|
||||
.as_ref()
|
||||
.map(|tup| Either::Left(tup.all_elements().copied()))
|
||||
.unwrap_or(Either::Right(std::iter::once(arguments)));
|
||||
|
||||
let probably_meant_literal = argument_elements.all(|ty| match ty {
|
||||
Type::StringLiteral(_)
|
||||
| Type::BytesLiteral(_)
|
||||
| Type::EnumLiteral(_)
|
||||
| Type::BooleanLiteral(_) => true,
|
||||
Type::NominalInstance(instance) => {
|
||||
instance.has_known_class(db, KnownClass::NoneType)
|
||||
}
|
||||
_ => false,
|
||||
});
|
||||
|
||||
if probably_meant_literal {
|
||||
diag.annotate(
|
||||
self.context
|
||||
.secondary(&*subscript.value)
|
||||
.message("Did you mean `Literal`?"),
|
||||
);
|
||||
diag.set_concise_message(
|
||||
"`LiteralString` expects no type parameter - did you mean `Literal`?",
|
||||
);
|
||||
}
|
||||
}
|
||||
Type::unknown()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user