[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:
David Peter 2024-11-18 13:03:27 +01:00 committed by GitHub
parent d99210c049
commit d81b6cd334
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 42 additions and 50 deletions

View file

@ -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",

View file

@ -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]);
}
})

View file

@ -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),
];