Skip to content

Commit d635117

Browse files
authored
C++: sort types topologically before emitting definitions (#1619)
* C++: sort types topologically before emitting definitions Specifically, we sort the types while taking the parameter and return types of resource functions into consideration. This ensures that types are declared (and, if necessary, defined) before they are used in the emitted source code. Fixes #1615 * remove redundant resource type forward declarations * use `wit_parser::TypeIdVisitor` to simplify `sort_types`
1 parent 557ca94 commit d635117

4 files changed

Lines changed: 128 additions & 16 deletions

File tree

‎Cargo.lock‎

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎crates/cpp/Cargo.toml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ wit-bindgen-c = { workspace = true }
2626
anyhow = { workspace = true }
2727
heck = { workspace = true }
2828
clap = { workspace = true, optional = true }
29+
indexmap = { workspace = true }
30+
wit-parser = { workspace = true }
2931

3032
[dev-dependencies]
3133
test-helpers = { path = '../test-helpers' }

‎crates/cpp/src/lib.rs‎

Lines changed: 84 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use anyhow::bail;
22
use heck::{ToPascalCase, ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase};
3+
use indexmap::{IndexMap, IndexSet};
34
use std::{
45
collections::{HashMap, HashSet},
56
fmt::{self, Display, Write as FmtWrite},
@@ -20,6 +21,7 @@ use wit_bindgen_core::{
2021
WorldKey,
2122
},
2223
};
24+
use wit_parser::TypeIdVisitor;
2325

2426
// mod wamr;
2527
mod symbol_name;
@@ -878,22 +880,12 @@ impl CppInterfaceGenerator<'_> {
878880
fn types(&mut self, iface: InterfaceId) {
879881
let iface_data = &self.resolve().interfaces[iface];
880882

881-
// First pass: emit forward declarations for all resources
882-
// This ensures resources can reference each other in method signatures
883-
for (name, id) in iface_data.types.iter() {
884-
let ty = &self.resolve().types[*id];
885-
if matches!(&ty.kind, TypeDefKind::Resource) {
886-
let pascal = name.to_upper_camel_case();
887-
let guest_import = self.r#gen.imported_interfaces.contains(&iface);
888-
let namespc = namespace(self.resolve, &ty.owner, !guest_import, &self.r#gen.opts);
889-
self.r#gen.h_src.change_namespace(&namespc);
890-
uwriteln!(self.r#gen.h_src.src, "class {pascal};");
891-
}
892-
}
893-
894-
// Second pass: emit full type definitions
895-
for (name, id) in iface_data.types.iter() {
896-
self.define_type(name, *id);
883+
// Here we sort the types topologically before emitting code for them,
884+
// taking into consideration the parameter and return types of functions
885+
// associated with resource types. This ensures each type is declared
886+
// before any uses of it.
887+
for (name, id) in sort_types(self.resolve, &iface_data.types) {
888+
self.define_type(name, id);
897889
}
898890
}
899891

@@ -3634,3 +3626,79 @@ fn is_special_method(func: &Function) -> SpecialMethod {
36343626
SpecialMethod::None
36353627
}
36363628
}
3629+
3630+
/// Sort the specified types topologically, taking the parameter and result
3631+
/// types of resource functions into consideration.
3632+
fn sort_types<'a>(
3633+
resolve: &Resolve,
3634+
types: &'a IndexMap<String, TypeId>,
3635+
) -> IndexMap<&'a str, TypeId> {
3636+
struct Visitor<'a> {
3637+
resolve: &'a Resolve,
3638+
sorted: IndexSet<TypeId>,
3639+
visited: HashSet<TypeId>,
3640+
}
3641+
3642+
impl TypeIdVisitor for Visitor<'_> {
3643+
fn before_visit_type_id(&mut self, id: TypeId) -> bool {
3644+
let ty = &self.resolve.types[id];
3645+
if let TypeDefKind::Resource = &ty.kind {
3646+
// Here we avoid infinite recursion by remembering if we've seen
3647+
// this type already.
3648+
if self.visited.contains(&id) {
3649+
false
3650+
} else {
3651+
self.visited.insert(id);
3652+
3653+
// TypeIdVisitor does not consider the parameter and return
3654+
// types of resource associated functions to be transitive
3655+
// types, so we need to handle that ourselves here.
3656+
if let TypeOwner::Interface(interface) = ty.owner {
3657+
for function in self.resolve.interfaces[interface].functions.values() {
3658+
if let Some(resource) = function.kind.resource()
3659+
&& resource == id
3660+
{
3661+
for parameter in &function.params {
3662+
self.visit_type(self.resolve, &parameter.ty);
3663+
}
3664+
3665+
if let Some(ty) = function.result {
3666+
self.visit_type(self.resolve, &ty);
3667+
}
3668+
}
3669+
}
3670+
}
3671+
3672+
true
3673+
}
3674+
} else {
3675+
true
3676+
}
3677+
}
3678+
3679+
fn after_visit_type_id(&mut self, id: TypeId) {
3680+
self.sorted.insert(id);
3681+
}
3682+
}
3683+
3684+
let mut visitor = Visitor {
3685+
resolve,
3686+
sorted: Default::default(),
3687+
visited: Default::default(),
3688+
};
3689+
3690+
for &id in types.values() {
3691+
visitor.visit_type_id(resolve, id);
3692+
}
3693+
3694+
let names = types
3695+
.iter()
3696+
.map(|(k, v)| (*v, k.as_str()))
3697+
.collect::<HashMap<_, _>>();
3698+
3699+
visitor
3700+
.sorted
3701+
.into_iter()
3702+
.filter_map(|v| names.get(&v).map(|k| (*k, v)))
3703+
.collect()
3704+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package test:test;
2+
3+
interface sqlite {
4+
resource connection {
5+
open: static func(database: string) -> result<connection, error>;
6+
execute: func(statement: string, parameters: list<value>) -> result<query-result, error>;
7+
last-insert-rowid: func() -> s64;
8+
changes: func() -> u64;
9+
}
10+
11+
variant error {
12+
no-such-database,
13+
access-denied,
14+
invalid-connection,
15+
database-full,
16+
io(string)
17+
}
18+
19+
record query-result {
20+
columns: list<string>,
21+
rows: list<row-result>,
22+
}
23+
24+
record row-result {
25+
values: list<value>
26+
}
27+
28+
variant value {
29+
integer(s64),
30+
real(f64),
31+
text(string),
32+
blob(list<u8>),
33+
null
34+
}
35+
}
36+
37+
world test {
38+
import sqlite;
39+
export foo: func();
40+
}

0 commit comments

Comments
 (0)