]> git.lizzy.rs Git - rust.git/blob - src/mod.rs
Update for new FnKind::FkMethod signature
[rust.git] / src / mod.rs
1 // Copyright 2015 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 #![feature(box_syntax)]
12 #![feature(box_patterns)]
13 #![feature(rustc_private)]
14 #![feature(collections)]
15 #![feature(os)]
16 #![feature(core)]
17 #![feature(unicode)]
18 #![feature(exit_status)]
19 #![feature(path)]
20
21 // TODO we're going to allocate a whole bunch of temp Strings, is it worth
22 // keeping some scratch mem for this and running our own StrPool?
23
24 #[macro_use]
25 extern crate log;
26
27 extern crate getopts;
28 extern crate rustc;
29 extern crate rustc_driver;
30 extern crate syntax;
31
32 use rustc::session::Session;
33 use rustc::session::config::{self, Input};
34 use rustc_driver::{driver, CompilerCalls, Compilation};
35
36 use syntax::{ast, ptr};
37 use syntax::codemap::{self, CodeMap, Span, Pos, BytePos};
38 use syntax::diagnostics;
39 use syntax::parse::token;
40 use syntax::print::pprust;
41 use syntax::visit;
42
43 use std::path::PathBuf;
44 use std::slice::SliceConcatExt;
45
46 use changes::ChangeSet;
47
48 pub mod rope;
49 pub mod string_buffer;
50 mod changes;
51
52 const IDEAL_WIDTH: usize = 80;
53 const LEEWAY: usize = 5;
54 const MAX_WIDTH: usize = 100;
55 const MIN_STRING: usize = 10;
56
57 // Formatting which depends on the AST.
58 fn fmt_ast<'a>(krate: &ast::Crate, codemap: &'a CodeMap) -> ChangeSet<'a> {
59     let mut visitor = FmtVisitor::from_codemap(codemap);
60     visit::walk_crate(&mut visitor, krate);
61     let files = codemap.files.borrow();
62     if let Some(last) = files.last() {
63         visitor.format_missing(last.end_pos);
64     }
65
66     visitor.changes
67 }
68
69 // Formatting done on a char by char basis.
70 fn fmt_lines(changes: &mut ChangeSet) {
71     // Iterate over the chars in the change set.
72     for (f, text) in changes.text() {
73         let mut trims = vec![];
74         let mut last_wspace = None;
75         let mut line_len = 0;
76         let mut cur_line = 1;
77         for (c, b) in text.chars() {
78             if c == '\n' { // TOOD test for \r too
79                 // Check for (and record) trailing whitespace.
80                 if let Some(lw) = last_wspace {
81                     trims.push((cur_line, lw, b));
82                     line_len -= b - lw;
83                 }
84                 // Check for any line width errors we couldn't correct.
85                 if line_len > MAX_WIDTH {
86                     // FIXME store the error rather than reporting immediately.
87                     println!("Rustfmt couldn't fix (sorry). {}:{}: line longer than {} characters",
88                              f, cur_line, MAX_WIDTH);
89                 }
90                 line_len = 0;
91                 cur_line += 1;
92                 last_wspace = None;
93             } else {
94                 line_len += 1;
95                 if c.is_whitespace() {
96                     if last_wspace.is_none() {
97                         last_wspace = Some(b);
98                     }
99                 } else {
100                     last_wspace = None;
101                 }
102             }
103         }
104
105         for &(l, _, _) in trims.iter() {
106             // FIXME store the error rather than reporting immediately.
107             println!("Rustfmt left trailing whitespace at {}:{} (sorry)", f, l);
108         }
109     }
110 }
111
112 struct FmtVisitor<'a> {
113     codemap: &'a CodeMap,
114     changes: ChangeSet<'a>,
115     last_pos: BytePos,
116     block_indent: usize,
117 }
118
119 impl<'a, 'v> visit::Visitor<'v> for FmtVisitor<'a> {
120     fn visit_expr(&mut self, ex: &'v ast::Expr) {
121         self.format_missing(ex.span.lo);
122         let offset = self.changes.cur_offset_span(ex.span);
123         let new_str = self.rewrite_expr(ex, MAX_WIDTH - offset, offset);
124         self.changes.push_str_span(ex.span, &new_str);
125         self.last_pos = ex.span.hi;
126     }
127
128     fn visit_block(&mut self, b: &'v ast::Block) {
129         self.format_missing(b.span.lo);
130
131         self.changes.push_str_span(b.span, "{");
132         self.last_pos = self.last_pos + BytePos(1);
133         self.block_indent += 4;
134
135         for stmt in &b.stmts {
136             self.format_missing_with_indent(stmt.span.lo);
137             self.visit_stmt(&**stmt)
138         }
139         match b.expr {
140             Some(ref e) => {
141                 self.format_missing_with_indent(e.span.lo);
142                 self.visit_expr(e);
143             }
144             None => {}
145         }
146
147         self.block_indent -= 4;
148         // TODO we should compress any newlines here to just one
149         self.format_missing_with_indent(b.span.hi - BytePos(1));
150         self.changes.push_str_span(b.span, "}");
151         self.last_pos = b.span.hi;
152     }
153
154     fn visit_fn(&mut self,
155                 fk: visit::FnKind<'v>,
156                 fd: &'v ast::FnDecl,
157                 b: &'v ast::Block,
158                 s: Span,
159                 _: ast::NodeId) {
160         if let Some(new_str) = self.formal_args(fk, fd) {
161             self.changes.push_str_span(s, &new_str);            
162         }
163         visit::walk_fn(self, fk, fd, b, s);
164     }
165
166     fn visit_item(&mut self, item: &'v ast::Item) {
167         match item.node {
168             ast::Item_::ItemUse(ref vp) => {
169                 match vp.node {
170                     ast::ViewPath_::ViewPathList(ref path, ref path_list) => {
171                         self.format_missing(item.span.lo);
172                         let new_str = self.fix_use_list(path, path_list, vp.span);
173                         self.changes.push_str_span(item.span, &new_str);
174                         self.last_pos = item.span.hi;
175                     }
176                     ast::ViewPath_::ViewPathGlob(_) => {
177                         // FIXME convert to list?
178                     }
179                     _ => {}
180                 }
181                 visit::walk_item(self, item);
182             }
183             ast::Item_::ItemImpl(..) => {
184                 self.block_indent += 4;
185                 visit::walk_item(self, item);
186                 self.block_indent -= 4;
187             }
188             _ => {
189                 visit::walk_item(self, item);
190             }
191         }
192     }
193 }
194
195 fn make_indent(width: usize) -> String {
196     let mut indent = String::with_capacity(width);
197     for _ in 0..width {
198         indent.push(' ')
199     }
200     indent
201 }
202
203 impl<'a> FmtVisitor<'a> {
204     fn from_codemap<'b>(codemap: &'b CodeMap) -> FmtVisitor<'b> {
205         FmtVisitor {
206             codemap: codemap,
207             changes: ChangeSet::from_codemap(codemap),
208             last_pos: BytePos(0),
209             block_indent: 0,
210         }
211     }
212
213     fn format_missing(&mut self, end: BytePos) {
214         self.format_missing_inner(end, |this, last_snippet, span, _| {
215             this.changes.push_str_span(span, last_snippet)
216         })
217     }
218
219     fn format_missing_with_indent(&mut self, end: BytePos) {
220         self.format_missing_inner(end, |this, last_snippet, span, snippet| {
221             if last_snippet == snippet {
222                 // No new lines
223                 this.changes.push_str_span(span, last_snippet);
224                 this.changes.push_str_span(span, "\n");
225             } else {
226                 this.changes.push_str_span(span, last_snippet.trim_right());
227             }
228             let indent = make_indent(this.block_indent);
229             this.changes.push_str_span(span, &indent);           
230         })
231     }
232
233     fn format_missing_inner<F: Fn(&mut FmtVisitor, &str, Span, &str)>(&mut self,
234                                                                       end: BytePos,
235                                                                       process_last_snippet: F)
236     {
237         let start = self.last_pos;
238         // TODO(#11) gets tricky if we're missing more than one file
239         assert!(self.codemap.lookup_char_pos(start).file.name == self.codemap.lookup_char_pos(end).file.name,
240                 "not implemented: unformated span across files");
241
242         self.last_pos = end;
243         let span = codemap::mk_sp(start, end);
244         let snippet = self.snippet(span);
245
246         // Annoyingly, the library functions for splitting by lines etc. are not
247         // quite right, so we must do it ourselves.
248         let mut line_start = 0;
249         let mut last_wspace = None;
250         for (i, c) in snippet.char_indices() {
251             if c == '\n' {
252                 if let Some(lw) = last_wspace {
253                     self.changes.push_str_span(span, &snippet[line_start..lw]);
254                     self.changes.push_str_span(span, "\n");
255                 } else {
256                     self.changes.push_str_span(span, &snippet[line_start..i+1]);
257                 }
258
259                 line_start = i + 1;
260                 last_wspace = None;
261             } else {
262                 if c.is_whitespace() {
263                     if last_wspace.is_none() {
264                         last_wspace = Some(i);
265                     }
266                 } else {
267                     last_wspace = None;
268                 }
269             }
270         }
271         process_last_snippet(self, &snippet[line_start..], span, &snippet);
272     }
273
274     fn snippet(&self, span: Span) -> String {
275         match self.codemap.span_to_snippet(span) {
276             Ok(s) => s,
277             Err(_) => {
278                 println!("Couldn't make snippet for span {:?}", span);
279                 "".to_string()
280             }
281         }
282     }
283
284     // TODO NEEDS TESTS
285     fn rewrite_string(&mut self, s: &str, span: Span, width: usize, offset: usize) -> String {
286         // FIXME I bet this stomps unicode escapes in the source string
287
288         // Check if there is anything to fix: we always try to fixup multi-line
289         // strings, or if the string is too long for the line.
290         let l_loc = self.codemap.lookup_char_pos(span.lo);
291         let r_loc = self.codemap.lookup_char_pos(span.hi);
292         if l_loc.line == r_loc.line && r_loc.col.to_usize() <= MAX_WIDTH {
293             return self.snippet(span);
294         }
295
296         // TODO if lo.col > IDEAL - 10, start a new line (need cur indent for that)
297
298         let s = s.escape_default();
299
300         let offset = offset + 1;
301         let indent = make_indent(offset);
302         let indent = &indent;
303
304         let max_chars = width - 1;
305
306         let mut cur_start = 0;
307         let mut result = String::new();
308         result.push('"');
309         loop {
310             let mut cur_end = cur_start + max_chars;
311
312             if cur_end >= s.len() {
313                 result.push_str(&s[cur_start..]);
314                 break;
315             }
316
317             // Make sure we're on a char boundary.
318             cur_end = next_char(&s, cur_end);
319
320             // Push cur_end left until we reach whitespace
321             while !s.char_at(cur_end-1).is_whitespace() {
322                 cur_end = prev_char(&s, cur_end);
323
324                 if cur_end - cur_start < MIN_STRING {
325                     // We can't break at whitespace, fall back to splitting
326                     // anywhere that doesn't break an escape sequence
327                     cur_end = next_char(&s, cur_start + max_chars);
328                     while s.char_at(cur_end) == '\\' {
329                         cur_end = prev_char(&s, cur_end);
330                     }
331                 }
332             }
333             // Make sure there is no whitespace to the right of the break.
334             while cur_end < s.len() && s.char_at(cur_end).is_whitespace() {
335                 cur_end = next_char(&s, cur_end+1);
336             }
337             result.push_str(&s[cur_start..cur_end]);
338             result.push_str("\\\n");
339             result.push_str(indent);
340
341             cur_start = cur_end;
342         }
343         result.push('"');
344
345         result
346     }
347
348     // Basically just pretty prints a multi-item import.
349     fn fix_use_list(&mut self,
350                     path: &ast::Path,
351                     path_list: &[ast::PathListItem],
352                     vp_span: Span) -> String {
353         // FIXME remove unused imports
354
355         // FIXME check indentation
356         let l_loc = self.codemap.lookup_char_pos(vp_span.lo);
357         let path_str = pprust::path_to_string(&path);
358         let indent = l_loc.col.0;
359         // After accounting for the overhead, how much space left for
360         // the item list? ( 5 = :: + { + } + ; )
361         let space = IDEAL_WIDTH - (indent + path_str.len() + 5);
362         // 4 = `use` + one space
363         // TODO might be pub use
364         let indent = make_indent(indent-4);
365
366         let mut cur_str = String::new();
367         let mut first = true;
368         // If `self` is in the list, put it first.
369         if path_list.iter().any(|vpi|
370             if let ast::PathListItem_::PathListMod{ .. } = vpi.node {
371                 true
372             } else {
373                 false
374             }
375         ) {
376             cur_str = "self".to_string();
377             first = false;
378         }
379
380         let mut new_str = String::new();
381         for vpi in path_list.iter() {
382             match vpi.node {
383                 ast::PathListItem_::PathListIdent{ name, .. } => {
384                     let next_item = &token::get_ident(name);
385                     if cur_str.len() + next_item.len() > space {
386                         let cur_line = format!("{}use {}::{{{}}};\n", indent, path_str, cur_str);
387                         new_str.push_str(&cur_line);
388
389                         cur_str = String::new();
390                         first = true;
391                     }
392
393                     if first {
394                         first = false;
395                     } else {
396                         cur_str.push_str(", ");
397                     }
398
399                     cur_str.push_str(next_item);
400                 }
401                 ast::PathListItem_::PathListMod{ .. } => {}
402             }
403         }
404
405         assert!(!first);
406         let cur_line = format!("{}use {}::{{{}}};", indent, path_str, cur_str);
407         new_str.push_str(&cur_line);
408
409         new_str
410     }
411
412     fn formal_args<'v>(&mut self, fk: visit::FnKind<'v>, fd: &'v ast::FnDecl) -> Option<String> {
413         // For now, just check the arguments line up and make them per-row if the line is too long.
414         let args = &fd.inputs;
415
416         let ret_str = match fd.output {
417             ast::FunctionRetTy::DefaultReturn(_) => "".to_string(),
418             ast::FunctionRetTy::NoReturn(_) => " -> !".to_string(),
419             ast::FunctionRetTy::Return(ref ty) => pprust::ty_to_string(ty),
420         };
421
422         // TODO don't return, want to do the return type etc.
423         if args.len() == 0 {
424             return None;
425         }
426
427         // TODO not really using the hi positions
428         let spans: Vec<_> = args.iter().map(|a| (a.pat.span.lo, a.ty.span.hi)).collect();
429         let locs: Vec<_> = spans.iter().map(|&(a, b)| {
430             (self.codemap.lookup_char_pos(a), self.codemap.lookup_char_pos(b))
431         }).collect();
432         let first_col = locs[0].0.col.0;
433
434         // Print up to the start of the args.
435         self.format_missing(spans[0].0);
436         self.last_pos = spans.last().unwrap().1;
437
438         let arg_strs: Vec<_> = args.iter().map(|a| format!("{}: {}",
439                                                            pprust::pat_to_string(&a.pat),
440                                                            pprust::ty_to_string(&a.ty))).collect();
441
442         // Try putting everything on one row:
443         let mut len = arg_strs.iter().fold(0, |a, b| a + b.len());
444         // Account for punctuation and spacing.
445         len += 2 * arg_strs.len() + 2 * (arg_strs.len()-1);
446         // Return type.
447         len += ret_str.len();
448         // Opening brace if no where clause.
449         match fk {
450             visit::FnKind::FkItemFn(_, &ref g, _, _) |
451             visit::FnKind::FkMethod(_, &ast::MethodSig { generics: ref g, ..})
452             if g.where_clause.predicates.len() > 0 => {}
453             _ => len += 2 // ` {`
454         }
455         len += first_col;
456
457         if len <= IDEAL_WIDTH + LEEWAY || args.len() == 1 {
458             // It should all fit on one line.
459             return Some(arg_strs.connect(", "));
460         } else {
461             // TODO multi-line
462             let mut indent = String::with_capacity(first_col + 2);
463             indent.push_str(",\n");
464             for _ in 0..first_col { indent.push(' '); }
465             return Some(arg_strs.connect(&indent));
466         }
467     }
468
469     fn rewrite_call(&mut self,
470                     callee: &ast::Expr,
471                     args: &[ptr::P<ast::Expr>],
472                     width: usize,
473                     offset: usize)
474         -> String
475     {
476         debug!("rewrite_call, width: {}, offset: {}", width, offset);
477
478         // TODO using byte lens instead of char lens (and probably all over the place too)
479         let callee_str = self.rewrite_expr(callee, width, offset);
480         debug!("rewrite_call, callee_str: `{}`", callee_str);
481         // 2 is for parens.
482         let remaining_width = width - callee_str.len() - 2;
483         let offset = callee_str.len() + 1 + offset;
484         let arg_count = args.len();
485         let args: Vec<_> = args.iter().map(|e| self.rewrite_expr(e,
486                                                                  remaining_width,
487                                                                  offset)).collect();
488         debug!("rewrite_call, args: `{}`", args.connect(","));
489
490         let multi_line = args.iter().any(|s| s.contains('\n'));
491         let args_width = args.iter().map(|s| s.len()).fold(0, |a, l| a + l);
492         let over_wide = args_width + (arg_count - 1) * 2 > remaining_width;
493         let args_str = if multi_line || over_wide {
494             args.connect(&(",\n".to_string() + &make_indent(offset)))
495         } else {
496             args.connect(", ")
497         };
498
499         format!("{}({})", callee_str, args_str)
500     }
501
502     fn rewrite_expr(&mut self, expr: &ast::Expr, width: usize, offset: usize) -> String {
503         match expr.node {
504             ast::Expr_::ExprLit(ref l) => {
505                 match l.node {
506                     ast::Lit_::LitStr(ref is, _) => {
507                         return self.rewrite_string(&is, l.span, width, offset);
508                     }
509                     _ => {}
510                 }
511             }
512             ast::Expr_::ExprCall(ref callee, ref args) => {
513                 return self.rewrite_call(callee, args, width, offset);
514             }
515             _ => {}
516         }
517
518         let result = self.snippet(expr.span);
519         debug!("snippet: {}", result);
520         result
521     }
522 }
523
524 #[inline]
525 fn prev_char(s: &str, mut i: usize) -> usize {
526     if i == 0 { return 0; }
527
528     i -= 1;
529     while !s.is_char_boundary(i) {
530         i -= 1;
531     }
532     i
533 }
534
535 #[inline]
536 fn next_char(s: &str, mut i: usize) -> usize {
537     if i >= s.len() { return s.len(); }
538
539     while !s.is_char_boundary(i) {
540         i += 1;
541     }
542     i
543 }
544
545 struct RustFmtCalls {
546     input_path: Option<PathBuf>,
547 }
548
549 impl<'a> CompilerCalls<'a> for RustFmtCalls {
550     fn early_callback(&mut self,
551                       _: &getopts::Matches,
552                       _: &diagnostics::registry::Registry)
553                       -> Compilation {
554         Compilation::Continue
555     }
556
557     fn some_input(&mut self, input: Input, input_path: Option<PathBuf>) -> (Input, Option<PathBuf>) {
558         match input_path {
559             Some(ref ip) => self.input_path = Some(ip.clone()),
560             _ => {
561                 // FIXME should handle string input and write to stdout or something
562                 panic!("No input path");
563             }
564         }
565         (input, input_path)
566     }
567
568     fn no_input(&mut self,
569                 _: &getopts::Matches,
570                 _: &config::Options,
571                 _: &Option<PathBuf>,
572                 _: &Option<PathBuf>,
573                 _: &diagnostics::registry::Registry)
574                 -> Option<(Input, Option<PathBuf>)> {
575         panic!("No input supplied to RustFmt");
576     }
577
578     fn late_callback(&mut self,
579                      _: &getopts::Matches,
580                      _: &Session,
581                      _: &Input,
582                      _: &Option<PathBuf>,
583                      _: &Option<PathBuf>)
584                      -> Compilation {
585         Compilation::Continue
586     }
587
588     fn build_controller(&mut self, _: &Session) -> driver::CompileController<'a> {
589         let mut control = driver::CompileController::basic();
590         control.after_parse.stop = Compilation::Stop;
591         control.after_parse.callback = box |state| {
592             let krate = state.krate.unwrap();
593             let codemap = state.session.codemap();
594             let mut changes = fmt_ast(krate, codemap);
595             fmt_lines(&mut changes);
596
597             println!("{}", changes);
598             // FIXME(#5) Should be user specified whether to show or replace.
599
600             // TODO we stop before expansion, but we still seem to get expanded for loops which
601             // cause problems - probably a rustc bug
602         };
603
604         control
605     }
606 }
607
608 fn main() {
609     let args = std::os::args();
610     let mut call_ctxt = RustFmtCalls { input_path: None };
611     rustc_driver::run_compiler(&args, &mut call_ctxt);
612     std::env::set_exit_status(0);
613 }
614
615 // FIXME comments
616 // comments aren't in the AST, which makes processing them difficult, but then
617 // comments are complicated anyway. I think I am happy putting off tackling them
618 // for now. Long term the soluton is for comments to be in the AST, but that means
619 // only the libsyntax AST, not the rustc one, which means waiting for the ASTs
620 // to diverge one day....
621
622 // Once we do have comments, we just have to implement a simple word wrapping
623 // algorithm to keep the width under IDEAL_WIDTH. We should also convert multiline
624 // /* ... */ comments to // and check doc comments are in the right place and of
625 // the right kind.
626
627 // Should also make sure comments have the right indent