mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 13:51:37 +00:00
[red-knot] Types for subexpressions of annotations (#14426)
## Summary This patches up various missing paths where sub-expressions of type annotations previously had no type attached. Examples include: ```py tuple[int, str] # ~~~~~~~~ type[MyClass] # ~~~~~~~ Literal["foo"] # ~~~~~ Literal["foo", Literal[1, 2]] # ~~~~~~~~~~~~~ Literal[1, "a", random.illegal(sub[expr + ession])] # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ``` ## Test Plan ``` cargo nextest run -p red_knot_workspace -- --ignored linter_af linter_gz ```
This commit is contained in:
parent
d99210c049
commit
d81b6cd334
3 changed files with 42 additions and 50 deletions
|
@ -51,6 +51,8 @@ invalid1: Literal[3 + 4]
|
||||||
invalid2: Literal[4 + 3j]
|
invalid2: Literal[4 + 3j]
|
||||||
# error: [invalid-literal-parameter]
|
# error: [invalid-literal-parameter]
|
||||||
invalid3: Literal[(3, 4)]
|
invalid3: Literal[(3, 4)]
|
||||||
|
|
||||||
|
hello = "hello"
|
||||||
invalid4: Literal[
|
invalid4: Literal[
|
||||||
1 + 2, # error: [invalid-literal-parameter]
|
1 + 2, # error: [invalid-literal-parameter]
|
||||||
"foo",
|
"foo",
|
||||||
|
|
|
@ -4443,11 +4443,18 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
element_types.push(element_ty);
|
element_types.push(element_ty);
|
||||||
}
|
}
|
||||||
|
|
||||||
if return_todo {
|
let ty = if return_todo {
|
||||||
Type::Todo
|
Type::Todo
|
||||||
} else {
|
} else {
|
||||||
Type::tuple(self.db, &element_types)
|
Type::tuple(self.db, &element_types)
|
||||||
}
|
};
|
||||||
|
|
||||||
|
// Here, we store the type for the inner `int, str` tuple-expression,
|
||||||
|
// while the type for the outer `tuple[int, str]` slice-expression is
|
||||||
|
// stored in the surrounding `infer_type_expression` call:
|
||||||
|
self.store_expression_type(tuple_slice, ty);
|
||||||
|
|
||||||
|
ty
|
||||||
}
|
}
|
||||||
single_element => {
|
single_element => {
|
||||||
let single_element_ty = self.infer_type_expression(single_element);
|
let single_element_ty = self.infer_type_expression(single_element);
|
||||||
|
@ -4463,8 +4470,8 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
/// Given the slice of a `type[]` annotation, return the type that the annotation represents
|
/// Given the slice of a `type[]` annotation, return the type that the annotation represents
|
||||||
fn infer_subclass_of_type_expression(&mut self, slice: &ast::Expr) -> Type<'db> {
|
fn infer_subclass_of_type_expression(&mut self, slice: &ast::Expr) -> Type<'db> {
|
||||||
match slice {
|
match slice {
|
||||||
ast::Expr::Name(name) => {
|
ast::Expr::Name(_) => {
|
||||||
let name_ty = self.infer_name_expression(name);
|
let name_ty = self.infer_expression(slice);
|
||||||
if let Some(ClassLiteralType { class }) = name_ty.into_class_literal() {
|
if let Some(ClassLiteralType { class }) = name_ty.into_class_literal() {
|
||||||
Type::subclass_of(class)
|
Type::subclass_of(class)
|
||||||
} else {
|
} else {
|
||||||
|
@ -4541,8 +4548,15 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
ast::Expr::Subscript(ast::ExprSubscript { value, slice, .. }) => {
|
ast::Expr::Subscript(ast::ExprSubscript { value, slice, .. }) => {
|
||||||
let value_ty = self.infer_expression(value);
|
let value_ty = self.infer_expression(value);
|
||||||
if matches!(value_ty, Type::KnownInstance(KnownInstanceType::Literal)) {
|
if matches!(value_ty, Type::KnownInstance(KnownInstanceType::Literal)) {
|
||||||
self.infer_literal_parameter_type(slice)?
|
let ty = self.infer_literal_parameter_type(slice)?;
|
||||||
|
|
||||||
|
// This branch deals with annotations such as `Literal[Literal[1]]`.
|
||||||
|
// Here, we store the type for the inner `Literal[1]` expression:
|
||||||
|
self.store_expression_type(parameters, ty);
|
||||||
|
ty
|
||||||
} else {
|
} else {
|
||||||
|
self.store_expression_type(parameters, Type::Unknown);
|
||||||
|
|
||||||
return Err(vec![parameters]);
|
return Err(vec![parameters]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4560,15 +4574,27 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if errors.is_empty() {
|
if errors.is_empty() {
|
||||||
builder.build()
|
let union_type = builder.build();
|
||||||
|
|
||||||
|
// This branch deals with annotations such as `Literal[1, 2]`. Here, we
|
||||||
|
// store the type for the inner `1, 2` tuple-expression:
|
||||||
|
self.store_expression_type(parameters, union_type);
|
||||||
|
|
||||||
|
union_type
|
||||||
} else {
|
} else {
|
||||||
|
self.store_expression_type(parameters, Type::Unknown);
|
||||||
|
|
||||||
return Err(errors);
|
return Err(errors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ast::Expr::StringLiteral(literal) => self.infer_string_literal_expression(literal),
|
literal @ (ast::Expr::StringLiteral(_)
|
||||||
ast::Expr::BytesLiteral(literal) => self.infer_bytes_literal_expression(literal),
|
| ast::Expr::BytesLiteral(_)
|
||||||
ast::Expr::BooleanLiteral(literal) => self.infer_boolean_literal_expression(literal),
|
| ast::Expr::BooleanLiteral(_)
|
||||||
|
| ast::Expr::NoneLiteral(_)) => self.infer_expression(literal),
|
||||||
|
literal @ ast::Expr::NumberLiteral(ref number) if number.value.is_int() => {
|
||||||
|
self.infer_expression(literal)
|
||||||
|
}
|
||||||
// For enum values
|
// For enum values
|
||||||
ast::Expr::Attribute(ast::ExprAttribute { value, attr, .. }) => {
|
ast::Expr::Attribute(ast::ExprAttribute { value, attr, .. }) => {
|
||||||
let value_ty = self.infer_expression(value);
|
let value_ty = self.infer_expression(value);
|
||||||
|
@ -4578,7 +4604,6 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
.ignore_possibly_unbound()
|
.ignore_possibly_unbound()
|
||||||
.unwrap_or(Type::Unknown)
|
.unwrap_or(Type::Unknown)
|
||||||
}
|
}
|
||||||
ast::Expr::NoneLiteral(_) => Type::none(self.db),
|
|
||||||
// for negative and positive numbers
|
// for negative and positive numbers
|
||||||
ast::Expr::UnaryOp(ref u)
|
ast::Expr::UnaryOp(ref u)
|
||||||
if matches!(u.op, UnaryOp::USub | UnaryOp::UAdd)
|
if matches!(u.op, UnaryOp::USub | UnaryOp::UAdd)
|
||||||
|
@ -4586,10 +4611,8 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
{
|
{
|
||||||
self.infer_unary_expression(u)
|
self.infer_unary_expression(u)
|
||||||
}
|
}
|
||||||
ast::Expr::NumberLiteral(ref number) if number.value.is_int() => {
|
|
||||||
self.infer_number_literal_expression(number)
|
|
||||||
}
|
|
||||||
_ => {
|
_ => {
|
||||||
|
self.infer_expression(parameters);
|
||||||
return Err(vec![parameters]);
|
return Err(vec![parameters]);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -275,50 +275,17 @@ const KNOWN_FAILURES: &[(&str, bool, bool)] = &[
|
||||||
("crates/ruff_python_parser/resources/inline/ok/type_param_type_var_tuple.py", true, true),
|
("crates/ruff_python_parser/resources/inline/ok/type_param_type_var_tuple.py", true, true),
|
||||||
("crates/ruff_python_parser/resources/inline/ok/type_param_type_var.py", true, true),
|
("crates/ruff_python_parser/resources/inline/ok/type_param_type_var.py", true, true),
|
||||||
("crates/ruff_python_parser/resources/valid/statement/type.py", true, true),
|
("crates/ruff_python_parser/resources/valid/statement/type.py", true, true),
|
||||||
// Fails for unknown reasons:
|
|
||||||
("crates/ruff_linter/resources/test/fixtures/flake8_future_annotations/no_future_import_uses_union_inner.py", true, true),
|
|
||||||
("crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI011.py", true, true),
|
|
||||||
("crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI015.py", true, true),
|
|
||||||
("crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI016.py", true, true),
|
|
||||||
("crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI019.py", true, true),
|
|
||||||
("crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI020.py", true, true),
|
|
||||||
("crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI030.py", true, true),
|
|
||||||
("crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI034.py", true, true),
|
|
||||||
("crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI035.py", true, true),
|
|
||||||
("crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI036.py", true, true),
|
|
||||||
("crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI041.py", true, true),
|
|
||||||
("crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI051.py", true, true),
|
|
||||||
("crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI052.py", true, true),
|
|
||||||
("crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI055.py", true, true),
|
|
||||||
("crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI061.py", true, true),
|
|
||||||
("crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI062.py", true, true),
|
|
||||||
("crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI063.py", true, true),
|
|
||||||
("crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI064.py", true, true),
|
|
||||||
("crates/ruff_linter/resources/test/fixtures/flake8_type_checking/quote.py", true, true),
|
|
||||||
("crates/ruff_linter/resources/test/fixtures/flake8_type_checking/quote2.py", true, true),
|
|
||||||
("crates/ruff_linter/resources/test/fixtures/flake8_type_checking/quote3.py", true, true),
|
|
||||||
("crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TCH004_13.py", true, true),
|
|
||||||
("crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TCH004_15.py", true, true),
|
("crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TCH004_15.py", true, true),
|
||||||
("crates/ruff_linter/resources/test/fixtures/pyflakes/F401_19.py", true, true),
|
("crates/ruff_linter/resources/test/fixtures/pyflakes/F401_19.py", true, true),
|
||||||
("crates/ruff_linter/resources/test/fixtures/pyflakes/F541.py", true, true),
|
|
||||||
("crates/ruff_linter/resources/test/fixtures/pyflakes/F632.py", true, true),
|
|
||||||
("crates/ruff_linter/resources/test/fixtures/pyflakes/F811_19.py", true, false),
|
|
||||||
("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_0.py", true, true),
|
|
||||||
("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_14.py", false, true),
|
("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_14.py", false, true),
|
||||||
("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_15.py", true, true),
|
("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_15.py", true, true),
|
||||||
("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_17.py", true, true),
|
("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_17.py", true, true),
|
||||||
("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_2.py", true, true),
|
|
||||||
("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_20.py", true, true),
|
("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_20.py", true, true),
|
||||||
("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_26.py", true, false),
|
("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_26.py", true, false),
|
||||||
("crates/ruff_linter/resources/test/fixtures/pyflakes/project/foo/bar.py", true, true),
|
// Fails for unknown reasons:
|
||||||
("crates/ruff_linter/resources/test/fixtures/pyflakes/project/foo/bop/baz.py", true, true),
|
("crates/ruff_linter/resources/test/fixtures/pyflakes/F541.py", true, true),
|
||||||
("crates/ruff_linter/resources/test/fixtures/pylint/single_string_slots.py", true, true),
|
("crates/ruff_linter/resources/test/fixtures/pyflakes/F632.py", true, true),
|
||||||
("crates/ruff_linter/resources/test/fixtures/pyupgrade/UP037_0.py", true, true),
|
("crates/ruff_linter/resources/test/fixtures/pyflakes/F811_19.py", true, false),
|
||||||
("crates/ruff_linter/resources/test/fixtures/pyupgrade/UP039.py", true, false),
|
("crates/ruff_linter/resources/test/fixtures/pyupgrade/UP039.py", true, false),
|
||||||
("crates/ruff_linter/resources/test/fixtures/pyupgrade/UP044.py", true, true),
|
|
||||||
("crates/ruff_linter/resources/test/fixtures/ruff/RUF013_0.py", true, true),
|
|
||||||
("crates/ruff_linter/resources/test/fixtures/ruff/RUF013_3.py", true, true),
|
|
||||||
("crates/ruff_linter/resources/test/fixtures/ruff/RUF022.py", true, true),
|
|
||||||
("crates/ruff_linter/resources/test/fixtures/ruff/RUF038.py", true, true),
|
|
||||||
("crates/ruff_python_parser/resources/valid/expressions/f_string.py", true, true),
|
("crates/ruff_python_parser/resources/valid/expressions/f_string.py", true, true),
|
||||||
];
|
];
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue