Skip to content

Commit 83f43a0

Browse files
committed
Handle all cases in cmp_by_source_order.
1 parent d5e8204 commit 83f43a0

File tree

2 files changed

+157
-27
lines changed

2 files changed

+157
-27
lines changed

bindgen/clang.rs

+138-25
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
#![allow(non_upper_case_globals, dead_code)]
55
#![deny(clippy::missing_docs_in_private_items)]
66

7-
use crate::ir::context::BindgenContext;
7+
use crate::ir::context::{BindgenContext, IncludeLocation};
88
use clang_sys::*;
99
use std::cmp;
1010

@@ -552,32 +552,145 @@ impl Cursor {
552552
return offset.cmp(&other_offset);
553553
}
554554

555-
// `None` here means `file`/`other_file` is the main header file.
556-
let include_location = ctx.included_file_location(&file);
557-
let other_include_location = ctx.included_file_location(&other_file);
555+
let mut include_location = ctx.included_file_location(&file);
556+
let mut other_include_location =
557+
ctx.included_file_location(&other_file);
558558

559-
match (include_location, other_include_location) {
560-
// The main header file (`None`) comes after header passed as CLI argument (`Some((None, _))`).
561-
(None, Some((None, _))) => cmp::Ordering::Greater,
562-
(Some((None, _)), None) => cmp::Ordering::Less,
563-
// If an item was included in the same source file as the other item,
564-
// compare its `#include` location offset the offset of the other item.
565-
(Some((Some(file2), offset2)), _) if file2 == other_file => {
566-
offset2.cmp(&other_offset)
567-
}
568-
(_, Some((Some(other_file2), other_offset2)))
569-
if file == other_file2 =>
570-
{
571-
offset.cmp(&other_offset2)
572-
}
573-
// If both items were included in the same file, compare the offset of their `#include` directives.
574-
(Some((file2, offset2)), Some((other_file2, other_offset2)))
575-
if file2 == other_file2 =>
576-
{
577-
offset2.cmp(&other_offset2)
559+
use IncludeLocation::*;
560+
561+
loop {
562+
match (&include_location, &other_include_location) {
563+
// Both items are in the main header file, this should already have been handled at this point.
564+
(Main, Main) => {
565+
unreachable!("Should have been handled at this point.")
566+
}
567+
// Headers passed as CLI arguments come before the main header file.
568+
(Main, Cli { .. }) => return cmp::Ordering::Greater,
569+
(Cli { .. }, Main) => return cmp::Ordering::Less,
570+
// If both were included via CLI arguments, compare their offset.
571+
(
572+
Cli { offset: offset2 },
573+
Cli {
574+
offset: other_offset2,
575+
},
576+
) => return offset2.cmp(&other_offset2),
577+
// If an item was included in the same source file as the other item,
578+
// compare its `#include` location offset the offset of the other item.
579+
(
580+
File {
581+
file_name: ref file2,
582+
offset: offset2,
583+
},
584+
Main,
585+
) => {
586+
if *file2 == other_file {
587+
return offset2.cmp(&other_offset);
588+
}
589+
590+
// Continue checking one level up.
591+
include_location = ctx.included_file_location(&file2);
592+
}
593+
(
594+
Main,
595+
File {
596+
file_name: ref other_file2,
597+
offset: other_offset2,
598+
},
599+
) => {
600+
if file == *other_file2 {
601+
return offset.cmp(&other_offset2);
602+
}
603+
604+
// Continue checking one level up.
605+
other_include_location =
606+
ctx.included_file_location(&other_file2);
607+
}
608+
(
609+
File {
610+
file_name: file2,
611+
offset: offset2,
612+
},
613+
File {
614+
file_name: other_file2,
615+
offset: other_offset2,
616+
},
617+
) => {
618+
// If both items were included in the same file, compare the offset of their `#include` directives.
619+
if file2 == other_file2 {
620+
return offset2.cmp(&other_offset2);
621+
}
622+
623+
// Find the offset of where `file` is transivitely included in `ancestor_file`.
624+
let offset_in_ancestor =
625+
|mut file: String, ancestor_file: &str| {
626+
while file != ancestor_file {
627+
let mut include_location =
628+
ctx.included_file_location(&file);
629+
file = if let IncludeLocation::File {
630+
file_name: file,
631+
offset,
632+
} = include_location
633+
{
634+
if file == ancestor_file {
635+
return Some(offset);
636+
}
637+
638+
file
639+
} else {
640+
break;
641+
}
642+
}
643+
644+
None
645+
};
646+
647+
if let Some(offset2) =
648+
offset_in_ancestor(file2.clone(), &other_file2)
649+
{
650+
return offset2.cmp(&other_offset2);
651+
}
652+
653+
if let Some(other_offset2) =
654+
offset_in_ancestor(other_file2.clone(), &file2)
655+
{
656+
return offset2.cmp(&other_offset2);
657+
}
658+
659+
// Otherwise, go one level up.
660+
//
661+
// # Example
662+
//
663+
// a.h
664+
// ├── b.h
665+
// └── c.h
666+
//
667+
// When comparing items inside `b.h` and `c.h`, go up one level and
668+
// compare the include locations of `b.h` and `c.h` in `a.h` instead.
669+
include_location = ctx.included_file_location(file2);
670+
other_include_location =
671+
ctx.included_file_location(other_file2);
672+
}
673+
(
674+
File {
675+
file_name: file2, ..
676+
},
677+
Cli { .. },
678+
) => {
679+
// Continue checking one level up.
680+
include_location = ctx.included_file_location(&file2);
681+
}
682+
(
683+
Cli { .. },
684+
File {
685+
file_name: other_file2,
686+
..
687+
},
688+
) => {
689+
// Continue checking one level up.
690+
other_include_location =
691+
ctx.included_file_location(&other_file2);
692+
}
578693
}
579-
// Otherwise, keep the original sorting.
580-
_ => cmp::Ordering::Equal,
581694
}
582695
}
583696

bindgen/ir/context.rs

+19-2
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,14 @@ enum TypeKey {
305305
Declaration(Cursor),
306306
}
307307

308+
/// Specifies where a header was included from.
309+
#[derive(Debug)]
310+
pub(crate) enum IncludeLocation {
311+
Main,
312+
Cli { offset: usize },
313+
File { file_name: String, offset: usize },
314+
}
315+
308316
/// A context used during parsing and generation of structs.
309317
#[derive(Debug)]
310318
pub(crate) struct BindgenContext {
@@ -659,8 +667,17 @@ If you encounter an error missing from this list, please file an issue or a PR!"
659667
pub(crate) fn included_file_location(
660668
&self,
661669
included_file: &str,
662-
) -> Option<(Option<String>, usize)> {
663-
self.includes.get(included_file).cloned()
670+
) -> IncludeLocation {
671+
match self.includes.get(included_file).cloned() {
672+
// Header was not included anywhere, so it must be the main header.
673+
None => IncludeLocation::Main,
674+
// Header has no source location, so it must have been included via CLI arguments.
675+
Some((None, offset)) => IncludeLocation::Cli { offset },
676+
// Header was included with an `#include` directive.
677+
Some((Some(file_name), offset)) => {
678+
IncludeLocation::File { file_name, offset }
679+
}
680+
}
664681
}
665682

666683
/// Add an included file.

0 commit comments

Comments
 (0)