From 9d0cd08f1fe67bdb8024fd7bcbe6a7342708bc7c Mon Sep 17 00:00:00 2001 From: Melody Ma Date: Sun, 20 Sep 2026 16:44:20 +0800 Subject: [PATCH 1/5] [meta model] Add function declaration --- tools/metamodel/class/class_logic.rs | 46 ++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tools/metamodel/class/class_logic.rs b/tools/metamodel/class/class_logic.rs index aed9d3bb..df4225e8 100644 --- a/tools/metamodel/class/class_logic.rs +++ b/tools/metamodel/class/class_logic.rs @@ -18,7 +18,10 @@ pub use source_location::SourceLocation; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)] pub struct ClassDiagram { pub name: String, + #[serde(default)] pub entities: Vec, + #[serde(default)] + pub free_functions: Vec, } /// Represents a class, struct, interface, enum, or other type entity @@ -259,6 +262,33 @@ pub struct EnumLiteral { pub source_location: SourceLocation, } +/// Represents a global- or namespace-scope function declaration. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)] +pub struct FreeFunctionDecl { + /// Function name without its namespace qualification. + pub name: String, + /// Namespace containing the function, if any. + pub enclosing_namespace_id: Option, + /// Return type. + pub return_type: Option, + /// Function parameters. + pub parameters: Vec, + /// Template parameters for generic functions. + pub template_parameters: Option>, + /// Source location in input. + pub source_location: SourceLocation, +} + +impl FreeFunctionDecl { + /// Returns the function name qualified by its containing namespace. + pub fn qualified_name(&self) -> String { + match self.enclosing_namespace_id.as_deref() { + Some(namespace) if !namespace.is_empty() => format!("{namespace}::{}", self.name), + _ => self.name.clone(), + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -313,6 +343,22 @@ mod tests { assert_eq!(inheritance.relation_type, RelationType::Inheritance); } + #[test] + fn free_function_qualified_name_includes_namespace_when_present() { + let global = FreeFunctionDecl { + name: "log".to_string(), + ..Default::default() + }; + let namespaced = FreeFunctionDecl { + name: "log".to_string(), + enclosing_namespace_id: Some("app::internal".to_string()), + ..Default::default() + }; + + assert_eq!(global.qualified_name(), "log"); + assert_eq!(namespaced.qualified_name(), "app::internal::log"); + } + #[test] fn test_partial_plantuml_entity() { // PlantUML often has incomplete information - this should still work From dd0c1f7509f12ce1a963bcdbe5876a73a10e0b9b Mon Sep 17 00:00:00 2001 From: Melody Ma Date: Sun, 20 Sep 2026 16:45:12 +0800 Subject: [PATCH 2/5] [cpp parser] extract free function declaration --- cpp/libclang/src/main.rs | 44 ++- cpp/libclang/src/semantics/src/callable.rs | 2 +- cpp/libclang/src/utils/write.rs | 12 +- cpp/libclang/src/visitor/BUILD | 2 + .../src/visitor/src/callable_declaration.rs | 124 +++++++ .../src/class_relationship_resolver.rs | 106 +++--- cpp/libclang/src/visitor/src/class_visitor.rs | 328 ++++-------------- cpp/libclang/src/visitor/src/context.rs | 69 +++- cpp/libclang/src/visitor/src/context_ext.rs | 177 ++++++++++ .../src/visitor/src/function_visitor.rs | 305 +++++++++++++--- cpp/libclang/src/visitor/src/lib.rs | 5 +- cpp/libclang/src/visitor/src/visitor.rs | 27 +- 12 files changed, 810 insertions(+), 391 deletions(-) create mode 100644 cpp/libclang/src/visitor/src/callable_declaration.rs create mode 100644 cpp/libclang/src/visitor/src/context_ext.rs diff --git a/cpp/libclang/src/main.rs b/cpp/libclang/src/main.rs index 3c060a1b..7ca84d3b 100644 --- a/cpp/libclang/src/main.rs +++ b/cpp/libclang/src/main.rs @@ -18,13 +18,13 @@ use std::collections::{BTreeMap, HashSet}; use std::fs; use std::path::{Path, PathBuf}; -use class_diagram::{ClassDiagram, SimpleEntity}; +use class_diagram::{ClassDiagram, FreeFunctionDecl, SimpleEntity}; use class_serializer::ClassSerializer; use utils::{render_entity_tree, write_debug_json, write_entity_tree, write_fbs_output}; use visit_tu::{ - is_external_dependency_path, FunctionDef, FunctionDefinitionKey, SourceFileCache, VisitContext, - Visitor, + is_external_dependency_path, CallableIdentityKey, EntityMapExt, FunctionDef, SourceEntityKey, + SourceFileCache, VisitContext, Visitor, }; #[derive(ClapParser, Debug)] @@ -53,13 +53,16 @@ struct Args { #[derive(Default)] struct ParseOutputs { types: BTreeMap, + free_function_declarations: Vec, functions: Vec, } #[derive(Default)] struct ParseState { source_files: SourceFileCache, - seen_function_definitions: HashSet, + seen_free_function_declarations: HashSet, + seen_method_declarations: HashSet, + seen_function_definitions: HashSet, } impl ParseOutputs { @@ -72,9 +75,20 @@ impl ParseOutputs { for (type_name, entity) in ctx.types { debug!("Type {}:\n{:#?}", type_name, entity); - self.types.insert(type_name, entity); + self.types.insert_or_merge_type(type_name, entity); } - + self.free_function_declarations + .extend( + ctx.free_function_declarations + .into_iter() + .map(|declaration| { + debug!( + "Free function declaration: {}", + declaration.declaration.qualified_name() + ); + declaration.declaration + }), + ); self.functions.extend( ctx.functions .into_iter() @@ -165,6 +179,8 @@ fn parse_file( let mut visitor = Visitor::new( &mut ctx, &mut state.source_files, + &mut state.seen_free_function_declarations, + &mut state.seen_method_declarations, &mut state.seen_function_definitions, ); visitor.visit(entity); @@ -179,11 +195,13 @@ fn parse_file( fn serialize_class_diagram( output_path: &Path, entities: BTreeMap, + free_functions: Vec, ) -> Result<(), std::io::Error> { let entities: Vec<_> = entities.into_values().collect(); let class_diagram = ClassDiagram { name: String::new(), // no name for c++ side entities, + free_functions, }; let output_fbs = ClassSerializer::serialize(&class_diagram); @@ -234,10 +252,20 @@ fn main() -> Result<(), Box> { } if let Some(debug_json_output) = &command_line_args.debug_json_output { - write_debug_json(debug_json_output, &outputs.types, &outputs.functions)?; + write_debug_json( + debug_json_output, + &outputs.types, + (!outputs.free_function_declarations.is_empty()) + .then_some(&outputs.free_function_declarations), + &outputs.functions, + )?; } - serialize_class_diagram(&command_line_args.class_fbs_output, outputs.types)?; + serialize_class_diagram( + &command_line_args.class_fbs_output, + outputs.types, + outputs.free_function_declarations, + )?; Ok(()) } diff --git a/cpp/libclang/src/semantics/src/callable.rs b/cpp/libclang/src/semantics/src/callable.rs index 386fa7ff..f0b4cbda 100644 --- a/cpp/libclang/src/semantics/src/callable.rs +++ b/cpp/libclang/src/semantics/src/callable.rs @@ -59,7 +59,7 @@ pub enum FunctionKind { Conversion, } -/// Stable semantic identity of a C++ callable. +/// Lightweight semantic identity of a C++ callable within this model. #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct FunctionId { pub scope: Scope, diff --git a/cpp/libclang/src/utils/write.rs b/cpp/libclang/src/utils/write.rs index b451670f..14cc7a49 100644 --- a/cpp/libclang/src/utils/write.rs +++ b/cpp/libclang/src/utils/write.rs @@ -42,17 +42,25 @@ fn write_entity_tree_inner(path: &Path, entity_tree: &str) -> std::io::Result<() file_out.flush() } -pub fn write_debug_json( +pub fn write_debug_json( output_path: &Path, types: &T, - functions: &U, + free_function_declarations: Option<&U>, + functions: &V, ) -> Result<(), Box> where T: Serialize, U: Serialize, + V: Serialize, { let mut debug_json = serde_json::Map::new(); debug_json.insert("types".to_owned(), serde_json::to_value(types)?); + if let Some(free_function_declarations) = free_function_declarations { + debug_json.insert( + "free_function_declarations".to_owned(), + serde_json::to_value(free_function_declarations)?, + ); + } debug_json.insert("functions".to_owned(), serde_json::to_value(functions)?); let output_json = serde_json::to_string_pretty(&debug_json)?; diff --git a/cpp/libclang/src/visitor/BUILD b/cpp/libclang/src/visitor/BUILD index 64f96fa0..6af50ef6 100644 --- a/cpp/libclang/src/visitor/BUILD +++ b/cpp/libclang/src/visitor/BUILD @@ -15,6 +15,7 @@ load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") rust_library( name = "visit_tu", srcs = [ + "src/callable_declaration.rs", "src/clang_adapter/mod.rs", "src/clang_adapter/scope.rs", "src/clang_adapter/source_filter.rs", @@ -22,6 +23,7 @@ rust_library( "src/class_relationship_resolver.rs", "src/class_visitor.rs", "src/context.rs", + "src/context_ext.rs", "src/enum_visitor.rs", "src/function_visitor.rs", "src/lib.rs", diff --git a/cpp/libclang/src/visitor/src/callable_declaration.rs b/cpp/libclang/src/visitor/src/callable_declaration.rs new file mode 100644 index 00000000..66a80522 --- /dev/null +++ b/cpp/libclang/src/visitor/src/callable_declaration.rs @@ -0,0 +1,124 @@ +// ******************************************************************************* +// Copyright (c) 2026 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* + +use clang::{Entity, EntityKind}; +use class_diagram::{FunctionArgument, TemplateParameter}; + +use crate::types::renderer::render_type_for_display; +use crate::types::resolver::resolve_type; + +/// Returns callable parameters, including the fallback required for template cursors. +/// +/// Normally libclang provides the parameter list via `Entity::get_arguments()`. +/// However, for some cursor kinds (e.g. `FunctionTemplate`) or certain libclang +/// versions, `get_arguments()` may return `None` even though the AST still +/// contains `ParmDecl` child cursors. +pub(crate) fn callable_arguments<'tu>(entity: &Entity<'tu>) -> Vec> { + // fall back to collecting all direct `ParmDecl` children from + // the cursor to recover the parameter list. + entity.get_arguments().unwrap_or_else(|| { + entity + .get_children() + .into_iter() + .filter(|child| child.get_kind() == EntityKind::ParmDecl) + .collect() + }) +} + +pub(crate) fn parse_function_parameters(entity: &Entity) -> Vec { + let mut parameters: Vec = callable_arguments(entity) + .into_iter() + .map(|argument| { + let raw_param_type = argument + .get_type() + .map(|ty| ty.get_display_name()) + .unwrap_or_default(); + + FunctionArgument { + name: argument.get_name().unwrap_or_default(), + param_type: Some(normalize_pack_expansion_type(&raw_param_type)), + is_variadic: false, + is_pack_expansion: raw_param_type.contains("..."), + } + }) + .collect(); + + if entity.get_type().is_some_and(|ty| ty.is_variadic()) { + parameters.push(FunctionArgument { + name: String::new(), + param_type: None, + is_variadic: true, + is_pack_expansion: false, + }); + } + + parameters +} + +pub(crate) fn parse_callable_return_type(entity: &Entity) -> Option { + entity.get_result_type().map(|return_type| { + let resolved_type = resolve_type(&return_type); + render_type_for_display(&return_type, &resolved_type) + }) +} + +pub(crate) fn parse_template_parameters(entity: &Entity) -> Option> { + let parameters = entity + .get_children() + .into_iter() + .enumerate() + .filter_map(|(index, child)| match child.get_kind() { + // template → "name: Foo, is_pack: False" + // template -> "name: T0, is_pack: False", "name: T1, is_pack: False" + // template -> "name: Foo, is_pack: True" + EntityKind::TemplateTypeParameter => Some(TemplateParameter::Type { + name: child.get_name().unwrap_or_else(|| format!("T{index}")), + is_pack: is_template_parameter_pack(&child), + }), + // template → "name: N, value_type: int" + EntityKind::NonTypeTemplateParameter => Some(TemplateParameter::NonType { + name: child.get_name().unwrap_or_default(), + value_type: child + .get_type() + .map(|ty| ty.get_display_name()) + .unwrap_or_default(), + is_pack: is_template_parameter_pack(&child), + }), + // template class C> → "name: C, parameters: [...], is_pack: False" + EntityKind::TemplateTemplateParameter => Some(TemplateParameter::Template { + name: child.get_name().unwrap_or_else(|| format!("T{index}")), + parameters: parse_template_parameters(&child).unwrap_or_default(), + is_pack: is_template_parameter_pack(&child), + }), + _ => None, + }) + .collect::>(); + + (!parameters.is_empty()).then_some(parameters) +} + +fn normalize_pack_expansion_type(param_type: &str) -> String { + param_type.replace("...", "").trim().to_string() +} + +fn is_template_parameter_pack(entity: &Entity) -> bool { + entity.get_range().is_some_and(|range| { + range + .tokenize() + .iter() + .any(|token| token.get_spelling() == "...") + }) || entity + .get_display_name() + .as_deref() + .is_some_and(|display_name| display_name.contains("...")) +} diff --git a/cpp/libclang/src/visitor/src/class_relationship_resolver.rs b/cpp/libclang/src/visitor/src/class_relationship_resolver.rs index eda98b9b..8e79f0d5 100644 --- a/cpp/libclang/src/visitor/src/class_relationship_resolver.rs +++ b/cpp/libclang/src/visitor/src/class_relationship_resolver.rs @@ -24,7 +24,7 @@ pub(crate) fn resolve_relationships(ctx: &mut VisitContext) { let builders = std::mem::take(&mut ctx.parsed_class_info); let known_type_ids: HashSet = ctx.types.keys().cloned().collect(); - for builder in builders { + for builder in builders.into_values() { build_relationships_for_class(ctx, &builder); infer_relationships_from_builder(ctx, &builder, &known_type_ids); } @@ -267,21 +267,25 @@ mod tests { }, ); - ctx.parsed_class_info.push(ParsedClassInfo { - id: "Car".to_string(), - base_classes: vec![], - variable_types: vec![ParsedVariableType { - name: "engine".to_string(), - resolved_type: ResolvedType::UserDefined("Engine".to_string()), - source_location: SourceLocation::new(source_file, 5), - }], - method_types: vec![ParsedMethodType { - name: "buildEngine".to_string(), - return_type: ResolvedType::UserDefined("Engine".to_string()), - parameter_types: vec![], - source_location: SourceLocation::new(source_file, 6), - }], - }); + ctx.parsed_class_info.insert( + "Car".to_string(), + ParsedClassInfo { + id: "Car".to_string(), + base_classes: vec![], + variable_types: vec![ParsedVariableType { + name: "engine".to_string(), + resolved_type: ResolvedType::UserDefined("Engine".to_string()), + source_location: SourceLocation::new(source_file, 5), + }], + method_types: vec![ParsedMethodType { + name: "buildEngine".to_string(), + return_type: ResolvedType::UserDefined("Engine".to_string()), + parameter_types: vec![], + source_location: SourceLocation::new(source_file, 6), + }], + ..Default::default() + }, + ); resolve_relationships(&mut ctx); @@ -349,27 +353,31 @@ mod tests { ..Default::default() }, ); - ctx.parsed_class_info.push(ParsedClassInfo { - id: "amp::detail::is_maplike_container".to_string(), - base_classes: vec![ - // Unresolvable dependent expression — must be skipped, not panic. - ParsedBaseClass { - resolved_type: ResolvedType::Dependent( - "decltype(is_maplike_container_impl(std::declval()))".to_string(), - ), - source_location: SourceLocation::new(source_file, 5), - }, - // A normal, resolvable base class alongside the dependent one. - ParsedBaseClass { - resolved_type: ResolvedType::UserDefined( - "amp::detail::is_container_base".to_string(), - ), - source_location: SourceLocation::new(source_file, 5), - }, - ], - variable_types: vec![], - method_types: vec![], - }); + ctx.parsed_class_info.insert( + "amp::detail::is_maplike_container".to_string(), + ParsedClassInfo { + id: "amp::detail::is_maplike_container".to_string(), + base_classes: vec![ + // Unresolvable dependent expression — must be skipped, not panic. + ParsedBaseClass { + resolved_type: ResolvedType::Dependent( + "decltype(is_maplike_container_impl(std::declval()))".to_string(), + ), + source_location: SourceLocation::new(source_file, 5), + }, + // A normal, resolvable base class alongside the dependent one. + ParsedBaseClass { + resolved_type: ResolvedType::UserDefined( + "amp::detail::is_container_base".to_string(), + ), + source_location: SourceLocation::new(source_file, 5), + }, + ], + variable_types: vec![], + method_types: vec![], + ..Default::default() + }, + ); // Must not panic. resolve_relationships(&mut ctx); @@ -419,16 +427,20 @@ mod tests { ..Default::default() }, ); - ctx.parsed_class_info.push(ParsedClassInfo { - id: "Derived".to_string(), - base_classes: vec![ParsedBaseClass { - // Not `Dependent`: an unexpected, unresolvable base type. - resolved_type: ResolvedType::Unknown("SomeWeirdType".to_string()), - source_location: SourceLocation::new(source_file, 1), - }], - variable_types: vec![], - method_types: vec![], - }); + ctx.parsed_class_info.insert( + "Derived".to_string(), + ParsedClassInfo { + id: "Derived".to_string(), + base_classes: vec![ParsedBaseClass { + // Not `Dependent`: an unexpected, unresolvable base type. + resolved_type: ResolvedType::Unknown("SomeWeirdType".to_string()), + source_location: SourceLocation::new(source_file, 1), + }], + variable_types: vec![], + method_types: vec![], + ..Default::default() + }, + ); // Must not panic. resolve_relationships(&mut ctx); diff --git a/cpp/libclang/src/visitor/src/class_visitor.rs b/cpp/libclang/src/visitor/src/class_visitor.rs index ba3c4225..3fa2b772 100644 --- a/cpp/libclang/src/visitor/src/class_visitor.rs +++ b/cpp/libclang/src/visitor/src/class_visitor.rs @@ -11,18 +11,17 @@ // SPDX-License-Identifier: Apache-2.0 // ******************************************************************************* -use clang::{Entity, EntityKind, ExceptionSpecification}; +use clang::{Entity, EntityKind}; use class_diagram::{ - EntityType, FunctionArgument, MemberVariable, Method, MethodModifier, SimpleEntity, - TemplateParameter, TypeAlias, Visibility, + EntityType, MemberVariable, Method, MethodModifier, SimpleEntity, TypeAlias, Visibility, }; -use cpp_semantics::ResolvedType; +use crate::callable_declaration::parse_template_parameters; use crate::clang_adapter::scope::{namespace_id, semantic_parent_id}; use crate::clang_adapter::source_location::parse_source_location; use crate::context::{ - ParsedBaseClass, ParsedClassInfo, ParsedMethodType, ParsedVariableType, VisitContext, + ExtractedMethodDeclaration, ParsedBaseClass, ParsedClassInfo, ParsedVariableType, VisitContext, }; use crate::types::renderer::render_type_for_display; use crate::types::resolver::resolve_type; @@ -45,7 +44,7 @@ impl AstVisitor for ClassVisitor { Self::visit_class(&entity, semantic_parent.as_deref(), namespace.as_deref()) { class_entity.template_parameters = template_params; - ctx.parsed_class_info.push(builder); + ctx.parsed_class_info.insert(builder.id.clone(), builder); ctx.types.insert(class_entity.id.clone(), class_entity); } } @@ -58,6 +57,30 @@ impl ClassVisitor { crate::class_relationship_resolver::resolve_relationships(ctx); } + /// Adds a callable declaration to its owning class and preserves the + /// class-level metadata used by relationship inference. + pub(crate) fn add_method_declaration( + ctx: &mut VisitContext, + declaration: ExtractedMethodDeclaration, + ) { + let (types, parsed_class_info) = (&mut ctx.types, &mut ctx.parsed_class_info); + let (Some(class), Some(builder)) = ( + types.get_mut(&declaration.class_id), + parsed_class_info.get_mut(&declaration.class_id), + ) else { + log::warn!( + "method '{}' has incompletely registered owning class '{}'; skipping declaration", + declaration.method.name, + declaration.class_id + ); + return; + }; + + update_entity_type_for_method(class, builder, &declaration.method); + class.methods.push(declaration.method); + builder.method_types.push(declaration.method_type); + } + fn visit_class( entity: &Entity, semantic_parent: Option<&str>, @@ -75,6 +98,8 @@ impl ClassVisitor { base_classes: vec![], variable_types: vec![], method_types: vec![], + has_abstract_methods: false, + has_concrete_methods: false, }; let mut class_entity = SimpleEntity { @@ -89,7 +114,9 @@ impl ClassVisitor { Self::visit_member(&child, &mut class_entity, &mut builder); } - class_entity.entity_type = infer_entity_type_from_members(entity.get_kind(), &class_entity); + if entity.get_kind() == EntityKind::StructDecl { + class_entity.entity_type = EntityType::Struct; + } class_entity.source_location = parse_source_location(entity); @@ -106,12 +133,6 @@ impl ClassVisitor { }); } } - EntityKind::Method | EntityKind::Constructor | EntityKind::Destructor => { - let parsed_method_type = collect_method_type(entity, builder); - if let Some(method) = parse_method(entity, &parsed_method_type) { - class.methods.push(method); - } - } EntityKind::FieldDecl | EntityKind::VarDecl => { let Some(parsed_variable_type) = collect_variable_type(entity) else { return; @@ -122,17 +143,6 @@ impl ClassVisitor { class.variables.push(variable); } } - EntityKind::FunctionTemplate => { - let template_params = parse_template_parameters(entity); - let parsed_method_type = collect_method_type(entity, builder); - - // In current libclang/clang-rs output, method templates are represented - // directly on the FunctionTemplate entity. - if let Some(mut method) = parse_method(entity, &parsed_method_type) { - method.template_parameters = template_params; - class.methods.push(method); - } - } // `using Alias = OriginalType;` -> TypeAliasDecl // `typedef OriginalType Alias;` -> TypedefDecl EntityKind::TypeAliasDecl | EntityKind::TypedefDecl => { @@ -180,45 +190,6 @@ fn collect_variable_type(entity: &Entity) -> Option { }) } -fn collect_method_type(entity: &Entity, builder: &mut ParsedClassInfo) -> ParsedMethodType { - let name = entity.get_name().unwrap_or_default(); - - let return_type = entity - .get_result_type() - .map(|t| resolve_type(&t)) - .unwrap_or_else(|| ResolvedType::Builtin("void".to_string())); - let parameter_types = method_arguments(entity) - .into_iter() - .filter_map(|arg| arg.get_type().map(|t| resolve_type(&t))) - .collect(); - - let parsed_method_type = ParsedMethodType { - name, - return_type, - parameter_types, - source_location: parse_source_location(entity), - }; - builder.method_types.push(parsed_method_type.clone()); - - parsed_method_type -} - -/// Normally libclang provides the parameter list via `Entity::get_arguments()`. -/// However, for some cursor kinds (e.g. `FunctionTemplate`) or certain libclang -/// versions, `get_arguments()` may return `None` even though the AST still -/// contains `ParmDecl` child cursors. -fn method_arguments<'tu>(entity: &Entity<'tu>) -> Vec> { - entity.get_arguments().unwrap_or_else(|| { - // fall back to collecting all direct `ParmDecl` children from - // the cursor to recover the parameter list. - entity - .get_children() - .into_iter() - .filter(|child| child.get_kind() == EntityKind::ParmDecl) - .collect() - }) -} - fn parse_type_alias(entity: &Entity) -> Option { let Some(alias) = entity.get_name() else { log::debug!("skipping type alias: entity has no name"); @@ -243,99 +214,6 @@ fn parse_type_alias(entity: &Entity) -> Option { }) } -fn parse_method(entity: &Entity, parsed_method_type: &ParsedMethodType) -> Option { - let kind = entity.get_kind(); - let name = entity.get_name()?; - let is_override_method = entity - .get_overridden_methods() - .map(|methods| !methods.is_empty()) - .unwrap_or(false); - let is_final_method = entity - .get_children() - .into_iter() - .any(|child| child.get_kind() == EntityKind::FinalAttr); - - // Only the bare `noexcept` specifier is modeled (mirrors the PlantUML grammar, which has - // no support for the conditional `noexcept(expr)` form). Requiring `BasicNoexcept` filters - // out `noexcept(expr)`, but on its own it isn't enough: for an implicit/defaulted special - // member (e.g. `~Foo() = default;` with no written specifier at all), the compiler-computed - // specification also resolves to `BasicNoexcept` once evaluated -- and that evaluation is - // lazily triggered by unrelated code (e.g. a derived class use), making it unstable. So this - // also requires the literal `noexcept` token to appear in the declarator (the tokens up to - // the first `{` or `;`), which excludes both that case and `noexcept` written inside a - // lambda in the method body. - let has_noexcept_token = entity.get_range().is_some_and(|range| { - range - .tokenize() - .iter() - .take_while(|token| !matches!(token.get_spelling().as_str(), "{" | ";")) - .any(|token| token.get_spelling() == "noexcept") - }); - - let is_noexcept_method = has_noexcept_token - && matches!( - entity.get_exception_specification(), - Some(ExceptionSpecification::BasicNoexcept) - ); - - let return_type = if matches!(kind, EntityKind::Constructor | EntityKind::Destructor) { - None - } else { - entity - .get_result_type() - .map(|ret| render_type_for_display(&ret, &parsed_method_type.return_type)) - }; - - let mut parameters = Vec::new(); - let method_is_variadic = entity.get_type().map(|t| t.is_variadic()).unwrap_or(false); - - let args = method_arguments(entity); - - for arg in args { - let raw_param_type = arg - .get_type() - .map(|ty| ty.get_display_name()) - .unwrap_or_default(); - let is_pack_expansion = raw_param_type.contains("..."); - let param_type = normalize_pack_expansion_type(&raw_param_type); - - parameters.push(FunctionArgument { - name: arg.get_name().unwrap_or_default(), - param_type: Some(param_type), - is_variadic: false, - is_pack_expansion, - }); - } - - if method_is_variadic { - parameters.push(FunctionArgument { - name: String::new(), - param_type: None, - is_variadic: true, - is_pack_expansion: false, - }); - } - - Some(Method { - name, - return_type, - visibility: parse_visibility(entity), - parameters, - template_parameters: None, - modifiers: MethodModifier::from_conditions([ - (entity.is_static_method(), MethodModifier::Static), - (entity.is_virtual_method(), MethodModifier::Virtual), - (entity.is_pure_virtual_method(), MethodModifier::Abstract), - (is_override_method, MethodModifier::Override), - (is_noexcept_method, MethodModifier::Noexcept), - (kind == EntityKind::Constructor, MethodModifier::Constructor), - (kind == EntityKind::Destructor, MethodModifier::Destructor), - (is_final_method, MethodModifier::Final), - ]), - source_location: parse_source_location(entity), - }) -} - fn parse_variable( entity: &Entity, parsed_variable_type: &ParsedVariableType, @@ -351,76 +229,7 @@ fn parse_variable( }) } -fn parse_template_parameters(entity: &Entity) -> Option> { - let params: Vec = entity - .get_children() - .into_iter() - .enumerate() - .filter_map(|(idx, child)| match child.get_kind() { - EntityKind::TemplateTypeParameter => { - // template → "name: Foo, is_pack: False" - // template -> "name: T0, is_pack: False", "name: T1, is_pack: False" - // template -> "name: Foo, is_pack: True" - let name = child.get_name().unwrap_or_else(|| format!("T{idx}")); - - Some(TemplateParameter::Type { - name, - is_pack: is_template_parameter_pack(&child), - }) - } - EntityKind::NonTypeTemplateParameter => { - // template → "name: N, value_type: int" - let type_name = child - .get_type() - .map(|t| t.get_display_name()) - .unwrap_or_default(); - let name = child.get_name().unwrap_or_default(); - - Some(TemplateParameter::NonType { - name, - value_type: type_name, - is_pack: is_template_parameter_pack(&child), - }) - } - EntityKind::TemplateTemplateParameter => { - // template class C> → "name: C, parameters: [...], is_pack: False" - let parameters = parse_template_parameters(&child).unwrap_or_default(); - let name = child.get_name().unwrap_or_else(|| format!("T{idx}")); - - Some(TemplateParameter::Template { - name, - parameters, - is_pack: is_template_parameter_pack(&child), - }) - } - _ => None, - }) - .collect(); - - if params.is_empty() { - None - } else { - Some(params) - } -} - -fn normalize_pack_expansion_type(param_type: &str) -> String { - param_type.replace("...", "").trim().to_string() -} - -fn is_template_parameter_pack(entity: &Entity) -> bool { - entity.get_range().is_some_and(|range| { - range - .tokenize() - .iter() - .any(|token| token.get_spelling() == "...") - }) || entity - .get_display_name() - .as_deref() - .is_some_and(|display_name| display_name.contains("...")) -} - -fn parse_visibility(entity: &Entity) -> Visibility { +pub(crate) fn parse_visibility(entity: &Entity) -> Visibility { match entity.get_accessibility() { Some(clang::Accessibility::Public) => Visibility::Public, Some(clang::Accessibility::Private) => Visibility::Private, @@ -429,39 +238,44 @@ fn parse_visibility(entity: &Entity) -> Visibility { } } -fn infer_entity_type_from_members(kind: EntityKind, class: &SimpleEntity) -> EntityType { - if kind == EntityKind::StructDecl { - return EntityType::Struct; +fn update_entity_type_for_method( + class: &mut SimpleEntity, + builder: &mut ParsedClassInfo, + method: &Method, +) { + if class.entity_type == EntityType::Struct { + return; } - let has_data_members = !class.variables.is_empty(); - let mut has_abstract_methods = false; - let mut has_concrete_methods = false; - - for method in &class.methods { - let is_abstract = method - .modifiers - .iter() - .any(|m| matches!(m, MethodModifier::Abstract)); - let is_constructor_or_destructor = method - .modifiers - .iter() - .any(|m| matches!(m, MethodModifier::Constructor | MethodModifier::Destructor)); - - if is_abstract { - has_abstract_methods = true; - } else if !is_constructor_or_destructor { - has_concrete_methods = true; - } - } + update_method_flags(builder, method); - if has_abstract_methods { - if !has_concrete_methods && !has_data_members { - EntityType::Interface - } else { - EntityType::AbstractClass - } - } else { - EntityType::Class + class.entity_type = match ( + builder.has_abstract_methods, + builder.has_concrete_methods, + class.variables.is_empty(), + ) { + (true, false, true) => EntityType::Interface, + (true, _, _) => EntityType::AbstractClass, + _ => EntityType::Class, + }; +} + +fn update_method_flags(builder: &mut ParsedClassInfo, method: &Method) { + let is_abstract = method + .modifiers + .iter() + .any(|modifier| matches!(modifier, MethodModifier::Abstract)); + + let is_special_method = method.modifiers.iter().any(|modifier| { + matches!( + modifier, + MethodModifier::Constructor | MethodModifier::Destructor + ) + }); + + if is_abstract { + builder.has_abstract_methods = true; + } else if !is_special_method { + builder.has_concrete_methods = true; } } diff --git a/cpp/libclang/src/visitor/src/context.rs b/cpp/libclang/src/visitor/src/context.rs index 912cd868..c4777311 100644 --- a/cpp/libclang/src/visitor/src/context.rs +++ b/cpp/libclang/src/visitor/src/context.rs @@ -11,35 +11,82 @@ // SPDX-License-Identifier: Apache-2.0 // ******************************************************************************* -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use std::path::PathBuf; -use class_diagram::{SimpleEntity, SourceLocation}; +use class_diagram::{FreeFunctionDecl, FunctionArgument, Method, SimpleEntity, SourceLocation}; use cpp_semantics::{FunctionDef, ResolvedType}; use serde::{Deserialize, Serialize}; -pub type TypeMap = HashMap; - -/// Identifies a function definition within one parser execution. +/// Identifies an AST entity within one parser execution. /// -/// This source-position key deduplicates project header definitions visible +/// This source-position key deduplicates project header declarations and definitions visible /// through multiple translation units. It is not stable across source revisions. #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub struct FunctionDefinitionKey { +pub struct SourceEntityKey { pub source_file: PathBuf, pub source_offset: u32, } +/// Identifies a free function by its logical signature for class-diagram output. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct CallableIdentityKey { + pub owner: CallableOwnerIdentityKey, + pub name: String, + pub parameters: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum CallableOwnerIdentityKey { + FreeFunction { + enclosing_namespace_id: Option, + }, + Method { + class_id: String, + }, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct CallableArgumentIdentityKey { + pub param_type: Option, + pub is_variadic: bool, + pub is_pack_expansion: bool, +} + +impl From<&FunctionArgument> for CallableArgumentIdentityKey { + fn from(argument: &FunctionArgument) -> Self { + Self { + param_type: argument.param_type.clone(), + is_variadic: argument.is_variadic, + is_pack_expansion: argument.is_pack_expansion, + } + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ExtractedFunction { - pub key: FunctionDefinitionKey, + pub key: SourceEntityKey, pub definition: FunctionDef, } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ExtractedFreeFunctionDeclaration { + pub key: SourceEntityKey, + pub declaration: FreeFunctionDecl, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ExtractedMethodDeclaration { + pub class_id: String, + pub method: Method, + pub method_type: ParsedMethodType, +} + #[derive(Default, Debug, Clone, Serialize, Deserialize)] pub struct VisitContext { - pub types: TypeMap, - pub parsed_class_info: Vec, + pub types: BTreeMap, + pub parsed_class_info: HashMap, + pub free_function_declarations: Vec, pub functions: Vec, } @@ -49,6 +96,8 @@ pub struct ParsedClassInfo { pub base_classes: Vec, pub variable_types: Vec, pub method_types: Vec, + pub has_abstract_methods: bool, + pub has_concrete_methods: bool, } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/cpp/libclang/src/visitor/src/context_ext.rs b/cpp/libclang/src/visitor/src/context_ext.rs new file mode 100644 index 00000000..0bb714a7 --- /dev/null +++ b/cpp/libclang/src/visitor/src/context_ext.rs @@ -0,0 +1,177 @@ +// ******************************************************************************* +// Copyright (c) 2026 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* + +use log::warn; +use std::collections::BTreeMap; + +use class_diagram::SimpleEntity; + +pub trait EntityMapExt { + fn insert_or_merge_type(&mut self, type_name: String, entity: SimpleEntity); +} + +impl EntityMapExt for BTreeMap { + fn insert_or_merge_type(&mut self, type_name: String, entity: SimpleEntity) { + match self.get_mut(&type_name) { + Some(existing) => merge_simple_entity(existing, entity), + None => { + self.insert(type_name, entity); + } + } + } +} + +fn merge_simple_entity(existing: &mut SimpleEntity, incoming: SimpleEntity) { + if existing.name != incoming.name { + warn!( + "conflicting entity names while merging '{}': keeping {:?}, dropping {:?}", + existing.id, existing.name, incoming.name + ); + } + if existing.enclosing_namespace_id != incoming.enclosing_namespace_id { + warn!( + "conflicting enclosing namespaces while merging '{}': keeping {:?}, dropping {:?}", + existing.id, existing.enclosing_namespace_id, incoming.enclosing_namespace_id + ); + } + if existing.template_parameters.is_none() { + existing.template_parameters = incoming.template_parameters.clone(); + } + if existing.source_location == Default::default() { + existing.source_location = incoming.source_location.clone(); + } + + if existing.entity_type != incoming.entity_type { + warn!( + "conflicting entity types while merging '{}': keeping {:?}, dropping {:?}", + existing.id, existing.entity_type, incoming.entity_type + ); + } + + extend_unique(&mut existing.stereotypes, incoming.stereotypes); + extend_unique(&mut existing.type_aliases, incoming.type_aliases); + extend_unique(&mut existing.variables, incoming.variables); + extend_unique(&mut existing.methods, incoming.methods); + extend_unique(&mut existing.enum_literals, incoming.enum_literals); + extend_unique(&mut existing.relationships, incoming.relationships); +} + +fn extend_unique(existing: &mut Vec, incoming: Vec) { + for item in incoming { + if !existing.contains(&item) { + existing.push(item); + } + } +} + +#[cfg(test)] +mod tests { + use super::EntityMapExt; + use class_diagram::{EntityType, Method, SimpleEntity, SourceLocation, Visibility}; + use std::collections::BTreeMap; + + #[test] + fn insert_or_merge_type_preserves_members_when_later_entity_is_sparser() { + let mut types = BTreeMap::new(); + types.insert_or_merge_type( + "util::Widget".to_string(), + SimpleEntity { + id: "util::Widget".to_string(), + name: "Widget".to_string(), + enclosing_namespace_id: Some("util".to_string()), + entity_type: EntityType::Class, + methods: vec![Method { + name: "compute".to_string(), + return_type: Some("int".to_string()), + visibility: Visibility::Public, + source_location: SourceLocation::new("first.h", 10), + ..Default::default() + }], + source_location: SourceLocation::new("first.h", 1), + ..Default::default() + }, + ); + + types.insert_or_merge_type( + "util::Widget".to_string(), + SimpleEntity { + id: "util::Widget".to_string(), + name: "Widget".to_string(), + enclosing_namespace_id: Some("util".to_string()), + entity_type: EntityType::Class, + stereotypes: vec!["header-only".to_string()], + source_location: SourceLocation::new("second.h", 1), + ..Default::default() + }, + ); + + let widget = types.get("util::Widget").expect("merged type should exist"); + + assert_eq!(widget.methods.len(), 1); + assert_eq!(widget.methods[0].name, "compute"); + assert_eq!( + widget.methods[0].source_location, + SourceLocation::new("first.h", 10) + ); + assert_eq!(widget.stereotypes, vec!["header-only"]); + assert_eq!(widget.source_location, SourceLocation::new("first.h", 1)); + assert_eq!(widget.enclosing_namespace_id.as_deref(), Some("util")); + } + + #[test] + fn insert_or_merge_type_adds_missing_details_when_later_entity_is_richer() { + let mut types = BTreeMap::new(); + types.insert_or_merge_type( + "util::Widget".to_string(), + SimpleEntity { + id: "util::Widget".to_string(), + name: "Widget".to_string(), + enclosing_namespace_id: Some("util".to_string()), + entity_type: EntityType::Class, + ..Default::default() + }, + ); + + types.insert_or_merge_type( + "util::Widget".to_string(), + SimpleEntity { + id: "util::Widget".to_string(), + name: "Widget".to_string(), + enclosing_namespace_id: Some("util".to_string()), + entity_type: EntityType::Class, + methods: vec![Method { + name: "compute".to_string(), + return_type: Some("int".to_string()), + visibility: Visibility::Public, + source_location: SourceLocation::new("second.h", 10), + ..Default::default() + }], + stereotypes: vec!["header-only".to_string()], + source_location: SourceLocation::new("second.h", 1), + ..Default::default() + }, + ); + + let widget = types.get("util::Widget").expect("merged type should exist"); + + assert_eq!(widget.methods.len(), 1); + assert_eq!(widget.methods[0].name, "compute"); + assert_eq!( + widget.methods[0].source_location, + SourceLocation::new("second.h", 10) + ); + assert_eq!(widget.stereotypes, vec!["header-only"]); + assert_eq!(widget.source_location, SourceLocation::new("second.h", 1)); + assert_eq!(widget.enclosing_namespace_id.as_deref(), Some("util")); + } +} diff --git a/cpp/libclang/src/visitor/src/function_visitor.rs b/cpp/libclang/src/visitor/src/function_visitor.rs index 36cfc070..0b3e9328 100644 --- a/cpp/libclang/src/visitor/src/function_visitor.rs +++ b/cpp/libclang/src/visitor/src/function_visitor.rs @@ -15,16 +15,27 @@ //! Preserves structured calls, branches, and loops for supported AST shapes, //! and falls back to conservative traversal for unsupported control-flow forms. -use clang::{Entity, EntityKind}; +use clang::{Entity, EntityKind, ExceptionSpecification}; +use class_diagram::{FreeFunctionDecl, Method, MethodModifier}; use cpp_semantics::{ BodyItem, BranchCase, FunctionDef, FunctionId, FunctionKind, GuardExpression, LoopKind, + ResolvedType, Scope, }; use std::collections::HashSet; -use crate::clang_adapter::scope::callable_scope; +use crate::callable_declaration::{ + callable_arguments, parse_callable_return_type, parse_function_parameters, + parse_template_parameters, +}; +use crate::clang_adapter::scope::{callable_scope, namespace_id}; use crate::clang_adapter::source_filter; use crate::clang_adapter::source_location::parse_source_location; -use crate::context::{ExtractedFunction, FunctionDefinitionKey}; +use crate::class_visitor::{parse_visibility, ClassVisitor}; +use crate::context::{ + CallableArgumentIdentityKey, CallableIdentityKey, CallableOwnerIdentityKey, + ExtractedFreeFunctionDeclaration, ExtractedFunction, ExtractedMethodDeclaration, + ParsedMethodType, SourceEntityKey, +}; use crate::types::resolver::resolve_type; use crate::visitor::{normalize_source_identity_path, SourceFileCache}; use crate::VisitContext; @@ -43,24 +54,204 @@ impl FunctionVisitor { pub(crate) fn visit_with_state( ctx: &mut VisitContext, source_files: &mut SourceFileCache, - seen_function_definitions: &mut HashSet, + seen_free_function_declarations: &mut HashSet, + seen_method_declarations: &mut HashSet, + seen_function_definitions: &mut HashSet, entity: Entity, ) { - if let Some(function) = - Self::extract_function_def(source_files, seen_function_definitions, entity) - { + let Some((function_id, function_kind)) = Self::extract_callable(&entity) else { + return; + }; + + match &function_id.scope { + Scope::Type { .. } => { + if let Some(declaration) = Self::extract_method_declaration( + seen_method_declarations, + &entity, + &function_id, + function_kind, + ) { + ClassVisitor::add_method_declaration(ctx, declaration); + } else { + log::debug!( + "skipping type-scoped callable '{}': unsupported function kind {:?}", + function_id.qualified_name(), + function_kind + ); + } + } + Scope::Global | Scope::Namespace(_) => { + if let Some(declaration) = Self::extract_free_function_declaration( + seen_free_function_declarations, + &entity, + &function_id, + ) { + ctx.free_function_declarations.push(declaration); + } + } + } + + if let Some(function) = Self::extract_function_def( + entity, + function_id, + function_kind, + source_files, + seen_function_definitions, + ) { ctx.functions.push(function); } } // ── Top-level extraction ────────────────────────────────────────────────── + fn extract_method_declaration( + seen_method_declarations: &mut HashSet, + entity: &Entity, + id: &FunctionId, + kind: FunctionKind, + ) -> Option { + if !matches!( + kind, + FunctionKind::Method + | FunctionKind::StaticMethod + | FunctionKind::Constructor + | FunctionKind::Destructor + ) { + return None; + } + + let parameters = parse_function_parameters(entity); + let class_id = id.scope.qualified_name(); + if !Self::insert_callable_identity( + seen_method_declarations, + CallableOwnerIdentityKey::Method { + class_id: class_id.clone(), + }, + &id.name, + ¶meters, + ) { + return None; + } + + let return_type = entity + .get_result_type() + .map(|ty| resolve_type(&ty)) + .unwrap_or_else(|| ResolvedType::Builtin("void".to_string())); + let method_type = ParsedMethodType { + name: id.name.clone(), + return_type: return_type.clone(), + parameter_types: callable_arguments(entity) + .into_iter() + .filter_map(|argument| argument.get_type().map(|ty| resolve_type(&ty))) + .collect(), + source_location: parse_source_location(entity), + }; + + let is_override_method = entity + .get_overridden_methods() + .is_some_and(|methods| !methods.is_empty()); + let is_final_method = entity + .get_children() + .into_iter() + .any(|child| child.get_kind() == EntityKind::FinalAttr); + + // Only the bare `noexcept` specifier is modeled (mirrors the PlantUML grammar, which has + // no support for the conditional `noexcept(expr)` form). Requiring `BasicNoexcept` filters + // out `noexcept(expr)`, but on its own it isn't enough: for an implicit/defaulted special + // member (e.g. `~Foo() = default;` with no written specifier at all), the compiler-computed + // specification also resolves to `BasicNoexcept` once evaluated -- and that evaluation is + // lazily triggered by unrelated code (e.g. a derived class use), making it unstable. So this + // also requires the literal `noexcept` token to appear in the declarator (the tokens up to + // the first `{` or `;`), which excludes both that case and `noexcept` written inside a + // lambda in the method body. + let has_noexcept_token = entity.get_range().is_some_and(|range| { + range + .tokenize() + .iter() + .take_while(|token| !matches!(token.get_spelling().as_str(), "{" | ";")) + .any(|token| token.get_spelling() == "noexcept") + }); + + let is_noexcept_method = has_noexcept_token + && matches!( + entity.get_exception_specification(), + Some(ExceptionSpecification::BasicNoexcept) + ); + + let return_type = if matches!(kind, FunctionKind::Constructor | FunctionKind::Destructor) { + None + } else { + parse_callable_return_type(entity) + }; + + let method = Method { + name: id.name.clone(), + return_type, + visibility: parse_visibility(entity), + parameters, + template_parameters: parse_template_parameters(entity), + modifiers: MethodModifier::from_conditions([ + (entity.is_static_method(), MethodModifier::Static), + (entity.is_virtual_method(), MethodModifier::Virtual), + (entity.is_pure_virtual_method(), MethodModifier::Abstract), + (is_override_method, MethodModifier::Override), + (is_noexcept_method, MethodModifier::Noexcept), + ( + kind == FunctionKind::Constructor, + MethodModifier::Constructor, + ), + (kind == FunctionKind::Destructor, MethodModifier::Destructor), + (is_final_method, MethodModifier::Final), + ]), + source_location: parse_source_location(entity), + }; + + Some(ExtractedMethodDeclaration { + class_id, + method, + method_type, + }) + } + + fn extract_free_function_declaration( + seen_free_function_declarations: &mut HashSet, + entity: &Entity, + id: &FunctionId, + ) -> Option { + let key = Self::extract_source_entity_key(entity)?; + let parameters = parse_function_parameters(entity); + if !Self::insert_callable_identity( + seen_free_function_declarations, + CallableOwnerIdentityKey::FreeFunction { + enclosing_namespace_id: namespace_id(entity), + }, + &id.name, + ¶meters, + ) { + return None; + } + + Some(ExtractedFreeFunctionDeclaration { + key, + declaration: FreeFunctionDecl { + name: id.name.clone(), + enclosing_namespace_id: namespace_id(entity), + return_type: parse_callable_return_type(entity), + parameters, + template_parameters: parse_template_parameters(entity), + source_location: parse_source_location(entity), + }, + }) + } + fn extract_function_def( - source_files: &mut SourceFileCache, - seen_function_definitions: &mut HashSet, entity: Entity, + id: FunctionId, + kind: FunctionKind, + source_files: &mut SourceFileCache, + seen_function_definitions: &mut HashSet, ) -> Option { - let key = Self::extract_definition_key(&entity)?; + let key = Self::extract_source_entity_key(&entity)?; if seen_function_definitions.contains(&key) { log::debug!( @@ -71,23 +262,6 @@ impl FunctionVisitor { return None; } - let Some(id) = Self::extract_function_id(&entity) else { - log::debug!( - "skipping callable '{}': no supported function identity", - entity.get_name().unwrap_or_default() - ); - return None; - }; - - let Some(kind) = Self::extract_function_kind(&entity) else { - log::debug!( - "skipping callable '{}': unsupported callable kind {:?}", - id.qualified_name(), - entity.get_kind() - ); - return None; - }; - let Some(body) = Self::process_function_body(source_files, entity, &id) else { log::debug!( "skipping callable '{}': no compound statement body (declaration-only?)", @@ -116,8 +290,30 @@ impl FunctionVisitor { Some(extracted_function) } + fn insert_callable_identity( + seen_declarations: &mut HashSet, + owner: CallableOwnerIdentityKey, + name: &str, + parameters: &[class_diagram::FunctionArgument], + ) -> bool { + seen_declarations.insert(CallableIdentityKey { + owner, + name: name.to_string(), + parameters: parameters + .iter() + .map(CallableArgumentIdentityKey::from) + .collect(), + }) + } + // ── AST navigation helpers ──────────────────────────────────────────────── + fn extract_callable(entity: &Entity) -> Option<(FunctionId, FunctionKind)> { + let function_id = Self::extract_function_id(entity)?; + let function_kind = Self::extract_function_kind(entity, &function_id.scope)?; + Some((function_id, function_kind)) + } + fn extract_function_id(entity: &Entity) -> Option { Some(FunctionId { scope: callable_scope(entity)?, @@ -125,9 +321,32 @@ impl FunctionVisitor { }) } - fn extract_definition_key(entity: &Entity) -> Option { + fn extract_function_kind(entity: &Entity, scope: &Scope) -> Option { + match entity.get_kind() { + EntityKind::FunctionDecl => Some(FunctionKind::Free), + EntityKind::FunctionTemplate => match scope { + Scope::Type { .. } => Some(Self::method_function_kind(entity)), + Scope::Global | Scope::Namespace(_) => Some(FunctionKind::Free), + }, + EntityKind::Method => Some(Self::method_function_kind(entity)), + EntityKind::Constructor => Some(FunctionKind::Constructor), + EntityKind::Destructor => Some(FunctionKind::Destructor), + EntityKind::ConversionFunction => Some(FunctionKind::Conversion), + _ => None, + } + } + + fn method_function_kind(entity: &Entity) -> FunctionKind { + if entity.is_static_method() { + FunctionKind::StaticMethod + } else { + FunctionKind::Method + } + } + + fn extract_source_entity_key(entity: &Entity) -> Option { let location = entity.get_location()?.get_file_location(); - Some(FunctionDefinitionKey { + Some(SourceEntityKey { source_file: normalize_source_identity_path(&location.file?.get_path()), source_offset: location.offset, }) @@ -164,31 +383,6 @@ impl FunctionVisitor { .unwrap_or_default() } - fn extract_function_kind(entity: &Entity) -> Option { - match entity.get_kind() { - EntityKind::FunctionDecl => Some(FunctionKind::Free), - EntityKind::FunctionTemplate => match callable_scope(entity)? { - cpp_semantics::Scope::Type { .. } => Some(if entity.is_static_method() { - FunctionKind::StaticMethod - } else { - FunctionKind::Method - }), - cpp_semantics::Scope::Global | cpp_semantics::Scope::Namespace(_) => { - Some(FunctionKind::Free) - } - }, - EntityKind::Method => Some(if entity.is_static_method() { - FunctionKind::StaticMethod - } else { - FunctionKind::Method - }), - EntityKind::Constructor => Some(FunctionKind::Constructor), - EntityKind::Destructor => Some(FunctionKind::Destructor), - EntityKind::ConversionFunction => Some(FunctionKind::Conversion), - _ => None, - } - } - /// Resolves a call expression to its semantic callable target. fn extract_call_target(call_expr: Entity) -> Option { // Direct reference works for simple `obj.method()` calls. @@ -205,8 +399,7 @@ impl FunctionVisitor { return None; } - Self::extract_function_kind(&resolved)?; - Self::extract_function_id(&resolved) + Self::extract_callable(&resolved).map(|(function_id, _)| function_id) } fn is_cross_owner_call(caller: &FunctionId, callee: &FunctionId) -> bool { diff --git a/cpp/libclang/src/visitor/src/lib.rs b/cpp/libclang/src/visitor/src/lib.rs index 9e87d26d..145a9bee 100644 --- a/cpp/libclang/src/visitor/src/lib.rs +++ b/cpp/libclang/src/visitor/src/lib.rs @@ -11,10 +11,12 @@ // SPDX-License-Identifier: Apache-2.0 // ******************************************************************************* +mod callable_declaration; mod clang_adapter; mod class_relationship_resolver; mod class_visitor; pub mod context; +mod context_ext; mod enum_visitor; mod function_visitor; mod types; @@ -24,7 +26,8 @@ pub use cpp_semantics::{BodyItem, FunctionDef, ResolvedType}; pub use clang_adapter::source_filter::is_external_dependency_path; pub use class_visitor::ClassVisitor; -pub use context::{FunctionDefinitionKey, VisitContext}; +pub use context::{CallableIdentityKey, CallableOwnerIdentityKey, SourceEntityKey, VisitContext}; +pub use context_ext::EntityMapExt; pub use enum_visitor::EnumVisitor; pub use function_visitor::FunctionVisitor; pub use visitor::{AstVisitor, SourceFileCache, Visitor}; diff --git a/cpp/libclang/src/visitor/src/visitor.rs b/cpp/libclang/src/visitor/src/visitor.rs index cc49b8e5..0564fdba 100644 --- a/cpp/libclang/src/visitor/src/visitor.rs +++ b/cpp/libclang/src/visitor/src/visitor.rs @@ -19,7 +19,7 @@ use log::warn; use crate::clang_adapter::source_filter; use crate::class_visitor::ClassVisitor; -use crate::context::{FunctionDefinitionKey, VisitContext}; +use crate::context::{CallableIdentityKey, SourceEntityKey, VisitContext}; use crate::enum_visitor::EnumVisitor; use crate::function_visitor::FunctionVisitor; @@ -58,18 +58,24 @@ impl SourceFileCache { pub struct Visitor<'a> { ctx: &'a mut VisitContext, source_files: &'a mut SourceFileCache, - seen_function_definitions: &'a mut HashSet, + seen_free_function_declarations: &'a mut HashSet, + seen_method_declarations: &'a mut HashSet, + seen_function_definitions: &'a mut HashSet, } impl<'a> Visitor<'a> { pub fn new( ctx: &'a mut VisitContext, source_files: &'a mut SourceFileCache, - seen_function_definitions: &'a mut HashSet, + seen_free_function_declarations: &'a mut HashSet, + seen_method_declarations: &'a mut HashSet, + seen_function_definitions: &'a mut HashSet, ) -> Self { Self { ctx, source_files, + seen_free_function_declarations, + seen_method_declarations, seen_function_definitions, } } @@ -92,19 +98,22 @@ impl<'a> Visitor<'a> { ClassVisitor::visit(self.ctx, entity); } EntityKind::EnumDecl => EnumVisitor::visit(self.ctx, entity), - EntityKind::FunctionDecl | EntityKind::FunctionTemplate | EntityKind::Method => { + EntityKind::FunctionDecl + | EntityKind::FunctionTemplate + | EntityKind::Method + | EntityKind::Constructor + | EntityKind::Destructor => { FunctionVisitor::visit_with_state( self.ctx, self.source_files, + self.seen_free_function_declarations, + self.seen_method_declarations, self.seen_function_definitions, entity, ); } - EntityKind::Constructor | EntityKind::Destructor | EntityKind::ConversionFunction => { - warn!( - "Ignoring constructor, destructor, or conversion function: {:?}", - entity - ); + EntityKind::ConversionFunction => { + warn!("Ignoring conversion function: {:?}", entity); } _ => {} } From f557a74576d96f63e66d5e1ce15ffcc4b4a8c852 Mon Sep 17 00:00:00 2001 From: Melody Ma Date: Sun, 20 Sep 2026 16:45:59 +0800 Subject: [PATCH 3/5] [Cpp test] update free function declaration integration tests --- .vscode/settings.json | 3 +- .../cases/definition_then_forward_decl/BUILD | 32 +++ .../expected.json | 33 +++ .../definition_then_forward_decl/first.cpp | 14 + .../definition_then_forward_decl/run_test.rs | 19 ++ .../definition_then_forward_decl/second.cpp | 14 + .../widget_forward.h | 18 ++ .../widget_full.h | 21 ++ .../dependent_decltype_base/expected.json | 55 ++++ .../class_method_template_body/expected.json | 13 + .../explicit_specialization/expected.json | 53 +++- .../free_function_identity/BUILD | 3 +- .../free_function_identity/expected.json | 46 +++ .../free_function_identity/functions.cpp | 2 +- .../free_function_identity/functions.hpp | 25 ++ .../free_function_template/expected.json | 54 ++++ .../guard_associativity/expected.json | 269 +++++++++++++++--- .../expected.json | 35 +++ .../expected.json | 119 +++++--- .../expected.json | 70 ++++- .../if_complex_condition/expected.json | 81 +++++- .../if_else_chain/expected.json | 89 +++++- .../if_initializer_fallback/expected.json | 134 ++++++--- .../if_without_braces/expected.json | 67 ++++- .../if_without_else/expected.json | 52 +++- .../expected.json | 13 + .../function_cases/nested_if/expected.json | 76 ++++- .../out_of_line_method_dedup/BUILD | 26 ++ .../out_of_line_method_dedup/expected.json | 54 ++++ .../out_of_line_method_dedup/functions.cpp | 16 ++ .../out_of_line_method_dedup/functions.hpp | 18 ++ .../out_of_line_method_dedup/run_test.rs | 19 ++ .../expected.json | 29 +- .../expected.json | 47 ++- 34 files changed, 1393 insertions(+), 226 deletions(-) create mode 100644 cpp/libclang/integration_test/cases/definition_then_forward_decl/BUILD create mode 100644 cpp/libclang/integration_test/cases/definition_then_forward_decl/expected.json create mode 100644 cpp/libclang/integration_test/cases/definition_then_forward_decl/first.cpp create mode 100644 cpp/libclang/integration_test/cases/definition_then_forward_decl/run_test.rs create mode 100644 cpp/libclang/integration_test/cases/definition_then_forward_decl/second.cpp create mode 100644 cpp/libclang/integration_test/cases/definition_then_forward_decl/widget_forward.h create mode 100644 cpp/libclang/integration_test/cases/definition_then_forward_decl/widget_full.h create mode 100644 cpp/libclang/integration_test/function_cases/free_function_identity/functions.hpp create mode 100644 cpp/libclang/integration_test/function_cases/out_of_line_method_dedup/BUILD create mode 100644 cpp/libclang/integration_test/function_cases/out_of_line_method_dedup/expected.json create mode 100644 cpp/libclang/integration_test/function_cases/out_of_line_method_dedup/functions.cpp create mode 100644 cpp/libclang/integration_test/function_cases/out_of_line_method_dedup/functions.hpp create mode 100644 cpp/libclang/integration_test/function_cases/out_of_line_method_dedup/run_test.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index f7df77b6..68aa8003 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -16,5 +16,6 @@ }, "python.analysis.typeCheckingMode": "off", "python.testing.unittestEnabled": false, - "python.testing.pytestEnabled": true + "python.testing.pytestEnabled": true, + "git.ignoreLimitWarning": true } diff --git a/cpp/libclang/integration_test/cases/definition_then_forward_decl/BUILD b/cpp/libclang/integration_test/cases/definition_then_forward_decl/BUILD new file mode 100644 index 00000000..121df8d0 --- /dev/null +++ b/cpp/libclang/integration_test/cases/definition_then_forward_decl/BUILD @@ -0,0 +1,32 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* +load("//cpp/libclang/integration_test:test_rules.bzl", "cpp_parser_integration_test") + +cc_library( + name = "definition_then_forward_decl", + srcs = [ + "first.cpp", + "second.cpp", + ], + hdrs = [ + "widget_forward.h", + "widget_full.h", + ], + visibility = ["//cpp/libclang:__subpackages__"], +) + +cpp_parser_integration_test( + name = "test_definition_then_forward_decl", + expected_output = ["expected.json"], + target = ":definition_then_forward_decl", +) diff --git a/cpp/libclang/integration_test/cases/definition_then_forward_decl/expected.json b/cpp/libclang/integration_test/cases/definition_then_forward_decl/expected.json new file mode 100644 index 00000000..afdac130 --- /dev/null +++ b/cpp/libclang/integration_test/cases/definition_then_forward_decl/expected.json @@ -0,0 +1,33 @@ +{ + "types": { + "util::Widget": { + "id": "util::Widget", + "name": "Widget", + "enclosing_namespace_id": "util", + "stereotypes": [], + "entity_type": "Class", + "type_aliases": [], + "variables": [ + { + "name": "value", + "data_type": "int", + "visibility": "public", + "is_static": false, + "source_location": { + "file": "cpp/libclang/integration_test/cases/definition_then_forward_decl/widget_full.h", + "line": 19 + } + } + ], + "methods": [], + "template_parameters": null, + "enum_literals": [], + "relationships": [], + "source_location": { + "file": "cpp/libclang/integration_test/cases/definition_then_forward_decl/widget_full.h", + "line": 17 + } + } + }, + "functions": [] +} diff --git a/cpp/libclang/integration_test/cases/definition_then_forward_decl/first.cpp b/cpp/libclang/integration_test/cases/definition_then_forward_decl/first.cpp new file mode 100644 index 00000000..b1d38b63 --- /dev/null +++ b/cpp/libclang/integration_test/cases/definition_then_forward_decl/first.cpp @@ -0,0 +1,14 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +#include "widget_forward.h" diff --git a/cpp/libclang/integration_test/cases/definition_then_forward_decl/run_test.rs b/cpp/libclang/integration_test/cases/definition_then_forward_decl/run_test.rs new file mode 100644 index 00000000..6579d290 --- /dev/null +++ b/cpp/libclang/integration_test/cases/definition_then_forward_decl/run_test.rs @@ -0,0 +1,19 @@ +// ******************************************************************************* +// Copyright (c) 2026 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* + +use test_framework::run_parser_case; + +#[test] +fn test_definition_then_forward_decl() { + run_parser_case(); +} diff --git a/cpp/libclang/integration_test/cases/definition_then_forward_decl/second.cpp b/cpp/libclang/integration_test/cases/definition_then_forward_decl/second.cpp new file mode 100644 index 00000000..9b58f0bf --- /dev/null +++ b/cpp/libclang/integration_test/cases/definition_then_forward_decl/second.cpp @@ -0,0 +1,14 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +#include "widget_full.h" diff --git a/cpp/libclang/integration_test/cases/definition_then_forward_decl/widget_forward.h b/cpp/libclang/integration_test/cases/definition_then_forward_decl/widget_forward.h new file mode 100644 index 00000000..f1644243 --- /dev/null +++ b/cpp/libclang/integration_test/cases/definition_then_forward_decl/widget_forward.h @@ -0,0 +1,18 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +#pragma once + +namespace util { +class Widget; +} // namespace util diff --git a/cpp/libclang/integration_test/cases/definition_then_forward_decl/widget_full.h b/cpp/libclang/integration_test/cases/definition_then_forward_decl/widget_full.h new file mode 100644 index 00000000..4c8804bc --- /dev/null +++ b/cpp/libclang/integration_test/cases/definition_then_forward_decl/widget_full.h @@ -0,0 +1,21 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +#pragma once + +namespace util { +class Widget { +public: + int value; +}; +} // namespace util diff --git a/cpp/libclang/integration_test/cases/dependent_decltype_base/expected.json b/cpp/libclang/integration_test/cases/dependent_decltype_base/expected.json index 60d04f3f..3691259d 100644 --- a/cpp/libclang/integration_test/cases/dependent_decltype_base/expected.json +++ b/cpp/libclang/integration_test/cases/dependent_decltype_base/expected.json @@ -148,5 +148,60 @@ } } }, + "free_function_declarations": [ + { + "name": "declval", + "enclosing_namespace_id": null, + "return_type": "T", + "parameters": [], + "template_parameters": [ + { + "Type": { + "name": "T", + "is_pack": false + } + } + ], + "source_location": { + "file": "cpp/libclang/integration_test/cases/dependent_decltype_base/dependent_base.cpp", + "line": 19 + } + }, + { + "name": "is_maplike_container_impl", + "enclosing_namespace_id": null, + "return_type": "decltype(value.begin())", + "parameters": [ + { + "name": "value", + "param_type": "T", + "is_variadic": false + } + ], + "template_parameters": [ + { + "Type": { + "name": "T", + "is_pack": false + } + } + ], + "source_location": { + "file": "cpp/libclang/integration_test/cases/dependent_decltype_base/dependent_base.cpp", + "line": 22 + } + }, + { + "name": "make_widget", + "enclosing_namespace_id": null, + "return_type": "Widget", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/cases/dependent_decltype_base/dependent_base.cpp", + "line": 43 + } + } + ], "functions": [] } diff --git a/cpp/libclang/integration_test/function_cases/class_method_template_body/expected.json b/cpp/libclang/integration_test/function_cases/class_method_template_body/expected.json index 27632630..ec6797c9 100644 --- a/cpp/libclang/integration_test/function_cases/class_method_template_body/expected.json +++ b/cpp/libclang/integration_test/function_cases/class_method_template_body/expected.json @@ -1,4 +1,17 @@ { + "free_function_declarations": [ + { + "name": "notify", + "enclosing_namespace_id": null, + "return_type": "void", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/class_method_template_body/functions.cpp", + "line": 14 + } + } + ], "functions": [ { "id": { diff --git a/cpp/libclang/integration_test/function_cases/explicit_specialization/expected.json b/cpp/libclang/integration_test/function_cases/explicit_specialization/expected.json index 810b7872..b083b7cf 100644 --- a/cpp/libclang/integration_test/function_cases/explicit_specialization/expected.json +++ b/cpp/libclang/integration_test/function_cases/explicit_specialization/expected.json @@ -1,13 +1,57 @@ { + "types": {}, + "free_function_declarations": [ + { + "name": "specialized", + "enclosing_namespace_id": "utility", + "return_type": "T", + "parameters": [ + { + "name": "value", + "param_type": "T", + "is_variadic": false + } + ], + "template_parameters": [ + { + "Type": { + "name": "T", + "is_pack": false + } + } + ], + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/explicit_specialization/specialization.h", + "line": 21 + } + }, + { + "name": "specialized", + "enclosing_namespace_id": "utility", + "return_type": "int", + "parameters": [ + { + "name": "value", + "param_type": "int", + "is_variadic": false + } + ], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/explicit_specialization/specialization.h", + "line": 25 + } + } + ], "functions": [ { "id": { - "name": "specialized", "scope": { "Namespace": [ "utility" ] - } + }, + "name": "specialized" }, "kind": "Free", "return_type": { @@ -15,6 +59,5 @@ }, "body": [] } - ], - "types": {} -} + ] +} \ No newline at end of file diff --git a/cpp/libclang/integration_test/function_cases/free_function_identity/BUILD b/cpp/libclang/integration_test/function_cases/free_function_identity/BUILD index 63de8b23..e4d6e6cb 100644 --- a/cpp/libclang/integration_test/function_cases/free_function_identity/BUILD +++ b/cpp/libclang/integration_test/function_cases/free_function_identity/BUILD @@ -14,7 +14,8 @@ load("//cpp/libclang/integration_test:test_rules.bzl", "cpp_parser_integration_t cc_library( name = "free_function_identity", - srcs = glob(["*.cpp"]), + srcs = ["functions.cpp"], + hdrs = ["functions.hpp"], visibility = ["//cpp/libclang:__subpackages__"], ) diff --git a/cpp/libclang/integration_test/function_cases/free_function_identity/expected.json b/cpp/libclang/integration_test/function_cases/free_function_identity/expected.json index f24885bc..ef0778f4 100644 --- a/cpp/libclang/integration_test/function_cases/free_function_identity/expected.json +++ b/cpp/libclang/integration_test/function_cases/free_function_identity/expected.json @@ -1,4 +1,50 @@ { + "free_function_declarations": [ + { + "name": "declared_only", + "enclosing_namespace_id": null, + "return_type": "void", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/free_function_identity/functions.hpp", + "line": 16 + } + }, + { + "name": "global_value", + "enclosing_namespace_id": null, + "return_type": "int", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/free_function_identity/functions.hpp", + "line": 18 + } + }, + { + "name": "run", + "enclosing_namespace_id": "app", + "return_type": "void", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/free_function_identity/functions.hpp", + "line": 23 + } + }, + { + "name": "enabled", + "enclosing_namespace_id": "app::internal", + "return_type": "bool", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/free_function_identity/functions.cpp", + "line": 28 + } + } + ], "functions": [ { "id": { diff --git a/cpp/libclang/integration_test/function_cases/free_function_identity/functions.cpp b/cpp/libclang/integration_test/function_cases/free_function_identity/functions.cpp index df8ab3a9..174a79ce 100644 --- a/cpp/libclang/integration_test/function_cases/free_function_identity/functions.cpp +++ b/cpp/libclang/integration_test/function_cases/free_function_identity/functions.cpp @@ -11,7 +11,7 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -void declared_only(); +#include "functions.hpp" int global_value() { return 1; diff --git a/cpp/libclang/integration_test/function_cases/free_function_identity/functions.hpp b/cpp/libclang/integration_test/function_cases/free_function_identity/functions.hpp new file mode 100644 index 00000000..990b7f3f --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/free_function_identity/functions.hpp @@ -0,0 +1,25 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +#pragma once + +void declared_only(); + +int global_value(); + +namespace app +{ + +void run(); + +} // namespace app diff --git a/cpp/libclang/integration_test/function_cases/free_function_template/expected.json b/cpp/libclang/integration_test/function_cases/free_function_template/expected.json index f6b56ecc..1edab3ae 100644 --- a/cpp/libclang/integration_test/function_cases/free_function_template/expected.json +++ b/cpp/libclang/integration_test/function_cases/free_function_template/expected.json @@ -1,4 +1,58 @@ { + "free_function_declarations": [ + { + "name": "consume", + "enclosing_namespace_id": null, + "return_type": "void", + "parameters": [ + { + "name": "", + "param_type": "int", + "is_variadic": false + } + ], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/free_function_template/optional.h", + "line": 14 + } + }, + { + "name": "make_optional", + "enclosing_namespace_id": "amp", + "return_type": "int", + "parameters": [ + { + "name": "value", + "param_type": "T &&", + "is_variadic": false + } + ], + "template_parameters": [ + { + "Type": { + "name": "T", + "is_pack": false + } + } + ], + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/free_function_template/optional.h", + "line": 18 + } + }, + { + "name": "run", + "enclosing_namespace_id": null, + "return_type": "int", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/free_function_template/functions.cpp", + "line": 18 + } + } + ], "functions": [ { "id": { diff --git a/cpp/libclang/integration_test/function_cases/guard_associativity/expected.json b/cpp/libclang/integration_test/function_cases/guard_associativity/expected.json index 677d3592..c3f8119c 100644 --- a/cpp/libclang/integration_test/function_cases/guard_associativity/expected.json +++ b/cpp/libclang/integration_test/function_cases/guard_associativity/expected.json @@ -1,13 +1,207 @@ { + "types": { + "Flag": { + "id": "Flag", + "name": "Flag", + "enclosing_namespace_id": null, + "stereotypes": [], + "entity_type": "Struct", + "type_aliases": [], + "variables": [], + "methods": [], + "template_parameters": null, + "enum_literals": [], + "relationships": [], + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/guard_associativity/flow.cpp", + "line": 30 + } + } + }, + "free_function_declarations": [ + { + "name": "first", + "enclosing_namespace_id": null, + "return_type": "bool", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/guard_associativity/flow.cpp", + "line": 14 + } + }, + { + "name": "second", + "enclosing_namespace_id": null, + "return_type": "bool", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/guard_associativity/flow.cpp", + "line": 15 + } + }, + { + "name": "third", + "enclosing_namespace_id": null, + "return_type": "bool", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/guard_associativity/flow.cpp", + "line": 16 + } + }, + { + "name": "handle_and", + "enclosing_namespace_id": null, + "return_type": "void", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/guard_associativity/flow.cpp", + "line": 17 + } + }, + { + "name": "handle_or", + "enclosing_namespace_id": null, + "return_type": "void", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/guard_associativity/flow.cpp", + "line": 18 + } + }, + { + "name": "handle_mixed", + "enclosing_namespace_id": null, + "return_type": "void", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/guard_associativity/flow.cpp", + "line": 19 + } + }, + { + "name": "handle_alternative", + "enclosing_namespace_id": null, + "return_type": "void", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/guard_associativity/flow.cpp", + "line": 20 + } + }, + { + "name": "handle_template_operand", + "enclosing_namespace_id": null, + "return_type": "void", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/guard_associativity/flow.cpp", + "line": 21 + } + }, + { + "name": "handle_operator_function", + "enclosing_namespace_id": null, + "return_type": "void", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/guard_associativity/flow.cpp", + "line": 22 + } + }, + { + "name": "check_template", + "enclosing_namespace_id": null, + "return_type": "bool", + "parameters": [], + "template_parameters": [ + { + "NonType": { + "name": "", + "value_type": "bool", + "is_pack": false + } + } + ], + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/guard_associativity/flow.cpp", + "line": 25 + } + }, + { + "name": "first_flag", + "enclosing_namespace_id": null, + "return_type": "Flag", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/guard_associativity/flow.cpp", + "line": 31 + } + }, + { + "name": "second_flag", + "enclosing_namespace_id": null, + "return_type": "Flag", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/guard_associativity/flow.cpp", + "line": 32 + } + }, + { + "name": "operator&&", + "enclosing_namespace_id": null, + "return_type": "bool", + "parameters": [ + { + "name": "", + "param_type": "Flag", + "is_variadic": false + }, + { + "name": "", + "param_type": "Flag", + "is_variadic": false + } + ], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/guard_associativity/flow.cpp", + "line": 34 + } + }, + { + "name": "guard_associativity", + "enclosing_namespace_id": "flow", + "return_type": "void", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/guard_associativity/flow.cpp", + "line": 37 + } + } + ], "functions": [ { "id": { - "name": "guard_associativity", "scope": { "Namespace": [ "flow" ] - } + }, + "name": "guard_associativity" }, "kind": "Free", "return_type": { @@ -15,6 +209,7 @@ }, "body": [ { + "type": "branch", "cases": [ { "guard": { @@ -51,12 +246,12 @@ }, "body": [ { + "type": "call", "target": "handle_and", "source_location": { "file": "cpp/libclang/integration_test/function_cases/guard_associativity/flow.cpp", "line": 40 - }, - "type": "call" + } } ], "source_location": { @@ -64,10 +259,10 @@ "line": 39 } } - ], - "type": "branch" + ] }, { + "type": "branch", "cases": [ { "guard": { @@ -109,12 +304,12 @@ }, "body": [ { + "type": "call", "target": "handle_or", "source_location": { "file": "cpp/libclang/integration_test/function_cases/guard_associativity/flow.cpp", "line": 45 - }, - "type": "call" + } } ], "source_location": { @@ -122,10 +317,10 @@ "line": 44 } } - ], - "type": "branch" + ] }, { + "type": "branch", "cases": [ { "guard": { @@ -167,12 +362,12 @@ }, "body": [ { + "type": "call", "target": "handle_mixed", "source_location": { "file": "cpp/libclang/integration_test/function_cases/guard_associativity/flow.cpp", "line": 50 - }, - "type": "call" + } } ], "source_location": { @@ -180,10 +375,10 @@ "line": 49 } } - ], - "type": "branch" + ] }, { + "type": "branch", "cases": [ { "guard": { @@ -211,12 +406,12 @@ }, "body": [ { + "type": "call", "target": "handle_alternative", "source_location": { "file": "cpp/libclang/integration_test/function_cases/guard_associativity/flow.cpp", "line": 55 - }, - "type": "call" + } } ], "source_location": { @@ -224,10 +419,10 @@ "line": 54 } } - ], - "type": "branch" + ] }, { + "type": "branch", "cases": [ { "guard": { @@ -240,12 +435,12 @@ }, "body": [ { + "type": "call", "target": "handle_template_operand", "source_location": { "file": "cpp/libclang/integration_test/function_cases/guard_associativity/flow.cpp", "line": 60 - }, - "type": "call" + } } ], "source_location": { @@ -253,10 +448,10 @@ "line": 59 } } - ], - "type": "branch" + ] }, { + "type": "branch", "cases": [ { "guard": { @@ -269,12 +464,12 @@ }, "body": [ { + "type": "call", "target": "handle_operator_function", "source_location": { "file": "cpp/libclang/integration_test/function_cases/guard_associativity/flow.cpp", "line": 65 - }, - "type": "call" + } } ], "source_location": { @@ -282,29 +477,9 @@ "line": 64 } } - ], - "type": "branch" + ] } ] } - ], - "types": { - "Flag": { - "id": "Flag", - "name": "Flag", - "enclosing_namespace_id": null, - "stereotypes": [], - "entity_type": "Struct", - "type_aliases": [], - "variables": [], - "methods": [], - "template_parameters": null, - "enum_literals": [], - "relationships": [], - "source_location": { - "file": "cpp/libclang/integration_test/function_cases/guard_associativity/flow.cpp", - "line": 30 - } - } - } -} + ] +} \ No newline at end of file diff --git a/cpp/libclang/integration_test/function_cases/header_inline_function_dedup/expected.json b/cpp/libclang/integration_test/function_cases/header_inline_function_dedup/expected.json index abd289ab..53e65328 100644 --- a/cpp/libclang/integration_test/function_cases/header_inline_function_dedup/expected.json +++ b/cpp/libclang/integration_test/function_cases/header_inline_function_dedup/expected.json @@ -1,4 +1,39 @@ { + "free_function_declarations": [ + { + "name": "shared", + "enclosing_namespace_id": null, + "return_type": "int", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/header_inline_function_dedup/inline_function.h", + "line": 16 + } + }, + { + "name": "first", + "enclosing_namespace_id": null, + "return_type": "int", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/header_inline_function_dedup/first.cpp", + "line": 16 + } + }, + { + "name": "second", + "enclosing_namespace_id": null, + "return_type": "int", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/header_inline_function_dedup/second.cpp", + "line": 16 + } + } + ], "functions": [ { "id": { diff --git a/cpp/libclang/integration_test/function_cases/header_inline_member_function_dedup/expected.json b/cpp/libclang/integration_test/function_cases/header_inline_member_function_dedup/expected.json index e2d811ae..4cccc982 100644 --- a/cpp/libclang/integration_test/function_cases/header_inline_member_function_dedup/expected.json +++ b/cpp/libclang/integration_test/function_cases/header_inline_member_function_dedup/expected.json @@ -1,13 +1,80 @@ { + "types": { + "util::Widget": { + "id": "util::Widget", + "name": "Widget", + "enclosing_namespace_id": "util", + "stereotypes": [], + "entity_type": "Class", + "type_aliases": [], + "variables": [], + "methods": [ + { + "name": "compute", + "return_type": "int", + "visibility": "public", + "parameters": [], + "template_parameters": null, + "modifiers": [], + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/header_inline_member_function_dedup/inline_member.h", + "line": 21 + } + } + ], + "template_parameters": null, + "enum_literals": [], + "relationships": [], + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/header_inline_member_function_dedup/inline_member.h", + "line": 19 + } + } + }, + "free_function_declarations": [ + { + "name": "ping", + "enclosing_namespace_id": "util", + "return_type": "void", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/header_inline_member_function_dedup/inline_member.h", + "line": 17 + } + }, + { + "name": "first", + "enclosing_namespace_id": null, + "return_type": "int", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/header_inline_member_function_dedup/first.cpp", + "line": 16 + } + }, + { + "name": "second", + "enclosing_namespace_id": null, + "return_type": "int", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/header_inline_member_function_dedup/second.cpp", + "line": 16 + } + } + ], "functions": [ { "id": { - "name": "ping", "scope": { "Namespace": [ "util" ] - } + }, + "name": "ping" }, "kind": "Free", "return_type": { @@ -17,7 +84,6 @@ }, { "id": { - "name": "compute", "scope": { "Type": { "namespace": [ @@ -27,7 +93,8 @@ "Widget" ] } - } + }, + "name": "compute" }, "kind": "Method", "return_type": { @@ -46,8 +113,8 @@ }, { "id": { - "name": "first", - "scope": "Global" + "scope": "Global", + "name": "first" }, "kind": "Free", "return_type": { @@ -74,8 +141,8 @@ }, { "id": { - "name": "second", - "scope": "Global" + "scope": "Global", + "name": "second" }, "kind": "Free", "return_type": { @@ -100,37 +167,5 @@ } ] } - ], - "types": { - "util::Widget": { - "id": "util::Widget", - "name": "Widget", - "enclosing_namespace_id": "util", - "entity_type": "Class", - "enum_literals": [], - "methods": [ - { - "name": "compute", - "return_type": "int", - "parameters": [], - "modifiers": [], - "template_parameters": null, - "visibility": "public", - "source_location": { - "file": "cpp/libclang/integration_test/function_cases/header_inline_member_function_dedup/inline_member.h", - "line": 21 - } - } - ], - "relationships": [], - "stereotypes": [], - "template_parameters": null, - "type_aliases": [], - "variables": [], - "source_location": { - "file": "cpp/libclang/integration_test/function_cases/header_inline_member_function_dedup/inline_member.h", - "line": 19 - } - } - } -} + ] +} \ No newline at end of file diff --git a/cpp/libclang/integration_test/function_cases/header_template_function_dedup/expected.json b/cpp/libclang/integration_test/function_cases/header_template_function_dedup/expected.json index 95d8ff5d..9793ab48 100644 --- a/cpp/libclang/integration_test/function_cases/header_template_function_dedup/expected.json +++ b/cpp/libclang/integration_test/function_cases/header_template_function_dedup/expected.json @@ -1,13 +1,62 @@ { + "types": {}, + "free_function_declarations": [ + { + "name": "identity", + "enclosing_namespace_id": "utility", + "return_type": "T", + "parameters": [ + { + "name": "value", + "param_type": "T", + "is_variadic": false + } + ], + "template_parameters": [ + { + "Type": { + "name": "T", + "is_pack": false + } + } + ], + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/header_template_function_dedup/template.h", + "line": 18 + } + }, + { + "name": "first", + "enclosing_namespace_id": null, + "return_type": "int", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/header_template_function_dedup/first.cpp", + "line": 16 + } + }, + { + "name": "second", + "enclosing_namespace_id": null, + "return_type": "int", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/header_template_function_dedup/second.cpp", + "line": 16 + } + } + ], "functions": [ { "id": { - "name": "identity", "scope": { "Namespace": [ "utility" ] - } + }, + "name": "identity" }, "kind": "Free", "return_type": { @@ -17,8 +66,8 @@ }, { "id": { - "name": "first", - "scope": "Global" + "scope": "Global", + "name": "first" }, "kind": "Free", "return_type": { @@ -30,15 +79,15 @@ "target": "utility::identity", "source_location": { "file": "cpp/libclang/integration_test/function_cases/header_template_function_dedup/first.cpp", - "line": 17 + "line": 17 } } ] }, { "id": { - "name": "second", - "scope": "Global" + "scope": "Global", + "name": "second" }, "kind": "Free", "return_type": { @@ -50,11 +99,10 @@ "target": "utility::identity", "source_location": { "file": "cpp/libclang/integration_test/function_cases/header_template_function_dedup/second.cpp", - "line": 17 + "line": 17 } } ] } - ], - "types": {} -} + ] +} \ No newline at end of file diff --git a/cpp/libclang/integration_test/function_cases/if_complex_condition/expected.json b/cpp/libclang/integration_test/function_cases/if_complex_condition/expected.json index 84bf6a58..59f6f2c6 100644 --- a/cpp/libclang/integration_test/function_cases/if_complex_condition/expected.json +++ b/cpp/libclang/integration_test/function_cases/if_complex_condition/expected.json @@ -1,13 +1,71 @@ { + "types": {}, + "free_function_declarations": [ + { + "name": "is_ready", + "enclosing_namespace_id": null, + "return_type": "bool", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/if_complex_condition/flow.cpp", + "line": 14 + } + }, + { + "name": "is_allowed", + "enclosing_namespace_id": null, + "return_type": "bool", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/if_complex_condition/flow.cpp", + "line": 15 + } + }, + { + "name": "has_permission", + "enclosing_namespace_id": null, + "return_type": "bool", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/if_complex_condition/flow.cpp", + "line": 16 + } + }, + { + "name": "handle_complex", + "enclosing_namespace_id": null, + "return_type": "void", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/if_complex_condition/flow.cpp", + "line": 17 + } + }, + { + "name": "complex_condition", + "enclosing_namespace_id": "flow", + "return_type": "void", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/if_complex_condition/flow.cpp", + "line": 20 + } + } + ], "functions": [ { "id": { - "name": "complex_condition", "scope": { "Namespace": [ "flow" ] - } + }, + "name": "complex_condition" }, "kind": "Free", "return_type": { @@ -15,6 +73,7 @@ }, "body": [ { + "type": "branch", "cases": [ { "guard": { @@ -26,7 +85,7 @@ "text": "is_ready()", "source_location": { "file": "cpp/libclang/integration_test/function_cases/if_complex_condition/flow.cpp", - "line": 21 + "line": 21 } }, { @@ -38,7 +97,7 @@ "text": "is_allowed()", "source_location": { "file": "cpp/libclang/integration_test/function_cases/if_complex_condition/flow.cpp", - "line": 21 + "line": 21 } }, { @@ -49,7 +108,7 @@ "text": "has_permission()", "source_location": { "file": "cpp/libclang/integration_test/function_cases/if_complex_condition/flow.cpp", - "line": 21 + "line": 21 } } } @@ -59,12 +118,12 @@ }, "body": [ { + "type": "call", "target": "handle_complex", "source_location": { "file": "cpp/libclang/integration_test/function_cases/if_complex_condition/flow.cpp", "line": 22 - }, - "type": "call" + } } ], "source_location": { @@ -72,11 +131,9 @@ "line": 21 } } - ], - "type": "branch" + ] } ] } - ], - "types": {} -} + ] +} \ No newline at end of file diff --git a/cpp/libclang/integration_test/function_cases/if_else_chain/expected.json b/cpp/libclang/integration_test/function_cases/if_else_chain/expected.json index bee3509a..8848e5af 100644 --- a/cpp/libclang/integration_test/function_cases/if_else_chain/expected.json +++ b/cpp/libclang/integration_test/function_cases/if_else_chain/expected.json @@ -1,13 +1,77 @@ { + "types": {}, + "free_function_declarations": [ + { + "name": "is_ready", + "enclosing_namespace_id": null, + "return_type": "bool", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/if_else_chain/flow.cpp", + "line": 14 + } + }, + { + "name": "handle_ready", + "enclosing_namespace_id": null, + "return_type": "void", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/if_else_chain/flow.cpp", + "line": 15 + } + }, + { + "name": "handle_retry", + "enclosing_namespace_id": null, + "return_type": "void", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/if_else_chain/flow.cpp", + "line": 16 + } + }, + { + "name": "handle_failure", + "enclosing_namespace_id": null, + "return_type": "void", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/if_else_chain/flow.cpp", + "line": 17 + } + }, + { + "name": "evaluate", + "enclosing_namespace_id": "flow", + "return_type": "void", + "parameters": [ + { + "name": "retry", + "param_type": "bool", + "is_variadic": false + } + ], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/if_else_chain/flow.cpp", + "line": 22 + } + } + ], "functions": [ { "id": { - "name": "evaluate", "scope": { "Namespace": [ "flow" ] - } + }, + "name": "evaluate" }, "kind": "Free", "return_type": { @@ -15,6 +79,7 @@ }, "body": [ { + "type": "branch", "cases": [ { "guard": { @@ -28,12 +93,12 @@ }, "body": [ { + "type": "call", "target": "handle_ready", "source_location": { "file": "cpp/libclang/integration_test/function_cases/if_else_chain/flow.cpp", "line": 26 - }, - "type": "call" + } } ], "source_location": { @@ -52,12 +117,12 @@ }, "body": [ { + "type": "call", "target": "handle_retry", "source_location": { "file": "cpp/libclang/integration_test/function_cases/if_else_chain/flow.cpp", "line": 30 - }, - "type": "call" + } } ], "source_location": { @@ -69,12 +134,12 @@ "guard": null, "body": [ { + "type": "call", "target": "handle_failure", "source_location": { "file": "cpp/libclang/integration_test/function_cases/if_else_chain/flow.cpp", "line": 34 - }, - "type": "call" + } } ], "source_location": { @@ -82,11 +147,9 @@ "line": 33 } } - ], - "type": "branch" + ] } ] } - ], - "types": {} -} + ] +} \ No newline at end of file diff --git a/cpp/libclang/integration_test/function_cases/if_initializer_fallback/expected.json b/cpp/libclang/integration_test/function_cases/if_initializer_fallback/expected.json index 2f0a756b..05c5a329 100644 --- a/cpp/libclang/integration_test/function_cases/if_initializer_fallback/expected.json +++ b/cpp/libclang/integration_test/function_cases/if_initializer_fallback/expected.json @@ -1,45 +1,91 @@ { - "types": {}, - "functions": [ - { - "id": { - "scope": { - "Namespace": [ - "flow" - ] - }, - "name": "if_initializer_fallback" - }, - "kind": "Free", - "return_type": { - "Builtin": "void" - }, - "body": [ - { - "type": "call", - "target": "initialize", - "source_location": { - "file": "cpp/libclang/integration_test/function_cases/if_initializer_fallback/flow.cpp", - "line": 20 - } - }, - { - "type": "call", - "target": "handle_ready", - "source_location": { - "file": "cpp/libclang/integration_test/function_cases/if_initializer_fallback/flow.cpp", - "line": 21 - } - }, - { - "type": "call", - "target": "handle_failure", - "source_location": { - "file": "cpp/libclang/integration_test/function_cases/if_initializer_fallback/flow.cpp", - "line": 23 - } - } - ] - } - ] -} + "types": {}, + "free_function_declarations": [ + { + "name": "initialize", + "enclosing_namespace_id": null, + "return_type": "bool", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/if_initializer_fallback/flow.cpp", + "line": 14 + } + }, + { + "name": "handle_ready", + "enclosing_namespace_id": null, + "return_type": "void", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/if_initializer_fallback/flow.cpp", + "line": 15 + } + }, + { + "name": "handle_failure", + "enclosing_namespace_id": null, + "return_type": "void", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/if_initializer_fallback/flow.cpp", + "line": 16 + } + }, + { + "name": "if_initializer_fallback", + "enclosing_namespace_id": "flow", + "return_type": "void", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/if_initializer_fallback/flow.cpp", + "line": 19 + } + } + ], + "functions": [ + { + "id": { + "scope": { + "Namespace": [ + "flow" + ] + }, + "name": "if_initializer_fallback" + }, + "kind": "Free", + "return_type": { + "Builtin": "void" + }, + "body": [ + { + "type": "call", + "target": "initialize", + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/if_initializer_fallback/flow.cpp", + "line": 20 + } + }, + { + "type": "call", + "target": "handle_ready", + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/if_initializer_fallback/flow.cpp", + "line": 21 + } + }, + { + "type": "call", + "target": "handle_failure", + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/if_initializer_fallback/flow.cpp", + "line": 23 + } + } + ] + } + ] +} \ No newline at end of file diff --git a/cpp/libclang/integration_test/function_cases/if_without_braces/expected.json b/cpp/libclang/integration_test/function_cases/if_without_braces/expected.json index d8f1e993..852d6cc2 100644 --- a/cpp/libclang/integration_test/function_cases/if_without_braces/expected.json +++ b/cpp/libclang/integration_test/function_cases/if_without_braces/expected.json @@ -1,13 +1,55 @@ { + "types": {}, + "free_function_declarations": [ + { + "name": "handle_unbraced", + "enclosing_namespace_id": null, + "return_type": "void", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/if_without_braces/flow.cpp", + "line": 14 + } + }, + { + "name": "handle_failure", + "enclosing_namespace_id": null, + "return_type": "void", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/if_without_braces/flow.cpp", + "line": 15 + } + }, + { + "name": "without_braces", + "enclosing_namespace_id": "flow", + "return_type": "void", + "parameters": [ + { + "name": "enabled", + "param_type": "bool", + "is_variadic": false + } + ], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/if_without_braces/flow.cpp", + "line": 18 + } + } + ], "functions": [ { "id": { - "name": "without_braces", "scope": { "Namespace": [ "flow" ] - } + }, + "name": "without_braces" }, "kind": "Free", "return_type": { @@ -15,6 +57,7 @@ }, "body": [ { + "type": "branch", "cases": [ { "guard": { @@ -22,17 +65,17 @@ "text": "enabled", "source_location": { "file": "cpp/libclang/integration_test/function_cases/if_without_braces/flow.cpp", - "line": 19 + "line": 19 } }, "body": [ { + "type": "call", "target": "handle_unbraced", "source_location": { "file": "cpp/libclang/integration_test/function_cases/if_without_braces/flow.cpp", - "line": 20 - }, - "type": "call" + "line": 20 + } } ], "source_location": { @@ -44,12 +87,12 @@ "guard": null, "body": [ { + "type": "call", "target": "handle_failure", "source_location": { "file": "cpp/libclang/integration_test/function_cases/if_without_braces/flow.cpp", "line": 22 - }, - "type": "call" + } } ], "source_location": { @@ -57,11 +100,9 @@ "line": 22 } } - ], - "type": "branch" + ] } ] } - ], - "types": {} -} + ] +} \ No newline at end of file diff --git a/cpp/libclang/integration_test/function_cases/if_without_else/expected.json b/cpp/libclang/integration_test/function_cases/if_without_else/expected.json index 23186f8a..fe90c4d7 100644 --- a/cpp/libclang/integration_test/function_cases/if_without_else/expected.json +++ b/cpp/libclang/integration_test/function_cases/if_without_else/expected.json @@ -1,13 +1,44 @@ { + "types": {}, + "free_function_declarations": [ + { + "name": "handle_no_else", + "enclosing_namespace_id": null, + "return_type": "void", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/if_without_else/flow.cpp", + "line": 14 + } + }, + { + "name": "without_else", + "enclosing_namespace_id": "flow", + "return_type": "void", + "parameters": [ + { + "name": "enabled", + "param_type": "bool", + "is_variadic": false + } + ], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/if_without_else/flow.cpp", + "line": 17 + } + } + ], "functions": [ { "id": { - "name": "without_else", "scope": { "Namespace": [ "flow" ] - } + }, + "name": "without_else" }, "kind": "Free", "return_type": { @@ -15,6 +46,7 @@ }, "body": [ { + "type": "branch", "cases": [ { "guard": { @@ -22,17 +54,17 @@ "text": "enabled", "source_location": { "file": "cpp/libclang/integration_test/function_cases/if_without_else/flow.cpp", - "line": 18 + "line": 18 } }, "body": [ { + "type": "call", "target": "handle_no_else", "source_location": { "file": "cpp/libclang/integration_test/function_cases/if_without_else/flow.cpp", - "line": 19 - }, - "type": "call" + "line": 19 + } } ], "source_location": { @@ -40,11 +72,9 @@ "line": 18 } } - ], - "type": "branch" + ] } ] } - ], - "types": {} -} + ] +} \ No newline at end of file diff --git a/cpp/libclang/integration_test/function_cases/inherited_class_method_template/expected.json b/cpp/libclang/integration_test/function_cases/inherited_class_method_template/expected.json index 52564e7e..c163d048 100644 --- a/cpp/libclang/integration_test/function_cases/inherited_class_method_template/expected.json +++ b/cpp/libclang/integration_test/function_cases/inherited_class_method_template/expected.json @@ -86,6 +86,19 @@ } } }, + "free_function_declarations": [ + { + "name": "notify", + "enclosing_namespace_id": null, + "return_type": "void", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/inherited_class_method_template/base.h", + "line": 16 + } + } + ], "functions": [ { "id": { diff --git a/cpp/libclang/integration_test/function_cases/nested_if/expected.json b/cpp/libclang/integration_test/function_cases/nested_if/expected.json index 33a7c827..7b9f51ca 100644 --- a/cpp/libclang/integration_test/function_cases/nested_if/expected.json +++ b/cpp/libclang/integration_test/function_cases/nested_if/expected.json @@ -1,13 +1,60 @@ { + "types": {}, + "free_function_declarations": [ + { + "name": "handle_outer_else", + "enclosing_namespace_id": null, + "return_type": "void", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/nested_if/flow.cpp", + "line": 14 + } + }, + { + "name": "handle_nested", + "enclosing_namespace_id": null, + "return_type": "void", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/nested_if/flow.cpp", + "line": 15 + } + }, + { + "name": "nested_if", + "enclosing_namespace_id": "flow", + "return_type": "void", + "parameters": [ + { + "name": "outer", + "param_type": "bool", + "is_variadic": false + }, + { + "name": "inner", + "param_type": "bool", + "is_variadic": false + } + ], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/nested_if/flow.cpp", + "line": 18 + } + } + ], "functions": [ { "id": { - "name": "nested_if", "scope": { "Namespace": [ "flow" ] - } + }, + "name": "nested_if" }, "kind": "Free", "return_type": { @@ -15,6 +62,7 @@ }, "body": [ { + "type": "branch", "cases": [ { "guard": { @@ -22,11 +70,12 @@ "text": "outer", "source_location": { "file": "cpp/libclang/integration_test/function_cases/nested_if/flow.cpp", - "line": 19 + "line": 19 } }, "body": [ { + "type": "branch", "cases": [ { "guard": { @@ -39,12 +88,12 @@ }, "body": [ { + "type": "call", "target": "handle_nested", "source_location": { "file": "cpp/libclang/integration_test/function_cases/nested_if/flow.cpp", "line": 21 - }, - "type": "call" + } } ], "source_location": { @@ -52,8 +101,7 @@ "line": 20 } } - ], - "type": "branch" + ] } ], "source_location": { @@ -65,12 +113,12 @@ "guard": null, "body": [ { + "type": "call", "target": "handle_outer_else", "source_location": { "file": "cpp/libclang/integration_test/function_cases/nested_if/flow.cpp", - "line": 24 - }, - "type": "call" + "line": 24 + } } ], "source_location": { @@ -78,11 +126,9 @@ "line": 23 } } - ], - "type": "branch" + ] } ] } - ], - "types": {} -} + ] +} \ No newline at end of file diff --git a/cpp/libclang/integration_test/function_cases/out_of_line_method_dedup/BUILD b/cpp/libclang/integration_test/function_cases/out_of_line_method_dedup/BUILD new file mode 100644 index 00000000..8695623f --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/out_of_line_method_dedup/BUILD @@ -0,0 +1,26 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* +load("//cpp/libclang/integration_test:test_rules.bzl", "cpp_parser_integration_test") + +cc_library( + name = "out_of_line_method_dedup", + srcs = ["functions.cpp"], + hdrs = ["functions.hpp"], + visibility = ["//cpp/libclang:__subpackages__"], +) + +cpp_parser_integration_test( + name = "test_out_of_line_method_dedup", + expected_output = ["expected.json"], + target = ":out_of_line_method_dedup", +) diff --git a/cpp/libclang/integration_test/function_cases/out_of_line_method_dedup/expected.json b/cpp/libclang/integration_test/function_cases/out_of_line_method_dedup/expected.json new file mode 100644 index 00000000..509ce8af --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/out_of_line_method_dedup/expected.json @@ -0,0 +1,54 @@ +{ + "functions": [ + { + "id": { + "scope": { + "Type": { + "namespace": [], + "type_path": [ + "A" + ] + } + }, + "name": "run" + }, + "kind": "Method", + "return_type": { + "Builtin": "void" + }, + "body": [] + } + ], + "types": { + "A": { + "id": "A", + "name": "A", + "enclosing_namespace_id": null, + "entity_type": "Struct", + "enum_literals": [], + "methods": [ + { + "name": "run", + "return_type": "void", + "visibility": "public", + "parameters": [], + "template_parameters": null, + "modifiers": [], + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/out_of_line_method_dedup/functions.hpp", + "line": 17 + } + } + ], + "relationships": [], + "stereotypes": [], + "template_parameters": null, + "type_aliases": [], + "variables": [], + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/out_of_line_method_dedup/functions.hpp", + "line": 16 + } + } + } +} diff --git a/cpp/libclang/integration_test/function_cases/out_of_line_method_dedup/functions.cpp b/cpp/libclang/integration_test/function_cases/out_of_line_method_dedup/functions.cpp new file mode 100644 index 00000000..bd3ab783 --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/out_of_line_method_dedup/functions.cpp @@ -0,0 +1,16 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +#include "functions.hpp" + +void A::run() {} diff --git a/cpp/libclang/integration_test/function_cases/out_of_line_method_dedup/functions.hpp b/cpp/libclang/integration_test/function_cases/out_of_line_method_dedup/functions.hpp new file mode 100644 index 00000000..9814c1e1 --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/out_of_line_method_dedup/functions.hpp @@ -0,0 +1,18 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +#pragma once + +struct A { + void run(); +}; diff --git a/cpp/libclang/integration_test/function_cases/out_of_line_method_dedup/run_test.rs b/cpp/libclang/integration_test/function_cases/out_of_line_method_dedup/run_test.rs new file mode 100644 index 00000000..ed482d1d --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/out_of_line_method_dedup/run_test.rs @@ -0,0 +1,19 @@ +// ******************************************************************************* +// Copyright (c) 2026 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* + +use test_framework::run_parser_case; + +#[test] +fn test_out_of_line_method_dedup() { + run_parser_case(); +} diff --git a/cpp/libclang/integration_test/function_cases/standard_library_header_filter/expected.json b/cpp/libclang/integration_test/function_cases/standard_library_header_filter/expected.json index 4e8ec0b9..893476f6 100644 --- a/cpp/libclang/integration_test/function_cases/standard_library_header_filter/expected.json +++ b/cpp/libclang/integration_test/function_cases/standard_library_header_filter/expected.json @@ -1,9 +1,29 @@ { + "types": {}, + "free_function_declarations": [ + { + "name": "local_function", + "enclosing_namespace_id": null, + "return_type": "int", + "parameters": [ + { + "name": "value", + "param_type": "int", + "is_variadic": false + } + ], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/standard_library_header_filter/functions.cpp", + "line": 16 + } + } + ], "functions": [ { "id": { - "name": "local_function", - "scope": "Global" + "scope": "Global", + "name": "local_function" }, "kind": "Free", "return_type": { @@ -11,6 +31,5 @@ }, "body": [] } - ], - "types": {} -} + ] +} \ No newline at end of file diff --git a/cpp/libclang/integration_test/function_cases/template_declaration_without_definition/expected.json b/cpp/libclang/integration_test/function_cases/template_declaration_without_definition/expected.json index 27b800f2..dcf519bf 100644 --- a/cpp/libclang/integration_test/function_cases/template_declaration_without_definition/expected.json +++ b/cpp/libclang/integration_test/function_cases/template_declaration_without_definition/expected.json @@ -1,9 +1,47 @@ { + "types": {}, + "free_function_declarations": [ + { + "name": "declared_only", + "enclosing_namespace_id": "utility", + "return_type": "T", + "parameters": [ + { + "name": "value", + "param_type": "T", + "is_variadic": false + } + ], + "template_parameters": [ + { + "Type": { + "name": "T", + "is_pack": false + } + } + ], + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/template_declaration_without_definition/declaration_only.h", + "line": 21 + } + }, + { + "name": "run", + "enclosing_namespace_id": null, + "return_type": "int", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/template_declaration_without_definition/functions.cpp", + "line": 16 + } + } + ], "functions": [ { "id": { - "name": "run", - "scope": "Global" + "scope": "Global", + "name": "run" }, "kind": "Free", "return_type": { @@ -20,6 +58,5 @@ } ] } - ], - "types": {} -} + ] +} \ No newline at end of file From fa097b34dd00acaecc014679fb4f4bc683eb7aaf Mon Sep 17 00:00:00 2001 From: Melody Ma Date: Sun, 20 Sep 2026 16:46:23 +0800 Subject: [PATCH 4/5] [adapter] other parts adapter meta model --- .vscode/settings.json | 21 ------------------- cpp/libclang/docs/ast-traversal.md | 2 +- cpp/libclang/docs/function-extraction.md | 8 ++++--- plantuml/parser/puml_idmap/src/lib.rs | 10 +++++++++ plantuml/parser/puml_lobster/src/lib.rs | 2 ++ .../src/class_diagram/src/class_resolver.rs | 2 ++ .../core/src/models/class_diagram_models.rs | 3 +++ .../core/src/readers/class_diagram_reader.rs | 1 + .../class_design_implementation_validator.rs | 1 + .../class_design_sequence_validator_test.rs | 1 + .../component_public_api_validator_test.rs | 1 + .../core/src/validators/test/fixtures.rs | 1 + 12 files changed, 28 insertions(+), 25 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 68aa8003..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - // General Settings - "files.insertFinalNewline": true, - "files.trimFinalNewlines": true, - "files.trimTrailingWhitespace": true, - "editor.rulers": [88], - - // Python Settings - "[python]": { - // Opinionated option for the future: - // "editor.formatOnSave": true, - "editor.codeActionsOnSave": { - "source.sortImports": "explicit" - }, - "editor.defaultFormatter": "charliermarsh.ruff" - }, - "python.analysis.typeCheckingMode": "off", - "python.testing.unittestEnabled": false, - "python.testing.pytestEnabled": true, - "git.ignoreLimitWarning": true -} diff --git a/cpp/libclang/docs/ast-traversal.md b/cpp/libclang/docs/ast-traversal.md index d69f90f1..65189674 100644 --- a/cpp/libclang/docs/ast-traversal.md +++ b/cpp/libclang/docs/ast-traversal.md @@ -45,7 +45,7 @@ not filtered out, it dispatches to the relevant specialized visitor: | --- | --- | | `ClassDecl`, `StructDecl`, `ClassTemplate`, and `ClassTemplatePartialSpecialization` | Extract class/struct entities, members, aliases, bases, and relationship inputs. | | `EnumDecl` | Extract enum entities and literals. | -| `FunctionDecl`, `FunctionTemplate`, and `Method` | Extract callable definitions and their body control flow. Function templates are classified as free functions, methods, or static methods according to their scope. | +| `FunctionDecl`, `FunctionTemplate`, `Method`, `Constructor`, and `Destructor` | Extract callable definitions and their body control flow. Function templates are classified as free functions, methods, or static methods according to their scope. Constructors and destructors are routed through the same callable visitor and participate in body extraction when they have a direct compound body. | After traversal, class relationship resolution uses the collected base, variable, and method type information to populate the class-diagram diff --git a/cpp/libclang/docs/function-extraction.md b/cpp/libclang/docs/function-extraction.md index 8af4f75a..4e96d2f5 100644 --- a/cpp/libclang/docs/function-extraction.md +++ b/cpp/libclang/docs/function-extraction.md @@ -195,15 +195,17 @@ The top-level visitor currently dispatches these cursor kinds to | `FunctionDecl` | `Free` | | `FunctionTemplate` | `Free` at global or namespace scope; `Method` or `StaticMethod` at type scope | | `Method` | `Method` or `StaticMethod` | +| `Constructor` | `Constructor` | +| `Destructor` | `Destructor` | C++ member operator overloads such as `operator+` and `operator[]` are normally reported as `Method`; the current model does not use a distinct operator-method kind. `FunctionVisitor` has internal kind mappings for `Constructor`, `Destructor`, -and `ConversionFunction`, but the top-level visitor currently logs and ignores -those cursor kinds. Therefore they do not currently produce `FunctionDef` -entries. A conversion operator such as `operator bool()` is a +and `ConversionFunction`. The top-level visitor currently dispatches +constructors and destructors for extraction, but still logs and ignores +`ConversionFunction`. A conversion operator such as `operator bool()` is a `ConversionFunction` and is distinct from a normal operator overload. Namespace-level function templates are extracted as `FunctionDef` entries with diff --git a/plantuml/parser/puml_idmap/src/lib.rs b/plantuml/parser/puml_idmap/src/lib.rs index c8360081..46b1c293 100644 --- a/plantuml/parser/puml_idmap/src/lib.rs +++ b/plantuml/parser/puml_idmap/src/lib.rs @@ -627,6 +627,7 @@ mod tests { let model = ClassDiagram { name: "d".to_string(), entities: vec![with_members, without_members], + free_functions: vec![], }; let idmap = class_model_to_idmap(&model, "pkg/classes.puml"); @@ -677,6 +678,7 @@ mod tests { let model = ClassDiagram { name: "sorted".to_string(), entities: vec![with_members_z, ref_m, with_members_a, ref_b], + free_functions: vec![], }; let idmap = class_model_to_idmap(&model, "pkg/class_sorted.puml"); @@ -782,6 +784,7 @@ mod tests { let model = ClassDiagram { name: "d".to_string(), entities: vec![define], + free_functions: vec![], }; let idmap = class_model_to_idmap(&model, "pkg/classes.puml"); @@ -815,6 +818,7 @@ mod tests { let model = ClassDiagram { name: "d".to_string(), entities: vec![a, b], + free_functions: vec![], }; let idmap = class_model_to_idmap(&model, "pkg/classes.puml"); @@ -851,6 +855,7 @@ mod tests { let model = ClassDiagram { name: "d".to_string(), entities: vec![child], + free_functions: vec![], }; let idmap = class_model_to_idmap(&model, "pkg/ns.puml"); @@ -897,6 +902,7 @@ mod tests { let model = ClassDiagram { name: "d".to_string(), entities: vec![child], + free_functions: vec![], }; let idmap = class_model_to_idmap(&model, "pkg/ns.puml"); @@ -928,6 +934,7 @@ mod tests { let model = ClassDiagram { name: "unit_1_class_diagram".to_string(), entities: vec![foo], + free_functions: vec![], }; let idmap = class_model_to_idmap(&model, "unit_1/docs/unit_1_class_diagram.puml"); @@ -962,6 +969,7 @@ mod tests { let model = ClassDiagram { name: "d".to_string(), entities: vec![child, container_as_real_entity], + free_functions: vec![], }; let idmap = class_model_to_idmap(&model, "pkg/ns.puml"); @@ -995,6 +1003,7 @@ mod tests { let model = ClassDiagram { name: "Proxy".to_string(), entities: vec![proxy, leaf], + free_functions: vec![], }; let idmap = class_model_to_idmap(&model, "pkg/proxy.puml"); @@ -1129,6 +1138,7 @@ mod tests { let model = ClassDiagram { name: "d".to_string(), entities: vec![with_members], + free_functions: vec![], }; let input = Path::new("some/dir/classes.puml"); diff --git a/plantuml/parser/puml_lobster/src/lib.rs b/plantuml/parser/puml_lobster/src/lib.rs index 050dfcaf..f5786511 100644 --- a/plantuml/parser/puml_lobster/src/lib.rs +++ b/plantuml/parser/puml_lobster/src/lib.rs @@ -261,6 +261,7 @@ mod tests { let model = ClassDiagram { name: "d".to_string(), entities: vec![entity], + free_functions: vec![], }; let dir = unique_tmp_dir("class"); let input = Path::new("some/dir/classes.puml"); @@ -291,6 +292,7 @@ mod tests { let model = ClassDiagram { name: "d".to_string(), entities: vec![entity], + free_functions: vec![], }; let dir = unique_tmp_dir("class_override"); let input = Path::new("some/dir/classes.puml"); diff --git a/plantuml/parser/puml_resolver/src/class_diagram/src/class_resolver.rs b/plantuml/parser/puml_resolver/src/class_diagram/src/class_resolver.rs index 268d1c42..ddc8bff8 100644 --- a/plantuml/parser/puml_resolver/src/class_diagram/src/class_resolver.rs +++ b/plantuml/parser/puml_resolver/src/class_diagram/src/class_resolver.rs @@ -70,6 +70,7 @@ impl ClassResolver { logic: ClassDiagram { name: String::new(), entities: Vec::new(), + free_functions: Vec::new(), }, name_map: HashMap::new(), } @@ -846,6 +847,7 @@ impl DiagramResolver for ClassResolver { ClassDiagram { name: String::new(), entities: Vec::new(), + free_functions: Vec::new(), }, ); diff --git a/validation/core/src/models/class_diagram_models.rs b/validation/core/src/models/class_diagram_models.rs index d0d68ad9..658b2105 100644 --- a/validation/core/src/models/class_diagram_models.rs +++ b/validation/core/src/models/class_diagram_models.rs @@ -206,6 +206,7 @@ mod tests { entity("Unit.Sample", "design_a.puml", 12), entity("unit.sample", "design_b.puml", 34), ], + free_functions: Vec::new(), }]; let mut result = ValidationResult::default(); @@ -251,6 +252,7 @@ mod tests { source_location: SourceLocation::new("test.puml", 1), }, ], + free_functions: Vec::new(), }]; let index = InternalApiIndex::build_index(&diagrams); @@ -300,6 +302,7 @@ mod tests { source_location: SourceLocation::new("test.puml", 1), }, ], + free_functions: Vec::new(), }]; let index = InternalApiIndex::build_index(&diagrams); diff --git a/validation/core/src/readers/class_diagram_reader.rs b/validation/core/src/readers/class_diagram_reader.rs index 0eca0f00..d99faaec 100644 --- a/validation/core/src/readers/class_diagram_reader.rs +++ b/validation/core/src/readers/class_diagram_reader.rs @@ -324,6 +324,7 @@ impl Reader for ClassDiagramReader { diagrams.push(ClassDiagram { name: diagram.name().to_string(), entities, + free_functions: Vec::new(), }); } diff --git a/validation/core/src/validators/class_design_implementation_validator.rs b/validation/core/src/validators/class_design_implementation_validator.rs index e7a4159d..b43b27fd 100644 --- a/validation/core/src/validators/class_design_implementation_validator.rs +++ b/validation/core/src/validators/class_design_implementation_validator.rs @@ -1102,6 +1102,7 @@ mod tests { let diagrams: ClassDiagramInputs = vec![ClassDiagram { name: "unit".to_string(), entities, + free_functions: Vec::new(), }]; ClassEntityIndex::build_index(&diagrams, &mut ValidationResult::default()) } diff --git a/validation/core/src/validators/test/class_design_sequence_validator_test.rs b/validation/core/src/validators/test/class_design_sequence_validator_test.rs index a85cbfca..9bb52a2f 100644 --- a/validation/core/src/validators/test/class_design_sequence_validator_test.rs +++ b/validation/core/src/validators/test/class_design_sequence_validator_test.rs @@ -37,6 +37,7 @@ fn class_diagrams(entities: Vec) -> ClassDiagramInp vec![ClassDiagram { name: "class_design".to_string(), entities, + free_functions: Vec::new(), }] } diff --git a/validation/core/src/validators/test/component_public_api_validator_test.rs b/validation/core/src/validators/test/component_public_api_validator_test.rs index dfdfaf89..8c7a9504 100644 --- a/validation/core/src/validators/test/component_public_api_validator_test.rs +++ b/validation/core/src/validators/test/component_public_api_validator_test.rs @@ -41,6 +41,7 @@ fn public_api_index(interfaces: Vec<(&str, Option<&str>)>) -> PublicApiIndex { .into_iter() .map(|(interface_name, namespace)| class_interface(interface_name, namespace)) .collect(), + free_functions: Vec::new(), }]; PublicApiIndex::build_index(&diagrams) diff --git a/validation/core/src/validators/test/fixtures.rs b/validation/core/src/validators/test/fixtures.rs index 15f35966..a146d7f0 100644 --- a/validation/core/src/validators/test/fixtures.rs +++ b/validation/core/src/validators/test/fixtures.rs @@ -187,6 +187,7 @@ pub(super) fn internal_api_index(interfaces: Vec<(&str, Vec<&str>)>) -> Internal interface }) .collect(), + free_functions: Vec::new(), }]; InternalApiIndex::build_index(&diagrams) From 625e509421d575291703470963daee4e1496fe7c Mon Sep 17 00:00:00 2001 From: Melody Ma Date: Wed, 23 Sep 2026 17:55:05 +0800 Subject: [PATCH 5/5] [cpp parser] normalize callable declaration identity and add regression tests --- .../function_cases/extern_c_identity/BUILD | 26 ++++ .../extern_c_identity/expected.json | 63 +++++++++ .../extern_c_identity/functions.cpp | 24 ++++ .../extern_c_identity/functions.hpp | 22 +++ .../extern_c_identity/run_test.rs | 19 +++ .../BUILD | 30 ++++ .../expected.json | 28 ++++ .../first.cpp | 14 ++ .../internal_linkage.h | 20 +++ .../run_test.rs | 19 +++ .../second.cpp | 14 ++ .../BUILD | 29 ++++ .../expected.json | 50 +++++++ .../first.cpp | 18 +++ .../run_test.rs | 19 +++ .../second.cpp | 18 +++ .../namespace_operator_identity/BUILD | 26 ++++ .../namespace_operator_identity/expected.json | 62 ++++++++ .../namespace_operator_identity/functions.cpp | 22 +++ .../namespace_operator_identity/functions.hpp | 22 +++ .../namespace_operator_identity/run_test.rs | 19 +++ .../overloaded_parameter_identity/BUILD | 26 ++++ .../expected.json | 40 ++++++ .../functions.cpp | 14 ++ .../functions.hpp | 17 +++ .../overloaded_parameter_identity/run_test.rs | 19 +++ .../pointee_const_parameter_identity/BUILD | 26 ++++ .../expected.json | 63 +++++++++ .../functions.cpp | 17 +++ .../functions.hpp | 17 +++ .../run_test.rs | 19 +++ .../top_level_const_parameter_dedup/BUILD | 26 ++++ .../expected.json | 90 ++++++++++++ .../functions.cpp | 18 +++ .../functions.hpp | 20 +++ .../run_test.rs | 19 +++ .../variadic_parameter_identity/BUILD | 26 ++++ .../variadic_parameter_identity/expected.json | 68 +++++++++ .../variadic_parameter_identity/functions.cpp | 18 +++ .../variadic_parameter_identity/functions.hpp | 17 +++ .../variadic_parameter_identity/run_test.rs | 19 +++ cpp/libclang/src/main.rs | 8 +- .../src/visitor/src/callable_declaration.rs | 84 ++++++++--- .../src/visitor/src/clang_adapter/scope.rs | 10 +- cpp/libclang/src/visitor/src/class_visitor.rs | 132 +++++++++++++++++- cpp/libclang/src/visitor/src/context.rs | 39 ++++-- .../src/visitor/src/function_visitor.rs | 96 ++++++------- cpp/libclang/src/visitor/src/lib.rs | 2 +- cpp/libclang/src/visitor/src/visitor.rs | 10 +- 49 files changed, 1429 insertions(+), 95 deletions(-) create mode 100644 cpp/libclang/integration_test/function_cases/extern_c_identity/BUILD create mode 100644 cpp/libclang/integration_test/function_cases/extern_c_identity/expected.json create mode 100644 cpp/libclang/integration_test/function_cases/extern_c_identity/functions.cpp create mode 100644 cpp/libclang/integration_test/function_cases/extern_c_identity/functions.hpp create mode 100644 cpp/libclang/integration_test/function_cases/extern_c_identity/run_test.rs create mode 100644 cpp/libclang/integration_test/function_cases/header_internal_linkage_declaration_identity/BUILD create mode 100644 cpp/libclang/integration_test/function_cases/header_internal_linkage_declaration_identity/expected.json create mode 100644 cpp/libclang/integration_test/function_cases/header_internal_linkage_declaration_identity/first.cpp create mode 100644 cpp/libclang/integration_test/function_cases/header_internal_linkage_declaration_identity/internal_linkage.h create mode 100644 cpp/libclang/integration_test/function_cases/header_internal_linkage_declaration_identity/run_test.rs create mode 100644 cpp/libclang/integration_test/function_cases/header_internal_linkage_declaration_identity/second.cpp create mode 100644 cpp/libclang/integration_test/function_cases/internal_linkage_declaration_identity/BUILD create mode 100644 cpp/libclang/integration_test/function_cases/internal_linkage_declaration_identity/expected.json create mode 100644 cpp/libclang/integration_test/function_cases/internal_linkage_declaration_identity/first.cpp create mode 100644 cpp/libclang/integration_test/function_cases/internal_linkage_declaration_identity/run_test.rs create mode 100644 cpp/libclang/integration_test/function_cases/internal_linkage_declaration_identity/second.cpp create mode 100644 cpp/libclang/integration_test/function_cases/namespace_operator_identity/BUILD create mode 100644 cpp/libclang/integration_test/function_cases/namespace_operator_identity/expected.json create mode 100644 cpp/libclang/integration_test/function_cases/namespace_operator_identity/functions.cpp create mode 100644 cpp/libclang/integration_test/function_cases/namespace_operator_identity/functions.hpp create mode 100644 cpp/libclang/integration_test/function_cases/namespace_operator_identity/run_test.rs create mode 100644 cpp/libclang/integration_test/function_cases/overloaded_parameter_identity/BUILD create mode 100644 cpp/libclang/integration_test/function_cases/overloaded_parameter_identity/expected.json create mode 100644 cpp/libclang/integration_test/function_cases/overloaded_parameter_identity/functions.cpp create mode 100644 cpp/libclang/integration_test/function_cases/overloaded_parameter_identity/functions.hpp create mode 100644 cpp/libclang/integration_test/function_cases/overloaded_parameter_identity/run_test.rs create mode 100644 cpp/libclang/integration_test/function_cases/pointee_const_parameter_identity/BUILD create mode 100644 cpp/libclang/integration_test/function_cases/pointee_const_parameter_identity/expected.json create mode 100644 cpp/libclang/integration_test/function_cases/pointee_const_parameter_identity/functions.cpp create mode 100644 cpp/libclang/integration_test/function_cases/pointee_const_parameter_identity/functions.hpp create mode 100644 cpp/libclang/integration_test/function_cases/pointee_const_parameter_identity/run_test.rs create mode 100644 cpp/libclang/integration_test/function_cases/top_level_const_parameter_dedup/BUILD create mode 100644 cpp/libclang/integration_test/function_cases/top_level_const_parameter_dedup/expected.json create mode 100644 cpp/libclang/integration_test/function_cases/top_level_const_parameter_dedup/functions.cpp create mode 100644 cpp/libclang/integration_test/function_cases/top_level_const_parameter_dedup/functions.hpp create mode 100644 cpp/libclang/integration_test/function_cases/top_level_const_parameter_dedup/run_test.rs create mode 100644 cpp/libclang/integration_test/function_cases/variadic_parameter_identity/BUILD create mode 100644 cpp/libclang/integration_test/function_cases/variadic_parameter_identity/expected.json create mode 100644 cpp/libclang/integration_test/function_cases/variadic_parameter_identity/functions.cpp create mode 100644 cpp/libclang/integration_test/function_cases/variadic_parameter_identity/functions.hpp create mode 100644 cpp/libclang/integration_test/function_cases/variadic_parameter_identity/run_test.rs diff --git a/cpp/libclang/integration_test/function_cases/extern_c_identity/BUILD b/cpp/libclang/integration_test/function_cases/extern_c_identity/BUILD new file mode 100644 index 00000000..d9d4cf80 --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/extern_c_identity/BUILD @@ -0,0 +1,26 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* +load("//cpp/libclang/integration_test:test_rules.bzl", "cpp_parser_integration_test") + +cc_library( + name = "extern_c_identity", + srcs = ["functions.cpp"], + hdrs = ["functions.hpp"], + visibility = ["//cpp/libclang:__subpackages__"], +) + +cpp_parser_integration_test( + name = "test_extern_c_identity", + expected_output = ["expected.json"], + target = ":extern_c_identity", +) diff --git a/cpp/libclang/integration_test/function_cases/extern_c_identity/expected.json b/cpp/libclang/integration_test/function_cases/extern_c_identity/expected.json new file mode 100644 index 00000000..979890bd --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/extern_c_identity/expected.json @@ -0,0 +1,63 @@ +{ + "free_function_declarations": [ + { + "name": "c_api", + "enclosing_namespace_id": null, + "return_type": "int", + "parameters": [ + { + "name": "", + "param_type": "int", + "is_variadic": false + } + ], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/extern_c_identity/functions.hpp", + "line": 16 + } + }, + { + "name": "c_api_block", + "enclosing_namespace_id": null, + "return_type": "int", + "parameters": [ + { + "name": "", + "param_type": "double", + "is_variadic": false + } + ], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/extern_c_identity/functions.hpp", + "line": 19 + } + } + ], + "functions": [ + { + "id": { + "name": "c_api", + "scope": "Global" + }, + "kind": "Free", + "return_type": { + "Builtin": "int" + }, + "body": [] + }, + { + "id": { + "name": "c_api_block", + "scope": "Global" + }, + "kind": "Free", + "return_type": { + "Builtin": "int" + }, + "body": [] + } + ], + "types": {} +} diff --git a/cpp/libclang/integration_test/function_cases/extern_c_identity/functions.cpp b/cpp/libclang/integration_test/function_cases/extern_c_identity/functions.cpp new file mode 100644 index 00000000..6e1e9656 --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/extern_c_identity/functions.cpp @@ -0,0 +1,24 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +#include "functions.hpp" + +extern "C" { +int c_api(int value) { + return value; +} + +int c_api_block(double value) { + return static_cast(value); +} +} \ No newline at end of file diff --git a/cpp/libclang/integration_test/function_cases/extern_c_identity/functions.hpp b/cpp/libclang/integration_test/function_cases/extern_c_identity/functions.hpp new file mode 100644 index 00000000..51460490 --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/extern_c_identity/functions.hpp @@ -0,0 +1,22 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +#pragma once + +extern "C" int c_api(int); + +extern "C" { +int c_api_block(double); +} + + diff --git a/cpp/libclang/integration_test/function_cases/extern_c_identity/run_test.rs b/cpp/libclang/integration_test/function_cases/extern_c_identity/run_test.rs new file mode 100644 index 00000000..0c86ef6d --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/extern_c_identity/run_test.rs @@ -0,0 +1,19 @@ +// ******************************************************************************* +// Copyright (c) 2026 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* + +use test_framework::run_parser_case; + +#[test] +fn test_extern_c_identity() { + run_parser_case(); +} diff --git a/cpp/libclang/integration_test/function_cases/header_internal_linkage_declaration_identity/BUILD b/cpp/libclang/integration_test/function_cases/header_internal_linkage_declaration_identity/BUILD new file mode 100644 index 00000000..86245f74 --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/header_internal_linkage_declaration_identity/BUILD @@ -0,0 +1,30 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +load("//cpp/libclang/integration_test:test_rules.bzl", "cpp_parser_integration_test") + +cc_library( + name = "header_internal_linkage_declaration_identity", + srcs = [ + "first.cpp", + "second.cpp", + ], + hdrs = ["internal_linkage.h"], + visibility = ["//cpp/libclang:__subpackages__"], +) + +cpp_parser_integration_test( + name = "test_header_internal_linkage_declaration_identity", + expected_output = ["expected.json"], + target = ":header_internal_linkage_declaration_identity", +) diff --git a/cpp/libclang/integration_test/function_cases/header_internal_linkage_declaration_identity/expected.json b/cpp/libclang/integration_test/function_cases/header_internal_linkage_declaration_identity/expected.json new file mode 100644 index 00000000..df1abb2b --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/header_internal_linkage_declaration_identity/expected.json @@ -0,0 +1,28 @@ +{ + "free_function_declarations": [ + { + "name": "hidden", + "enclosing_namespace_id": null, + "return_type": "void", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/header_internal_linkage_declaration_identity/internal_linkage.h", + "line": 17 + } + }, + { + "name": "local_static", + "enclosing_namespace_id": null, + "return_type": "void", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/header_internal_linkage_declaration_identity/internal_linkage.h", + "line": 20 + } + } + ], + "functions": [], + "types": {} +} diff --git a/cpp/libclang/integration_test/function_cases/header_internal_linkage_declaration_identity/first.cpp b/cpp/libclang/integration_test/function_cases/header_internal_linkage_declaration_identity/first.cpp new file mode 100644 index 00000000..9add8676 --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/header_internal_linkage_declaration_identity/first.cpp @@ -0,0 +1,14 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +#include "cpp/libclang/integration_test/function_cases/header_internal_linkage_declaration_identity/internal_linkage.h" diff --git a/cpp/libclang/integration_test/function_cases/header_internal_linkage_declaration_identity/internal_linkage.h b/cpp/libclang/integration_test/function_cases/header_internal_linkage_declaration_identity/internal_linkage.h new file mode 100644 index 00000000..6450dc22 --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/header_internal_linkage_declaration_identity/internal_linkage.h @@ -0,0 +1,20 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +// Forbidden based on MISRA C++:2023 Rule 10.3.1. + +namespace { +void hidden(); +} + +static void local_static(); diff --git a/cpp/libclang/integration_test/function_cases/header_internal_linkage_declaration_identity/run_test.rs b/cpp/libclang/integration_test/function_cases/header_internal_linkage_declaration_identity/run_test.rs new file mode 100644 index 00000000..341b9048 --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/header_internal_linkage_declaration_identity/run_test.rs @@ -0,0 +1,19 @@ +// ******************************************************************************* +// Copyright (c) 2026 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* + +use test_framework::run_parser_case; + +#[test] +fn test_header_internal_linkage_declaration_identity() { + run_parser_case(); +} diff --git a/cpp/libclang/integration_test/function_cases/header_internal_linkage_declaration_identity/second.cpp b/cpp/libclang/integration_test/function_cases/header_internal_linkage_declaration_identity/second.cpp new file mode 100644 index 00000000..c5da30e8 --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/header_internal_linkage_declaration_identity/second.cpp @@ -0,0 +1,14 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +#include "internal_linkage.h" diff --git a/cpp/libclang/integration_test/function_cases/internal_linkage_declaration_identity/BUILD b/cpp/libclang/integration_test/function_cases/internal_linkage_declaration_identity/BUILD new file mode 100644 index 00000000..fbd3690d --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/internal_linkage_declaration_identity/BUILD @@ -0,0 +1,29 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +load("//cpp/libclang/integration_test:test_rules.bzl", "cpp_parser_integration_test") + +cc_library( + name = "internal_linkage_declaration_identity", + srcs = [ + "first.cpp", + "second.cpp", + ], + visibility = ["//cpp/libclang:__subpackages__"], +) + +cpp_parser_integration_test( + name = "test_internal_linkage_declaration_identity", + expected_output = ["expected.json"], + target = ":internal_linkage_declaration_identity", +) diff --git a/cpp/libclang/integration_test/function_cases/internal_linkage_declaration_identity/expected.json b/cpp/libclang/integration_test/function_cases/internal_linkage_declaration_identity/expected.json new file mode 100644 index 00000000..a171b18e --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/internal_linkage_declaration_identity/expected.json @@ -0,0 +1,50 @@ +{ + "free_function_declarations": [ + { + "name": "hidden", + "enclosing_namespace_id": null, + "return_type": "void", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/internal_linkage_declaration_identity/first.cpp", + "line": 15 + } + }, + { + "name": "local_static", + "enclosing_namespace_id": null, + "return_type": "void", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/internal_linkage_declaration_identity/first.cpp", + "line": 18 + } + }, + { + "name": "hidden", + "enclosing_namespace_id": null, + "return_type": "void", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/internal_linkage_declaration_identity/second.cpp", + "line": 15 + } + }, + { + "name": "local_static", + "enclosing_namespace_id": null, + "return_type": "void", + "parameters": [], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/internal_linkage_declaration_identity/second.cpp", + "line": 18 + } + } + ], + "functions": [], + "types": {} +} diff --git a/cpp/libclang/integration_test/function_cases/internal_linkage_declaration_identity/first.cpp b/cpp/libclang/integration_test/function_cases/internal_linkage_declaration_identity/first.cpp new file mode 100644 index 00000000..1ed71482 --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/internal_linkage_declaration_identity/first.cpp @@ -0,0 +1,18 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +namespace { +void hidden(); +} + +static void local_static(); diff --git a/cpp/libclang/integration_test/function_cases/internal_linkage_declaration_identity/run_test.rs b/cpp/libclang/integration_test/function_cases/internal_linkage_declaration_identity/run_test.rs new file mode 100644 index 00000000..423fb638 --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/internal_linkage_declaration_identity/run_test.rs @@ -0,0 +1,19 @@ +// ******************************************************************************* +// Copyright (c) 2026 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* + +use test_framework::run_parser_case; + +#[test] +fn test_internal_linkage_declaration_identity() { + run_parser_case(); +} diff --git a/cpp/libclang/integration_test/function_cases/internal_linkage_declaration_identity/second.cpp b/cpp/libclang/integration_test/function_cases/internal_linkage_declaration_identity/second.cpp new file mode 100644 index 00000000..1ed71482 --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/internal_linkage_declaration_identity/second.cpp @@ -0,0 +1,18 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +namespace { +void hidden(); +} + +static void local_static(); diff --git a/cpp/libclang/integration_test/function_cases/namespace_operator_identity/BUILD b/cpp/libclang/integration_test/function_cases/namespace_operator_identity/BUILD new file mode 100644 index 00000000..0a95d16f --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/namespace_operator_identity/BUILD @@ -0,0 +1,26 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* +load("//cpp/libclang/integration_test:test_rules.bzl", "cpp_parser_integration_test") + +cc_library( + name = "namespace_operator_identity", + srcs = ["functions.cpp"], + hdrs = ["functions.hpp"], + visibility = ["//cpp/libclang:__subpackages__"], +) + +cpp_parser_integration_test( + name = "test_namespace_operator_identity", + expected_output = ["expected.json"], + target = ":namespace_operator_identity", +) diff --git a/cpp/libclang/integration_test/function_cases/namespace_operator_identity/expected.json b/cpp/libclang/integration_test/function_cases/namespace_operator_identity/expected.json new file mode 100644 index 00000000..0506c1e7 --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/namespace_operator_identity/expected.json @@ -0,0 +1,62 @@ +{ + "free_function_declarations": [ + { + "name": "operator==", + "enclosing_namespace_id": "app", + "return_type": "bool", + "parameters": [ + { + "name": "", + "param_type": "const Token &", + "is_variadic": false + }, + { + "name": "", + "param_type": "const Token &", + "is_variadic": false + } + ], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/namespace_operator_identity/functions.hpp", + "line": 20 + } + } + ], + "functions": [ + { + "id": { + "name": "operator==", + "scope": { + "Namespace": [ + "app" + ] + } + }, + "kind": "Free", + "return_type": { + "Builtin": "bool" + }, + "body": [] + } + ], + "types": { + "app::Token": { + "id": "app::Token", + "name": "Token", + "enclosing_namespace_id": "app", + "entity_type": "Struct", + "enum_literals": [], + "methods": [], + "relationships": [], + "stereotypes": [], + "template_parameters": null, + "type_aliases": [], + "variables": [], + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/namespace_operator_identity/functions.hpp", + "line": 18 + } + } + } +} \ No newline at end of file diff --git a/cpp/libclang/integration_test/function_cases/namespace_operator_identity/functions.cpp b/cpp/libclang/integration_test/function_cases/namespace_operator_identity/functions.cpp new file mode 100644 index 00000000..89891e77 --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/namespace_operator_identity/functions.cpp @@ -0,0 +1,22 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +#include "functions.hpp" + +namespace app { + +bool operator==(const Token& lhs, const Token& rhs) { + return &lhs == &rhs; +} + +} // namespace app \ No newline at end of file diff --git a/cpp/libclang/integration_test/function_cases/namespace_operator_identity/functions.hpp b/cpp/libclang/integration_test/function_cases/namespace_operator_identity/functions.hpp new file mode 100644 index 00000000..a91c4c56 --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/namespace_operator_identity/functions.hpp @@ -0,0 +1,22 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +#pragma once + +namespace app { + +struct Token {}; + +bool operator==(const Token&, const Token&); + +} // namespace app \ No newline at end of file diff --git a/cpp/libclang/integration_test/function_cases/namespace_operator_identity/run_test.rs b/cpp/libclang/integration_test/function_cases/namespace_operator_identity/run_test.rs new file mode 100644 index 00000000..0dc1c35e --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/namespace_operator_identity/run_test.rs @@ -0,0 +1,19 @@ +// ******************************************************************************* +// Copyright (c) 2026 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* + +use test_framework::run_parser_case; + +#[test] +fn test_namespace_operator_identity() { + run_parser_case(); +} diff --git a/cpp/libclang/integration_test/function_cases/overloaded_parameter_identity/BUILD b/cpp/libclang/integration_test/function_cases/overloaded_parameter_identity/BUILD new file mode 100644 index 00000000..315532c8 --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/overloaded_parameter_identity/BUILD @@ -0,0 +1,26 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* +load("//cpp/libclang/integration_test:test_rules.bzl", "cpp_parser_integration_test") + +cc_library( + name = "overloaded_parameter_identity", + srcs = ["functions.cpp"], + hdrs = ["functions.hpp"], + visibility = ["//cpp/libclang:__subpackages__"], +) + +cpp_parser_integration_test( + name = "test_overloaded_parameter_identity", + expected_output = ["expected.json"], + target = ":overloaded_parameter_identity", +) diff --git a/cpp/libclang/integration_test/function_cases/overloaded_parameter_identity/expected.json b/cpp/libclang/integration_test/function_cases/overloaded_parameter_identity/expected.json new file mode 100644 index 00000000..f4c66ed9 --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/overloaded_parameter_identity/expected.json @@ -0,0 +1,40 @@ +{ + "free_function_declarations": [ + { + "name": "choose", + "enclosing_namespace_id": null, + "return_type": "void", + "parameters": [ + { + "name": "", + "param_type": "int", + "is_variadic": false + } + ], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/overloaded_parameter_identity/functions.hpp", + "line": 16 + } + }, + { + "name": "choose", + "enclosing_namespace_id": null, + "return_type": "void", + "parameters": [ + { + "name": "", + "param_type": "double", + "is_variadic": false + } + ], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/overloaded_parameter_identity/functions.hpp", + "line": 17 + } + } + ], + "functions": [], + "types": {} +} diff --git a/cpp/libclang/integration_test/function_cases/overloaded_parameter_identity/functions.cpp b/cpp/libclang/integration_test/function_cases/overloaded_parameter_identity/functions.cpp new file mode 100644 index 00000000..28c6664e --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/overloaded_parameter_identity/functions.cpp @@ -0,0 +1,14 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +#include "functions.hpp" diff --git a/cpp/libclang/integration_test/function_cases/overloaded_parameter_identity/functions.hpp b/cpp/libclang/integration_test/function_cases/overloaded_parameter_identity/functions.hpp new file mode 100644 index 00000000..c2f1c15e --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/overloaded_parameter_identity/functions.hpp @@ -0,0 +1,17 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +#pragma once + +void choose(int); +void choose(double); \ No newline at end of file diff --git a/cpp/libclang/integration_test/function_cases/overloaded_parameter_identity/run_test.rs b/cpp/libclang/integration_test/function_cases/overloaded_parameter_identity/run_test.rs new file mode 100644 index 00000000..7ee1c525 --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/overloaded_parameter_identity/run_test.rs @@ -0,0 +1,19 @@ +// ******************************************************************************* +// Copyright (c) 2026 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* + +use test_framework::run_parser_case; + +#[test] +fn test_overloaded_parameter_identity() { + run_parser_case(); +} diff --git a/cpp/libclang/integration_test/function_cases/pointee_const_parameter_identity/BUILD b/cpp/libclang/integration_test/function_cases/pointee_const_parameter_identity/BUILD new file mode 100644 index 00000000..d23e0502 --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/pointee_const_parameter_identity/BUILD @@ -0,0 +1,26 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* +load("//cpp/libclang/integration_test:test_rules.bzl", "cpp_parser_integration_test") + +cc_library( + name = "pointee_const_parameter_identity", + srcs = ["functions.cpp"], + hdrs = ["functions.hpp"], + visibility = ["//cpp/libclang:__subpackages__"], +) + +cpp_parser_integration_test( + name = "test_pointee_const_parameter_identity", + expected_output = ["expected.json"], + target = ":pointee_const_parameter_identity", +) diff --git a/cpp/libclang/integration_test/function_cases/pointee_const_parameter_identity/expected.json b/cpp/libclang/integration_test/function_cases/pointee_const_parameter_identity/expected.json new file mode 100644 index 00000000..f5cd9772 --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/pointee_const_parameter_identity/expected.json @@ -0,0 +1,63 @@ +{ + "free_function_declarations": [ + { + "name": "pointee_const", + "enclosing_namespace_id": null, + "return_type": "void", + "parameters": [ + { + "name": "", + "param_type": "int *", + "is_variadic": false + } + ], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/pointee_const_parameter_identity/functions.hpp", + "line": 16 + } + }, + { + "name": "pointee_const", + "enclosing_namespace_id": null, + "return_type": "void", + "parameters": [ + { + "name": "", + "param_type": "const int *", + "is_variadic": false + } + ], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/pointee_const_parameter_identity/functions.hpp", + "line": 17 + } + } + ], + "functions": [ + { + "id": { + "name": "pointee_const", + "scope": "Global" + }, + "kind": "Free", + "return_type": { + "Builtin": "void" + }, + "body": [] + }, + { + "id": { + "name": "pointee_const", + "scope": "Global" + }, + "kind": "Free", + "return_type": { + "Builtin": "void" + }, + "body": [] + } + ], + "types": {} +} diff --git a/cpp/libclang/integration_test/function_cases/pointee_const_parameter_identity/functions.cpp b/cpp/libclang/integration_test/function_cases/pointee_const_parameter_identity/functions.cpp new file mode 100644 index 00000000..90e9f856 --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/pointee_const_parameter_identity/functions.cpp @@ -0,0 +1,17 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +#include "functions.hpp" + +void pointee_const(int *value) {} +void pointee_const(const int *value) {} diff --git a/cpp/libclang/integration_test/function_cases/pointee_const_parameter_identity/functions.hpp b/cpp/libclang/integration_test/function_cases/pointee_const_parameter_identity/functions.hpp new file mode 100644 index 00000000..4671fb04 --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/pointee_const_parameter_identity/functions.hpp @@ -0,0 +1,17 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +#pragma once + +void pointee_const(int *); +void pointee_const(const int *); diff --git a/cpp/libclang/integration_test/function_cases/pointee_const_parameter_identity/run_test.rs b/cpp/libclang/integration_test/function_cases/pointee_const_parameter_identity/run_test.rs new file mode 100644 index 00000000..742d918e --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/pointee_const_parameter_identity/run_test.rs @@ -0,0 +1,19 @@ +// ******************************************************************************* +// Copyright (c) 2026 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* + +use test_framework::run_parser_case; + +#[test] +fn test_pointee_const_parameter_identity() { + run_parser_case(); +} diff --git a/cpp/libclang/integration_test/function_cases/top_level_const_parameter_dedup/BUILD b/cpp/libclang/integration_test/function_cases/top_level_const_parameter_dedup/BUILD new file mode 100644 index 00000000..dbddc7be --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/top_level_const_parameter_dedup/BUILD @@ -0,0 +1,26 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* +load("//cpp/libclang/integration_test:test_rules.bzl", "cpp_parser_integration_test") + +cc_library( + name = "top_level_const_parameter_dedup", + srcs = ["functions.cpp"], + hdrs = ["functions.hpp"], + visibility = ["//cpp/libclang:__subpackages__"], +) + +cpp_parser_integration_test( + name = "test_top_level_const_parameter_dedup", + expected_output = ["expected.json"], + target = ":top_level_const_parameter_dedup", +) diff --git a/cpp/libclang/integration_test/function_cases/top_level_const_parameter_dedup/expected.json b/cpp/libclang/integration_test/function_cases/top_level_const_parameter_dedup/expected.json new file mode 100644 index 00000000..066ac8c3 --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/top_level_const_parameter_dedup/expected.json @@ -0,0 +1,90 @@ +{ + "free_function_declarations": [ + { + "name": "topconst", + "enclosing_namespace_id": null, + "return_type": "void", + "parameters": [ + { + "name": "", + "param_type": "int", + "is_variadic": false + } + ], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/top_level_const_parameter_dedup/functions.hpp", + "line": 16 + } + } + ], + "functions": [ + { + "id": { + "name": "topconst", + "scope": "Global" + }, + "kind": "Free", + "return_type": { + "Builtin": "void" + }, + "body": [] + }, + { + "id": { + "scope": { + "Type": { + "namespace": [], + "type_path": [ + "Widget" + ] + } + }, + "name": "topvolatile" + }, + "kind": "Method", + "return_type": { + "Builtin": "void" + }, + "body": [] + } + ], + "types": { + "Widget": { + "id": "Widget", + "name": "Widget", + "enclosing_namespace_id": null, + "entity_type": "Struct", + "enum_literals": [], + "methods": [ + { + "name": "topvolatile", + "return_type": "void", + "visibility": "public", + "parameters": [ + { + "name": "", + "param_type": "int", + "is_variadic": false + } + ], + "template_parameters": null, + "modifiers": [], + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/top_level_const_parameter_dedup/functions.hpp", + "line": 19 + } + } + ], + "relationships": [], + "stereotypes": [], + "template_parameters": null, + "type_aliases": [], + "variables": [], + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/top_level_const_parameter_dedup/functions.hpp", + "line": 18 + } + } + } +} diff --git a/cpp/libclang/integration_test/function_cases/top_level_const_parameter_dedup/functions.cpp b/cpp/libclang/integration_test/function_cases/top_level_const_parameter_dedup/functions.cpp new file mode 100644 index 00000000..1a3786b9 --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/top_level_const_parameter_dedup/functions.cpp @@ -0,0 +1,18 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +#include "functions.hpp" + +void topconst(const int value) {} + +void Widget::topvolatile(volatile int value) {} diff --git a/cpp/libclang/integration_test/function_cases/top_level_const_parameter_dedup/functions.hpp b/cpp/libclang/integration_test/function_cases/top_level_const_parameter_dedup/functions.hpp new file mode 100644 index 00000000..80851e92 --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/top_level_const_parameter_dedup/functions.hpp @@ -0,0 +1,20 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +#pragma once + +void topconst(int); + +struct Widget { + void topvolatile(int); +}; diff --git a/cpp/libclang/integration_test/function_cases/top_level_const_parameter_dedup/run_test.rs b/cpp/libclang/integration_test/function_cases/top_level_const_parameter_dedup/run_test.rs new file mode 100644 index 00000000..99771af6 --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/top_level_const_parameter_dedup/run_test.rs @@ -0,0 +1,19 @@ +// ******************************************************************************* +// Copyright (c) 2026 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* + +use test_framework::run_parser_case; + +#[test] +fn test_top_level_const_parameter_dedup() { + run_parser_case(); +} diff --git a/cpp/libclang/integration_test/function_cases/variadic_parameter_identity/BUILD b/cpp/libclang/integration_test/function_cases/variadic_parameter_identity/BUILD new file mode 100644 index 00000000..f9f4f9b9 --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/variadic_parameter_identity/BUILD @@ -0,0 +1,26 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* +load("//cpp/libclang/integration_test:test_rules.bzl", "cpp_parser_integration_test") + +cc_library( + name = "variadic_parameter_identity", + srcs = ["functions.cpp"], + hdrs = ["functions.hpp"], + visibility = ["//cpp/libclang:__subpackages__"], +) + +cpp_parser_integration_test( + name = "test_variadic_parameter_identity", + expected_output = ["expected.json"], + target = ":variadic_parameter_identity", +) diff --git a/cpp/libclang/integration_test/function_cases/variadic_parameter_identity/expected.json b/cpp/libclang/integration_test/function_cases/variadic_parameter_identity/expected.json new file mode 100644 index 00000000..6916c4cc --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/variadic_parameter_identity/expected.json @@ -0,0 +1,68 @@ +{ + "free_function_declarations": [ + { + "name": "log_message", + "enclosing_namespace_id": null, + "return_type": "void", + "parameters": [ + { + "name": "", + "param_type": "int", + "is_variadic": false + } + ], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/variadic_parameter_identity/functions.hpp", + "line": 16 + } + }, + { + "name": "log_message", + "enclosing_namespace_id": null, + "return_type": "void", + "parameters": [ + { + "name": "", + "param_type": "int", + "is_variadic": false + }, + { + "name": "", + "param_type": null, + "is_variadic": true + } + ], + "template_parameters": null, + "source_location": { + "file": "cpp/libclang/integration_test/function_cases/variadic_parameter_identity/functions.hpp", + "line": 17 + } + } + ], + "functions": [ + { + "id": { + "name": "log_message", + "scope": "Global" + }, + "kind": "Free", + "return_type": { + "Builtin": "void" + }, + "body": [] + }, + { + "id": { + "name": "log_message", + "scope": "Global" + }, + "kind": "Free", + "return_type": { + "Builtin": "void" + }, + "body": [] + } + ], + "types": {} +} \ No newline at end of file diff --git a/cpp/libclang/integration_test/function_cases/variadic_parameter_identity/functions.cpp b/cpp/libclang/integration_test/function_cases/variadic_parameter_identity/functions.cpp new file mode 100644 index 00000000..4fa270fd --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/variadic_parameter_identity/functions.cpp @@ -0,0 +1,18 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +#include "functions.hpp" + +void log_message(int value) {} + +void log_message(int value, ...) {} \ No newline at end of file diff --git a/cpp/libclang/integration_test/function_cases/variadic_parameter_identity/functions.hpp b/cpp/libclang/integration_test/function_cases/variadic_parameter_identity/functions.hpp new file mode 100644 index 00000000..a9680810 --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/variadic_parameter_identity/functions.hpp @@ -0,0 +1,17 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +#pragma once + +void log_message(int); +void log_message(int, ...); \ No newline at end of file diff --git a/cpp/libclang/integration_test/function_cases/variadic_parameter_identity/run_test.rs b/cpp/libclang/integration_test/function_cases/variadic_parameter_identity/run_test.rs new file mode 100644 index 00000000..30580978 --- /dev/null +++ b/cpp/libclang/integration_test/function_cases/variadic_parameter_identity/run_test.rs @@ -0,0 +1,19 @@ +// ******************************************************************************* +// Copyright (c) 2026 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* + +use test_framework::run_parser_case; + +#[test] +fn test_variadic_parameter_identity() { + run_parser_case(); +} diff --git a/cpp/libclang/src/main.rs b/cpp/libclang/src/main.rs index 7ca84d3b..d38f8b7b 100644 --- a/cpp/libclang/src/main.rs +++ b/cpp/libclang/src/main.rs @@ -23,8 +23,8 @@ use class_serializer::ClassSerializer; use utils::{render_entity_tree, write_debug_json, write_entity_tree, write_fbs_output}; use visit_tu::{ - is_external_dependency_path, CallableIdentityKey, EntityMapExt, FunctionDef, SourceEntityKey, - SourceFileCache, VisitContext, Visitor, + is_external_dependency_path, CallableDeclarationKey, EntityMapExt, FunctionDef, + SourceEntityKey, SourceFileCache, VisitContext, Visitor, }; #[derive(ClapParser, Debug)] @@ -60,8 +60,8 @@ struct ParseOutputs { #[derive(Default)] struct ParseState { source_files: SourceFileCache, - seen_free_function_declarations: HashSet, - seen_method_declarations: HashSet, + seen_free_function_declarations: HashSet, + seen_method_declarations: HashSet, seen_function_definitions: HashSet, } diff --git a/cpp/libclang/src/visitor/src/callable_declaration.rs b/cpp/libclang/src/visitor/src/callable_declaration.rs index 66a80522..7df91c00 100644 --- a/cpp/libclang/src/visitor/src/callable_declaration.rs +++ b/cpp/libclang/src/visitor/src/callable_declaration.rs @@ -13,10 +13,23 @@ use clang::{Entity, EntityKind}; use class_diagram::{FunctionArgument, TemplateParameter}; +use cpp_semantics::ResolvedType; +use crate::context::CallableArgumentKey; use crate::types::renderer::render_type_for_display; use crate::types::resolver::resolve_type; +/// Parameter data extracted once from a callable cursor. +/// +/// `parameters`: display output. +/// `parameter_types`: semantic analysis. +/// `parameter_keys`: declaration deduplication. +pub(crate) struct ParsedCallableParameters { + pub parameters: Vec, + pub parameter_types: Vec, + pub parameter_keys: Vec, +} + /// Returns callable parameters, including the fallback required for template cursors. /// /// Normally libclang provides the parameter list via `Entity::get_arguments()`. @@ -35,23 +48,40 @@ pub(crate) fn callable_arguments<'tu>(entity: &Entity<'tu>) -> Vec> }) } -pub(crate) fn parse_function_parameters(entity: &Entity) -> Vec { - let mut parameters: Vec = callable_arguments(entity) - .into_iter() - .map(|argument| { - let raw_param_type = argument - .get_type() - .map(|ty| ty.get_display_name()) - .unwrap_or_default(); - - FunctionArgument { - name: argument.get_name().unwrap_or_default(), - param_type: Some(normalize_pack_expansion_type(&raw_param_type)), +pub(crate) fn parse_callable_parameters(entity: &Entity) -> ParsedCallableParameters { + let mut parameters = Vec::new(); + let mut parameter_types = Vec::new(); + let mut parameter_keys = Vec::new(); + + for argument in callable_arguments(entity) { + let raw_param_type = argument + .get_type() + .map(|ty| ty.get_display_name()) + .unwrap_or_default(); + let resolved_type = argument.get_type().map(|ty| resolve_type(&ty)); + + parameters.push(FunctionArgument { + name: argument.get_name().unwrap_or_default(), + param_type: Some(normalize_pack_expansion_type(&raw_param_type)), + is_variadic: false, + is_pack_expansion: raw_param_type.contains("..."), + }); + + if let Some(resolved_type) = resolved_type { + parameter_keys.push(CallableArgumentKey { + param_type: Some(render_resolved_type_for_signature_identity(&resolved_type)), is_variadic: false, is_pack_expansion: raw_param_type.contains("..."), - } - }) - .collect(); + }); + parameter_types.push(resolved_type); + } else { + parameter_keys.push(CallableArgumentKey { + param_type: None, + is_variadic: false, + is_pack_expansion: raw_param_type.contains("..."), + }); + } + } if entity.get_type().is_some_and(|ty| ty.is_variadic()) { parameters.push(FunctionArgument { @@ -60,9 +90,18 @@ pub(crate) fn parse_function_parameters(entity: &Entity) -> Vec Option { @@ -111,6 +150,19 @@ fn normalize_pack_expansion_type(param_type: &str) -> String { param_type.replace("...", "").trim().to_string() } +fn render_resolved_type_for_signature_identity(resolved: &ResolvedType) -> String { + strip_top_level_cv_qualifiers_ref(resolved).render_for_display() +} + +fn strip_top_level_cv_qualifiers_ref(resolved: &ResolvedType) -> &ResolvedType { + match resolved { + ResolvedType::Const(inner) | ResolvedType::Volatile(inner) => { + strip_top_level_cv_qualifiers_ref(inner) + } + other => other, + } +} + fn is_template_parameter_pack(entity: &Entity) -> bool { entity.get_range().is_some_and(|range| { range diff --git a/cpp/libclang/src/visitor/src/clang_adapter/scope.rs b/cpp/libclang/src/visitor/src/clang_adapter/scope.rs index 4b0877f4..efc35a1e 100644 --- a/cpp/libclang/src/visitor/src/clang_adapter/scope.rs +++ b/cpp/libclang/src/visitor/src/clang_adapter/scope.rs @@ -13,7 +13,7 @@ //! Shared semantic-scope extraction helpers for libclang entities. -use clang::{Entity, EntityKind}; +use clang::{Entity, EntityKind, Linkage}; use cpp_semantics::Scope; // ── Namespace scopes ─────────────────────────────────────────────────────── @@ -45,6 +45,14 @@ pub(crate) fn namespace_id(entity: &Entity) -> Option { (!path.is_empty()).then(|| path.join("::")) } +/// Returns whether the entity has linkage local to a single translation unit. +pub(crate) fn has_translation_unit_local_linkage(entity: &Entity) -> bool { + matches!( + entity.get_linkage(), + Some(Linkage::Internal | Linkage::UniqueExternal) + ) +} + // ── Type scopes ──────────────────────────────────────────────────────────── /// Returns the enclosing type names from outermost to innermost. diff --git a/cpp/libclang/src/visitor/src/class_visitor.rs b/cpp/libclang/src/visitor/src/class_visitor.rs index 3fa2b772..d989d65b 100644 --- a/cpp/libclang/src/visitor/src/class_visitor.rs +++ b/cpp/libclang/src/visitor/src/class_visitor.rs @@ -12,6 +12,7 @@ // ******************************************************************************* use clang::{Entity, EntityKind}; +use std::collections::HashSet; use class_diagram::{ EntityType, MemberVariable, Method, MethodModifier, SimpleEntity, TypeAlias, Visibility, @@ -21,7 +22,8 @@ use crate::callable_declaration::parse_template_parameters; use crate::clang_adapter::scope::{namespace_id, semantic_parent_id}; use crate::clang_adapter::source_location::parse_source_location; use crate::context::{ - ExtractedMethodDeclaration, ParsedBaseClass, ParsedClassInfo, ParsedVariableType, VisitContext, + CallableDeclarationKey, CallableOwnerKey, ExtractedMethodDeclaration, ParsedBaseClass, + ParsedClassInfo, ParsedVariableType, VisitContext, }; use crate::types::renderer::render_type_for_display; use crate::types::resolver::resolve_type; @@ -57,12 +59,37 @@ impl ClassVisitor { crate::class_relationship_resolver::resolve_relationships(ctx); } + /// Registers a method declaration exactly once, but only if its owning class + /// has already been registered in the visit context. + pub(crate) fn register_method_declaration( + ctx: &mut VisitContext, + seen_method_declarations: &mut HashSet, + declaration: ExtractedMethodDeclaration, + ) -> bool { + let identity = CallableDeclarationKey { + owner: CallableOwnerKey::Method { + class_id: declaration.class_id.clone(), + }, + signature: declaration.signature_key.clone(), + }; + + if seen_method_declarations.contains(&identity) { + return false; + } + + if !Self::attach_method_declaration(ctx, declaration) { + return false; + } + + seen_method_declarations.insert(identity) + } + /// Adds a callable declaration to its owning class and preserves the /// class-level metadata used by relationship inference. - pub(crate) fn add_method_declaration( + fn attach_method_declaration( ctx: &mut VisitContext, declaration: ExtractedMethodDeclaration, - ) { + ) -> bool { let (types, parsed_class_info) = (&mut ctx.types, &mut ctx.parsed_class_info); let (Some(class), Some(builder)) = ( types.get_mut(&declaration.class_id), @@ -73,12 +100,13 @@ impl ClassVisitor { declaration.method.name, declaration.class_id ); - return; + return false; }; update_entity_type_for_method(class, builder, &declaration.method); class.methods.push(declaration.method); builder.method_types.push(declaration.method_type); + true } fn visit_class( @@ -279,3 +307,99 @@ fn update_method_flags(builder: &mut ParsedClassInfo, method: &Method) { builder.has_concrete_methods = true; } } + +#[cfg(test)] +mod tests { + use super::ClassVisitor; + use crate::context::{ + CallableDeclarationKey, CallableOwnerKey, CallableSignatureKey, ExtractedMethodDeclaration, + ParsedClassInfo, ParsedMethodType, VisitContext, + }; + use class_diagram::{Method, SimpleEntity}; + use cpp_semantics::ResolvedType; + use std::collections::HashSet; + + #[test] + fn missing_owning_class_does_not_burn_method_identity() { + let mut ctx = VisitContext::default(); + let mut seen = HashSet::::new(); + let declaration = method_declaration(); + + assert!(!ClassVisitor::register_method_declaration( + &mut ctx, + &mut seen, + declaration, + )); + assert!(seen.is_empty()); + } + + #[test] + fn successful_method_insert_is_still_deduplicated() { + let mut ctx = VisitContext::default(); + ctx.types.insert( + "Widget".to_string(), + SimpleEntity { + id: "Widget".to_string(), + name: "Widget".to_string(), + ..Default::default() + }, + ); + ctx.parsed_class_info.insert( + "Widget".to_string(), + ParsedClassInfo { + id: "Widget".to_string(), + ..Default::default() + }, + ); + + let mut seen = HashSet::::new(); + + assert!(ClassVisitor::register_method_declaration( + &mut ctx, + &mut seen, + method_declaration(), + )); + assert!(!ClassVisitor::register_method_declaration( + &mut ctx, + &mut seen, + method_declaration(), + )); + + let class = ctx.types.get("Widget").expect("class should exist"); + assert_eq!(class.methods.len(), 1); + assert_eq!(ctx.parsed_class_info["Widget"].method_types.len(), 1); + assert_eq!( + seen, + HashSet::from([CallableDeclarationKey { + owner: CallableOwnerKey::Method { + class_id: "Widget".to_string(), + }, + signature: CallableSignatureKey { + name: "compute".to_string(), + parameters: vec![], + }, + }]) + ); + } + + fn method_declaration() -> ExtractedMethodDeclaration { + ExtractedMethodDeclaration { + class_id: "Widget".to_string(), + method: Method { + name: "compute".to_string(), + parameters: vec![], + ..Default::default() + }, + method_type: ParsedMethodType { + name: "compute".to_string(), + return_type: ResolvedType::Builtin("void".to_string()), + parameter_types: vec![], + source_location: Default::default(), + }, + signature_key: CallableSignatureKey { + name: "compute".to_string(), + parameters: vec![], + }, + } + } +} diff --git a/cpp/libclang/src/visitor/src/context.rs b/cpp/libclang/src/visitor/src/context.rs index c4777311..e516bcf3 100644 --- a/cpp/libclang/src/visitor/src/context.rs +++ b/cpp/libclang/src/visitor/src/context.rs @@ -28,32 +28,50 @@ pub struct SourceEntityKey { pub source_offset: u32, } -/// Identifies a free function by its logical signature for class-diagram output. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct CallableIdentityKey { - pub owner: CallableOwnerIdentityKey, +/// Identifies a callable declaration by owner and logical signature for deduplication. +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct CallableDeclarationKey { + pub owner: CallableOwnerKey, + pub signature: CallableSignatureKey, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct CallableSignatureKey { pub name: String, - pub parameters: Vec, + pub parameters: Vec, } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum CallableOwnerIdentityKey { +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum CallableOwnerKey { FreeFunction { enclosing_namespace_id: Option, + linkage_scope: CallableLinkageScope, }, Method { class_id: String, }, } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct CallableArgumentIdentityKey { +/// Distinguishes free functions whose logical identity can span translation +/// units from those that are local to a single translation unit. +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum CallableLinkageScope { + /// The callable has external linkage, so repeated declarations from + /// different translation units can be deduplicated by logical signature. + External, + /// The callable has translation-unit-local linkage, so declarations from + /// different source files must remain distinct. + TranslationUnitLocal { source_file: PathBuf }, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct CallableArgumentKey { pub param_type: Option, pub is_variadic: bool, pub is_pack_expansion: bool, } -impl From<&FunctionArgument> for CallableArgumentIdentityKey { +impl From<&FunctionArgument> for CallableArgumentKey { fn from(argument: &FunctionArgument) -> Self { Self { param_type: argument.param_type.clone(), @@ -80,6 +98,7 @@ pub struct ExtractedMethodDeclaration { pub class_id: String, pub method: Method, pub method_type: ParsedMethodType, + pub signature_key: CallableSignatureKey, } #[derive(Default, Debug, Clone, Serialize, Deserialize)] diff --git a/cpp/libclang/src/visitor/src/function_visitor.rs b/cpp/libclang/src/visitor/src/function_visitor.rs index 0b3e9328..a3312100 100644 --- a/cpp/libclang/src/visitor/src/function_visitor.rs +++ b/cpp/libclang/src/visitor/src/function_visitor.rs @@ -24,15 +24,16 @@ use cpp_semantics::{ use std::collections::HashSet; use crate::callable_declaration::{ - callable_arguments, parse_callable_return_type, parse_function_parameters, - parse_template_parameters, + parse_callable_parameters, parse_callable_return_type, parse_template_parameters, +}; +use crate::clang_adapter::scope::{ + callable_scope, has_translation_unit_local_linkage, namespace_id, }; -use crate::clang_adapter::scope::{callable_scope, namespace_id}; use crate::clang_adapter::source_filter; use crate::clang_adapter::source_location::parse_source_location; use crate::class_visitor::{parse_visibility, ClassVisitor}; use crate::context::{ - CallableArgumentIdentityKey, CallableIdentityKey, CallableOwnerIdentityKey, + CallableDeclarationKey, CallableLinkageScope, CallableOwnerKey, CallableSignatureKey, ExtractedFreeFunctionDeclaration, ExtractedFunction, ExtractedMethodDeclaration, ParsedMethodType, SourceEntityKey, }; @@ -54,8 +55,8 @@ impl FunctionVisitor { pub(crate) fn visit_with_state( ctx: &mut VisitContext, source_files: &mut SourceFileCache, - seen_free_function_declarations: &mut HashSet, - seen_method_declarations: &mut HashSet, + seen_free_function_declarations: &mut HashSet, + seen_method_declarations: &mut HashSet, seen_function_definitions: &mut HashSet, entity: Entity, ) { @@ -65,13 +66,14 @@ impl FunctionVisitor { match &function_id.scope { Scope::Type { .. } => { - if let Some(declaration) = Self::extract_method_declaration( - seen_method_declarations, - &entity, - &function_id, - function_kind, - ) { - ClassVisitor::add_method_declaration(ctx, declaration); + if let Some(declaration) = + Self::extract_method_declaration(&entity, &function_id, function_kind) + { + ClassVisitor::register_method_declaration( + ctx, + seen_method_declarations, + declaration, + ); } else { log::debug!( "skipping type-scoped callable '{}': unsupported function kind {:?}", @@ -105,7 +107,6 @@ impl FunctionVisitor { // ── Top-level extraction ────────────────────────────────────────────────── fn extract_method_declaration( - seen_method_declarations: &mut HashSet, entity: &Entity, id: &FunctionId, kind: FunctionKind, @@ -120,18 +121,8 @@ impl FunctionVisitor { return None; } - let parameters = parse_function_parameters(entity); + let parsed_parameters = parse_callable_parameters(entity); let class_id = id.scope.qualified_name(); - if !Self::insert_callable_identity( - seen_method_declarations, - CallableOwnerIdentityKey::Method { - class_id: class_id.clone(), - }, - &id.name, - ¶meters, - ) { - return None; - } let return_type = entity .get_result_type() @@ -140,10 +131,7 @@ impl FunctionVisitor { let method_type = ParsedMethodType { name: id.name.clone(), return_type: return_type.clone(), - parameter_types: callable_arguments(entity) - .into_iter() - .filter_map(|argument| argument.get_type().map(|ty| resolve_type(&ty))) - .collect(), + parameter_types: parsed_parameters.parameter_types.clone(), source_location: parse_source_location(entity), }; @@ -188,7 +176,7 @@ impl FunctionVisitor { name: id.name.clone(), return_type, visibility: parse_visibility(entity), - parameters, + parameters: parsed_parameters.parameters, template_parameters: parse_template_parameters(entity), modifiers: MethodModifier::from_conditions([ (entity.is_static_method(), MethodModifier::Static), @@ -210,24 +198,27 @@ impl FunctionVisitor { class_id, method, method_type, + signature_key: CallableSignatureKey { + name: id.name.clone(), + parameters: parsed_parameters.parameter_keys, + }, }) } fn extract_free_function_declaration( - seen_free_function_declarations: &mut HashSet, + seen_free_function_declarations: &mut HashSet, entity: &Entity, id: &FunctionId, ) -> Option { let key = Self::extract_source_entity_key(entity)?; - let parameters = parse_function_parameters(entity); - if !Self::insert_callable_identity( - seen_free_function_declarations, - CallableOwnerIdentityKey::FreeFunction { - enclosing_namespace_id: namespace_id(entity), + let parsed_parameters = parse_callable_parameters(entity); + if !seen_free_function_declarations.insert(CallableDeclarationKey { + owner: Self::free_function_owner_key(entity, &key), + signature: CallableSignatureKey { + name: id.name.clone(), + parameters: parsed_parameters.parameter_keys, }, - &id.name, - ¶meters, - ) { + }) { return None; } @@ -237,7 +228,7 @@ impl FunctionVisitor { name: id.name.clone(), enclosing_namespace_id: namespace_id(entity), return_type: parse_callable_return_type(entity), - parameters, + parameters: parsed_parameters.parameters, template_parameters: parse_template_parameters(entity), source_location: parse_source_location(entity), }, @@ -290,20 +281,17 @@ impl FunctionVisitor { Some(extracted_function) } - fn insert_callable_identity( - seen_declarations: &mut HashSet, - owner: CallableOwnerIdentityKey, - name: &str, - parameters: &[class_diagram::FunctionArgument], - ) -> bool { - seen_declarations.insert(CallableIdentityKey { - owner, - name: name.to_string(), - parameters: parameters - .iter() - .map(CallableArgumentIdentityKey::from) - .collect(), - }) + fn free_function_owner_key(entity: &Entity, key: &SourceEntityKey) -> CallableOwnerKey { + CallableOwnerKey::FreeFunction { + enclosing_namespace_id: namespace_id(entity), + linkage_scope: if has_translation_unit_local_linkage(entity) { + CallableLinkageScope::TranslationUnitLocal { + source_file: key.source_file.clone(), + } + } else { + CallableLinkageScope::External + }, + } } // ── AST navigation helpers ──────────────────────────────────────────────── diff --git a/cpp/libclang/src/visitor/src/lib.rs b/cpp/libclang/src/visitor/src/lib.rs index 145a9bee..facf0774 100644 --- a/cpp/libclang/src/visitor/src/lib.rs +++ b/cpp/libclang/src/visitor/src/lib.rs @@ -26,7 +26,7 @@ pub use cpp_semantics::{BodyItem, FunctionDef, ResolvedType}; pub use clang_adapter::source_filter::is_external_dependency_path; pub use class_visitor::ClassVisitor; -pub use context::{CallableIdentityKey, CallableOwnerIdentityKey, SourceEntityKey, VisitContext}; +pub use context::{CallableDeclarationKey, CallableOwnerKey, SourceEntityKey, VisitContext}; pub use context_ext::EntityMapExt; pub use enum_visitor::EnumVisitor; pub use function_visitor::FunctionVisitor; diff --git a/cpp/libclang/src/visitor/src/visitor.rs b/cpp/libclang/src/visitor/src/visitor.rs index 0564fdba..49c7ede3 100644 --- a/cpp/libclang/src/visitor/src/visitor.rs +++ b/cpp/libclang/src/visitor/src/visitor.rs @@ -19,7 +19,7 @@ use log::warn; use crate::clang_adapter::source_filter; use crate::class_visitor::ClassVisitor; -use crate::context::{CallableIdentityKey, SourceEntityKey, VisitContext}; +use crate::context::{CallableDeclarationKey, SourceEntityKey, VisitContext}; use crate::enum_visitor::EnumVisitor; use crate::function_visitor::FunctionVisitor; @@ -58,8 +58,8 @@ impl SourceFileCache { pub struct Visitor<'a> { ctx: &'a mut VisitContext, source_files: &'a mut SourceFileCache, - seen_free_function_declarations: &'a mut HashSet, - seen_method_declarations: &'a mut HashSet, + seen_free_function_declarations: &'a mut HashSet, + seen_method_declarations: &'a mut HashSet, seen_function_definitions: &'a mut HashSet, } @@ -67,8 +67,8 @@ impl<'a> Visitor<'a> { pub fn new( ctx: &'a mut VisitContext, source_files: &'a mut SourceFileCache, - seen_free_function_declarations: &'a mut HashSet, - seen_method_declarations: &'a mut HashSet, + seen_free_function_declarations: &'a mut HashSet, + seen_method_declarations: &'a mut HashSet, seen_function_definitions: &'a mut HashSet, ) -> Self { Self {