|
1 | 1 | //! Display implementations for types.
|
2 | 2 |
|
3 |
| -use std::fmt::{self, Display, Formatter}; |
| 3 | +use std::fmt::{self, Display, Formatter, Write}; |
4 | 4 |
|
5 | 5 | use ruff_db::display::FormatterJoinExtension;
|
6 | 6 | use ruff_python_ast::str::Quote;
|
7 | 7 | use ruff_python_literal::escape::AsciiEscape;
|
8 | 8 |
|
9 | 9 | use crate::types::{
|
10 |
| - ClassLiteralType, InstanceType, IntersectionType, KnownClass, SubclassOfType, Type, UnionType, |
| 10 | + ClassLiteralType, InstanceType, IntersectionType, KnownClass, StringLiteralType, |
| 11 | + SubclassOfType, Type, UnionType, |
11 | 12 | };
|
12 | 13 | use crate::Db;
|
13 | 14 | use rustc_hash::FxHashMap;
|
@@ -91,9 +92,7 @@ impl Display for DisplayRepresentation<'_> {
|
91 | 92 | Type::Intersection(intersection) => intersection.display(self.db).fmt(f),
|
92 | 93 | Type::IntLiteral(n) => n.fmt(f),
|
93 | 94 | Type::BooleanLiteral(boolean) => f.write_str(if boolean { "True" } else { "False" }),
|
94 |
| - Type::StringLiteral(string) => { |
95 |
| - write!(f, r#""{}""#, string.value(self.db).replace('"', r#"\""#)) |
96 |
| - } |
| 95 | + Type::StringLiteral(string) => string.display(self.db).fmt(f), |
97 | 96 | Type::LiteralString => f.write_str("LiteralString"),
|
98 | 97 | Type::BytesLiteral(bytes) => {
|
99 | 98 | let escape =
|
@@ -328,13 +327,40 @@ impl<'db> Display for DisplayTypeArray<'_, 'db> {
|
328 | 327 | }
|
329 | 328 | }
|
330 | 329 |
|
| 330 | +impl<'db> StringLiteralType<'db> { |
| 331 | + fn display(&'db self, db: &'db dyn Db) -> DisplayStringLiteralType<'db> { |
| 332 | + DisplayStringLiteralType { db, ty: self } |
| 333 | + } |
| 334 | +} |
| 335 | + |
| 336 | +struct DisplayStringLiteralType<'db> { |
| 337 | + ty: &'db StringLiteralType<'db>, |
| 338 | + db: &'db dyn Db, |
| 339 | +} |
| 340 | + |
| 341 | +impl Display for DisplayStringLiteralType<'_> { |
| 342 | + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { |
| 343 | + let value = self.ty.value(self.db); |
| 344 | + f.write_char('"')?; |
| 345 | + for ch in value.chars() { |
| 346 | + match ch { |
| 347 | + // `escape_debug` will escape even single quotes, which is not necessary for our |
| 348 | + // use case as we are already using double quotes to wrap the string. |
| 349 | + '\'' => f.write_char('\'')?, |
| 350 | + _ => write!(f, "{}", ch.escape_debug())?, |
| 351 | + } |
| 352 | + } |
| 353 | + f.write_char('"') |
| 354 | + } |
| 355 | +} |
| 356 | + |
331 | 357 | #[cfg(test)]
|
332 | 358 | mod tests {
|
333 | 359 | use ruff_db::files::system_path_to_file;
|
334 | 360 | use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
|
335 | 361 |
|
336 | 362 | use crate::db::tests::TestDb;
|
337 |
| - use crate::types::{global_symbol, SliceLiteralType, Type, UnionType}; |
| 363 | + use crate::types::{global_symbol, SliceLiteralType, StringLiteralType, Type, UnionType}; |
338 | 364 | use crate::{Program, ProgramSettings, PythonVersion, SearchPathSettings};
|
339 | 365 |
|
340 | 366 | fn setup_db() -> TestDb {
|
@@ -451,4 +477,28 @@ mod tests {
|
451 | 477 | "slice[None, None, Literal[2]]"
|
452 | 478 | );
|
453 | 479 | }
|
| 480 | + |
| 481 | + #[test] |
| 482 | + fn string_literal_display() { |
| 483 | + let db = setup_db(); |
| 484 | + |
| 485 | + assert_eq!( |
| 486 | + Type::StringLiteral(StringLiteralType::new(&db, r"\n")) |
| 487 | + .display(&db) |
| 488 | + .to_string(), |
| 489 | + r#"Literal["\\n"]"# |
| 490 | + ); |
| 491 | + assert_eq!( |
| 492 | + Type::StringLiteral(StringLiteralType::new(&db, "'")) |
| 493 | + .display(&db) |
| 494 | + .to_string(), |
| 495 | + r#"Literal["'"]"# |
| 496 | + ); |
| 497 | + assert_eq!( |
| 498 | + Type::StringLiteral(StringLiteralType::new(&db, r#"""#)) |
| 499 | + .display(&db) |
| 500 | + .to_string(), |
| 501 | + r#"Literal["\""]"# |
| 502 | + ); |
| 503 | + } |
454 | 504 | }
|
0 commit comments