Skip to content

Commit 1c11f4b

Browse files
authored
Merge pull request #1187 from google/recursive-struct-fields
Fix problem with field circular dependencies
2 parents 034e104 + f68e08d commit 1c11f4b

File tree

6 files changed

+100
-13
lines changed

6 files changed

+100
-13
lines changed

engine/src/conversion/analysis/abstract_types.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ pub(crate) fn mark_types_abstract(mut apis: ApiVec<FnPrePhase2>) -> ApiVec<FnPre
6363
kind: TypeKind::Pod | TypeKind::NonPod,
6464
castable_bases,
6565
field_deps,
66+
field_definition_deps,
6667
field_info,
6768
is_generic,
6869
in_anonymous_namespace,
@@ -84,6 +85,7 @@ pub(crate) fn mark_types_abstract(mut apis: ApiVec<FnPrePhase2>) -> ApiVec<FnPre
8485
kind: TypeKind::Abstract,
8586
castable_bases,
8687
field_deps,
88+
field_definition_deps,
8789
field_info,
8890
is_generic,
8991
in_anonymous_namespace,

engine/src/conversion/analysis/depth_first.rs

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,29 +14,42 @@ use itertools::Itertools;
1414

1515
use crate::types::QualifiedName;
1616

17-
use super::deps::HasDependencies;
17+
/// A little like `HasDependencies` but accounts for only direct fiele
18+
/// and bases.
19+
pub(crate) trait HasFieldsAndBases {
20+
fn name(&self) -> &QualifiedName;
21+
/// Return field and base class dependencies of this item.
22+
/// This should only include those items where a definition is required,
23+
/// not merely a declaration. So if the field type is
24+
/// `std::unique_ptr<A>`, this should only return `std::unique_ptr`.
25+
fn field_and_base_deps(&self) -> Box<dyn Iterator<Item = &QualifiedName> + '_>;
26+
}
1827

19-
/// Return APIs in a depth-first order, i.e. those with no dependencies first.
20-
pub(super) fn depth_first<'a, T: HasDependencies + Debug + 'a>(
28+
/// Iterate through APIs in an order such that later APIs have no fields or bases
29+
/// other than those whose types have already been processed.
30+
pub(super) fn fields_and_bases_first<'a, T: HasFieldsAndBases + Debug + 'a>(
2131
inputs: impl Iterator<Item = &'a T> + 'a,
2232
) -> impl Iterator<Item = &'a T> {
2333
let queue: VecDeque<_> = inputs.collect();
2434
let yet_to_do = queue.iter().map(|api| api.name()).collect();
2535
DepthFirstIter { queue, yet_to_do }
2636
}
2737

28-
struct DepthFirstIter<'a, T: HasDependencies + Debug> {
38+
struct DepthFirstIter<'a, T: HasFieldsAndBases + Debug> {
2939
queue: VecDeque<&'a T>,
3040
yet_to_do: HashSet<&'a QualifiedName>,
3141
}
3242

33-
impl<'a, T: HasDependencies + Debug> Iterator for DepthFirstIter<'a, T> {
43+
impl<'a, T: HasFieldsAndBases + Debug> Iterator for DepthFirstIter<'a, T> {
3444
type Item = &'a T;
3545

3646
fn next(&mut self) -> Option<Self::Item> {
3747
let first_candidate = self.queue.get(0).map(|api| api.name());
3848
while let Some(candidate) = self.queue.pop_front() {
39-
if !candidate.deps().any(|d| self.yet_to_do.contains(&d)) {
49+
if !candidate
50+
.field_and_base_deps()
51+
.any(|d| self.yet_to_do.contains(&d))
52+
{
4053
self.yet_to_do.remove(candidate.name());
4154
return Some(candidate);
4255
}
@@ -46,7 +59,11 @@ impl<'a, T: HasDependencies + Debug> Iterator for DepthFirstIter<'a, T> {
4659
"Failed to find a candidate; there must be a circular dependency. Queue is {}",
4760
self.queue
4861
.iter()
49-
.map(|item| format!("{}: {}", item.name(), item.deps().join(",")))
62+
.map(|item| format!(
63+
"{}: {}",
64+
item.name(),
65+
item.field_and_base_deps().join(",")
66+
))
5067
.join("\n")
5168
);
5269
}
@@ -59,17 +76,17 @@ impl<'a, T: HasDependencies + Debug> Iterator for DepthFirstIter<'a, T> {
5976
mod test {
6077
use crate::types::QualifiedName;
6178

62-
use super::{depth_first, HasDependencies};
79+
use super::{fields_and_bases_first, HasFieldsAndBases};
6380

6481
#[derive(Debug)]
6582
struct Thing(QualifiedName, Vec<QualifiedName>);
6683

67-
impl HasDependencies for Thing {
84+
impl HasFieldsAndBases for Thing {
6885
fn name(&self) -> &QualifiedName {
6986
&self.0
7087
}
7188

72-
fn deps(&self) -> Box<dyn Iterator<Item = &QualifiedName> + '_> {
89+
fn field_and_base_deps(&self) -> Box<dyn Iterator<Item = &QualifiedName> + '_> {
7390
Box::new(self.1.iter())
7491
}
7592
}
@@ -89,7 +106,7 @@ mod test {
89106
vec![QualifiedName::new_from_cpp_name("a")],
90107
);
91108
let api_list = vec![a, b, c];
92-
let mut it = depth_first(api_list.iter());
109+
let mut it = fields_and_bases_first(api_list.iter());
93110
assert_eq!(it.next().unwrap().0, QualifiedName::new_from_cpp_name("a"));
94111
assert_eq!(it.next().unwrap().0, QualifiedName::new_from_cpp_name("c"));
95112
assert_eq!(it.next().unwrap().0, QualifiedName::new_from_cpp_name("b"));

engine/src/conversion/analysis/fun/implicit_constructors.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ use syn::{Type, TypeArray};
1414
use crate::conversion::api::DeletedOrDefaulted;
1515
use crate::{
1616
conversion::{
17-
analysis::{depth_first::depth_first, pod::PodAnalysis, type_converter::TypeKind},
17+
analysis::{
18+
depth_first::fields_and_bases_first, pod::PodAnalysis, type_converter::TypeKind,
19+
},
1820
api::{Api, ApiName, CppVisibility, FuncToConvert, SpecialMemberKind},
1921
apivec::ApiVec,
2022
convert_error::ConvertErrorWithContext,
@@ -197,7 +199,7 @@ pub(super) fn find_constructors_present(
197199
// These analyses include all bases and members of each class.
198200
let mut all_items_found: HashMap<QualifiedName, ItemsFound> = HashMap::new();
199201

200-
for api in depth_first(apis.iter()) {
202+
for api in fields_and_bases_first(apis.iter()) {
201203
if let Api::Struct {
202204
name,
203205
analysis:

engine/src/conversion/analysis/fun/mod.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ use self::{
6464
};
6565

6666
use super::{
67+
depth_first::HasFieldsAndBases,
6768
doc_label::make_doc_attrs,
6869
pod::{PodAnalysis, PodPhase},
6970
tdef::TypedefAnalysis,
@@ -2226,3 +2227,24 @@ fn extract_type_from_pinned_mut_ref(ty: &TypePath) -> Type {
22262227
_ => panic!("did not find angle bracketed args"),
22272228
}
22282229
}
2230+
2231+
impl HasFieldsAndBases for Api<FnPrePhase1> {
2232+
fn name(&self) -> &QualifiedName {
2233+
self.name()
2234+
}
2235+
2236+
fn field_and_base_deps(&self) -> Box<dyn Iterator<Item = &QualifiedName> + '_> {
2237+
match self {
2238+
Api::Struct {
2239+
analysis:
2240+
PodAnalysis {
2241+
field_definition_deps,
2242+
bases,
2243+
..
2244+
},
2245+
..
2246+
} => Box::new(field_definition_deps.iter().chain(bases.iter())),
2247+
_ => Box::new(std::iter::empty()),
2248+
}
2249+
}
2250+
}

engine/src/conversion/analysis/pod/mod.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,12 @@ pub(crate) struct PodAnalysis {
4343
/// because otherwise we don't know whether they're
4444
/// abstract or not.
4545
pub(crate) castable_bases: HashSet<QualifiedName>,
46+
/// All field types. e.g. for std::unique_ptr<A>, this would include
47+
/// both std::unique_ptr and A
4648
pub(crate) field_deps: HashSet<QualifiedName>,
49+
/// Types within fields where we need a definition, e.g. for
50+
/// std::unique_ptr<A> it would just be std::unique_ptr.
51+
pub(crate) field_definition_deps: HashSet<QualifiedName>,
4752
pub(crate) field_info: Vec<FieldInfo>,
4853
pub(crate) is_generic: bool,
4954
pub(crate) in_anonymous_namespace: bool,
@@ -138,12 +143,14 @@ fn analyze_struct(
138143
metadata.check_for_fatal_attrs(&id)?;
139144
let bases = get_bases(&details.item);
140145
let mut field_deps = HashSet::new();
146+
let mut field_definition_deps = HashSet::new();
141147
let mut field_info = Vec::new();
142148
let field_conversion_errors = get_struct_field_types(
143149
type_converter,
144150
name.name.get_namespace(),
145151
&details.item,
146152
&mut field_deps,
153+
&mut field_definition_deps,
147154
&mut field_info,
148155
extra_apis,
149156
);
@@ -186,6 +193,7 @@ fn analyze_struct(
186193
bases: bases.into_keys().collect(),
187194
castable_bases,
188195
field_deps,
196+
field_definition_deps,
189197
field_info,
190198
is_generic,
191199
in_anonymous_namespace,
@@ -198,6 +206,7 @@ fn get_struct_field_types(
198206
ns: &Namespace,
199207
s: &ItemStruct,
200208
field_deps: &mut HashSet<QualifiedName>,
209+
field_definition_deps: &mut HashSet<QualifiedName>,
201210
field_info: &mut Vec<FieldInfo>,
202211
extra_apis: &mut ApiVec<NullPhase>,
203212
) -> Vec<ConvertErrorFromCpp> {
@@ -225,6 +234,14 @@ fn get_struct_field_types(
225234
.unwrap_or(false)
226235
{
227236
field_deps.extend(r.types_encountered);
237+
if let Type::Path(typ) = &r.ty {
238+
// Later analyses need to know about the field
239+
// types where we need full definitions, as opposed
240+
// to just declarations. That means just the outermost
241+
// type path.
242+
// TODO: consider arrays.
243+
field_definition_deps.insert(QualifiedName::from_type_path(typ));
244+
}
228245
field_info.push(FieldInfo {
229246
ty: r.ty,
230247
type_kind: r.kind,

integration-tests/tests/integration_test.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11309,6 +11309,33 @@ fn test_enum_in_ns() {
1130911309
run_test("", hdr, quote! {}, &["a::b"], &[]);
1131011310
}
1131111311

11312+
#[test]
11313+
fn test_recursive_field() {
11314+
let hdr = indoc! {"
11315+
#include <memory>
11316+
struct A {
11317+
std::unique_ptr<A> a;
11318+
};
11319+
"};
11320+
run_test("", hdr, quote! {}, &["A"], &[]);
11321+
}
11322+
11323+
#[test]
11324+
fn test_recursive_field_indirect() {
11325+
let hdr = indoc! {"
11326+
#include <memory>
11327+
struct B;
11328+
struct A {
11329+
std::unique_ptr<B> a;
11330+
};
11331+
struct B {
11332+
std::unique_ptr<A> a1;
11333+
A a2;
11334+
};
11335+
"};
11336+
run_test("", hdr, quote! {}, &["A", "B"], &[]);
11337+
}
11338+
1131211339
#[test]
1131311340
fn test_typedef_unsupported_type_pub() {
1131411341
let hdr = indoc! {"

0 commit comments

Comments
 (0)