]> git.lizzy.rs Git - rust.git/blob - crates/syntax/src/ast/token_ext.rs
Merge #11021
[rust.git] / crates / syntax / src / ast / token_ext.rs
1 //! There are many AstNodes, but only a few tokens, so we hand-write them here.
2
3 use std::borrow::Cow;
4
5 use rustc_lexer::unescape::{unescape_literal, Mode};
6
7 use crate::{
8     ast::{self, AstToken},
9     TextRange, TextSize,
10 };
11
12 impl ast::Comment {
13     pub fn kind(&self) -> CommentKind {
14         CommentKind::from_text(self.text())
15     }
16
17     pub fn is_inner(&self) -> bool {
18         self.kind().doc == Some(CommentPlacement::Inner)
19     }
20
21     pub fn is_outer(&self) -> bool {
22         self.kind().doc == Some(CommentPlacement::Outer)
23     }
24
25     pub fn prefix(&self) -> &'static str {
26         let &(prefix, _kind) = CommentKind::BY_PREFIX
27             .iter()
28             .find(|&(prefix, kind)| self.kind() == *kind && self.text().starts_with(prefix))
29             .unwrap();
30         prefix
31     }
32
33     /// Returns the textual content of a doc comment node as a single string with prefix and suffix
34     /// removed.
35     pub fn doc_comment(&self) -> Option<&str> {
36         let kind = self.kind();
37         match kind {
38             CommentKind { shape, doc: Some(_) } => {
39                 let prefix = kind.prefix();
40                 let text = &self.text()[prefix.len()..];
41                 let text = if shape == CommentShape::Block {
42                     text.strip_suffix("*/").unwrap_or(text)
43                 } else {
44                     text
45                 };
46                 Some(text)
47             }
48             _ => None,
49         }
50     }
51 }
52
53 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
54 pub struct CommentKind {
55     pub shape: CommentShape,
56     pub doc: Option<CommentPlacement>,
57 }
58
59 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
60 pub enum CommentShape {
61     Line,
62     Block,
63 }
64
65 impl CommentShape {
66     pub fn is_line(self) -> bool {
67         self == CommentShape::Line
68     }
69
70     pub fn is_block(self) -> bool {
71         self == CommentShape::Block
72     }
73 }
74
75 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
76 pub enum CommentPlacement {
77     Inner,
78     Outer,
79 }
80
81 impl CommentKind {
82     const BY_PREFIX: [(&'static str, CommentKind); 9] = [
83         ("/**/", CommentKind { shape: CommentShape::Block, doc: None }),
84         ("/***", CommentKind { shape: CommentShape::Block, doc: None }),
85         ("////", CommentKind { shape: CommentShape::Line, doc: None }),
86         ("///", CommentKind { shape: CommentShape::Line, doc: Some(CommentPlacement::Outer) }),
87         ("//!", CommentKind { shape: CommentShape::Line, doc: Some(CommentPlacement::Inner) }),
88         ("/**", CommentKind { shape: CommentShape::Block, doc: Some(CommentPlacement::Outer) }),
89         ("/*!", CommentKind { shape: CommentShape::Block, doc: Some(CommentPlacement::Inner) }),
90         ("//", CommentKind { shape: CommentShape::Line, doc: None }),
91         ("/*", CommentKind { shape: CommentShape::Block, doc: None }),
92     ];
93
94     pub(crate) fn from_text(text: &str) -> CommentKind {
95         let &(_prefix, kind) = CommentKind::BY_PREFIX
96             .iter()
97             .find(|&(prefix, _kind)| text.starts_with(prefix))
98             .unwrap();
99         kind
100     }
101
102     pub fn prefix(&self) -> &'static str {
103         let &(prefix, _) =
104             CommentKind::BY_PREFIX.iter().rev().find(|(_, kind)| kind == self).unwrap();
105         prefix
106     }
107 }
108
109 impl ast::Whitespace {
110     pub fn spans_multiple_lines(&self) -> bool {
111         let text = self.text();
112         text.find('\n').map_or(false, |idx| text[idx + 1..].contains('\n'))
113     }
114 }
115
116 pub struct QuoteOffsets {
117     pub quotes: (TextRange, TextRange),
118     pub contents: TextRange,
119 }
120
121 impl QuoteOffsets {
122     fn new(literal: &str) -> Option<QuoteOffsets> {
123         let left_quote = literal.find('"')?;
124         let right_quote = literal.rfind('"')?;
125         if left_quote == right_quote {
126             // `literal` only contains one quote
127             return None;
128         }
129
130         let start = TextSize::from(0);
131         let left_quote = TextSize::try_from(left_quote).unwrap() + TextSize::of('"');
132         let right_quote = TextSize::try_from(right_quote).unwrap();
133         let end = TextSize::of(literal);
134
135         let res = QuoteOffsets {
136             quotes: (TextRange::new(start, left_quote), TextRange::new(right_quote, end)),
137             contents: TextRange::new(left_quote, right_quote),
138         };
139         Some(res)
140     }
141 }
142
143 pub trait IsString: AstToken {
144     fn quote_offsets(&self) -> Option<QuoteOffsets> {
145         let text = self.text();
146         let offsets = QuoteOffsets::new(text)?;
147         let o = self.syntax().text_range().start();
148         let offsets = QuoteOffsets {
149             quotes: (offsets.quotes.0 + o, offsets.quotes.1 + o),
150             contents: offsets.contents + o,
151         };
152         Some(offsets)
153     }
154     fn text_range_between_quotes(&self) -> Option<TextRange> {
155         self.quote_offsets().map(|it| it.contents)
156     }
157     fn open_quote_text_range(&self) -> Option<TextRange> {
158         self.quote_offsets().map(|it| it.quotes.0)
159     }
160     fn close_quote_text_range(&self) -> Option<TextRange> {
161         self.quote_offsets().map(|it| it.quotes.1)
162     }
163 }
164
165 impl IsString for ast::String {}
166
167 impl ast::String {
168     pub fn is_raw(&self) -> bool {
169         self.text().starts_with('r')
170     }
171     pub fn map_range_up(&self, range: TextRange) -> Option<TextRange> {
172         let contents_range = self.text_range_between_quotes()?;
173         assert!(TextRange::up_to(contents_range.len()).contains_range(range));
174         Some(range + contents_range.start())
175     }
176
177     pub fn value(&self) -> Option<Cow<'_, str>> {
178         if self.is_raw() {
179             let text = self.text();
180             let text =
181                 &text[self.text_range_between_quotes()? - self.syntax().text_range().start()];
182             return Some(Cow::Borrowed(text));
183         }
184
185         let text = self.text();
186         let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()];
187
188         let mut buf = String::new();
189         let mut text_iter = text.chars();
190         let mut has_error = false;
191         unescape_literal(text, Mode::Str, &mut |char_range, unescaped_char| match (
192             unescaped_char,
193             buf.capacity() == 0,
194         ) {
195             (Ok(c), false) => buf.push(c),
196             (Ok(c), true) if char_range.len() == 1 && Some(c) == text_iter.next() => (),
197             (Ok(c), true) => {
198                 buf.reserve_exact(text.len());
199                 buf.push_str(&text[..char_range.start]);
200                 buf.push(c);
201             }
202             (Err(_), _) => has_error = true,
203         });
204
205         match (has_error, buf.capacity() == 0) {
206             (true, _) => None,
207             (false, true) => Some(Cow::Borrowed(text)),
208             (false, false) => Some(Cow::Owned(buf)),
209         }
210     }
211 }
212
213 impl IsString for ast::ByteString {}
214
215 impl ast::ByteString {
216     pub fn is_raw(&self) -> bool {
217         self.text().starts_with("br")
218     }
219
220     pub fn value(&self) -> Option<Cow<'_, [u8]>> {
221         if self.is_raw() {
222             let text = self.text();
223             let text =
224                 &text[self.text_range_between_quotes()? - self.syntax().text_range().start()];
225             return Some(Cow::Borrowed(text.as_bytes()));
226         }
227
228         let text = self.text();
229         let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()];
230
231         let mut buf: Vec<u8> = Vec::new();
232         let mut text_iter = text.chars();
233         let mut has_error = false;
234         unescape_literal(text, Mode::ByteStr, &mut |char_range, unescaped_char| match (
235             unescaped_char,
236             buf.capacity() == 0,
237         ) {
238             (Ok(c), false) => buf.push(c as u8),
239             (Ok(c), true) if char_range.len() == 1 && Some(c) == text_iter.next() => (),
240             (Ok(c), true) => {
241                 buf.reserve_exact(text.len());
242                 buf.extend_from_slice(text[..char_range.start].as_bytes());
243                 buf.push(c as u8);
244             }
245             (Err(_), _) => has_error = true,
246         });
247
248         match (has_error, buf.capacity() == 0) {
249             (true, _) => None,
250             (false, true) => Some(Cow::Borrowed(text.as_bytes())),
251             (false, false) => Some(Cow::Owned(buf)),
252         }
253     }
254 }
255
256 #[derive(Debug)]
257 pub enum FormatSpecifier {
258     Open,
259     Close,
260     Integer,
261     Identifier,
262     Colon,
263     Fill,
264     Align,
265     Sign,
266     NumberSign,
267     Zero,
268     DollarSign,
269     Dot,
270     Asterisk,
271     QuestionMark,
272 }
273
274 pub trait HasFormatSpecifier: AstToken {
275     fn char_ranges(
276         &self,
277     ) -> Option<Vec<(TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>>;
278
279     fn lex_format_specifier<F>(&self, mut callback: F)
280     where
281         F: FnMut(TextRange, FormatSpecifier),
282     {
283         let char_ranges = match self.char_ranges() {
284             Some(char_ranges) => char_ranges,
285             None => return,
286         };
287         let mut chars = char_ranges.iter().peekable();
288
289         while let Some((range, first_char)) = chars.next() {
290             match first_char {
291                 Ok('{') => {
292                     // Format specifier, see syntax at https://doc.rust-lang.org/std/fmt/index.html#syntax
293                     if let Some((_, Ok('{'))) = chars.peek() {
294                         // Escaped format specifier, `{{`
295                         chars.next();
296                         continue;
297                     }
298
299                     callback(*range, FormatSpecifier::Open);
300
301                     // check for integer/identifier
302                     match chars
303                         .peek()
304                         .and_then(|next| next.1.as_ref().ok())
305                         .copied()
306                         .unwrap_or_default()
307                     {
308                         '0'..='9' => {
309                             // integer
310                             read_integer(&mut chars, &mut callback);
311                         }
312                         c if c == '_' || c.is_alphabetic() => {
313                             // identifier
314                             read_identifier(&mut chars, &mut callback);
315                         }
316                         _ => {}
317                     }
318
319                     if let Some((_, Ok(':'))) = chars.peek() {
320                         skip_char_and_emit(&mut chars, FormatSpecifier::Colon, &mut callback);
321
322                         // check for fill/align
323                         let mut cloned = chars.clone().take(2);
324                         let first = cloned
325                             .next()
326                             .and_then(|next| next.1.as_ref().ok())
327                             .copied()
328                             .unwrap_or_default();
329                         let second = cloned
330                             .next()
331                             .and_then(|next| next.1.as_ref().ok())
332                             .copied()
333                             .unwrap_or_default();
334                         match second {
335                             '<' | '^' | '>' => {
336                                 // alignment specifier, first char specifies fillment
337                                 skip_char_and_emit(
338                                     &mut chars,
339                                     FormatSpecifier::Fill,
340                                     &mut callback,
341                                 );
342                                 skip_char_and_emit(
343                                     &mut chars,
344                                     FormatSpecifier::Align,
345                                     &mut callback,
346                                 );
347                             }
348                             _ => match first {
349                                 '<' | '^' | '>' => {
350                                     skip_char_and_emit(
351                                         &mut chars,
352                                         FormatSpecifier::Align,
353                                         &mut callback,
354                                     );
355                                 }
356                                 _ => {}
357                             },
358                         }
359
360                         // check for sign
361                         match chars
362                             .peek()
363                             .and_then(|next| next.1.as_ref().ok())
364                             .copied()
365                             .unwrap_or_default()
366                         {
367                             '+' | '-' => {
368                                 skip_char_and_emit(
369                                     &mut chars,
370                                     FormatSpecifier::Sign,
371                                     &mut callback,
372                                 );
373                             }
374                             _ => {}
375                         }
376
377                         // check for `#`
378                         if let Some((_, Ok('#'))) = chars.peek() {
379                             skip_char_and_emit(
380                                 &mut chars,
381                                 FormatSpecifier::NumberSign,
382                                 &mut callback,
383                             );
384                         }
385
386                         // check for `0`
387                         let mut cloned = chars.clone().take(2);
388                         let first = cloned.next().and_then(|next| next.1.as_ref().ok()).copied();
389                         let second = cloned.next().and_then(|next| next.1.as_ref().ok()).copied();
390
391                         if first == Some('0') && second != Some('$') {
392                             skip_char_and_emit(&mut chars, FormatSpecifier::Zero, &mut callback);
393                         }
394
395                         // width
396                         match chars
397                             .peek()
398                             .and_then(|next| next.1.as_ref().ok())
399                             .copied()
400                             .unwrap_or_default()
401                         {
402                             '0'..='9' => {
403                                 read_integer(&mut chars, &mut callback);
404                                 if let Some((_, Ok('$'))) = chars.peek() {
405                                     skip_char_and_emit(
406                                         &mut chars,
407                                         FormatSpecifier::DollarSign,
408                                         &mut callback,
409                                     );
410                                 }
411                             }
412                             c if c == '_' || c.is_alphabetic() => {
413                                 read_identifier(&mut chars, &mut callback);
414
415                                 if chars.peek().and_then(|next| next.1.as_ref().ok()).copied()
416                                     == Some('?')
417                                 {
418                                     skip_char_and_emit(
419                                         &mut chars,
420                                         FormatSpecifier::QuestionMark,
421                                         &mut callback,
422                                     );
423                                 }
424
425                                 // can be either width (indicated by dollar sign, or type in which case
426                                 // the next sign has to be `}`)
427                                 let next =
428                                     chars.peek().and_then(|next| next.1.as_ref().ok()).copied();
429
430                                 match next {
431                                     Some('$') => skip_char_and_emit(
432                                         &mut chars,
433                                         FormatSpecifier::DollarSign,
434                                         &mut callback,
435                                     ),
436                                     Some('}') => {
437                                         skip_char_and_emit(
438                                             &mut chars,
439                                             FormatSpecifier::Close,
440                                             &mut callback,
441                                         );
442                                         continue;
443                                     }
444                                     _ => continue,
445                                 };
446                             }
447                             _ => {}
448                         }
449
450                         // precision
451                         if let Some((_, Ok('.'))) = chars.peek() {
452                             skip_char_and_emit(&mut chars, FormatSpecifier::Dot, &mut callback);
453
454                             match chars
455                                 .peek()
456                                 .and_then(|next| next.1.as_ref().ok())
457                                 .copied()
458                                 .unwrap_or_default()
459                             {
460                                 '*' => {
461                                     skip_char_and_emit(
462                                         &mut chars,
463                                         FormatSpecifier::Asterisk,
464                                         &mut callback,
465                                     );
466                                 }
467                                 '0'..='9' => {
468                                     read_integer(&mut chars, &mut callback);
469                                     if let Some((_, Ok('$'))) = chars.peek() {
470                                         skip_char_and_emit(
471                                             &mut chars,
472                                             FormatSpecifier::DollarSign,
473                                             &mut callback,
474                                         );
475                                     }
476                                 }
477                                 c if c == '_' || c.is_alphabetic() => {
478                                     read_identifier(&mut chars, &mut callback);
479                                     if chars.peek().and_then(|next| next.1.as_ref().ok()).copied()
480                                         != Some('$')
481                                     {
482                                         continue;
483                                     }
484                                     skip_char_and_emit(
485                                         &mut chars,
486                                         FormatSpecifier::DollarSign,
487                                         &mut callback,
488                                     );
489                                 }
490                                 _ => {
491                                     continue;
492                                 }
493                             }
494                         }
495
496                         // type
497                         match chars
498                             .peek()
499                             .and_then(|next| next.1.as_ref().ok())
500                             .copied()
501                             .unwrap_or_default()
502                         {
503                             '?' => {
504                                 skip_char_and_emit(
505                                     &mut chars,
506                                     FormatSpecifier::QuestionMark,
507                                     &mut callback,
508                                 );
509                             }
510                             c if c == '_' || c.is_alphabetic() => {
511                                 read_identifier(&mut chars, &mut callback);
512
513                                 if chars.peek().and_then(|next| next.1.as_ref().ok()).copied()
514                                     == Some('?')
515                                 {
516                                     skip_char_and_emit(
517                                         &mut chars,
518                                         FormatSpecifier::QuestionMark,
519                                         &mut callback,
520                                     );
521                                 }
522                             }
523                             _ => {}
524                         }
525                     }
526
527                     match chars.peek() {
528                         Some((_, Ok('}'))) => {
529                             skip_char_and_emit(&mut chars, FormatSpecifier::Close, &mut callback);
530                         }
531                         Some((_, _)) | None => continue,
532                     }
533                 }
534                 _ => {
535                     while let Some((_, Ok(next_char))) = chars.peek() {
536                         if next_char == &'{' {
537                             break;
538                         }
539                         chars.next();
540                     }
541                 }
542             };
543         }
544
545         fn skip_char_and_emit<'a, I, F>(
546             chars: &mut std::iter::Peekable<I>,
547             emit: FormatSpecifier,
548             callback: &mut F,
549         ) where
550             I: Iterator<Item = &'a (TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>,
551             F: FnMut(TextRange, FormatSpecifier),
552         {
553             let (range, _) = chars.next().unwrap();
554             callback(*range, emit);
555         }
556
557         fn read_integer<'a, I, F>(chars: &mut std::iter::Peekable<I>, callback: &mut F)
558         where
559             I: Iterator<Item = &'a (TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>,
560             F: FnMut(TextRange, FormatSpecifier),
561         {
562             let (mut range, c) = chars.next().unwrap();
563             assert!(c.as_ref().unwrap().is_ascii_digit());
564             while let Some((r, Ok(next_char))) = chars.peek() {
565                 if next_char.is_ascii_digit() {
566                     chars.next();
567                     range = range.cover(*r);
568                 } else {
569                     break;
570                 }
571             }
572             callback(range, FormatSpecifier::Integer);
573         }
574
575         fn read_identifier<'a, I, F>(chars: &mut std::iter::Peekable<I>, callback: &mut F)
576         where
577             I: Iterator<Item = &'a (TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>,
578             F: FnMut(TextRange, FormatSpecifier),
579         {
580             let (mut range, c) = chars.next().unwrap();
581             assert!(c.as_ref().unwrap().is_alphabetic() || *c.as_ref().unwrap() == '_');
582             while let Some((r, Ok(next_char))) = chars.peek() {
583                 if *next_char == '_' || next_char.is_ascii_digit() || next_char.is_alphabetic() {
584                     chars.next();
585                     range = range.cover(*r);
586                 } else {
587                     break;
588                 }
589             }
590             callback(range, FormatSpecifier::Identifier);
591         }
592     }
593 }
594
595 impl HasFormatSpecifier for ast::String {
596     fn char_ranges(
597         &self,
598     ) -> Option<Vec<(TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>> {
599         let text = self.text();
600         let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()];
601         let offset = self.text_range_between_quotes()?.start() - self.syntax().text_range().start();
602
603         let mut res = Vec::with_capacity(text.len());
604         unescape_literal(text, Mode::Str, &mut |range, unescaped_char| {
605             res.push((
606                 TextRange::new(range.start.try_into().unwrap(), range.end.try_into().unwrap())
607                     + offset,
608                 unescaped_char,
609             ));
610         });
611
612         Some(res)
613     }
614 }
615
616 impl ast::IntNumber {
617     pub fn radix(&self) -> Radix {
618         match self.text().get(..2).unwrap_or_default() {
619             "0b" => Radix::Binary,
620             "0o" => Radix::Octal,
621             "0x" => Radix::Hexadecimal,
622             _ => Radix::Decimal,
623         }
624     }
625
626     pub fn split_into_parts(&self) -> (&str, &str, &str) {
627         let radix = self.radix();
628         let (prefix, mut text) = self.text().split_at(radix.prefix_len());
629
630         let is_suffix_start: fn(&(usize, char)) -> bool = match radix {
631             Radix::Hexadecimal => |(_, c)| matches!(c, 'g'..='z' | 'G'..='Z'),
632             _ => |(_, c)| c.is_ascii_alphabetic(),
633         };
634
635         let mut suffix = "";
636         if let Some((suffix_start, _)) = text.char_indices().find(is_suffix_start) {
637             let (text2, suffix2) = text.split_at(suffix_start);
638             text = text2;
639             suffix = suffix2;
640         };
641
642         (prefix, text, suffix)
643     }
644
645     pub fn value(&self) -> Option<u128> {
646         let (_, text, _) = self.split_into_parts();
647         let value = u128::from_str_radix(&text.replace("_", ""), self.radix() as u32).ok()?;
648         Some(value)
649     }
650
651     pub fn suffix(&self) -> Option<&str> {
652         let (_, _, suffix) = self.split_into_parts();
653         if suffix.is_empty() {
654             None
655         } else {
656             Some(suffix)
657         }
658     }
659 }
660
661 impl ast::FloatNumber {
662     pub fn suffix(&self) -> Option<&str> {
663         let text = self.text();
664         let mut indices = text.char_indices();
665         let (mut suffix_start, c) = indices.by_ref().find(|(_, c)| c.is_ascii_alphabetic())?;
666         if c == 'e' || c == 'E' {
667             suffix_start = indices.find(|(_, c)| c.is_ascii_alphabetic())?.0;
668         }
669         Some(&text[suffix_start..])
670     }
671 }
672
673 #[derive(Debug, PartialEq, Eq, Copy, Clone)]
674 pub enum Radix {
675     Binary = 2,
676     Octal = 8,
677     Decimal = 10,
678     Hexadecimal = 16,
679 }
680
681 impl Radix {
682     pub const ALL: &'static [Radix] =
683         &[Radix::Binary, Radix::Octal, Radix::Decimal, Radix::Hexadecimal];
684
685     const fn prefix_len(self) -> usize {
686         match self {
687             Self::Decimal => 0,
688             _ => 2,
689         }
690     }
691 }
692
693 #[cfg(test)]
694 mod tests {
695     use crate::ast::{self, make, FloatNumber, IntNumber};
696
697     fn check_float_suffix<'a>(lit: &str, expected: impl Into<Option<&'a str>>) {
698         assert_eq!(FloatNumber { syntax: make::tokens::literal(lit) }.suffix(), expected.into());
699     }
700
701     fn check_int_suffix<'a>(lit: &str, expected: impl Into<Option<&'a str>>) {
702         assert_eq!(IntNumber { syntax: make::tokens::literal(lit) }.suffix(), expected.into());
703     }
704
705     #[test]
706     fn test_float_number_suffix() {
707         check_float_suffix("123.0", None);
708         check_float_suffix("123f32", "f32");
709         check_float_suffix("123.0e", None);
710         check_float_suffix("123.0e4", None);
711         check_float_suffix("123.0ef32", "f32");
712         check_float_suffix("123.0E4f32", "f32");
713         check_float_suffix("1_2_3.0_f32", "f32");
714     }
715
716     #[test]
717     fn test_int_number_suffix() {
718         check_int_suffix("123", None);
719         check_int_suffix("123i32", "i32");
720         check_int_suffix("1_0_1_l_o_l", "l_o_l");
721         check_int_suffix("0b11", None);
722         check_int_suffix("0o11", None);
723         check_int_suffix("0xff", None);
724         check_int_suffix("0b11u32", "u32");
725         check_int_suffix("0o11u32", "u32");
726         check_int_suffix("0xffu32", "u32");
727     }
728
729     fn check_string_value<'a>(lit: &str, expected: impl Into<Option<&'a str>>) {
730         assert_eq!(
731             ast::String { syntax: make::tokens::literal(&format!("\"{}\"", lit)) }
732                 .value()
733                 .as_deref(),
734             expected.into()
735         );
736     }
737
738     #[test]
739     fn test_string_escape() {
740         check_string_value(r"foobar", "foobar");
741         check_string_value(r"\foobar", None);
742         check_string_value(r"\nfoobar", "\nfoobar");
743         check_string_value(r"C:\\Windows\\System32\\", "C:\\Windows\\System32\\");
744     }
745 }