mirror of https://github.com/astral-sh/ruff
[ty] Improve several "Did you mean?" suggestions (#21597)
This commit is contained in:
parent
747c39a26a
commit
b19ddca69b
|
|
@ -35,6 +35,8 @@ bad_nesting: Literal[LiteralString] # error: [invalid-type-form]
|
||||||
|
|
||||||
`LiteralString` cannot be parameterized.
|
`LiteralString` cannot be parameterized.
|
||||||
|
|
||||||
|
<!-- snapshot-diagnostics -->
|
||||||
|
|
||||||
```py
|
```py
|
||||||
from typing_extensions import LiteralString
|
from typing_extensions import LiteralString
|
||||||
|
|
||||||
|
|
@ -42,7 +44,6 @@ from typing_extensions import LiteralString
|
||||||
a: LiteralString[str]
|
a: LiteralString[str]
|
||||||
|
|
||||||
# error: [invalid-type-form]
|
# error: [invalid-type-form]
|
||||||
# error: [unresolved-reference] "Name `foo` used when not defined"
|
|
||||||
b: LiteralString["foo"]
|
b: LiteralString["foo"]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,29 +34,27 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/annotations/invalid.md
|
||||||
# Diagnostics
|
# Diagnostics
|
||||||
|
|
||||||
```
|
```
|
||||||
error[invalid-type-form]: Variable of type `<module 'datetime'>` is not allowed in a type expression
|
error[invalid-type-form]: Module `datetime` is not valid in a type expression
|
||||||
--> src/foo.py:3:10
|
--> src/foo.py:3:10
|
||||||
|
|
|
|
||||||
1 | import datetime
|
1 | import datetime
|
||||||
2 |
|
2 |
|
||||||
3 | def f(x: datetime): ... # error: [invalid-type-form]
|
3 | def f(x: datetime): ... # error: [invalid-type-form]
|
||||||
| ^^^^^^^^
|
| ^^^^^^^^ Did you mean to use the module's member `datetime.datetime`?
|
||||||
|
|
|
|
||||||
info: Did you mean to use the module's member `datetime.datetime` instead?
|
|
||||||
info: rule `invalid-type-form` is enabled by default
|
info: rule `invalid-type-form` is enabled by default
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
```
|
||||||
error[invalid-type-form]: Variable of type `<module 'PIL.Image'>` is not allowed in a type expression
|
error[invalid-type-form]: Module `PIL.Image` is not valid in a type expression
|
||||||
--> src/bar.py:3:10
|
--> src/bar.py:3:10
|
||||||
|
|
|
|
||||||
1 | from PIL import Image
|
1 | from PIL import Image
|
||||||
2 |
|
2 |
|
||||||
3 | def g(x: Image): ... # error: [invalid-type-form]
|
3 | def g(x: Image): ... # error: [invalid-type-form]
|
||||||
| ^^^^^
|
| ^^^^^ Did you mean to use the module's member `Image.Image`?
|
||||||
|
|
|
|
||||||
info: Did you mean to use the module's member `Image.Image` instead?
|
|
||||||
info: rule `invalid-type-form` is enabled by default
|
info: rule `invalid-type-form` is enabled by default
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
---
|
||||||
|
source: crates/ty_test/src/lib.rs
|
||||||
|
expression: snapshot
|
||||||
|
---
|
||||||
|
---
|
||||||
|
mdtest name: literal_string.md - `LiteralString` - Usages - Parameterized
|
||||||
|
mdtest path: crates/ty_python_semantic/resources/mdtest/annotations/literal_string.md
|
||||||
|
---
|
||||||
|
|
||||||
|
# Python source files
|
||||||
|
|
||||||
|
## mdtest_snippet.py
|
||||||
|
|
||||||
|
```
|
||||||
|
1 | from typing_extensions import LiteralString
|
||||||
|
2 |
|
||||||
|
3 | # error: [invalid-type-form]
|
||||||
|
4 | a: LiteralString[str]
|
||||||
|
5 |
|
||||||
|
6 | # error: [invalid-type-form]
|
||||||
|
7 | b: LiteralString["foo"]
|
||||||
|
```
|
||||||
|
|
||||||
|
# Diagnostics
|
||||||
|
|
||||||
|
```
|
||||||
|
error[invalid-type-form]: `LiteralString` expects no type parameter
|
||||||
|
--> src/mdtest_snippet.py:4:4
|
||||||
|
|
|
||||||
|
3 | # error: [invalid-type-form]
|
||||||
|
4 | a: LiteralString[str]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^
|
||||||
|
5 |
|
||||||
|
6 | # error: [invalid-type-form]
|
||||||
|
|
|
||||||
|
info: rule `invalid-type-form` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
error[invalid-type-form]: `LiteralString` expects no type parameter
|
||||||
|
--> src/mdtest_snippet.py:7:4
|
||||||
|
|
|
||||||
|
6 | # error: [invalid-type-form]
|
||||||
|
7 | b: LiteralString["foo"]
|
||||||
|
| -------------^^^^^^^
|
||||||
|
| |
|
||||||
|
| Did you mean `Literal`?
|
||||||
|
|
|
||||||
|
info: rule `invalid-type-form` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
---
|
||||||
|
source: crates/ty_test/src/lib.rs
|
||||||
|
expression: snapshot
|
||||||
|
---
|
||||||
|
---
|
||||||
|
mdtest name: typed_dict.md - `TypedDict` - Error cases - `typing.TypedDict` is not allowed in type expressions
|
||||||
|
mdtest path: crates/ty_python_semantic/resources/mdtest/typed_dict.md
|
||||||
|
---
|
||||||
|
|
||||||
|
# Python source files
|
||||||
|
|
||||||
|
## mdtest_snippet.py
|
||||||
|
|
||||||
|
```
|
||||||
|
1 | from typing import TypedDict
|
||||||
|
2 |
|
||||||
|
3 | # error: [invalid-type-form] "The special form `typing.TypedDict` is not allowed in type expressions"
|
||||||
|
4 | x: TypedDict = {"name": "Alice"}
|
||||||
|
```
|
||||||
|
|
||||||
|
# Diagnostics
|
||||||
|
|
||||||
|
```
|
||||||
|
error[invalid-type-form]: The special form `typing.TypedDict` is not allowed in type expressions
|
||||||
|
--> src/mdtest_snippet.py:4:4
|
||||||
|
|
|
||||||
|
3 | # error: [invalid-type-form] "The special form `typing.TypedDict` is not allowed in type expressions"
|
||||||
|
4 | x: TypedDict = {"name": "Alice"}
|
||||||
|
| ^^^^^^^^^
|
||||||
|
|
|
||||||
|
help: You might have meant to use a concrete TypedDict or `collections.abc.Mapping[str, object]`
|
||||||
|
info: rule `invalid-type-form` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
@ -1407,10 +1407,12 @@ msg.content
|
||||||
|
|
||||||
### `typing.TypedDict` is not allowed in type expressions
|
### `typing.TypedDict` is not allowed in type expressions
|
||||||
|
|
||||||
|
<!-- snapshot-diagnostics -->
|
||||||
|
|
||||||
```py
|
```py
|
||||||
from typing import TypedDict
|
from typing import TypedDict
|
||||||
|
|
||||||
# error: [invalid-type-form] "The special form `typing.TypedDict` is not allowed in type expressions."
|
# error: [invalid-type-form] "The special form `typing.TypedDict` is not allowed in type expressions"
|
||||||
x: TypedDict = {"name": "Alice"}
|
x: TypedDict = {"name": "Alice"}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8597,25 +8597,23 @@ impl<'db> InvalidTypeExpression<'db> {
|
||||||
InvalidTypeExpression::Field => {
|
InvalidTypeExpression::Field => {
|
||||||
f.write_str("`dataclasses.Field` is not allowed in type expressions")
|
f.write_str("`dataclasses.Field` is not allowed in type expressions")
|
||||||
}
|
}
|
||||||
InvalidTypeExpression::ConstraintSet => {
|
InvalidTypeExpression::ConstraintSet => f.write_str(
|
||||||
f.write_str("`ty_extensions.ConstraintSet` is not allowed in type expressions")
|
"`ty_extensions.ConstraintSet` is not allowed in type expressions",
|
||||||
}
|
),
|
||||||
InvalidTypeExpression::GenericContext => {
|
InvalidTypeExpression::GenericContext => f.write_str(
|
||||||
f.write_str("`ty_extensions.GenericContext` is not allowed in type expressions")
|
"`ty_extensions.GenericContext` is not allowed in type expressions",
|
||||||
}
|
),
|
||||||
InvalidTypeExpression::Specialization => {
|
InvalidTypeExpression::Specialization => f.write_str(
|
||||||
f.write_str("`ty_extensions.GenericContext` is not allowed in type expressions")
|
"`ty_extensions.GenericContext` is not allowed in type expressions",
|
||||||
}
|
),
|
||||||
InvalidTypeExpression::TypedDict => {
|
InvalidTypeExpression::TypedDict => f.write_str(
|
||||||
f.write_str(
|
"The special form `typing.TypedDict` \
|
||||||
"The special form `typing.TypedDict` is not allowed in type expressions. \
|
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(
|
||||||
InvalidTypeExpression::TypeAlias => {
|
"`typing.TypeAlias` is only allowed \
|
||||||
f.write_str(
|
as the sole annotation on an annotated assignment",
|
||||||
"`typing.TypeAlias` is only allowed as the sole annotation on an annotated assignment",
|
),
|
||||||
)
|
|
||||||
}
|
|
||||||
InvalidTypeExpression::TypeQualifier(qualifier) => write!(
|
InvalidTypeExpression::TypeQualifier(qualifier) => write!(
|
||||||
f,
|
f,
|
||||||
"Type qualifier `{qualifier}` is not allowed in type expressions \
|
"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 \
|
"Type qualifier `{qualifier}` is not allowed in type expressions \
|
||||||
(only in annotation expressions, and only with exactly one argument)",
|
(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!(
|
InvalidTypeExpression::InvalidType(ty, _) => write!(
|
||||||
f,
|
f,
|
||||||
"Variable of type `{ty}` is not allowed in a type expression",
|
"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) {
|
fn add_subdiagnostics(self, db: &'db dyn Db, mut diagnostic: LintDiagnosticGuard) {
|
||||||
let InvalidTypeExpression::InvalidType(ty, scope) = self else {
|
if let InvalidTypeExpression::InvalidType(ty, scope) = self {
|
||||||
return;
|
let Type::ModuleLiteral(module_type) = ty 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 {
|
||||||
let module = module_type.module(db);
|
return;
|
||||||
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)
|
||||||
let Some(module_member_with_same_name) = ty
|
.place
|
||||||
.member(db, module_name_final_part)
|
.ignore_possibly_undefined()
|
||||||
.place
|
else {
|
||||||
.ignore_possibly_undefined()
|
return;
|
||||||
else {
|
};
|
||||||
return;
|
if module_member_with_same_name
|
||||||
};
|
.in_type_expression(db, scope, None)
|
||||||
if module_member_with_same_name
|
.is_err()
|
||||||
.in_type_expression(db, scope, None)
|
{
|
||||||
.is_err()
|
return;
|
||||||
{
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: showing a diff (and even having an autofix) would be even better
|
// TODO: showing a diff (and even having an autofix) would be even better
|
||||||
diagnostic.info(format_args!(
|
diagnostic.set_primary_message(format_args!(
|
||||||
"Did you mean to use the module's member \
|
"Did you mean to use the module's member \
|
||||||
`{module_name_final_part}.{module_name_final_part}` instead?"
|
`{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:
|
// anything else is an invalid annotation:
|
||||||
op => {
|
op => {
|
||||||
self.infer_binary_expression(binary, TypeContext::default());
|
self.infer_binary_expression(binary, TypeContext::default());
|
||||||
if let Some(mut diag) = self.report_invalid_type_expression(
|
self.report_invalid_type_expression(
|
||||||
expression,
|
expression,
|
||||||
format_args!(
|
format_args!(
|
||||||
"Invalid binary operator `{}` in type annotation",
|
"Invalid binary operator `{}` in type annotation",
|
||||||
op.as_str()
|
op.as_str()
|
||||||
),
|
),
|
||||||
) {
|
);
|
||||||
diag.info("Did you mean to use `|`?");
|
|
||||||
}
|
|
||||||
Type::unknown()
|
Type::unknown()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1446,13 +1444,40 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||||
Type::unknown()
|
Type::unknown()
|
||||||
}
|
}
|
||||||
SpecialFormType::LiteralString => {
|
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) {
|
if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) {
|
||||||
let mut diag = builder.into_diagnostic(format_args!(
|
let mut diag =
|
||||||
"Type `{special_form}` expected no type parameter",
|
builder.into_diagnostic("`LiteralString` expects no type parameter");
|
||||||
));
|
|
||||||
diag.info("Did you mean to use `Literal[...]` instead?");
|
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()
|
Type::unknown()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue