Skip to content

[Cpp Parser] Implement free function declarations - #476

Merged
hoe-jo merged 5 commits into
eclipse-score:mainfrom
melodyoncode:melody_function_declaration
Sep 24, 2026
Merged

hoe-jo merged 5 commits into
eclipse-score:mainfrom
melodyoncode:melody_function_declaration

Conversation

@melodyoncode

Copy link
Copy Markdown
Contributor

No description provided.

@melodyoncode
melodyoncode force-pushed the melody_function_declaration branch 4 times, most recently from f2da397 to 7a6290c Compare September 21, 2026 07:38

@hoe-jo hoe-jo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe add tests for:

  • overloads
  • static/anon-ns, variadic ...,
  • namespace-scope operator==
  • extern "C", decl/def const mismatch
  • multi-TU internal linkage
  • ctor/dtor bodies

Comment on lines +50 to +59
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
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

merge keeps first-seen entity_type and source_location, so a forward decl in an earlier TU wins over the real definition.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's correct. At the moment, source_location is intentionally modeled as a single representative location for the entity, so the merge keeps the first-seen value rather than trying to retain every declaration/definition site. The downside is that, when a forward declaration is seen first, it can win over the full definition.

For example,

// a.h
namespace util {
     class Widget;
}

// b.h
namespace util {
    class Widget {
      public:
        int value;
    };
}

If we see a.h first and b.h afterward, the merged entity may end up with the type-level source_location pointing to the forward declaration in a.h, while member-level source locations still point to their actual definitions in b.h.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

entity_type is different, since a later TU may refine the classification, and keeping the first-seen value can therefore be incorrect.
I will update the logic for entity_type

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

source_location is pinned to the first-seen entity, so a forward declaration parsed first wins over the definition. namespace svc { class IThing; } in a.cpp, abstract IThing (virtual void doIt() = 0) in b.cpp → svc::IThing -> Class, loc fwd.hpp:2

* SPDX-License-Identifier: Apache-2.0
********************************************************************************/

#include "widget_forward.h"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currect Include?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is intentional.
The purpose of this test case is to cover the scenario where the parser sees a forward declaration in one TU and the full definition in another, so first.cpp only includes the forward declaration on purpose.

Comment thread cpp/libclang/src/visitor/src/context.rs Outdated
Comment on lines +50 to +60
pub struct CallableArgumentIdentityKey {
pub param_type: Option<String>,
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,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CallableArgumentIdentityKey.param_type uses Type::get_display_name(), not the canonical type.
Top-level const on a param is not part of a C++ signature, so void topconst(int); + void topconst(const int v) {} emits 2 declarations for 1 function

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. add logic for top-level const and volatile.

})
}

fn extract_free_function_declaration(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

namespace_id() returns None for both global scope and anonymous namespaces, and seen_free_function_declarations spans all TUs — so static void f() / anon-ns f() in two different .cpp files collapse to one declaration while both functions entries survive

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

update the logic with test internal_linkage_declaration_identity

Comment on lines +125 to +134
if !Self::insert_callable_identity(
seen_method_declarations,
CallableOwnerIdentityKey::Method {
class_id: class_id.clone(),
},
&id.name,
&parameters,
) {
return None;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extract_method_declaration inserts into seen_method_declarations before add_method_declaration runs, so when that bails with "incompletely registered owning class" the entry is burned

@melodyoncode melodyoncode Sep 22, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. update the logic, only insert once add method declaration correctly.

@melodyoncode

Copy link
Copy Markdown
Contributor Author

maybe add tests for:

  • overloads
  • static/anon-ns, variadic ...,
  • namespace-scope operator==
  • extern "C", decl/def const mismatch
  • multi-TU internal linkage
  • ctor/dtor bodies
  • overloads: overloaded_parameter_identity
  • static/anon-ns & multi-TU internal linkage : internal_linkage_declaration_identity, header_internal_linkage_declaration_identity
  • namespace-scope operator==: namespace_operator_identity
  • extern "C": extern_c_identity
  • decl/def const mismatch: top_level_const_parameter_dedup
  • ctor/dtor: complex_class(already have)

@melodyoncode
melodyoncode force-pushed the melody_function_declaration branch from 7a6290c to c912d2c Compare September 23, 2026 10:07
@hoe-jo
hoe-jo self-requested a review September 23, 2026 14:43
Comment on lines +153 to +155
fn render_resolved_type_for_signature_identity(resolved: &ResolvedType) -> String {
strip_top_level_cv_qualifiers_ref(resolved).render_for_display()
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cv-stripping fixes only one of three C++ parameter adjustments:
using MyInt=int; void alias_fn(MyInt); + void alias_fn(int v){} → 2 declarations for one function. Same for typedef int OldInt, and for void arr_fn(int[5]) vs void arr_fn(int*) → 2 entries. ResolvedType retains typedef sugar and skips array→pointer decay

Comment on lines +50 to +59
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
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

source_location is pinned to the first-seen entity, so a forward declaration parsed first wins over the definition. namespace svc { class IThing; } in a.cpp, abstract IThing (virtual void doIt() = 0) in b.cpp → svc::IThing -> Class, loc fwd.hpp:2

@hoe-jo
hoe-jo force-pushed the melody_function_declaration branch from c912d2c to 625e509 Compare September 24, 2026 06:03
@hoe-jo
hoe-jo self-requested a review September 24, 2026 07:39

@hoe-jo hoe-jo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merge now, findings will be addressed in follow up PR

@hoe-jo
hoe-jo merged commit e112472 into eclipse-score:main Sep 24, 2026
33 of 39 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants