use std::collections::HashSet;
use llmcc_core::{
build_llmcc_graph_with_config,
graph_builder::{BlockKind, GraphNode, ProjectGraph},
GraphBuildConfig,
};
use llmcc_rust::{bind_symbols, build_llmcc_ir, collect_symbols, CompileCtxt, LangRust};
fn build_graph_with_config(sources: &[&str], config: GraphBuildConfig) -> ProjectGraph<'static> {
let source_bytes: Vec<Vec<u8>> = sources.iter().map(|s| s.as_bytes().to_vec()).collect();
let cc = Box::leak(Box::new(CompileCtxt::from_sources::<LangRust>(
&source_bytes,
)));
let globals = cc.create_globals();
let unit_count = sources.len();
let mut collections = Vec::new();
let mut graph = ProjectGraph::new(cc);
build_llmcc_ir::<LangRust>(cc).unwrap();
for unit_idx in 0..unit_count {
let unit = graph.cc.compile_unit(unit_idx);
collections.push(collect_symbols(unit, globals));
}
for unit_idx in 0..unit_count {
let unit = graph.cc.compile_unit(unit_idx);
bind_symbols(unit, globals);
let unit_graph = build_llmcc_graph_with_config::<LangRust>(unit, unit_idx, config).unwrap();
graph.add_child(unit_graph);
}
graph.link_units();
drop(collections);
graph
}
fn build_graph(sources: &[&str]) -> ProjectGraph<'static> {
build_graph_with_config(sources, GraphBuildConfig::default())
}
fn block_name(graph: &ProjectGraph<'static>, node: GraphNode) -> Option<String> {
graph
.block_info(node.block_id)
.and_then(|(_, name, _)| name)
}
fn get_depends_on(graph: &ProjectGraph<'static>, block: GraphNode) -> HashSet<String> {
graph
.get_block_depends(block)
.into_iter()
.filter_map(|node| block_name(graph, node))
.collect()
}
fn get_depended_by(graph: &ProjectGraph<'static>, block: GraphNode) -> HashSet<String> {
graph
.get_block_depended(block)
.into_iter()
.filter_map(|node| block_name(graph, node))
.collect()
}
fn get_all_related_names(graph: &ProjectGraph<'static>, block: GraphNode) -> HashSet<String> {
graph
.find_dpends_blocks_recursive(block)
.into_iter()
.filter_map(|node| block_name(graph, node))
.collect()
}
fn assert_depends_on(graph: &ProjectGraph<'static>, block: GraphNode, expected: &str) {
let deps = get_depends_on(graph, block);
assert!(
deps.contains(expected),
"Expected '{}' to depend on '{}', but found: {:?}",
block_name(graph, block).unwrap_or_default(),
expected,
deps
);
}
fn assert_depended_by(graph: &ProjectGraph<'static>, block: GraphNode, expected: &str) {
let depended = get_depended_by(graph, block);
assert!(
depended.contains(expected),
"Expected '{}' to be depended on by '{}', but found: {:?}",
block_name(graph, block).unwrap_or_default(),
expected,
depended
);
}
fn assert_depends_on_all(graph: &ProjectGraph<'static>, block: GraphNode, expected: &[&str]) {
let deps = get_depends_on(graph, block);
for exp in expected {
assert!(
deps.contains(*exp),
"Expected '{}' to depend on '{}', but found: {:?}",
block_name(graph, block).unwrap_or_default(),
exp,
deps
);
}
}
fn assert_depended_by_all(graph: &ProjectGraph<'static>, block: GraphNode, expected: &[&str]) {
let depended = get_depended_by(graph, block);
for exp in expected {
assert!(
depended.contains(*exp),
"Expected '{}' to be depended on by '{}', but found: {:?}",
block_name(graph, block).unwrap_or_default(),
exp,
depended
);
}
}
fn get_block_in_unit(graph: &ProjectGraph<'static>, unit_idx: usize, name: &str) -> GraphNode {
graph
.block_by_name_in(unit_idx, name)
.expect(&format!("{} in unit {}", name, unit_idx))
}
fn get_block(graph: &ProjectGraph<'static>, name: &str) -> GraphNode {
graph.block_by_name(name).expect(&format!("block {}", name))
}
#[test]
fn collects_function_blocks_in_unit() {
let graph = build_graph(&[r#"
fn helper() {}
fn caller() {
helper();
}
"#]);
let helper = get_block(&graph, "helper");
let caller = get_block(&graph, "caller");
assert_eq!(helper.unit_index, 0);
assert_eq!(caller.unit_index, 0);
let helper_info = graph.block_info(helper.block_id).unwrap();
assert_eq!(helper_info.2, BlockKind::Func);
let caller_info = graph.block_info(caller.block_id).unwrap();
assert_eq!(caller_info.2, BlockKind::Func);
let unit_blocks: HashSet<_> = graph
.blocks_in(0)
.into_iter()
.filter_map(|node| block_name(&graph, node))
.collect();
assert_eq!(unit_blocks.len(), 2);
assert!(unit_blocks.contains("helper"));
assert!(unit_blocks.contains("caller"));
let call_nodes = graph.blocks_by_kind_in(BlockKind::Call, 0);
assert_eq!(call_nodes.len(), 1);
let caller_dependencies = get_depends_on(&graph, caller);
assert_eq!(caller_dependencies, HashSet::from(["helper".to_string()]));
assert_depended_by(&graph, helper, "caller");
}
#[test]
fn finds_transitive_dependencies() {
let graph = build_graph(&[r#"
fn leaf() {}
fn middle_a() {
leaf();
}
fn middle_b() {
leaf();
}
fn top() {
middle_a();
middle_b();
}
"#]);
let top = get_block(&graph, "top");
let direct_deps = get_depends_on(&graph, top);
assert!(direct_deps.contains("middle_a"));
assert!(direct_deps.contains("middle_b"));
let leaf = get_block(&graph, "leaf");
let middle_a = get_block(&graph, "middle_a");
let middle_b = get_block(&graph, "middle_b");
let leaf_depended = get_depended_by(&graph, leaf);
assert!(leaf_depended.contains("middle_a"));
assert!(leaf_depended.contains("middle_b"));
assert_depended_by(&graph, middle_a, "top");
assert_depended_by(&graph, middle_b, "top");
let all_names = get_all_related_names(&graph, top);
assert!(all_names.contains("leaf"));
}
#[test]
fn filters_blocks_by_kind_and_unit() {
let graph = build_graph(&[
r#"
struct Foo;
impl Foo {
fn method(&self) {}
}
fn top_level() {}
"#,
r#"
const VALUE: i32 = 42;
fn helper() {}
"#,
]);
let unit0_funcs: HashSet<_> = graph
.blocks_by_kind_in(BlockKind::Func, 0)
.into_iter()
.filter_map(|node| block_name(&graph, node))
.collect();
assert!(unit0_funcs.contains("top_level"));
assert!(unit0_funcs.contains("method"));
let unit1_consts: HashSet<_> = graph
.blocks_by_kind_in(BlockKind::Const, 1)
.into_iter()
.filter_map(|node| block_name(&graph, node))
.collect();
assert!(unit1_consts.contains("VALUE"));
let helper = get_block_in_unit(&graph, 1, "helper");
assert_eq!(helper.unit_index, 1);
let helper_info = graph.block_info(helper.block_id).unwrap();
assert_eq!(helper_info.2, BlockKind::Func);
let both_helpers = graph.blocks_by_name("helper");
assert_eq!(both_helpers.len(), 1);
}
#[test]
fn type_records_dependencies_on_methods() {
let graph = build_graph(&[r#"
struct Foo;
impl Foo {
fn method(&self) {}
}
"#]);
let method = get_block(&graph, "method");
let method_info = graph.block_info(method.block_id).unwrap();
assert_eq!(method_info.2, BlockKind::Func);
}
#[test]
fn method_depends_on_inherent_method() {
let graph = build_graph(&[r#"
struct Foo;
impl Foo {
fn helper(&self) {}
fn caller(&self) {
self.helper();
}
}
"#]);
let caller = get_block(&graph, "caller");
assert_depends_on(&graph, caller, "helper");
}
#[test]
fn function_depends_on_argument_type() {
let graph = build_graph(&[r#"
struct Foo;
fn takes(_: Foo) {}
"#]);
let takes = get_block(&graph, "takes");
let foo = get_block(&graph, "Foo");
assert_depends_on(&graph, takes, "Foo");
assert_depended_by(&graph, foo, "takes");
}
#[test]
fn function_depends_on_return_type() {
let graph = build_graph(&[r#"
struct Bar;
fn returns() -> Bar {
Bar
}
"#]);
let returns = get_block(&graph, "returns");
let bar = get_block(&graph, "Bar");
assert_depends_on(&graph, returns, "Bar");
assert_depended_by(&graph, bar, "returns");
}
#[test]
fn struct_field_creates_dependency() {
let graph = build_graph(&[r#"
struct Inner;
struct Outer {
field: Inner,
}
"#]);
let outer = get_block(&graph, "Outer");
let inner = get_block(&graph, "Inner");
assert_depends_on(&graph, outer, "Inner");
assert_depended_by(&graph, inner, "Outer");
}
#[test]
fn struct_field_depends_on_enum_type() {
let graph = build_graph(&[r#"
enum Status {
Ready,
Busy,
}
struct Holder {
status: Status,
}
"#]);
let holder = get_block(&graph, "Holder");
let status = get_block(&graph, "Status");
assert_depends_on(&graph, holder, "Status");
assert_depended_by(&graph, status, "Holder");
}
#[test]
fn enum_variant_depends_on_struct_type() {
let graph = build_graph(&[r#"
struct Payload;
enum Message {
Empty,
With(Payload),
}
"#]);
let message = get_block(&graph, "Message");
let payload = get_block(&graph, "Payload");
assert_depends_on(&graph, message, "Payload");
assert_depended_by(&graph, payload, "Message");
}
#[test]
fn nested_struct_fields_create_chain() {
let graph = build_graph(&[r#"
struct A;
struct B {
a: A,
}
struct C {
b: B,
}
"#]);
let a = get_block(&graph, "A");
let b = get_block(&graph, "B");
let c = get_block(&graph, "C");
assert_depends_on(&graph, b, "A");
assert_depended_by(&graph, a, "B");
assert_depends_on(&graph, c, "B");
assert_depended_by(&graph, b, "C");
}
#[test]
fn function_chain_dependencies() {
let graph = build_graph(&[r#"
fn level1() {}
fn level2() {
level1();
}
fn level3() {
level2();
}
fn level4() {
level3();
}
"#]);
let level1 = get_block(&graph, "level1");
let level2 = get_block(&graph, "level2");
let level3 = get_block(&graph, "level3");
let level4 = get_block(&graph, "level4");
assert_depends_on(&graph, level2, "level1");
assert_depends_on(&graph, level3, "level2");
assert_depends_on(&graph, level4, "level3");
assert_depended_by(&graph, level1, "level2");
assert_depended_by(&graph, level2, "level3");
assert_depended_by(&graph, level3, "level4");
}
#[test]
fn multiple_dependencies_same_function() {
let graph = build_graph(&[r#"
fn dep1() {}
fn dep2() {}
fn dep3() {}
fn caller() {
dep1();
dep2();
dep3();
}
"#]);
let caller = get_block(&graph, "caller");
let dep1 = get_block(&graph, "dep1");
let dep2 = get_block(&graph, "dep2");
let dep3 = get_block(&graph, "dep3");
assert_depends_on_all(&graph, caller, &["dep1", "dep2", "dep3"]);
assert_depended_by_all(&graph, dep1, &["caller"]);
assert_depended_by_all(&graph, dep2, &["caller"]);
assert_depended_by_all(&graph, dep3, &["caller"]);
}
#[test]
fn method_depends_on_type_and_function() {
let graph = build_graph(&[r#"
fn external_helper() {}
struct Container;
impl Container {
fn method(&self) {
external_helper();
}
}
"#]);
let method = get_block(&graph, "method");
let external_helper = get_block(&graph, "external_helper");
assert_depends_on(&graph, method, "external_helper");
assert_depended_by(&graph, external_helper, "method");
}
#[test]
fn generic_type_parameter_creates_dependency() {
let graph = build_graph(&[r#"
struct Container<T> {
value: T,
}
struct Item;
fn uses(_: Container<Item>) {}
"#]);
let uses = get_block(&graph, "uses");
let container = get_block(&graph, "Container");
let item = get_block(&graph, "Item");
assert_depends_on_all(&graph, uses, &["Container", "Item"]);
assert_depended_by(&graph, container, "uses");
assert_depended_by(&graph, item, "uses");
}
#[test]
fn multiple_parameters_multiple_types() {
let graph = build_graph(&[r#"
struct First;
struct Second;
struct Third;
fn multi_param(_a: First, _b: Second, _c: Third) {}
"#]);
let multi = get_block(&graph, "multi_param");
let first = get_block(&graph, "First");
let second = get_block(&graph, "Second");
let third = get_block(&graph, "Third");
assert_depends_on_all(&graph, multi, &["First", "Second", "Third"]);
assert_depended_by(&graph, first, "multi_param");
assert_depended_by(&graph, second, "multi_param");
assert_depended_by(&graph, third, "multi_param");
}
#[test]
fn trait_impl_method_dependencies() {
let graph = build_graph(&[r#"
trait Processor {
fn process(&self);
}
struct Handler;
impl Processor for Handler {
fn process(&self) {}
}
"#]);
let process = get_block(&graph, "process");
let process_info = graph.block_info(process.block_id).unwrap();
assert_eq!(process_info.2, BlockKind::Func);
}
#[test]
#[ignore = "todo"]
fn associated_function_depends_on_type() {
let graph = build_graph(&[r#"
struct Builder;
impl Builder {
fn new() -> Builder {
Builder
}
}
"#]);
let new = get_block(&graph, "new");
assert_depends_on(&graph, new, "Builder");
}
#[test]
fn deeply_nested_dependency_chain() {
let graph = build_graph(&[r#"
fn level1() {}
fn level2() {
level1();
}
fn level3() {
level2();
}
fn level4() {
level3();
}
fn level5() {
level4();
}
fn level6() {
level5();
}
"#]);
let level1 = get_block(&graph, "level1");
let level6 = get_block(&graph, "level6");
assert_depends_on(&graph, level6, "level5");
let all_names = get_all_related_names(&graph, level6);
assert!(all_names.contains("level1"));
assert!(all_names.contains("level2"));
assert!(all_names.contains("level3"));
assert!(all_names.contains("level4"));
assert!(all_names.contains("level5"));
assert_depended_by(&graph, level1, "level2");
}
#[test]
fn multiple_files_cross_unit_dependencies() {
let graph = build_graph(&[
r#"
pub struct SharedType;
pub fn producer() -> SharedType {
SharedType
}
"#,
r#"
pub fn consumer_fn() {
println!("consuming");
}
"#,
]);
let producer = get_block_in_unit(&graph, 0, "producer");
let shared_type = get_block(&graph, "SharedType");
assert_depends_on(&graph, producer, "SharedType");
assert_depended_by(&graph, shared_type, "producer");
let consumer_fn = get_block_in_unit(&graph, 1, "consumer_fn");
assert_eq!(consumer_fn.unit_index, 1);
}
#[test]
fn multiple_files_multiple_levels_nested() {
let graph = build_graph(&[
r#"
pub struct Base;
pub fn base_fn() {}
"#,
r#"
pub struct Middle {
b: crate::Base,
}
pub fn middle_fn() {
crate::base_fn();
}
"#,
r#"
pub struct Top {
m: crate::Middle,
}
pub fn top_fn() {
crate::middle_fn();
}
"#,
]);
let top_fn = get_block_in_unit(&graph, 2, "top_fn");
let middle = get_block(&graph, "Middle");
let top = get_block(&graph, "Top");
assert_depends_on(&graph, top_fn, "middle_fn");
assert_depends_on(&graph, middle, "Base");
assert_depends_on(&graph, top, "Middle");
}
#[test]
fn same_name_in_different_units() {
let graph = build_graph(&[
r#"
pub fn process() {
println!("unit 0");
}
"#,
r#"
pub fn process() {
println!("unit 1");
}
"#,
r#"
pub fn process() {
println!("unit 2");
}
"#,
]);
let process_unit0 = get_block_in_unit(&graph, 0, "process");
let process_unit1 = get_block_in_unit(&graph, 1, "process");
let process_unit2 = get_block_in_unit(&graph, 2, "process");
assert_eq!(process_unit0.unit_index, 0);
assert_eq!(process_unit1.unit_index, 1);
assert_eq!(process_unit2.unit_index, 2);
let all_process = graph.blocks_by_name("process");
assert_eq!(all_process.len(), 3);
}
#[test]
fn same_struct_name_in_different_units() {
let graph = build_graph(&[
r#"
pub struct Data {
value: i32,
}
pub fn process_u0(_: Data) {}
"#,
r#"
pub struct Data {
value: String,
}
pub fn process_u1(_: Data) {}
"#,
]);
let data_unit0 = get_block_in_unit(&graph, 0, "Data");
let data_unit1 = get_block_in_unit(&graph, 1, "Data");
assert_eq!(data_unit0.unit_index, 0);
assert_eq!(data_unit1.unit_index, 1);
let process_u0 = get_block_in_unit(&graph, 0, "process_u0");
assert_depends_on(&graph, process_u0, "Data");
let process_u1 = get_block_in_unit(&graph, 1, "process_u1");
assert_depends_on(&graph, process_u1, "Data");
}
#[test]
fn diamond_dependency_pattern() {
let graph = build_graph(&[r#"
fn base() {}
fn left() {
base();
}
fn right() {
base();
}
fn top() {
left();
right();
}
"#]);
let base = get_block(&graph, "base");
let left = get_block(&graph, "left");
let right = get_block(&graph, "right");
let top = get_block(&graph, "top");
assert_depends_on_all(&graph, top, &["left", "right"]);
assert_depends_on(&graph, left, "base");
assert_depends_on(&graph, right, "base");
assert_depended_by_all(&graph, base, &["left", "right"]);
}
#[test]
fn multiple_nested_structs_with_same_field_names() {
let graph = build_graph(&[r#"
struct A;
struct B {
field: A,
}
struct C {
field: B,
}
struct D {
field: C,
}
fn uses(_: D) {}
"#]);
let a = get_block(&graph, "A");
let b = get_block(&graph, "B");
let c = get_block(&graph, "C");
let d = get_block(&graph, "D");
let uses = get_block(&graph, "uses");
assert_depends_on(&graph, b, "A");
assert_depends_on(&graph, c, "B");
assert_depends_on(&graph, d, "C");
assert_depends_on(&graph, uses, "D");
assert_depended_by(&graph, a, "B");
}
#[test]
fn complex_cross_file_diamond() {
let graph = build_graph(&[
r#"
pub struct Base;
pub fn base_fn() {}
"#,
r#"
pub fn left(_: crate::Base) {
crate::base_fn();
}
"#,
r#"
pub fn right(_: crate::Base) {
crate::base_fn();
}
"#,
r#"
pub fn coordinator() {
crate::left(crate::Base);
crate::right(crate::Base);
}
"#,
]);
let left = get_block_in_unit(&graph, 1, "left");
let right = get_block_in_unit(&graph, 2, "right");
assert_depends_on(&graph, left, "Base");
assert_depends_on(&graph, right, "Base");
let left_deps = get_depends_on(&graph, left);
let right_deps = get_depends_on(&graph, right);
assert!(left_deps.contains("base_fn") || left_deps.contains("Base"));
assert!(right_deps.contains("base_fn") || right_deps.contains("Base"));
}
#[test]
fn multiple_structs_same_type_field() {
let graph = build_graph(&[r#"
struct Shared;
struct A {
data: Shared,
}
struct B {
data: Shared,
}
struct C {
data: Shared,
}
fn use_all(_a: A, _b: B, _c: C) {}
"#]);
let shared = get_block(&graph, "Shared");
let a = get_block(&graph, "A");
let b = get_block(&graph, "B");
let c = get_block(&graph, "C");
assert_depends_on(&graph, a, "Shared");
assert_depends_on(&graph, b, "Shared");
assert_depends_on(&graph, c, "Shared");
assert_depended_by_all(&graph, shared, &["A", "B", "C"]);
}
#[test]
fn module_private_types_dependencies() {
let graph = build_graph(&[r#"
mod outer {
pub struct PublicType {
value: i32,
}
struct PrivateType {
value: i32,
}
pub fn uses_public(_: PublicType) {}
fn uses_private(_: PrivateType) {}
}
"#]);
let public_type = get_block(&graph, "PublicType");
let private_type = get_block(&graph, "PrivateType");
let uses_public = get_block(&graph, "uses_public");
let uses_private = get_block(&graph, "uses_private");
assert_depends_on(&graph, uses_public, "PublicType");
assert_depends_on(&graph, uses_private, "PrivateType");
assert_depended_by(&graph, public_type, "uses_public");
assert_depended_by(&graph, private_type, "uses_private");
}
#[test]
fn nested_module_cross_dependency() {
let graph = build_graph(&[r#"
mod outer {
pub struct OuterType;
pub mod inner {
pub fn inner_fn(_: crate::outer::OuterType) {}
}
}
"#]);
let outer_type = get_block(&graph, "OuterType");
let inner_fn = get_block(&graph, "inner_fn");
assert_depends_on(&graph, inner_fn, "OuterType");
assert_depended_by(&graph, outer_type, "inner_fn");
}
#[test]
fn trait_impl_with_type_parameter_dependency() {
let graph = build_graph(&[r#"
trait Handler<T> {
fn handle(&self, value: T);
}
struct MyType;
struct MyHandler;
impl Handler<MyType> for MyHandler {
fn handle(&self, _value: MyType) {}
}
"#]);
let my_type = get_block(&graph, "MyType");
let my_handler = get_block(&graph, "MyHandler");
let handle_method = get_block(&graph, "handle");
let my_handler_deps = get_depends_on(&graph, my_handler);
assert!(!my_handler_deps.is_empty());
assert_depends_on(&graph, handle_method, "MyType");
assert_depended_by(&graph, my_type, "handle");
}
#[test]
fn struct_with_private_and_public_methods() {
let graph = build_graph(&[r#"
struct Container;
impl Container {
pub fn public_method(&self) -> Container {
Container
}
fn private_method(&self) -> Container {
Container
}
}
"#]);
let container = get_block(&graph, "Container");
let public_method = get_block(&graph, "public_method");
let private_method = get_block(&graph, "private_method");
let public_info = graph.block_info(public_method.block_id).unwrap();
assert_eq!(public_info.2, BlockKind::Func);
let private_info = graph.block_info(private_method.block_id).unwrap();
assert_eq!(private_info.2, BlockKind::Func);
let container_info = graph.block_info(container.block_id).unwrap();
assert_eq!(container_info.2, BlockKind::Class);
}
#[test]
fn enum_with_complex_variants_dependency() {
let graph = build_graph(&[r#"
struct Data;
enum Result {
Ok(Data),
Err(String),
}
fn process_result(_: Result) {}
"#]);
let _data = get_block(&graph, "Data");
let result_enum = get_block(&graph, "Result");
let process = get_block(&graph, "process_result");
assert_depends_on(&graph, process, "Result");
assert_depended_by(&graph, result_enum, "process_result");
assert_depends_on(&graph, result_enum, "Data");
}
#[test]
fn multiple_impl_blocks_same_type() {
let graph = build_graph(&[r#"
struct Point;
impl Point {
fn x(&self) -> i32 { 0 }
}
impl Point {
fn y(&self) -> i32 { 0 }
}
"#]);
let point = get_block(&graph, "Point");
let x_method = get_block(&graph, "x");
let y_method = get_block(&graph, "y");
let x_info = graph.block_info(x_method.block_id).unwrap();
assert_eq!(x_info.2, BlockKind::Func);
let y_info = graph.block_info(y_method.block_id).unwrap();
assert_eq!(y_info.2, BlockKind::Func);
let point_info = graph.block_info(point.block_id).unwrap();
assert_eq!(point_info.2, BlockKind::Class);
}
#[test]
fn const_with_type_dependency() {
let graph = build_graph(&[r#"
struct Config {
value: i32,
}
const GLOBAL_CONFIG: Config = Config { value: 42 };
fn uses_config() -> Config {
Config { value: 0 }
}
"#]);
let config = get_block(&graph, "Config");
let uses_config = get_block(&graph, "uses_config");
assert_depends_on(&graph, uses_config, "Config");
assert_depended_by(&graph, config, "uses_config");
}
#[test]
fn generic_struct_with_bound_dependency() {
let graph = build_graph(&[r#"
struct ConcreteProcessor;
fn uses_concrete(_: ConcreteProcessor) {}
"#]);
let concrete = get_block(&graph, "ConcreteProcessor");
let uses_concrete = get_block(&graph, "uses_concrete");
assert_depends_on(&graph, uses_concrete, "ConcreteProcessor");
assert_depended_by(&graph, concrete, "uses_concrete");
}
#[test]
fn function_returning_trait_object() {
let graph = build_graph(&[r#"
struct FileReader;
fn get_reader() -> Box<FileReader> {
Box::new(FileReader)
}
"#]);
let file_reader = get_block(&graph, "FileReader");
let get_reader = get_block(&graph, "get_reader");
assert_depends_on(&graph, get_reader, "FileReader");
assert_depended_by(&graph, file_reader, "get_reader");
}
#[test]
fn enum_variant_with_struct_field() {
let graph = build_graph(&[r#"
struct Error {
message: String,
}
enum Result<T> {
Ok(T),
Err(Error),
}
struct Value;
fn process() -> Result<Value> {
Err(Error { message: String::new() })
}
"#]);
let error_struct = get_block(&graph, "Error");
let result_enum = get_block(&graph, "Result");
let _value = get_block(&graph, "Value");
let process = get_block(&graph, "process");
assert_depends_on(&graph, process, "Result");
assert_depends_on(&graph, result_enum, "Error");
assert_depended_by(&graph, error_struct, "Result");
}
#[test]
fn recursive_type_dependency() {
let graph = build_graph(&[r#"
struct Node {
value: i32,
next: Option<Box<Node>>,
}
fn traverse(_: Node) {}
"#]);
let node = get_block(&graph, "Node");
let traverse = get_block(&graph, "traverse");
assert_depends_on(&graph, traverse, "Node");
assert_depended_by(&graph, node, "traverse");
let node_deps = get_depends_on(&graph, node);
assert!(node_deps.is_empty() || node_deps.contains("Node"));
}
#[test]
fn compact_graph_matches_default_output() {
let sources = [r#"
struct Config;
struct Handler {
config: Config,
}
struct App {
handler: Handler,
}
"#];
let default_graph = build_graph_with_config(&sources, GraphBuildConfig::default());
let project_graph = build_graph_with_config(&sources, GraphBuildConfig::compact());
let default_render = default_graph.render_compact_graph();
let compact_render = project_graph.render_compact_graph();
assert_eq!(default_render, compact_render);
assert!(compact_render.contains("App\\n(class)"));
assert!(compact_render.contains("Handler\\n(class)"));
assert!(compact_render.contains("Config\\n(class)"));
assert!(compact_render.contains("n0 -> n2;"));
assert!(compact_render.contains("n2 -> n1;"));
}