mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-01 06:11:43 +00:00
[red-knot] Handle StringLiteral truncation (#13276)
When a type of the form `Literal["..."]` would be constructed with too large of a string, this PR converts it to `LiteralString` instead. We also extend inference for binary operations to include the case where one of the operands is `LiteralString`. Closes #13224
This commit is contained in:
parent
a7c936878d
commit
e4aa479515
1 changed files with 95 additions and 11 deletions
|
@ -1426,13 +1426,14 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn infer_string_literal_expression(&mut self, literal: &ast::ExprStringLiteral) -> Type<'db> {
|
fn infer_string_literal_expression(&mut self, literal: &ast::ExprStringLiteral) -> Type<'db> {
|
||||||
let value = if literal.value.len() <= Self::MAX_STRING_LITERAL_SIZE {
|
if literal.value.len() <= Self::MAX_STRING_LITERAL_SIZE {
|
||||||
literal.value.to_str().into()
|
Type::StringLiteral(StringLiteralType::new(
|
||||||
|
self.db,
|
||||||
|
literal.value.to_str().into(),
|
||||||
|
))
|
||||||
} else {
|
} else {
|
||||||
Box::default()
|
Type::LiteralString
|
||||||
};
|
}
|
||||||
|
|
||||||
Type::StringLiteral(StringLiteralType::new(self.db, value))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn infer_bytes_literal_expression(&mut self, literal: &ast::ExprBytesLiteral) -> Type<'db> {
|
fn infer_bytes_literal_expression(&mut self, literal: &ast::ExprBytesLiteral) -> Type<'db> {
|
||||||
|
@ -2041,13 +2042,23 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
}
|
}
|
||||||
|
|
||||||
(Type::StringLiteral(lhs), Type::StringLiteral(rhs), ast::Operator::Add) => {
|
(Type::StringLiteral(lhs), Type::StringLiteral(rhs), ast::Operator::Add) => {
|
||||||
Type::StringLiteral(StringLiteralType::new(self.db, {
|
let lhs_value = lhs.value(self.db).to_string();
|
||||||
let lhs_value = lhs.value(self.db).to_string();
|
let rhs_value = rhs.value(self.db).as_ref();
|
||||||
let rhs_value = rhs.value(self.db).as_ref();
|
if lhs_value.len() + rhs_value.len() <= Self::MAX_STRING_LITERAL_SIZE {
|
||||||
(lhs_value + rhs_value).into()
|
Type::StringLiteral(StringLiteralType::new(self.db, {
|
||||||
}))
|
(lhs_value + rhs_value).into()
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
Type::LiteralString
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
(
|
||||||
|
Type::StringLiteral(_) | Type::LiteralString,
|
||||||
|
Type::StringLiteral(_) | Type::LiteralString,
|
||||||
|
ast::Operator::Add,
|
||||||
|
) => Type::LiteralString,
|
||||||
|
|
||||||
(Type::StringLiteral(s), Type::IntLiteral(n), ast::Operator::Mult)
|
(Type::StringLiteral(s), Type::IntLiteral(n), ast::Operator::Mult)
|
||||||
| (Type::IntLiteral(n), Type::StringLiteral(s), ast::Operator::Mult) => {
|
| (Type::IntLiteral(n), Type::StringLiteral(s), ast::Operator::Mult) => {
|
||||||
if n < 1 {
|
if n < 1 {
|
||||||
|
@ -2066,6 +2077,15 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
(Type::LiteralString, Type::IntLiteral(n), ast::Operator::Mult)
|
||||||
|
| (Type::IntLiteral(n), Type::LiteralString, ast::Operator::Mult) => {
|
||||||
|
if n < 1 {
|
||||||
|
Type::StringLiteral(StringLiteralType::new(self.db, Box::default()))
|
||||||
|
} else {
|
||||||
|
Type::LiteralString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_ => Type::Unknown, // TODO
|
_ => Type::Unknown, // TODO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2892,6 +2912,70 @@ mod tests {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multiplied_literal_string() -> anyhow::Result<()> {
|
||||||
|
let mut db = setup_db();
|
||||||
|
let content = format!(
|
||||||
|
r#"
|
||||||
|
v = "{y}"
|
||||||
|
w = 10*"{y}"
|
||||||
|
x = "{y}"*10
|
||||||
|
z = 0*"{y}"
|
||||||
|
u = (-100)*"{y}"
|
||||||
|
"#,
|
||||||
|
y = "a".repeat(TypeInferenceBuilder::MAX_STRING_LITERAL_SIZE + 1),
|
||||||
|
);
|
||||||
|
db.write_dedented("src/a.py", &content)?;
|
||||||
|
|
||||||
|
assert_public_ty(&db, "src/a.py", "v", "LiteralString");
|
||||||
|
assert_public_ty(&db, "src/a.py", "w", "LiteralString");
|
||||||
|
assert_public_ty(&db, "src/a.py", "x", "LiteralString");
|
||||||
|
assert_public_ty(&db, "src/a.py", "z", r#"Literal[""]"#);
|
||||||
|
assert_public_ty(&db, "src/a.py", "u", r#"Literal[""]"#);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn truncated_string_literals_become_literal_string() -> anyhow::Result<()> {
|
||||||
|
let mut db = setup_db();
|
||||||
|
let content = format!(
|
||||||
|
r#"
|
||||||
|
w = "{y}"
|
||||||
|
x = "a" + "{z}"
|
||||||
|
"#,
|
||||||
|
y = "a".repeat(TypeInferenceBuilder::MAX_STRING_LITERAL_SIZE + 1),
|
||||||
|
z = "a".repeat(TypeInferenceBuilder::MAX_STRING_LITERAL_SIZE),
|
||||||
|
);
|
||||||
|
db.write_dedented("src/a.py", &content)?;
|
||||||
|
|
||||||
|
assert_public_ty(&db, "src/a.py", "w", "LiteralString");
|
||||||
|
assert_public_ty(&db, "src/a.py", "x", "LiteralString");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn adding_string_literals_and_literal_string() -> anyhow::Result<()> {
|
||||||
|
let mut db = setup_db();
|
||||||
|
let content = format!(
|
||||||
|
r#"
|
||||||
|
v = "{y}"
|
||||||
|
w = "{y}" + "a"
|
||||||
|
x = "a" + "{y}"
|
||||||
|
z = "{y}" + "{y}"
|
||||||
|
"#,
|
||||||
|
y = "a".repeat(TypeInferenceBuilder::MAX_STRING_LITERAL_SIZE + 1),
|
||||||
|
);
|
||||||
|
db.write_dedented("src/a.py", &content)?;
|
||||||
|
|
||||||
|
assert_public_ty(&db, "src/a.py", "v", "LiteralString");
|
||||||
|
assert_public_ty(&db, "src/a.py", "w", "LiteralString");
|
||||||
|
assert_public_ty(&db, "src/a.py", "x", "LiteralString");
|
||||||
|
assert_public_ty(&db, "src/a.py", "z", "LiteralString");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn bytes_type() -> anyhow::Result<()> {
|
fn bytes_type() -> anyhow::Result<()> {
|
||||||
let mut db = setup_db();
|
let mut db = setup_db();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue