|
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