Skip to content

Commit

Permalink
auto_fix_errors.py: remove Copy from types that don't support it
Browse files Browse the repository at this point in the history
  • Loading branch information
spernsteiner committed Dec 3, 2024
1 parent 39b22cf commit 2a24483
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 2 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,6 @@ marks.*.json
# Outputs of `c2rust-analyze`
inspect/
*.analysis.txt
*.rs.fixed
analysis.txt
polonius_cache/
1 change: 1 addition & 0 deletions c2rust-analyze/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/inspect/
*.rlib
/tests/auto_fix_errors/*.json
63 changes: 61 additions & 2 deletions c2rust-analyze/scripts/auto_fix_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,22 @@ class LifetimeBound:
# `bound_lifetime` is the string `"'b"`.
bound_lifetime: str

@dataclass(frozen=True)
class RemoveDeriveCopy:
file_path: str
line_number: int
# Byte offset of the start of the token `Copy`.
start_byte: int
# Byte offset of the end of the token `Copy`.
end_byte: int

MSG_LIFETIME_BOUND = 'lifetime may not live long enough'
MSG_DERIVE_COPY = 'the trait `Copy` may not be implemented for this type'

LIFETIME_DEFINED_RE = re.compile(r'^lifetime `([^`]*)` defined here$')
CONSIDER_ADDING_BOUND_RE = re.compile(r'^consider adding the following bound: `([^`:]*): ([^`]*)`$')
SPACE_COLON_RE = re.compile(rb'\s*:')
SPACE_COMMA_RE = re.compile(rb'\s*,')

def main():
args = parse_args()
Expand Down Expand Up @@ -104,6 +117,25 @@ def gather_lifetime_bounds(j):
bound_lifetime=lifetime_b,
))

remove_derive_copies = []
def handle_derive_copy(j):
# Find the "primary span", which covers the `Copy` token.
primary_span = None
for span in j['spans']:
if span.get('is_primary'):
primary_span = span
break

if primary_span is None:
return

remove_derive_copies.append(RemoveDeriveCopy(
file_path=primary_span['file_name'],
line_number=primary_span['line_start'],
start_byte=primary_span['byte_start'],
end_byte=primary_span['byte_end'],
))

with open(args.path, 'r') as f:
for line in f:
j = json.loads(line)
Expand All @@ -120,8 +152,10 @@ def gather_lifetime_bounds(j):

gather_fixes(j, j['message'])

if j['message'] == 'lifetime may not live long enough':
if j['message'] == MSG_LIFETIME_BOUND:
gather_lifetime_bounds(j)
elif j['message'] == MSG_DERIVE_COPY:
handle_derive_copy(j)

# Convert suggested lifetime bounds to fixes. We have to group the bounds
# first because there may be multiple suggested bounds for a single
Expand Down Expand Up @@ -162,9 +196,34 @@ def read_file(file_path):
start_byte=start_byte,
end_byte=fix_end_byte,
new_text=fix_new_text,
message='lifetime may not live long enough',
message=MSG_LIFETIME_BOUND,
))

# Convert errors about `#[derive(Copy)]` to fixes.
for rm in remove_derive_copies:
# The error span points to the `Copy` token, but we actually want to
# remove both `Copy` and the following comma, if one is present.
#
# #[derive(Foo, Copy, Bar)] -> #[derive(Foo, Bar)]
# #[derive(Foo, Copy)] -> #[derive(Foo,)]
# #[derive(Copy, Bar)] -> #[derive(Bar)]
# #[derive(Copy)] -> #[derive()]
#
# Note that `#[derive()]` is legal, though ideally it should be removed
# by a later cleanup pass.
content = read_file(rm.file_path)
m = SPACE_COMMA_RE.match(content, rm.end_byte)
fix_end_byte = m.end() if m is not None else rm.end_byte
fixes.append(Fix(
file_path=rm.file_path,
line_number=rm.line_number,
start_byte=rm.start_byte,
end_byte=fix_end_byte,
new_text='',
message=MSG_DERIVE_COPY,
))

# Group fixes by file
fixes_by_file = {}
for fix in fixes:
file_fixes = fixes_by_file.get(fix.file_path)
Expand Down
82 changes: 82 additions & 0 deletions c2rust-analyze/tests/auto_fix_errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
pub mod common;

use crate::common::{check_for_missing_tests_for, test_dir_for};
use std::fs::{self, File};
use std::process::Command;

#[test]
fn check_for_missing_tests() {
check_for_missing_tests_for(file!());
}

fn test(file_name: &str) {
let test_dir = test_dir_for(file!(), true);
let path = test_dir.join(file_name);
let fixed_path = path.with_extension("rs.fixed");
let json_path = path.with_extension("json");
let script_path = test_dir.join("../../scripts/auto_fix_errors.py");

fs::copy(&path, &fixed_path).unwrap();

// Run with `--error-format json` to produce JSON input for the script.
let mut cmd = Command::new("rustc");
cmd.arg("-A")
.arg("warnings")
.arg("--crate-name")
.arg(path.file_stem().unwrap())
.arg("--crate-type")
.arg("rlib")
.arg("--error-format")
.arg("json")
.arg(&fixed_path)
.stderr(File::create(&json_path).unwrap());
let status = cmd.status().unwrap();
assert_eq!(
status.code(),
Some(1),
"command {cmd:?} exited with code {status:?}"
);

// Run the script to fix errors.
let mut cmd = Command::new("python3");
cmd.arg(&script_path).arg(&json_path);
let status = cmd.status().unwrap();
assert!(
status.success(),
"command {cmd:?} exited with code {status:?}"
);

// There should be no more compile errors now.
let mut cmd = Command::new("rustc");
cmd.arg("-A")
.arg("warnings")
.arg("--crate-name")
.arg(path.file_stem().unwrap())
.arg("--crate-type")
.arg("rlib")
.arg(&fixed_path);
let status = cmd.status().unwrap();
assert!(
status.success(),
"command {cmd:?} exited with code {status:?}"
);
}

macro_rules! define_test {
($name:ident) => {
#[test]
fn $name() {
test(concat!(stringify!($name), ".rs"));
}
};
}

macro_rules! define_tests {
($($name:ident,)*) => {
$(define_test! { $name })*
}
}

define_tests! {
derive_copy,
}
49 changes: 49 additions & 0 deletions c2rust-analyze/tests/auto_fix_errors/derive_copy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#[derive(Clone, Copy)]
struct S1 {
x: Box<i32>,
y: &'static i32,
}

#[derive(Clone, Copy,)]
struct S2 {
x: Box<i32>,
y: &'static i32,
}


#[derive(Copy, Clone)]
struct S3 {
x: Box<i32>,
y: &'static i32,
}


#[derive(Copy, Clone,)]
struct S4 {
x: Box<i32>,
y: &'static i32,
}


#[derive(Copy)]
struct S5 {
x: Box<i32>,
y: &'static i32,
}

#[derive(Copy,)]
struct S6 {
x: Box<i32>,
y: &'static i32,
}


#[derive(
Copy
,
Clone
)]
struct S7 {
x: Box<i32>,
y: &'static i32,
}

0 comments on commit 2a24483

Please sign in to comment.