Skip to content

Commit df492e0

Browse files
authored
transpile: simplify and fix comparing SrcLocs by include paths (#1548)
* Fixes #1126. @spernsteiner, I'm pretty sure you were right about your suggestion in #1126 (comment). Include paths are set up such that `include_path.first()` is in the main/root file we're transpiling, the root include, while `include_path.last()` is the final include, with that path containing the definition. Thus, comparing with the definition's location at the end of the include path produces a natural ordering for the `SrcLoc`s should be trivially total (as long as the include paths are well-formed; they didn't use to be) as we're comparing two equivalent things (`include_path.iter().copied().chain([loc])`.
2 parents 520d67f + e6b04c2 commit df492e0

File tree

1 file changed

+67
-55
lines changed
  • c2rust-transpile/src/c_ast

1 file changed

+67
-55
lines changed

c2rust-transpile/src/c_ast/mod.rs

Lines changed: 67 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use c2rust_ast_exporter::clang_ast::LRValue;
44
use indexmap::{IndexMap, IndexSet};
55
use itertools::Itertools;
66
use std::cell::RefCell;
7-
use std::cmp::Ordering;
7+
use std::cmp::{Ordering, Reverse};
88
use std::collections::{HashMap, HashSet};
99
use std::fmt::{self, Debug, Display};
1010
use std::ops::Index;
@@ -141,6 +141,34 @@ impl<T> Located<T> {
141141
}
142142
}
143143

144+
/// This holds a [`SrcLoc`] and its [`include_path`](TypedAstContext::include_path)
145+
/// such that it is naturally ordered.
146+
///
147+
/// The include path starts from where the item is first included,
148+
/// working its way back to the last include path,
149+
/// which contains the definition of the item.
150+
/// Thus, to compare them, we compare the include path first
151+
/// and then the definition's location.
152+
#[derive(PartialEq, Eq, Ord, Debug)]
153+
struct SrcLocInclude<'a> {
154+
loc: SrcLoc,
155+
include_path: &'a [SrcLoc],
156+
}
157+
158+
impl SrcLocInclude<'_> {
159+
fn cmp_iter<'a>(&'a self) -> impl Iterator<Item = SrcLoc> + 'a {
160+
// See docs on `Self` for why this is the right comparison.
161+
let Self { loc, include_path } = *self;
162+
include_path.iter().copied().chain([loc])
163+
}
164+
}
165+
166+
impl PartialOrd for SrcLocInclude<'_> {
167+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
168+
Some(self.cmp_iter().cmp(other.cmp_iter()))
169+
}
170+
}
171+
144172
impl TypedAstContext {
145173
// TODO: build the TypedAstContext during initialization, rather than
146174
// building an empty one and filling it later.
@@ -216,6 +244,27 @@ impl TypedAstContext {
216244
includes
217245
}
218246

247+
/// Compare a [`SrcLoc`] based on its include path.
248+
pub fn cmp_loc_include<'a>(&'a self, loc: SrcLoc) -> impl Eq + Ord + Debug + 'a {
249+
SrcLocInclude {
250+
loc,
251+
include_path: self.include_path(loc),
252+
}
253+
}
254+
255+
/// Compare a [`SrcSpan`] based on its include path.
256+
pub fn cmp_span_include<'a>(&'a self, span: &SrcSpan) -> impl Eq + Ord + Debug + 'a {
257+
self.cmp_loc_include(span.begin())
258+
}
259+
260+
/// Compare a [`Located`] based on its include path.
261+
pub fn cmp_located_include<'a, T>(
262+
&'a self,
263+
located: &Located<T>,
264+
) -> impl Eq + Ord + Debug + 'a {
265+
located.loc.map(|span| self.cmp_span_include(&span))
266+
}
267+
219268
pub fn loc_to_string(&self, loc: SrcLoc) -> String {
220269
let SrcLoc {
221270
fileid,
@@ -237,43 +286,6 @@ impl TypedAstContext {
237286
.join("\n included from ")
238287
}
239288

240-
/// Compare two [`SrcLoc`]s based on their include path.
241-
pub fn compare_src_locs(&self, a: &SrcLoc, b: &SrcLoc) -> Ordering {
242-
/// Compare without regard to `fileid`.
243-
fn cmp_pos(a: &SrcLoc, b: &SrcLoc) -> Ordering {
244-
(a.line, a.column).cmp(&(b.line, b.column))
245-
}
246-
247-
use Ordering::*;
248-
let path_a = self.include_path(*a);
249-
let path_b = self.include_path(*b);
250-
251-
// Find the first include that does not match between the two
252-
let common_len = path_a.len().min(path_b.len());
253-
let order = path_a[..common_len].cmp(&path_b[..common_len]);
254-
if order != Equal {
255-
return order;
256-
}
257-
258-
// Either all parent includes are the same, or the include paths are of different lengths
259-
// because .zip() stops when one of the iterators is empty.
260-
match path_a.len().cmp(&path_b.len()) {
261-
Less => {
262-
// a has the shorter path, which means b was included in a's file
263-
// so extract that include and compare the position to a
264-
let b = &path_b[path_a.len()];
265-
cmp_pos(a, b)
266-
}
267-
Equal => a.cmp(b), // a and b have the same include path and are thus in the same file
268-
Greater => {
269-
// b has the shorter path, which means a was included in b's file
270-
// so extract that include and compare the position to b
271-
let a = &path_a[path_b.len()];
272-
cmp_pos(a, b)
273-
}
274-
}
275-
}
276-
277289
pub fn get_file_include_line_number(&self, file: FileId) -> Option<u64> {
278290
self.include_map[file].first().map(|loc| loc.line)
279291
}
@@ -1171,17 +1183,7 @@ impl TypedAstContext {
11711183
/// This preserves the order when we emit the converted declarations.
11721184
pub fn sort_top_decls_for_emitting(&mut self) {
11731185
let mut decls_top = mem::take(&mut self.c_decls_top);
1174-
decls_top.sort_unstable_by(|a, b| {
1175-
let a = self.index(*a);
1176-
let b = self.index(*b);
1177-
use Ordering::*;
1178-
match (&a.loc, &b.loc) {
1179-
(None, None) => Equal,
1180-
(None, _) => Less,
1181-
(_, None) => Greater,
1182-
(Some(a), Some(b)) => self.compare_src_locs(&a.begin(), &b.begin()),
1183-
}
1184-
});
1186+
decls_top.sort_unstable_by_key(|&decl| self.cmp_located_include(self.index(decl)));
11851187
self.c_decls_top = decls_top;
11861188
}
11871189

@@ -1249,9 +1251,10 @@ impl CommentContext {
12491251
// Sort in REVERSE! Last element is the first in file source
12501252
// ordering. This makes it easy to pop the next comment off.
12511253
for comments in comments_by_file.values_mut() {
1252-
comments.sort_by(|a, b| {
1253-
ast_context.compare_src_locs(&b.loc.unwrap().begin(), &a.loc.unwrap().begin())
1254-
});
1254+
for comment in comments.iter() {
1255+
comment.loc.unwrap();
1256+
}
1257+
comments.sort_by_key(|comment| Reverse(ast_context.cmp_located_include(comment)));
12551258
}
12561259

12571260
let comments_by_file = comments_by_file
@@ -1275,7 +1278,10 @@ impl CommentContext {
12751278
.unwrap()
12761279
.begin_loc()
12771280
.expect("All comments must have a source location");
1278-
if ctx.compare_src_locs(&next_comment_loc, &loc) != Ordering::Less {
1281+
1282+
let loc = ctx.cmp_loc_include(loc);
1283+
let next_comment_loc = ctx.cmp_loc_include(next_comment_loc);
1284+
if next_comment_loc >= loc {
12791285
break;
12801286
}
12811287

@@ -2723,6 +2729,8 @@ impl CTypeKind {
27232729

27242730
#[cfg(test)]
27252731
mod tests {
2732+
use std::cmp::Ordering;
2733+
27262734
use super::*;
27272735

27282736
#[track_caller]
@@ -2972,11 +2980,15 @@ c = {c}
29722980

29732981
check_transitivity(
29742982
locs,
2975-
|a, b| ctx.compare_src_locs(a, b),
2983+
|&a, &b| -> Ordering {
2984+
let a = ctx.cmp_loc_include(a);
2985+
let b = ctx.cmp_loc_include(b);
2986+
a.cmp(&b)
2987+
},
29762988
|loc| format!("{loc}"),
29772989
);
29782990

29792991
// This should not panic.
2980-
locs.sort_unstable_by(|a, b| ctx.compare_src_locs(a, b));
2992+
locs.sort_unstable_by_key(|&loc| ctx.cmp_loc_include(loc));
29812993
}
29822994
}

0 commit comments

Comments
 (0)