]> git.lizzy.rs Git - rust.git/blob - src/libsyntax/parse/lexer/mod.rs
a6dfed542d6a6ddd25ecb1b936ccaef8fc857dce
[rust.git] / src / libsyntax / parse / lexer / mod.rs
1 use crate::parse::token::{self, Token, TokenKind};
2 use crate::sess::ParseSess;
3 use crate::symbol::{sym, Symbol};
4
5 use errors::{FatalError, DiagnosticBuilder};
6 use syntax_pos::{BytePos, Pos, Span};
7 use rustc_lexer::Base;
8 use rustc_lexer::unescape;
9
10 use std::char;
11 use std::convert::TryInto;
12 use rustc_data_structures::sync::Lrc;
13 use log::debug;
14
15 #[cfg(test)]
16 mod tests;
17
18 pub mod comments;
19 mod tokentrees;
20 mod unicode_chars;
21 mod unescape_error_reporting;
22 use unescape_error_reporting::{emit_unescape_error, push_escaped_char};
23
24 #[derive(Clone, Debug)]
25 pub struct UnmatchedBrace {
26     pub expected_delim: token::DelimToken,
27     pub found_delim: Option<token::DelimToken>,
28     pub found_span: Span,
29     pub unclosed_span: Option<Span>,
30     pub candidate_span: Option<Span>,
31 }
32
33 pub struct StringReader<'a> {
34     sess: &'a ParseSess,
35     /// Initial position, read-only.
36     start_pos: BytePos,
37     /// The absolute offset within the source_map of the current character.
38     pos: BytePos,
39     /// Stop reading src at this index.
40     end_src_index: usize,
41     /// Source text to tokenize.
42     src: Lrc<String>,
43     override_span: Option<Span>,
44 }
45
46 impl<'a> StringReader<'a> {
47     pub fn new(sess: &'a ParseSess,
48                source_file: Lrc<syntax_pos::SourceFile>,
49                override_span: Option<Span>) -> Self {
50         if source_file.src.is_none() {
51             sess.span_diagnostic.bug(&format!("cannot lex `source_file` without source: {}",
52                                               source_file.name));
53         }
54
55         let src = (*source_file.src.as_ref().unwrap()).clone();
56
57         StringReader {
58             sess,
59             start_pos: source_file.start_pos,
60             pos: source_file.start_pos,
61             end_src_index: src.len(),
62             src,
63             override_span,
64         }
65     }
66
67     pub fn retokenize(sess: &'a ParseSess, mut span: Span) -> Self {
68         let begin = sess.source_map().lookup_byte_offset(span.lo());
69         let end = sess.source_map().lookup_byte_offset(span.hi());
70
71         // Make the range zero-length if the span is invalid.
72         if begin.sf.start_pos != end.sf.start_pos {
73             span = span.shrink_to_lo();
74         }
75
76         let mut sr = StringReader::new(sess, begin.sf, None);
77
78         // Seek the lexer to the right byte range.
79         sr.end_src_index = sr.src_index(span.hi());
80
81         sr
82     }
83
84
85     fn mk_sp(&self, lo: BytePos, hi: BytePos) -> Span {
86         self.override_span.unwrap_or_else(|| Span::with_root_ctxt(lo, hi))
87     }
88
89     /// Returns the next token, including trivia like whitespace or comments.
90     ///
91     /// `Err(())` means that some errors were encountered, which can be
92     /// retrieved using `buffer_fatal_errors`.
93     pub fn next_token(&mut self) -> Token {
94         let start_src_index = self.src_index(self.pos);
95         let text: &str = &self.src[start_src_index..self.end_src_index];
96
97         if text.is_empty() {
98             let span = self.mk_sp(self.pos, self.pos);
99             return Token::new(token::Eof, span);
100         }
101
102         {
103             let is_beginning_of_file = self.pos == self.start_pos;
104             if is_beginning_of_file {
105                 if let Some(shebang_len) = rustc_lexer::strip_shebang(text) {
106                     let start = self.pos;
107                     self.pos = self.pos + BytePos::from_usize(shebang_len);
108
109                     let sym = self.symbol_from(start + BytePos::from_usize("#!".len()));
110                     let kind = token::Shebang(sym);
111
112                     let span = self.mk_sp(start, self.pos);
113                     return Token::new(kind, span);
114                 }
115             }
116         }
117
118         let token = rustc_lexer::first_token(text);
119
120         let start = self.pos;
121         self.pos = self.pos + BytePos::from_usize(token.len);
122
123         debug!("try_next_token: {:?}({:?})", token.kind, self.str_from(start));
124
125         // This could use `?`, but that makes code significantly (10-20%) slower.
126         // https://github.com/rust-lang/rust/issues/37939
127         let kind = self.cook_lexer_token(token.kind, start);
128
129         let span = self.mk_sp(start, self.pos);
130         Token::new(kind, span)
131     }
132
133     /// Report a fatal lexical error with a given span.
134     fn fatal_span(&self, sp: Span, m: &str) -> FatalError {
135         self.sess.span_diagnostic.span_fatal(sp, m)
136     }
137
138     /// Report a lexical error with a given span.
139     fn err_span(&self, sp: Span, m: &str) {
140         self.sess.span_diagnostic.struct_span_err(sp, m).emit();
141     }
142
143
144     /// Report a fatal error spanning [`from_pos`, `to_pos`).
145     fn fatal_span_(&self, from_pos: BytePos, to_pos: BytePos, m: &str) -> FatalError {
146         self.fatal_span(self.mk_sp(from_pos, to_pos), m)
147     }
148
149     /// Report a lexical error spanning [`from_pos`, `to_pos`).
150     fn err_span_(&self, from_pos: BytePos, to_pos: BytePos, m: &str) {
151         self.err_span(self.mk_sp(from_pos, to_pos), m)
152     }
153
154     fn struct_span_fatal(&self, from_pos: BytePos, to_pos: BytePos, m: &str)
155         -> DiagnosticBuilder<'a>
156     {
157         self.sess.span_diagnostic.struct_span_fatal(self.mk_sp(from_pos, to_pos), m)
158     }
159
160     fn struct_fatal_span_char(&self, from_pos: BytePos, to_pos: BytePos, m: &str, c: char)
161         -> DiagnosticBuilder<'a>
162     {
163         let mut m = m.to_string();
164         m.push_str(": ");
165         push_escaped_char(&mut m, c);
166
167         self.sess.span_diagnostic.struct_span_fatal(self.mk_sp(from_pos, to_pos), &m[..])
168     }
169
170     /// Turns simple `rustc_lexer::TokenKind` enum into a rich
171     /// `libsyntax::TokenKind`. This turns strings into interned
172     /// symbols and runs additional validation.
173     fn cook_lexer_token(
174         &self,
175         token: rustc_lexer::TokenKind,
176         start: BytePos,
177     ) -> TokenKind {
178         match token {
179             rustc_lexer::TokenKind::LineComment => {
180                 let string = self.str_from(start);
181                 // comments with only more "/"s are not doc comments
182                 let tok = if is_doc_comment(string) {
183                     self.forbid_bare_cr(start, string, "bare CR not allowed in doc-comment");
184                     token::DocComment(Symbol::intern(string))
185                 } else {
186                     token::Comment
187                 };
188
189                 tok
190             }
191             rustc_lexer::TokenKind::BlockComment { terminated } => {
192                 let string = self.str_from(start);
193                 // block comments starting with "/**" or "/*!" are doc-comments
194                 // but comments with only "*"s between two "/"s are not
195                 let is_doc_comment = is_block_doc_comment(string);
196
197                 if !terminated {
198                     let msg = if is_doc_comment {
199                         "unterminated block doc-comment"
200                     } else {
201                         "unterminated block comment"
202                     };
203                     let last_bpos = self.pos;
204                     self.fatal_span_(start, last_bpos, msg).raise();
205                 }
206
207                 let tok = if is_doc_comment {
208                     self.forbid_bare_cr(start,
209                                         string,
210                                         "bare CR not allowed in block doc-comment");
211                     token::DocComment(Symbol::intern(string))
212                 } else {
213                     token::Comment
214                 };
215
216                 tok
217             }
218             rustc_lexer::TokenKind::Whitespace => token::Whitespace,
219             rustc_lexer::TokenKind::Ident | rustc_lexer::TokenKind::RawIdent => {
220                 let is_raw_ident = token == rustc_lexer::TokenKind::RawIdent;
221                 let mut ident_start = start;
222                 if is_raw_ident {
223                     ident_start = ident_start + BytePos(2);
224                 }
225                 // FIXME: perform NFKC normalization here. (Issue #2253)
226                 let sym = self.symbol_from(ident_start);
227                 if is_raw_ident {
228                     let span = self.mk_sp(start, self.pos);
229                     if !sym.can_be_raw() {
230                         self.err_span(span, &format!("`{}` cannot be a raw identifier", sym));
231                     }
232                     self.sess.raw_identifier_spans.borrow_mut().push(span);
233                 }
234                 token::Ident(sym, is_raw_ident)
235             }
236             rustc_lexer::TokenKind::Literal { kind, suffix_start } => {
237                 let suffix_start = start + BytePos(suffix_start as u32);
238                 let (kind, symbol) = self.cook_lexer_literal(start, suffix_start, kind);
239                 let suffix = if suffix_start < self.pos {
240                     let string = self.str_from(suffix_start);
241                     if string == "_" {
242                         self.sess.span_diagnostic
243                             .struct_span_warn(self.mk_sp(suffix_start, self.pos),
244                                               "underscore literal suffix is not allowed")
245                             .warn("this was previously accepted by the compiler but is \
246                                    being phased out; it will become a hard error in \
247                                    a future release!")
248                             .note("for more information, see issue #42326 \
249                                    <https://github.com/rust-lang/rust/issues/42326>")
250                             .emit();
251                         None
252                     } else {
253                         Some(Symbol::intern(string))
254                     }
255                 } else {
256                     None
257                 };
258                 token::Literal(token::Lit { kind, symbol, suffix })
259             }
260             rustc_lexer::TokenKind::Lifetime { starts_with_number } => {
261                 // Include the leading `'` in the real identifier, for macro
262                 // expansion purposes. See #12512 for the gory details of why
263                 // this is necessary.
264                 let lifetime_name = self.str_from(start);
265                 if starts_with_number {
266                     self.err_span_(
267                         start,
268                         self.pos,
269                         "lifetimes cannot start with a number",
270                     );
271                 }
272                 let ident = Symbol::intern(lifetime_name);
273                 token::Lifetime(ident)
274             }
275             rustc_lexer::TokenKind::Semi => token::Semi,
276             rustc_lexer::TokenKind::Comma => token::Comma,
277             rustc_lexer::TokenKind::Dot => token::Dot,
278             rustc_lexer::TokenKind::OpenParen => token::OpenDelim(token::Paren),
279             rustc_lexer::TokenKind::CloseParen => token::CloseDelim(token::Paren),
280             rustc_lexer::TokenKind::OpenBrace => token::OpenDelim(token::Brace),
281             rustc_lexer::TokenKind::CloseBrace => token::CloseDelim(token::Brace),
282             rustc_lexer::TokenKind::OpenBracket => token::OpenDelim(token::Bracket),
283             rustc_lexer::TokenKind::CloseBracket => token::CloseDelim(token::Bracket),
284             rustc_lexer::TokenKind::At => token::At,
285             rustc_lexer::TokenKind::Pound => token::Pound,
286             rustc_lexer::TokenKind::Tilde => token::Tilde,
287             rustc_lexer::TokenKind::Question => token::Question,
288             rustc_lexer::TokenKind::Colon => token::Colon,
289             rustc_lexer::TokenKind::Dollar => token::Dollar,
290             rustc_lexer::TokenKind::Eq => token::Eq,
291             rustc_lexer::TokenKind::Not => token::Not,
292             rustc_lexer::TokenKind::Lt => token::Lt,
293             rustc_lexer::TokenKind::Gt => token::Gt,
294             rustc_lexer::TokenKind::Minus => token::BinOp(token::Minus),
295             rustc_lexer::TokenKind::And => token::BinOp(token::And),
296             rustc_lexer::TokenKind::Or => token::BinOp(token::Or),
297             rustc_lexer::TokenKind::Plus => token::BinOp(token::Plus),
298             rustc_lexer::TokenKind::Star => token::BinOp(token::Star),
299             rustc_lexer::TokenKind::Slash => token::BinOp(token::Slash),
300             rustc_lexer::TokenKind::Caret => token::BinOp(token::Caret),
301             rustc_lexer::TokenKind::Percent => token::BinOp(token::Percent),
302
303             rustc_lexer::TokenKind::Unknown => {
304                 let c = self.str_from(start).chars().next().unwrap();
305                 let mut err = self.struct_fatal_span_char(start,
306                                                           self.pos,
307                                                           "unknown start of token",
308                                                           c);
309                 // FIXME: the lexer could be used to turn the ASCII version of unicode homoglyphs,
310                 // instead of keeping a table in `check_for_substitution`into the token. Ideally,
311                 // this should be inside `rustc_lexer`. However, we should first remove compound
312                 // tokens like `<<` from `rustc_lexer`, and then add fancier error recovery to it,
313                 // as there will be less overall work to do this way.
314                 let token = unicode_chars::check_for_substitution(self, start, c, &mut err)
315                     .unwrap_or_else(|| token::Unknown(self.symbol_from(start)));
316                 err.emit();
317                 token
318             }
319         }
320     }
321
322     fn cook_lexer_literal(
323         &self,
324         start: BytePos,
325         suffix_start: BytePos,
326         kind: rustc_lexer::LiteralKind
327     ) -> (token::LitKind, Symbol) {
328         match kind {
329             rustc_lexer::LiteralKind::Char { terminated } => {
330                 if !terminated {
331                     self.fatal_span_(start, suffix_start,
332                                      "unterminated character literal".into())
333                         .raise()
334                 }
335                 let content_start = start + BytePos(1);
336                 let content_end = suffix_start - BytePos(1);
337                 self.validate_char_escape(content_start, content_end);
338                 let id = self.symbol_from_to(content_start, content_end);
339                 (token::Char, id)
340             },
341             rustc_lexer::LiteralKind::Byte { terminated } => {
342                 if !terminated {
343                     self.fatal_span_(start + BytePos(1), suffix_start,
344                                      "unterminated byte constant".into())
345                         .raise()
346                 }
347                 let content_start = start + BytePos(2);
348                 let content_end = suffix_start - BytePos(1);
349                 self.validate_byte_escape(content_start, content_end);
350                 let id = self.symbol_from_to(content_start, content_end);
351                 (token::Byte, id)
352             },
353             rustc_lexer::LiteralKind::Str { terminated } => {
354                 if !terminated {
355                     self.fatal_span_(start, suffix_start,
356                                      "unterminated double quote string".into())
357                         .raise()
358                 }
359                 let content_start = start + BytePos(1);
360                 let content_end = suffix_start - BytePos(1);
361                 self.validate_str_escape(content_start, content_end);
362                 let id = self.symbol_from_to(content_start, content_end);
363                 (token::Str, id)
364             }
365             rustc_lexer::LiteralKind::ByteStr { terminated } => {
366                 if !terminated {
367                     self.fatal_span_(start + BytePos(1), suffix_start,
368                                      "unterminated double quote byte string".into())
369                         .raise()
370                 }
371                 let content_start = start + BytePos(2);
372                 let content_end = suffix_start - BytePos(1);
373                 self.validate_byte_str_escape(content_start, content_end);
374                 let id = self.symbol_from_to(content_start, content_end);
375                 (token::ByteStr, id)
376             }
377             rustc_lexer::LiteralKind::RawStr { n_hashes, started, terminated } => {
378                 if !started {
379                     self.report_non_started_raw_string(start);
380                 }
381                 if !terminated {
382                     self.report_unterminated_raw_string(start, n_hashes)
383                 }
384                 let n_hashes: u16 = self.restrict_n_hashes(start, n_hashes);
385                 let n = u32::from(n_hashes);
386                 let content_start = start + BytePos(2 + n);
387                 let content_end = suffix_start - BytePos(1 + n);
388                 self.validate_raw_str_escape(content_start, content_end);
389                 let id = self.symbol_from_to(content_start, content_end);
390                 (token::StrRaw(n_hashes), id)
391             }
392             rustc_lexer::LiteralKind::RawByteStr { n_hashes, started, terminated } => {
393                 if !started {
394                     self.report_non_started_raw_string(start);
395                 }
396                 if !terminated {
397                     self.report_unterminated_raw_string(start, n_hashes)
398                 }
399                 let n_hashes: u16 = self.restrict_n_hashes(start, n_hashes);
400                 let n = u32::from(n_hashes);
401                 let content_start = start + BytePos(3 + n);
402                 let content_end = suffix_start - BytePos(1 + n);
403                 self.validate_raw_byte_str_escape(content_start, content_end);
404                 let id = self.symbol_from_to(content_start, content_end);
405                 (token::ByteStrRaw(n_hashes), id)
406             }
407             rustc_lexer::LiteralKind::Int { base, empty_int } => {
408                 if empty_int {
409                     self.err_span_(start, suffix_start, "no valid digits found for number");
410                     (token::Integer, sym::integer(0))
411                 } else {
412                     self.validate_int_literal(base, start, suffix_start);
413                     (token::Integer, self.symbol_from_to(start, suffix_start))
414                 }
415             },
416             rustc_lexer::LiteralKind::Float { base, empty_exponent } => {
417                 if empty_exponent {
418                     let mut err = self.struct_span_fatal(
419                         start, self.pos,
420                         "expected at least one digit in exponent"
421                     );
422                     err.emit();
423                 }
424
425                 match base {
426                     Base::Hexadecimal => {
427                         self.err_span_(start, suffix_start,
428                                        "hexadecimal float literal is not supported")
429                     }
430                     Base::Octal => {
431                         self.err_span_(start, suffix_start,
432                                        "octal float literal is not supported")
433                     }
434                     Base::Binary => {
435                         self.err_span_(start, suffix_start,
436                                        "binary float literal is not supported")
437                     }
438                     _ => ()
439                 }
440
441                 let id = self.symbol_from_to(start, suffix_start);
442                 (token::Float, id)
443             },
444         }
445     }
446
447     #[inline]
448     fn src_index(&self, pos: BytePos) -> usize {
449         (pos - self.start_pos).to_usize()
450     }
451
452     /// Slice of the source text from `start` up to but excluding `self.pos`,
453     /// meaning the slice does not include the character `self.ch`.
454     fn str_from(&self, start: BytePos) -> &str
455     {
456         self.str_from_to(start, self.pos)
457     }
458
459     /// Creates a Symbol from a given offset to the current offset.
460     fn symbol_from(&self, start: BytePos) -> Symbol {
461         debug!("taking an ident from {:?} to {:?}", start, self.pos);
462         Symbol::intern(self.str_from(start))
463     }
464
465     /// As symbol_from, with an explicit endpoint.
466     fn symbol_from_to(&self, start: BytePos, end: BytePos) -> Symbol {
467         debug!("taking an ident from {:?} to {:?}", start, end);
468         Symbol::intern(self.str_from_to(start, end))
469     }
470
471     /// Slice of the source text spanning from `start` up to but excluding `end`.
472     fn str_from_to(&self, start: BytePos, end: BytePos) -> &str
473     {
474         &self.src[self.src_index(start)..self.src_index(end)]
475     }
476
477     fn forbid_bare_cr(&self, start: BytePos, s: &str, errmsg: &str) {
478         let mut idx = 0;
479         loop {
480             idx = match s[idx..].find('\r') {
481                 None => break,
482                 Some(it) => idx + it + 1
483             };
484             self.err_span_(start + BytePos(idx as u32 - 1),
485                            start + BytePos(idx as u32),
486                            errmsg);
487         }
488     }
489
490     fn report_non_started_raw_string(&self, start: BytePos) -> ! {
491         let bad_char = self.str_from(start).chars().last().unwrap();
492         self
493             .struct_fatal_span_char(
494                 start,
495                 self.pos,
496                 "found invalid character; only `#` is allowed \
497                  in raw string delimitation",
498                 bad_char,
499             )
500             .emit();
501         FatalError.raise()
502     }
503
504     fn report_unterminated_raw_string(&self, start: BytePos, n_hashes: usize) -> ! {
505         let mut err = self.struct_span_fatal(
506             start, start,
507             "unterminated raw string",
508         );
509         err.span_label(
510             self.mk_sp(start, start),
511             "unterminated raw string",
512         );
513
514         if n_hashes > 0 {
515             err.note(&format!("this raw string should be terminated with `\"{}`",
516                                 "#".repeat(n_hashes as usize)));
517         }
518
519         err.emit();
520         FatalError.raise()
521     }
522
523     fn restrict_n_hashes(&self, start: BytePos, n_hashes: usize) -> u16 {
524         match n_hashes.try_into() {
525             Ok(n_hashes) => n_hashes,
526             Err(_) => {
527                 self.fatal_span_(start,
528                                  self.pos,
529                                  "too many `#` symbols: raw strings may be \
530                                   delimited by up to 65535 `#` symbols").raise();
531             }
532         }
533     }
534
535     fn validate_char_escape(&self, content_start: BytePos, content_end: BytePos) {
536         let lit = self.str_from_to(content_start, content_end);
537         if let Err((off, err)) = unescape::unescape_char(lit) {
538             emit_unescape_error(
539                 &self.sess.span_diagnostic,
540                 lit,
541                 self.mk_sp(content_start - BytePos(1), content_end + BytePos(1)),
542                 unescape::Mode::Char,
543                 0..off,
544                 err,
545             )
546         }
547     }
548
549     fn validate_byte_escape(&self, content_start: BytePos, content_end: BytePos) {
550         let lit = self.str_from_to(content_start, content_end);
551         if let Err((off, err)) = unescape::unescape_byte(lit) {
552             emit_unescape_error(
553                 &self.sess.span_diagnostic,
554                 lit,
555                 self.mk_sp(content_start - BytePos(1), content_end + BytePos(1)),
556                 unescape::Mode::Byte,
557                 0..off,
558                 err,
559             )
560         }
561     }
562
563     fn validate_str_escape(&self, content_start: BytePos, content_end: BytePos) {
564         let lit = self.str_from_to(content_start, content_end);
565         unescape::unescape_str(lit, &mut |range, c| {
566             if let Err(err) = c {
567                 emit_unescape_error(
568                     &self.sess.span_diagnostic,
569                     lit,
570                     self.mk_sp(content_start - BytePos(1), content_end + BytePos(1)),
571                     unescape::Mode::Str,
572                     range,
573                     err,
574                 )
575             }
576         })
577     }
578
579     fn validate_raw_str_escape(&self, content_start: BytePos, content_end: BytePos) {
580         let lit = self.str_from_to(content_start, content_end);
581         unescape::unescape_raw_str(lit, &mut |range, c| {
582             if let Err(err) = c {
583                 emit_unescape_error(
584                     &self.sess.span_diagnostic,
585                     lit,
586                     self.mk_sp(content_start - BytePos(1), content_end + BytePos(1)),
587                     unescape::Mode::Str,
588                     range,
589                     err,
590                 )
591             }
592         })
593     }
594
595     fn validate_raw_byte_str_escape(&self, content_start: BytePos, content_end: BytePos) {
596         let lit = self.str_from_to(content_start, content_end);
597         unescape::unescape_raw_byte_str(lit, &mut |range, c| {
598             if let Err(err) = c {
599                 emit_unescape_error(
600                     &self.sess.span_diagnostic,
601                     lit,
602                     self.mk_sp(content_start - BytePos(1), content_end + BytePos(1)),
603                     unescape::Mode::ByteStr,
604                     range,
605                     err,
606                 )
607             }
608         })
609     }
610
611     fn validate_byte_str_escape(&self, content_start: BytePos, content_end: BytePos) {
612         let lit = self.str_from_to(content_start, content_end);
613         unescape::unescape_byte_str(lit, &mut |range, c| {
614             if let Err(err) = c {
615                 emit_unescape_error(
616                     &self.sess.span_diagnostic,
617                     lit,
618                     self.mk_sp(content_start - BytePos(1), content_end + BytePos(1)),
619                     unescape::Mode::ByteStr,
620                     range,
621                     err,
622                 )
623             }
624         })
625     }
626
627     fn validate_int_literal(&self, base: Base, content_start: BytePos, content_end: BytePos) {
628         let base = match base {
629             Base::Binary => 2,
630             Base::Octal => 8,
631             _ => return,
632         };
633         let s = self.str_from_to(content_start + BytePos(2), content_end);
634         for (idx, c) in s.char_indices() {
635             let idx = idx as u32;
636             if c != '_' && c.to_digit(base).is_none() {
637                 let lo = content_start + BytePos(2 + idx);
638                 let hi = content_start + BytePos(2 + idx + c.len_utf8() as u32);
639                 self.err_span_(lo, hi,
640                                &format!("invalid digit for a base {} literal", base));
641
642             }
643         }
644     }
645 }
646
647 fn is_doc_comment(s: &str) -> bool {
648     let res = (s.starts_with("///") && *s.as_bytes().get(3).unwrap_or(&b' ') != b'/') ||
649               s.starts_with("//!");
650     debug!("is {:?} a doc comment? {}", s, res);
651     res
652 }
653
654 fn is_block_doc_comment(s: &str) -> bool {
655     // Prevent `/**/` from being parsed as a doc comment
656     let res = ((s.starts_with("/**") && *s.as_bytes().get(3).unwrap_or(&b' ') != b'*') ||
657                s.starts_with("/*!")) && s.len() >= 5;
658     debug!("is {:?} a doc comment? {}", s, res);
659     res
660 }