Skip to content

Commit fd6aa14

Browse files
killercupalexcrichton
authored andcommitted
First step towards rustfix compiletest mode
This is the first small step towards testing auto-fixable compiler suggestions using compiletest. Currently, it only checks if next to a UI test there also happens to a `*.rs.fixed` file, and then uses rustfix (added as external crate) on the original file, and asserts that it produces the fixed version. To show that this works, I've included one such test. I picked this test case at random (and because it was simple) -- It is not relevant to the 2018 edition. Indeed, in the near future, we want to be able to restrict rustfix to edition-lints, so this test cast might go away soon. In case you still think this is somewhat feature-complete, here's a quick list of things currently missing that I want to add before telling people they can use this: - [ ] Make this an actual compiletest mode, with `test [fix] …` output and everything - [ ] Assert that fixed files still compile - [ ] Assert that fixed files produce no (or a known set of) diagnostics output - [ ] Update `update-references.sh` to support rustfix - [ ] Use a published version of rustfix (i.e.: publish a new version rustfix that exposes a useful API for this)
1 parent 91db9dc commit fd6aa14

File tree

6 files changed

+110
-2
lines changed

6 files changed

+110
-2
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright 2017 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+
// Point at the captured immutable outer variable
12+
13+
fn foo(mut f: Box<FnMut()>) {
14+
f();
15+
}
16+
17+
fn main() {
18+
let mut y = true;
19+
foo(Box::new(move || y = false) as Box<_>); //~ ERROR cannot assign to captured outer variable
20+
}

src/tools/compiletest/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ regex = "0.2"
1313
serde = "1.0"
1414
serde_json = "1.0"
1515
serde_derive = "1.0"
16+
rustfix = { git = "https://github.com/rust-lang-nursery/rustfix" }
1617

1718
[target.'cfg(unix)'.dependencies]
1819
libc = "0.2"

src/tools/compiletest/src/autofix.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
use rustfix::{get_suggestions_from_json, Replacement};
2+
use std::collections::HashSet;
3+
use std::error::Error;
4+
5+
pub fn run_rustfix(code: &str, json: &str) -> String {
6+
let suggestions = get_suggestions_from_json(&json, &HashSet::new())
7+
.expect("could not load suggestions");
8+
9+
let mut fixed = code.to_string();
10+
11+
for sug in suggestions.into_iter().rev() {
12+
for sol in sug.solutions {
13+
for r in sol.replacements {
14+
fixed = apply_suggestion(&mut fixed, &r)
15+
.expect("could not apply suggestion");
16+
}
17+
}
18+
}
19+
20+
fixed
21+
}
22+
23+
fn apply_suggestion(
24+
file_content: &mut String,
25+
suggestion: &Replacement,
26+
) -> Result<String, Box<Error>> {
27+
use std::cmp::max;
28+
29+
let mut new_content = String::new();
30+
31+
// Add the lines before the section we want to replace
32+
new_content.push_str(&file_content
33+
.lines()
34+
.take(max(suggestion.snippet.line_range.start.line - 1, 0) as usize)
35+
.collect::<Vec<_>>()
36+
.join("\n"));
37+
new_content.push_str("\n");
38+
39+
// Parts of line before replacement
40+
new_content.push_str(&file_content
41+
.lines()
42+
.nth(suggestion.snippet.line_range.start.line - 1)
43+
.unwrap_or("")
44+
.chars()
45+
.take(suggestion.snippet.line_range.start.column - 1)
46+
.collect::<String>());
47+
48+
// Insert new content! Finally!
49+
new_content.push_str(&suggestion.replacement);
50+
51+
// Parts of line after replacement
52+
new_content.push_str(&file_content
53+
.lines()
54+
.nth(suggestion.snippet.line_range.end.line - 1)
55+
.unwrap_or("")
56+
.chars()
57+
.skip(suggestion.snippet.line_range.end.column - 1)
58+
.collect::<String>());
59+
60+
// Add the lines after the section we want to replace
61+
new_content.push_str("\n");
62+
new_content.push_str(&file_content
63+
.lines()
64+
.skip(suggestion.snippet.line_range.end.line as usize)
65+
.collect::<Vec<_>>()
66+
.join("\n"));
67+
new_content.push_str("\n");
68+
69+
Ok(new_content)
70+
}

src/tools/compiletest/src/common.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ pub fn expected_output_path(testpaths: &TestPaths,
269269
testpaths.file.with_extension(extension)
270270
}
271271

272-
pub const UI_EXTENSIONS: &[&str] = &[UI_STDERR, UI_STDOUT];
272+
pub const UI_EXTENSIONS: &[&str] = &[UI_STDERR, UI_STDOUT, UI_FIXED];
273273
pub const UI_STDERR: &str = "stderr";
274274
pub const UI_STDOUT: &str = "stdout";
275+
pub const UI_FIXED: &str = "rs.fixed";

src/tools/compiletest/src/main.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ extern crate regex;
2626
extern crate serde_derive;
2727
extern crate serde_json;
2828
extern crate test;
29+
extern crate rustfix;
2930

3031
use std::env;
3132
use std::ffi::OsString;
@@ -52,6 +53,7 @@ pub mod common;
5253
pub mod errors;
5354
mod raise_fd_limit;
5455
mod read2;
56+
mod autofix;
5557

5658
fn main() {
5759
env_logger::init();

src/tools/compiletest/src/runtest.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use common::{Config, TestPaths};
1212
use common::{CompileFail, ParseFail, Pretty, RunFail, RunPass, RunPassValgrind};
1313
use common::{Codegen, CodegenUnits, DebugInfoGdb, DebugInfoLldb, Rustdoc};
1414
use common::{Incremental, MirOpt, RunMake, Ui};
15-
use common::{expected_output_path, UI_STDERR, UI_STDOUT};
15+
use common::{expected_output_path, UI_STDERR, UI_STDOUT, UI_FIXED};
1616
use common::CompareMode;
1717
use diff;
1818
use errors::{self, Error, ErrorKind};
@@ -35,6 +35,7 @@ use std::path::{Path, PathBuf};
3535
use std::process::{Child, Command, ExitStatus, Output, Stdio};
3636
use std::str;
3737

38+
use autofix::run_rustfix;
3839
use extract_gdb_version;
3940

4041
/// The name of the environment variable that holds dynamic library locations.
@@ -2603,6 +2604,19 @@ impl<'test> TestCx<'test> {
26032604
self.check_error_patterns(&proc_res.stderr, &proc_res);
26042605
}
26052606
}
2607+
2608+
let fixture_path = expected_output_path(&self.testpaths, None, &None, UI_FIXED);
2609+
if fixture_path.exists() {
2610+
let unfixed_code = self.load_expected_output_from_path(&self.testpaths.file)
2611+
.unwrap();
2612+
let expected_fixed = self.load_expected_output_from_path(&fixture_path).unwrap();
2613+
let fixed_code = run_rustfix(&unfixed_code, &proc_res.stderr);
2614+
let errors = self.compare_output("rs.fixed", &fixed_code, &expected_fixed);
2615+
if errors > 0 {
2616+
panic!("rustfix produced different fixed file!");
2617+
// TODO: Add info for update-references.sh call
2618+
}
2619+
}
26062620
}
26072621

26082622
fn run_mir_opt_test(&self) {

0 commit comments

Comments
 (0)