mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-03 10:23:11 +00:00
[red-knot] Emit error if int/float/complex/bytes/boolean literals appear in type expressions outside typing.Literal[]
(#16765)
## Summary Fixes https://github.com/astral-sh/ruff/issues/16532 ## Test Plan New mdtest assertions added --------- Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
parent
93ca4a96e0
commit
24707777af
5 changed files with 72 additions and 8 deletions
|
@ -47,8 +47,10 @@ def _(c: Callable[42, str]):
|
|||
Or, when one of the parameter type is invalid in the list:
|
||||
|
||||
```py
|
||||
# error: [invalid-type-form] "Int literals are not allowed in this context in a type expression"
|
||||
# error: [invalid-type-form] "Boolean literals are not allowed in this context in a type expression"
|
||||
def _(c: Callable[[int, 42, str, False], None]):
|
||||
# revealed: (int, @Todo(number literal in type expression), str, @Todo(boolean literal in type expression), /) -> None
|
||||
# revealed: (int, Unknown, str, Unknown, /) -> None
|
||||
reveal_type(c)
|
||||
```
|
||||
|
||||
|
|
|
@ -43,3 +43,21 @@ def _(
|
|||
reveal_type(q) # revealed: Unknown
|
||||
reveal_type(r) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Invalid AST nodes
|
||||
|
||||
```py
|
||||
def _(
|
||||
a: 1, # error: [invalid-type-form] "Int literals are not allowed in this context in a type expression"
|
||||
b: 2.3, # error: [invalid-type-form] "Float literals are not allowed in type expressions"
|
||||
c: 4j, # error: [invalid-type-form] "Complex literals are not allowed in type expressions"
|
||||
d: True, # error: [invalid-type-form] "Boolean literals are not allowed in this context in a type expression"
|
||||
# error: [invalid-type-form] "Bytes literals are not allowed in this context in a type expression"
|
||||
e: int | b"foo",
|
||||
):
|
||||
reveal_type(a) # revealed: Unknown
|
||||
reveal_type(b) # revealed: Unknown
|
||||
reveal_type(c) # revealed: Unknown
|
||||
reveal_type(d) # revealed: Unknown
|
||||
reveal_type(e) # revealed: int | Unknown
|
||||
```
|
||||
|
|
|
@ -127,6 +127,13 @@ Literal: _SpecialForm
|
|||
```py
|
||||
from other import Literal
|
||||
|
||||
# TODO: can we add a subdiagnostic here saying something like:
|
||||
#
|
||||
# `other.Literal` and `typing.Literal` have similar names, but are different symbols and don't have the same semantics
|
||||
#
|
||||
# ?
|
||||
#
|
||||
# error: [invalid-type-form] "Int literals are not allowed in this context in a type expression"
|
||||
a1: Literal[26]
|
||||
|
||||
def f():
|
||||
|
|
|
@ -16,8 +16,10 @@ reveal_type(cast(int | str, 1)) # revealed: int | str
|
|||
# error: [invalid-type-form]
|
||||
reveal_type(cast(Literal, True)) # revealed: Unknown
|
||||
|
||||
# error: [invalid-type-form]
|
||||
reveal_type(cast(1, True)) # revealed: Unknown
|
||||
|
||||
# TODO: These should be errors
|
||||
cast(1)
|
||||
cast(str)
|
||||
cast(str, b"ar", "foo")
|
||||
|
||||
|
|
|
@ -6085,6 +6085,13 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
/// Infer the type of a type expression without storing the result.
|
||||
fn infer_type_expression_no_store(&mut self, expression: &ast::Expr) -> Type<'db> {
|
||||
// https://typing.readthedocs.io/en/latest/spec/annotations.html#grammar-token-expression-grammar-type_expression
|
||||
|
||||
let report_invalid_type_expression = |message: std::fmt::Arguments| {
|
||||
self.context
|
||||
.report_lint(&INVALID_TYPE_FORM, expression, message);
|
||||
Type::unknown()
|
||||
};
|
||||
|
||||
match expression {
|
||||
ast::Expr::Name(name) => match name.ctx {
|
||||
ast::ExprContext::Load => self
|
||||
|
@ -6115,12 +6122,40 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
todo_type!("ellipsis literal in type expression")
|
||||
}
|
||||
|
||||
// Other literals do not have meaningful values in the annotation expression context.
|
||||
// However, we will we want to handle these differently when working with special forms,
|
||||
// since (e.g.) `123` is not valid in an annotation expression but `Literal[123]` is.
|
||||
ast::Expr::BytesLiteral(_literal) => todo_type!("bytes literal in type expression"),
|
||||
ast::Expr::NumberLiteral(_literal) => todo_type!("number literal in type expression"),
|
||||
ast::Expr::BooleanLiteral(_literal) => todo_type!("boolean literal in type expression"),
|
||||
// TODO: add a subdiagnostic linking to type-expression grammar
|
||||
// and stating that it is only valid in `typing.Literal[]` or `typing.Annotated[]`
|
||||
ast::Expr::BytesLiteral(_) => report_invalid_type_expression(format_args!(
|
||||
"Bytes literals are not allowed in this context in a type expression"
|
||||
)),
|
||||
|
||||
// TODO: add a subdiagnostic linking to type-expression grammar
|
||||
// and stating that it is only valid in `typing.Literal[]` or `typing.Annotated[]`
|
||||
ast::Expr::NumberLiteral(ast::ExprNumberLiteral {
|
||||
value: ast::Number::Int(_),
|
||||
..
|
||||
}) => report_invalid_type_expression(format_args!(
|
||||
"Int literals are not allowed in this context in a type expression"
|
||||
)),
|
||||
|
||||
ast::Expr::NumberLiteral(ast::ExprNumberLiteral {
|
||||
value: ast::Number::Float(_),
|
||||
..
|
||||
}) => report_invalid_type_expression(format_args!(
|
||||
"Float literals are not allowed in type expressions"
|
||||
)),
|
||||
|
||||
ast::Expr::NumberLiteral(ast::ExprNumberLiteral {
|
||||
value: ast::Number::Complex { .. },
|
||||
..
|
||||
}) => report_invalid_type_expression(format_args!(
|
||||
"Complex literals are not allowed in type expressions"
|
||||
)),
|
||||
|
||||
// TODO: add a subdiagnostic linking to type-expression grammar
|
||||
// and stating that it is only valid in `typing.Literal[]` or `typing.Annotated[]`
|
||||
ast::Expr::BooleanLiteral(_) => report_invalid_type_expression(format_args!(
|
||||
"Boolean literals are not allowed in this context in a type expression"
|
||||
)),
|
||||
|
||||
ast::Expr::Subscript(subscript) => {
|
||||
let ast::ExprSubscript {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue