mirror of
https://github.com/astral-sh/ruff.git
synced 2025-07-24 05:26:23 +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]
|
||||
# error: [invalid-literal-parameter]
|
||||
invalid3: Literal[(3, 4)]
|
||||
|
||||
hello = "hello"
|
||||
invalid4: Literal[
|
||||
1 + 2, # error: [invalid-literal-parameter]
|
||||
"foo",
|
||||
|
|
|
@ -4443,11 +4443,18 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
element_types.push(element_ty);
|
||||
}
|
||||
|
||||
if return_todo {
|
||||
let ty = if return_todo {
|
||||
Type::Todo
|
||||
} else {
|
||||
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 => {
|
||||
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
|
||||
fn infer_subclass_of_type_expression(&mut self, slice: &ast::Expr) -> Type<'db> {
|
||||
match slice {
|
||||
ast::Expr::Name(name) => {
|
||||
let name_ty = self.infer_name_expression(name);
|
||||
ast::Expr::Name(_) => {
|
||||
let name_ty = self.infer_expression(slice);
|
||||
if let Some(ClassLiteralType { class }) = name_ty.into_class_literal() {
|
||||
Type::subclass_of(class)
|
||||
} else {
|
||||
|
@ -4541,8 +4548,15 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
ast::Expr::Subscript(ast::ExprSubscript { value, slice, .. }) => {
|
||||
let value_ty = self.infer_expression(value);
|
||||
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 {
|
||||
self.store_expression_type(parameters, Type::Unknown);
|
||||
|
||||
return Err(vec![parameters]);
|
||||
}
|
||||
}
|
||||
|
@ -4560,15 +4574,27 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
}
|
||||
}
|
||||
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 {
|
||||
self.store_expression_type(parameters, Type::Unknown);
|
||||
|
||||
return Err(errors);
|
||||
}
|
||||
}
|
||||
|
||||
ast::Expr::StringLiteral(literal) => self.infer_string_literal_expression(literal),
|
||||
ast::Expr::BytesLiteral(literal) => self.infer_bytes_literal_expression(literal),
|
||||
ast::Expr::BooleanLiteral(literal) => self.infer_boolean_literal_expression(literal),
|
||||
literal @ (ast::Expr::StringLiteral(_)
|
||||
| ast::Expr::BytesLiteral(_)
|
||||
| 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
|
||||
ast::Expr::Attribute(ast::ExprAttribute { value, attr, .. }) => {
|
||||
let value_ty = self.infer_expression(value);
|
||||
|
@ -4578,7 +4604,6 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
.ignore_possibly_unbound()
|
||||
.unwrap_or(Type::Unknown)
|
||||
}
|
||||
ast::Expr::NoneLiteral(_) => Type::none(self.db),
|
||||
// for negative and positive numbers
|
||||
ast::Expr::UnaryOp(ref u)
|
||||
if matches!(u.op, UnaryOp::USub | UnaryOp::UAdd)
|
||||
|
@ -4586,10 +4611,8 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
{
|
||||
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]);
|
||||
}
|
||||
})
|
||||
|
|
|
@ -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.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/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_15.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_26.py", true, false),
|
||||
("crates/ruff_linter/resources/test/fixtures/pyflakes/project/foo/bar.py", true, true),
|
||||
("crates/ruff_linter/resources/test/fixtures/pyflakes/project/foo/bop/baz.py", true, true),
|
||||
("crates/ruff_linter/resources/test/fixtures/pylint/single_string_slots.py", true, true),
|
||||
("crates/ruff_linter/resources/test/fixtures/pyupgrade/UP037_0.py", true, true),
|
||||
// Fails for unknown reasons:
|
||||
("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/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),
|
||||
];
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue