mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-02 06:41:23 +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:
|
Or, when one of the parameter type is invalid in the list:
|
||||||
|
|
||||||
```py
|
```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]):
|
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)
|
reveal_type(c)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -43,3 +43,21 @@ def _(
|
||||||
reveal_type(q) # revealed: Unknown
|
reveal_type(q) # revealed: Unknown
|
||||||
reveal_type(r) # 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
|
```py
|
||||||
from other import Literal
|
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]
|
a1: Literal[26]
|
||||||
|
|
||||||
def f():
|
def f():
|
||||||
|
|
|
@ -16,8 +16,10 @@ reveal_type(cast(int | str, 1)) # revealed: int | str
|
||||||
# error: [invalid-type-form]
|
# error: [invalid-type-form]
|
||||||
reveal_type(cast(Literal, True)) # revealed: Unknown
|
reveal_type(cast(Literal, True)) # revealed: Unknown
|
||||||
|
|
||||||
|
# error: [invalid-type-form]
|
||||||
|
reveal_type(cast(1, True)) # revealed: Unknown
|
||||||
|
|
||||||
# TODO: These should be errors
|
# TODO: These should be errors
|
||||||
cast(1)
|
|
||||||
cast(str)
|
cast(str)
|
||||||
cast(str, b"ar", "foo")
|
cast(str, b"ar", "foo")
|
||||||
|
|
||||||
|
|
|
@ -6085,6 +6085,13 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
/// Infer the type of a type expression without storing the result.
|
/// Infer the type of a type expression without storing the result.
|
||||||
fn infer_type_expression_no_store(&mut self, expression: &ast::Expr) -> Type<'db> {
|
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
|
// 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 {
|
match expression {
|
||||||
ast::Expr::Name(name) => match name.ctx {
|
ast::Expr::Name(name) => match name.ctx {
|
||||||
ast::ExprContext::Load => self
|
ast::ExprContext::Load => self
|
||||||
|
@ -6115,12 +6122,40 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
todo_type!("ellipsis literal in type expression")
|
todo_type!("ellipsis literal in type expression")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Other literals do not have meaningful values in the annotation expression context.
|
// TODO: add a subdiagnostic linking to type-expression grammar
|
||||||
// However, we will we want to handle these differently when working with special forms,
|
// and stating that it is only valid in `typing.Literal[]` or `typing.Annotated[]`
|
||||||
// since (e.g.) `123` is not valid in an annotation expression but `Literal[123]` is.
|
ast::Expr::BytesLiteral(_) => report_invalid_type_expression(format_args!(
|
||||||
ast::Expr::BytesLiteral(_literal) => todo_type!("bytes literal in type expression"),
|
"Bytes literals are not allowed in this context in a 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::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) => {
|
ast::Expr::Subscript(subscript) => {
|
||||||
let ast::ExprSubscript {
|
let ast::ExprSubscript {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue