]> git.lizzy.rs Git - rust.git/blob - src/libsyntax/parse/mod.rs
Simplify PatIdent to contain an Ident rather than a Path
[rust.git] / src / libsyntax / parse / mod.rs
1 // Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 //! The main parser interface
12
13
14 use ast;
15 use codemap::{Span, CodeMap, FileMap};
16 use diagnostic::{SpanHandler, mk_span_handler, default_handler, Auto};
17 use parse::attr::ParserAttr;
18 use parse::parser::Parser;
19
20 use std::cell::RefCell;
21 use std::gc::Gc;
22 use std::io::File;
23 use std::rc::Rc;
24 use std::str;
25
26 pub mod lexer;
27 pub mod parser;
28 pub mod token;
29 pub mod attr;
30
31 pub mod common;
32 pub mod classify;
33 pub mod obsolete;
34
35 // info about a parsing session.
36 pub struct ParseSess {
37     pub span_diagnostic: SpanHandler, // better be the same as the one in the reader!
38     /// Used to determine and report recursive mod inclusions
39     included_mod_stack: RefCell<Vec<Path>>,
40 }
41
42 pub fn new_parse_sess() -> ParseSess {
43     ParseSess {
44         span_diagnostic: mk_span_handler(default_handler(Auto), CodeMap::new()),
45         included_mod_stack: RefCell::new(Vec::new()),
46     }
47 }
48
49 pub fn new_parse_sess_special_handler(sh: SpanHandler) -> ParseSess {
50     ParseSess {
51         span_diagnostic: sh,
52         included_mod_stack: RefCell::new(Vec::new()),
53     }
54 }
55
56 // a bunch of utility functions of the form parse_<thing>_from_<source>
57 // where <thing> includes crate, expr, item, stmt, tts, and one that
58 // uses a HOF to parse anything, and <source> includes file and
59 // source_str.
60
61 pub fn parse_crate_from_file(
62     input: &Path,
63     cfg: ast::CrateConfig,
64     sess: &ParseSess
65 ) -> ast::Crate {
66     new_parser_from_file(sess, cfg, input).parse_crate_mod()
67     // why is there no p.abort_if_errors here?
68 }
69
70 pub fn parse_crate_attrs_from_file(
71     input: &Path,
72     cfg: ast::CrateConfig,
73     sess: &ParseSess
74 ) -> Vec<ast::Attribute> {
75     let mut parser = new_parser_from_file(sess, cfg, input);
76     let (inner, _) = parser.parse_inner_attrs_and_next();
77     inner
78 }
79
80 pub fn parse_crate_from_source_str(name: String,
81                                    source: String,
82                                    cfg: ast::CrateConfig,
83                                    sess: &ParseSess)
84                                    -> ast::Crate {
85     let mut p = new_parser_from_source_str(sess,
86                                            cfg,
87                                            name,
88                                            source);
89     maybe_aborted(p.parse_crate_mod(),p)
90 }
91
92 pub fn parse_crate_attrs_from_source_str(name: String,
93                                          source: String,
94                                          cfg: ast::CrateConfig,
95                                          sess: &ParseSess)
96                                          -> Vec<ast::Attribute> {
97     let mut p = new_parser_from_source_str(sess,
98                                            cfg,
99                                            name,
100                                            source);
101     let (inner, _) = maybe_aborted(p.parse_inner_attrs_and_next(),p);
102     inner
103 }
104
105 pub fn parse_expr_from_source_str(name: String,
106                                   source: String,
107                                   cfg: ast::CrateConfig,
108                                   sess: &ParseSess)
109                                   -> Gc<ast::Expr> {
110     let mut p = new_parser_from_source_str(sess, cfg, name, source);
111     maybe_aborted(p.parse_expr(), p)
112 }
113
114 pub fn parse_item_from_source_str(name: String,
115                                   source: String,
116                                   cfg: ast::CrateConfig,
117                                   sess: &ParseSess)
118                                   -> Option<Gc<ast::Item>> {
119     let mut p = new_parser_from_source_str(sess, cfg, name, source);
120     maybe_aborted(p.parse_item_with_outer_attributes(),p)
121 }
122
123 pub fn parse_meta_from_source_str(name: String,
124                                   source: String,
125                                   cfg: ast::CrateConfig,
126                                   sess: &ParseSess)
127                                   -> Gc<ast::MetaItem> {
128     let mut p = new_parser_from_source_str(sess, cfg, name, source);
129     maybe_aborted(p.parse_meta_item(),p)
130 }
131
132 pub fn parse_stmt_from_source_str(name: String,
133                                   source: String,
134                                   cfg: ast::CrateConfig,
135                                   attrs: Vec<ast::Attribute> ,
136                                   sess: &ParseSess)
137                                   -> Gc<ast::Stmt> {
138     let mut p = new_parser_from_source_str(
139         sess,
140         cfg,
141         name,
142         source
143     );
144     maybe_aborted(p.parse_stmt(attrs),p)
145 }
146
147 pub fn parse_tts_from_source_str(name: String,
148                                  source: String,
149                                  cfg: ast::CrateConfig,
150                                  sess: &ParseSess)
151                                  -> Vec<ast::TokenTree> {
152     let mut p = new_parser_from_source_str(
153         sess,
154         cfg,
155         name,
156         source
157     );
158     p.quote_depth += 1u;
159     // right now this is re-creating the token trees from ... token trees.
160     maybe_aborted(p.parse_all_token_trees(),p)
161 }
162
163 // Create a new parser from a source string
164 pub fn new_parser_from_source_str<'a>(sess: &'a ParseSess,
165                                       cfg: ast::CrateConfig,
166                                       name: String,
167                                       source: String)
168                                       -> Parser<'a> {
169     filemap_to_parser(sess, string_to_filemap(sess, source, name), cfg)
170 }
171
172 /// Create a new parser, handling errors as appropriate
173 /// if the file doesn't exist
174 pub fn new_parser_from_file<'a>(sess: &'a ParseSess,
175                                 cfg: ast::CrateConfig,
176                                 path: &Path) -> Parser<'a> {
177     filemap_to_parser(sess, file_to_filemap(sess, path, None), cfg)
178 }
179
180 /// Given a session, a crate config, a path, and a span, add
181 /// the file at the given path to the codemap, and return a parser.
182 /// On an error, use the given span as the source of the problem.
183 pub fn new_sub_parser_from_file<'a>(sess: &'a ParseSess,
184                                     cfg: ast::CrateConfig,
185                                     path: &Path,
186                                     owns_directory: bool,
187                                     module_name: Option<String>,
188                                     sp: Span) -> Parser<'a> {
189     let mut p = filemap_to_parser(sess, file_to_filemap(sess, path, Some(sp)), cfg);
190     p.owns_directory = owns_directory;
191     p.root_module_name = module_name;
192     p
193 }
194
195 /// Given a filemap and config, return a parser
196 pub fn filemap_to_parser<'a>(sess: &'a ParseSess,
197                              filemap: Rc<FileMap>,
198                              cfg: ast::CrateConfig) -> Parser<'a> {
199     tts_to_parser(sess, filemap_to_tts(sess, filemap), cfg)
200 }
201
202 // must preserve old name for now, because quote! from the *existing*
203 // compiler expands into it
204 pub fn new_parser_from_tts<'a>(sess: &'a ParseSess,
205                                cfg: ast::CrateConfig,
206                                tts: Vec<ast::TokenTree>) -> Parser<'a> {
207     tts_to_parser(sess, tts, cfg)
208 }
209
210
211 // base abstractions
212
213 /// Given a session and a path and an optional span (for error reporting),
214 /// add the path to the session's codemap and return the new filemap.
215 pub fn file_to_filemap(sess: &ParseSess, path: &Path, spanopt: Option<Span>)
216     -> Rc<FileMap> {
217     let err = |msg: &str| {
218         match spanopt {
219             Some(sp) => sess.span_diagnostic.span_fatal(sp, msg),
220             None => sess.span_diagnostic.handler().fatal(msg),
221         }
222     };
223     let bytes = match File::open(path).read_to_end() {
224         Ok(bytes) => bytes,
225         Err(e) => {
226             err(format!("couldn't read {}: {}",
227                         path.display(),
228                         e).as_slice());
229             unreachable!()
230         }
231     };
232     match str::from_utf8(bytes.as_slice()) {
233         Some(s) => {
234             return string_to_filemap(sess, s.to_string(),
235                                      path.as_str().unwrap().to_string())
236         }
237         None => {
238             err(format!("{} is not UTF-8 encoded", path.display()).as_slice())
239         }
240     }
241     unreachable!()
242 }
243
244 // given a session and a string, add the string to
245 // the session's codemap and return the new filemap
246 pub fn string_to_filemap(sess: &ParseSess, source: String, path: String)
247                          -> Rc<FileMap> {
248     sess.span_diagnostic.cm.new_filemap(path, source)
249 }
250
251 // given a filemap, produce a sequence of token-trees
252 pub fn filemap_to_tts(sess: &ParseSess, filemap: Rc<FileMap>)
253     -> Vec<ast::TokenTree> {
254     // it appears to me that the cfg doesn't matter here... indeed,
255     // parsing tt's probably shouldn't require a parser at all.
256     let cfg = Vec::new();
257     let srdr = lexer::StringReader::new(&sess.span_diagnostic, filemap);
258     let mut p1 = Parser::new(sess, cfg, box srdr);
259     p1.parse_all_token_trees()
260 }
261
262 // given tts and cfg, produce a parser
263 pub fn tts_to_parser<'a>(sess: &'a ParseSess,
264                          tts: Vec<ast::TokenTree>,
265                          cfg: ast::CrateConfig) -> Parser<'a> {
266     let trdr = lexer::new_tt_reader(&sess.span_diagnostic, None, tts);
267     Parser::new(sess, cfg, box trdr)
268 }
269
270 // abort if necessary
271 pub fn maybe_aborted<T>(result: T, mut p: Parser) -> T {
272     p.abort_if_errors();
273     result
274 }
275
276
277
278 #[cfg(test)]
279 mod test {
280     use super::*;
281     use serialize::{json, Encodable};
282     use std::io;
283     use std::io::MemWriter;
284     use std::mem::transmute;
285     use std::str;
286     use std::gc::GC;
287     use codemap::{Span, BytePos, Spanned};
288     use owned_slice::OwnedSlice;
289     use ast;
290     use abi;
291     use attr;
292     use attr::AttrMetaMethods;
293     use parse::parser::Parser;
294     use parse::token::{str_to_ident};
295     use util::parser_testing::{string_to_tts, string_to_parser};
296     use util::parser_testing::{string_to_expr, string_to_item};
297     use util::parser_testing::string_to_stmt;
298
299     fn to_json_str<'a, E: Encodable<json::Encoder<'a>, io::IoError>>(val: &E) -> String {
300         let mut writer = MemWriter::new();
301         // FIXME(14302) remove the transmute and unsafe block.
302         unsafe {
303             let mut encoder = json::Encoder::new(&mut writer as &mut io::Writer);
304             let _ = val.encode(transmute(&mut encoder));
305         }
306         str::from_utf8(writer.unwrap().as_slice()).unwrap().to_string()
307     }
308
309     // produce a codemap::span
310     fn sp(a: u32, b: u32) -> Span {
311         Span{lo:BytePos(a),hi:BytePos(b),expn_info:None}
312     }
313
314     #[test] fn path_exprs_1() {
315         assert!(string_to_expr("a".to_string()) ==
316                    box(GC) ast::Expr{
317                     id: ast::DUMMY_NODE_ID,
318                     node: ast::ExprPath(ast::Path {
319                         span: sp(0, 1),
320                         global: false,
321                         segments: vec!(
322                             ast::PathSegment {
323                                 identifier: str_to_ident("a"),
324                                 lifetimes: Vec::new(),
325                                 types: OwnedSlice::empty(),
326                             }
327                         ),
328                     }),
329                     span: sp(0, 1)
330                    })
331     }
332
333     #[test] fn path_exprs_2 () {
334         assert!(string_to_expr("::a::b".to_string()) ==
335                    box(GC) ast::Expr {
336                     id: ast::DUMMY_NODE_ID,
337                     node: ast::ExprPath(ast::Path {
338                             span: sp(0, 6),
339                             global: true,
340                             segments: vec!(
341                                 ast::PathSegment {
342                                     identifier: str_to_ident("a"),
343                                     lifetimes: Vec::new(),
344                                     types: OwnedSlice::empty(),
345                                 },
346                                 ast::PathSegment {
347                                     identifier: str_to_ident("b"),
348                                     lifetimes: Vec::new(),
349                                     types: OwnedSlice::empty(),
350                                 }
351                             )
352                         }),
353                     span: sp(0, 6)
354                    })
355     }
356
357     #[should_fail]
358     #[test] fn bad_path_expr_1() {
359         string_to_expr("::abc::def::return".to_string());
360     }
361
362     // check the token-tree-ization of macros
363     #[test] fn string_to_tts_macro () {
364         let tts = string_to_tts("macro_rules! zip (($a)=>($a))".to_string());
365         let tts: &[ast::TokenTree] = tts.as_slice();
366         match tts {
367             [ast::TTTok(_,_),
368              ast::TTTok(_,token::NOT),
369              ast::TTTok(_,_),
370              ast::TTDelim(ref delim_elts)] => {
371                 let delim_elts: &[ast::TokenTree] = delim_elts.as_slice();
372                 match delim_elts {
373                     [ast::TTTok(_,token::LPAREN),
374                      ast::TTDelim(ref first_set),
375                      ast::TTTok(_,token::FAT_ARROW),
376                      ast::TTDelim(ref second_set),
377                      ast::TTTok(_,token::RPAREN)] => {
378                         let first_set: &[ast::TokenTree] =
379                             first_set.as_slice();
380                         match first_set {
381                             [ast::TTTok(_,token::LPAREN),
382                              ast::TTTok(_,token::DOLLAR),
383                              ast::TTTok(_,_),
384                              ast::TTTok(_,token::RPAREN)] => {
385                                 let second_set: &[ast::TokenTree] =
386                                     second_set.as_slice();
387                                 match second_set {
388                                     [ast::TTTok(_,token::LPAREN),
389                                      ast::TTTok(_,token::DOLLAR),
390                                      ast::TTTok(_,_),
391                                      ast::TTTok(_,token::RPAREN)] => {
392                                         assert_eq!("correct","correct")
393                                     }
394                                     _ => assert_eq!("wrong 4","correct")
395                                 }
396                             },
397                             _ => {
398                                 error!("failing value 3: {:?}",first_set);
399                                 assert_eq!("wrong 3","correct")
400                             }
401                         }
402                     },
403                     _ => {
404                         error!("failing value 2: {:?}",delim_elts);
405                         assert_eq!("wrong","correct");
406                     }
407                 }
408             },
409             _ => {
410                 error!("failing value: {:?}",tts);
411                 assert_eq!("wrong 1","correct");
412             }
413         }
414     }
415
416     #[test] fn string_to_tts_1 () {
417         let tts = string_to_tts("fn a (b : int) { b; }".to_string());
418         assert_eq!(to_json_str(&tts),
419         "[\
420     {\
421         \"variant\":\"TTTok\",\
422         \"fields\":[\
423             null,\
424             {\
425                 \"variant\":\"IDENT\",\
426                 \"fields\":[\
427                     \"fn\",\
428                     false\
429                 ]\
430             }\
431         ]\
432     },\
433     {\
434         \"variant\":\"TTTok\",\
435         \"fields\":[\
436             null,\
437             {\
438                 \"variant\":\"IDENT\",\
439                 \"fields\":[\
440                     \"a\",\
441                     false\
442                 ]\
443             }\
444         ]\
445     },\
446     {\
447         \"variant\":\"TTDelim\",\
448         \"fields\":[\
449             [\
450                 {\
451                     \"variant\":\"TTTok\",\
452                     \"fields\":[\
453                         null,\
454                         \"LPAREN\"\
455                     ]\
456                 },\
457                 {\
458                     \"variant\":\"TTTok\",\
459                     \"fields\":[\
460                         null,\
461                         {\
462                             \"variant\":\"IDENT\",\
463                             \"fields\":[\
464                                 \"b\",\
465                                 false\
466                             ]\
467                         }\
468                     ]\
469                 },\
470                 {\
471                     \"variant\":\"TTTok\",\
472                     \"fields\":[\
473                         null,\
474                         \"COLON\"\
475                     ]\
476                 },\
477                 {\
478                     \"variant\":\"TTTok\",\
479                     \"fields\":[\
480                         null,\
481                         {\
482                             \"variant\":\"IDENT\",\
483                             \"fields\":[\
484                                 \"int\",\
485                                 false\
486                             ]\
487                         }\
488                     ]\
489                 },\
490                 {\
491                     \"variant\":\"TTTok\",\
492                     \"fields\":[\
493                         null,\
494                         \"RPAREN\"\
495                     ]\
496                 }\
497             ]\
498         ]\
499     },\
500     {\
501         \"variant\":\"TTDelim\",\
502         \"fields\":[\
503             [\
504                 {\
505                     \"variant\":\"TTTok\",\
506                     \"fields\":[\
507                         null,\
508                         \"LBRACE\"\
509                     ]\
510                 },\
511                 {\
512                     \"variant\":\"TTTok\",\
513                     \"fields\":[\
514                         null,\
515                         {\
516                             \"variant\":\"IDENT\",\
517                             \"fields\":[\
518                                 \"b\",\
519                                 false\
520                             ]\
521                         }\
522                     ]\
523                 },\
524                 {\
525                     \"variant\":\"TTTok\",\
526                     \"fields\":[\
527                         null,\
528                         \"SEMI\"\
529                     ]\
530                 },\
531                 {\
532                     \"variant\":\"TTTok\",\
533                     \"fields\":[\
534                         null,\
535                         \"RBRACE\"\
536                     ]\
537                 }\
538             ]\
539         ]\
540     }\
541 ]".to_string()
542         );
543     }
544
545     #[test] fn ret_expr() {
546         assert!(string_to_expr("return d".to_string()) ==
547                    box(GC) ast::Expr{
548                     id: ast::DUMMY_NODE_ID,
549                     node:ast::ExprRet(Some(box(GC) ast::Expr{
550                         id: ast::DUMMY_NODE_ID,
551                         node:ast::ExprPath(ast::Path{
552                             span: sp(7, 8),
553                             global: false,
554                             segments: vec!(
555                                 ast::PathSegment {
556                                     identifier: str_to_ident("d"),
557                                     lifetimes: Vec::new(),
558                                     types: OwnedSlice::empty(),
559                                 }
560                             ),
561                         }),
562                         span:sp(7,8)
563                     })),
564                     span:sp(0,8)
565                    })
566     }
567
568     #[test] fn parse_stmt_1 () {
569         assert!(string_to_stmt("b;".to_string()) ==
570                    box(GC) Spanned{
571                        node: ast::StmtExpr(box(GC) ast::Expr {
572                            id: ast::DUMMY_NODE_ID,
573                            node: ast::ExprPath(ast::Path {
574                                span:sp(0,1),
575                                global:false,
576                                segments: vec!(
577                                 ast::PathSegment {
578                                     identifier: str_to_ident("b"),
579                                     lifetimes: Vec::new(),
580                                     types: OwnedSlice::empty(),
581                                 }
582                                ),
583                             }),
584                            span: sp(0,1)},
585                                            ast::DUMMY_NODE_ID),
586                        span: sp(0,1)})
587
588     }
589
590     fn parser_done(p: Parser){
591         assert_eq!(p.token.clone(), token::EOF);
592     }
593
594     #[test] fn parse_ident_pat () {
595         let sess = new_parse_sess();
596         let mut parser = string_to_parser(&sess, "b".to_string());
597         assert!(parser.parse_pat()
598                 == box(GC) ast::Pat{
599                 id: ast::DUMMY_NODE_ID,
600                 node: ast::PatIdent(ast::BindByValue(ast::MutImmutable),
601                                     Spanned{ span:sp(0, 1),
602                                              node: str_to_ident("b")
603                     },
604                                     None),
605                 span: sp(0,1)});
606         parser_done(parser);
607     }
608
609     // check the contents of the tt manually:
610     #[test] fn parse_fundecl () {
611         // this test depends on the intern order of "fn" and "int"
612         assert!(string_to_item("fn a (b : int) { b; }".to_string()) ==
613                   Some(
614                       box(GC) ast::Item{ident:str_to_ident("a"),
615                             attrs:Vec::new(),
616                             id: ast::DUMMY_NODE_ID,
617                             node: ast::ItemFn(ast::P(ast::FnDecl {
618                                 inputs: vec!(ast::Arg{
619                                     ty: ast::P(ast::Ty{id: ast::DUMMY_NODE_ID,
620                                                        node: ast::TyPath(ast::Path{
621                                         span:sp(10,13),
622                                         global:false,
623                                         segments: vec!(
624                                             ast::PathSegment {
625                                                 identifier:
626                                                     str_to_ident("int"),
627                                                 lifetimes: Vec::new(),
628                                                 types: OwnedSlice::empty(),
629                                             }
630                                         ),
631                                         }, None, ast::DUMMY_NODE_ID),
632                                         span:sp(10,13)
633                                     }),
634                                     pat: box(GC) ast::Pat {
635                                         id: ast::DUMMY_NODE_ID,
636                                         node: ast::PatIdent(
637                                             ast::BindByValue(ast::MutImmutable),
638                                                 Spanned{
639                                                     span: sp(6,7),
640                                                     node: str_to_ident("b")},
641                                                 None
642                                                     ),
643                                             span: sp(6,7)
644                                         },
645                                         id: ast::DUMMY_NODE_ID
646                                     }),
647                                 output: ast::P(ast::Ty{id: ast::DUMMY_NODE_ID,
648                                                        node: ast::TyNil,
649                                                        span:sp(15,15)}), // not sure
650                                 cf: ast::Return,
651                                 variadic: false
652                             }),
653                                     ast::NormalFn,
654                                     abi::Rust,
655                                     ast::Generics{ // no idea on either of these:
656                                         lifetimes: Vec::new(),
657                                         ty_params: OwnedSlice::empty(),
658                                     },
659                                     ast::P(ast::Block {
660                                         view_items: Vec::new(),
661                                         stmts: vec!(box(GC) Spanned{
662                                             node: ast::StmtSemi(box(GC) ast::Expr{
663                                                 id: ast::DUMMY_NODE_ID,
664                                                 node: ast::ExprPath(
665                                                       ast::Path{
666                                                         span:sp(17,18),
667                                                         global:false,
668                                                         segments: vec!(
669                                                             ast::PathSegment {
670                                                                 identifier:
671                                                                 str_to_ident(
672                                                                     "b"),
673                                                                 lifetimes:
674                                                                 Vec::new(),
675                                                                 types:
676                                                                 OwnedSlice::empty()
677                                                             }
678                                                         ),
679                                                       }),
680                                                 span: sp(17,18)},
681                                                 ast::DUMMY_NODE_ID),
682                                             span: sp(17,19)}),
683                                         expr: None,
684                                         id: ast::DUMMY_NODE_ID,
685                                         rules: ast::DefaultBlock, // no idea
686                                         span: sp(15,21),
687                                     })),
688                             vis: ast::Inherited,
689                             span: sp(0,21)}));
690     }
691
692
693     #[test] fn parse_exprs () {
694         // just make sure that they parse....
695         string_to_expr("3 + 4".to_string());
696         string_to_expr("a::z.froob(b,box(GC)(987+3))".to_string());
697     }
698
699     #[test] fn attrs_fix_bug () {
700         string_to_item("pub fn mk_file_writer(path: &Path, flags: &[FileFlag])
701                    -> Result<Gc<Writer>, String> {
702     #[cfg(windows)]
703     fn wb() -> c_int {
704       (O_WRONLY | libc::consts::os::extra::O_BINARY) as c_int
705     }
706
707     #[cfg(unix)]
708     fn wb() -> c_int { O_WRONLY as c_int }
709
710     let mut fflags: c_int = wb();
711 }".to_string());
712     }
713
714     #[test] fn crlf_doc_comments() {
715         let sess = new_parse_sess();
716
717         let name = "<source>".to_string();
718         let source = "/// doc comment\r\nfn foo() {}".to_string();
719         let item = parse_item_from_source_str(name.clone(), source, Vec::new(), &sess).unwrap();
720         let doc = attr::first_attr_value_str_by_name(item.attrs.as_slice(), "doc").unwrap();
721         assert_eq!(doc.get(), "/// doc comment");
722
723         let source = "/// doc comment\r\n/// line 2\r\nfn foo() {}".to_string();
724         let item = parse_item_from_source_str(name.clone(), source, Vec::new(), &sess).unwrap();
725         let docs = item.attrs.iter().filter(|a| a.name().get() == "doc")
726                     .map(|a| a.value_str().unwrap().get().to_string()).collect::<Vec<_>>();
727         assert_eq!(docs.as_slice(), &["/// doc comment".to_string(), "/// line 2".to_string()]);
728
729         let source = "/** doc comment\r\n *  with CRLF */\r\nfn foo() {}".to_string();
730         let item = parse_item_from_source_str(name, source, Vec::new(), &sess).unwrap();
731         let doc = attr::first_attr_value_str_by_name(item.attrs.as_slice(), "doc").unwrap();
732         assert_eq!(doc.get(), "/** doc comment\n *  with CRLF */");
733     }
734 }