Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions cpp/libclang/integration_test/cases/dependent_decltype_base/BUILD
Original file line number Diff line number Diff line change
@@ -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
# *******************************************************************************
load("//cpp/libclang/integration_test:test_rules.bzl", "cpp_parser_integration_test")

cc_library(
name = "dependent_decltype_base",
srcs = ["dependent_base.cpp"],
visibility = ["//cpp/libclang:__subpackages__"],
)

cpp_parser_integration_test(
name = "test_dependent_decltype_base",
expected_output = ["expected.json"],
target = ":dependent_decltype_base",
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/********************************************************************************
* 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
********************************************************************************/

class ContainerBase {
public:
virtual ~ContainerBase() = default;
};

template <typename T> T declval();

template <typename T>
auto is_maplike_container_impl(T value) -> decltype(value.begin());

// Regression test for a real parser crash: a base class specifier whose type
// depends on an uninstantiated template parameter, e.g. through the common
// decltype/SFINAE detection idiom, cannot be resolved to a concrete entity id
// without template instantiation. The parser must not panic on this; it
// should simply skip the unresolvable inheritance relationship.
template <typename T>
struct IsMaplikeContainer : decltype(is_maplike_container_impl(declval<T>())){};

// A normal, resolvable base class must still be parsed correctly even though
// the translation unit also contains the unresolvable template above.
class Container : public ContainerBase {};

// A concrete, non-template return type used purely so `decltype(make_widget())`
// below has something real to resolve to.
class Widget {
public:
Widget();
};

Widget make_widget();

class ConcreteWidgetUser : public decltype(make_widget()){};
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
{
"types": {
"ContainerBase": {
"id": "ContainerBase",
"name": "ContainerBase",
"enclosing_namespace_id": null,
"entity_type": "Class",
"enum_literals": [],
"methods": [
{
"name": "~ContainerBase",
"return_type": null,
"parameters": [],
"modifiers": [
"Virtual",
"Destructor"
],
"template_parameters": null,
"visibility": "public",
"source_location": {
"file": "",
"line": 16
}
}
],
"relationships": [],
"template_parameters": null,
"type_aliases": [],
"variables": [],
"source_location": {
"file": "cpp/libclang/integration_test/cases/dependent_decltype_base/dependent_base.cpp",
"line": 14
}
},
"IsMaplikeContainer": {
"id": "IsMaplikeContainer",
"name": "IsMaplikeContainer",
"enclosing_namespace_id": null,
"entity_type": "Class",
"enum_literals": [],
"methods": [],
"relationships": [],
"template_parameters": [
{
"Type": {
"name": "T",
"is_pack": false
}
}
],
"type_aliases": [],
"variables": [],
"source_location": {
"file": "cpp/libclang/integration_test/cases/dependent_decltype_base/dependent_base.cpp",
"line": 30
}
},
"Container": {
"id": "Container",
"name": "Container",
"enclosing_namespace_id": null,
"entity_type": "Class",
"enum_literals": [],
"methods": [],
"relationships": [
{
"source": "Container",
"target": "ContainerBase",
"relation_type": "Inheritance",
"source_multiplicity": null,
"target_multiplicity": null,
"source_location": {
"file": "",
"line": 34
}
}
],
"template_parameters": null,
"type_aliases": [],
"variables": [],
"source_location": {
"file": "cpp/libclang/integration_test/cases/dependent_decltype_base/dependent_base.cpp",
"line": 34
}
},
"Widget": {
"id": "Widget",
"name": "Widget",
"enclosing_namespace_id": null,
"entity_type": "Class",
"enum_literals": [],
"methods": [
{
"name": "Widget",
"return_type": null,
"parameters": [],
"modifiers": [
"Constructor"
],
"template_parameters": null,
"visibility": "public",
"source_location": {
"file": "",
"line": 40
}
}
],
"relationships": [],
"template_parameters": null,
"type_aliases": [],
"variables": [],
"source_location": {
"file": "cpp/libclang/integration_test/cases/dependent_decltype_base/dependent_base.cpp",
"line": 38
}
},
"ConcreteWidgetUser": {
"id": "ConcreteWidgetUser",
"name": "ConcreteWidgetUser",
"enclosing_namespace_id": null,
"entity_type": "Class",
"enum_literals": [],
"methods": [],
"relationships": [
{
"source": "ConcreteWidgetUser",
"target": "Widget",
"relation_type": "Inheritance",
"source_multiplicity": null,
"target_multiplicity": null,
"source_location": {
"file": "",
"line": 45
}
}
],
"template_parameters": null,
"type_aliases": [],
"variables": [],
"source_location": {
"file": "cpp/libclang/integration_test/cases/dependent_decltype_base/dependent_base.cpp",
"line": 45
}
}
},
"functions": []
}
Original file line number Diff line number Diff line change
@@ -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
// *******************************************************************************

use test_framework::run_parser_case;
#[test]
fn test_dependent_decltype_base() {
run_parser_case();
}
38 changes: 35 additions & 3 deletions cpp/libclang/src/visitor/src/class_parser_helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ pub enum ResolvedType {
},

Unknown(String),

/// A type that is structurally unresolvable without template instantiation,
/// e.g. `decltype(some_trait_impl(std::declval<T>()))` inside an
/// uninstantiated template (the common SFINAE/trait-detection idiom). This is
/// distinct from `Unknown`: it is an expected, permanent limitation of
/// AST-only analysis rather than a gap in the resolver, so callers should not
/// treat it as an error condition.
Dependent(String),
}

impl ResolvedType {
Expand Down Expand Up @@ -96,7 +104,9 @@ impl ResolvedType {
/// - Wrapper/qualifier/array nodes delegate to their inner element.
pub fn relationship_target_entity_id(&self) -> Option<&str> {
match self {
ResolvedType::Builtin(_) | ResolvedType::Unknown(_) => None,
ResolvedType::Builtin(_) | ResolvedType::Unknown(_) | ResolvedType::Dependent(_) => {
None
}
ResolvedType::UserDefined(id) => Some(id),
ResolvedType::Template { base, args } => args
.iter()
Expand Down Expand Up @@ -252,6 +262,8 @@ impl ResolvedType {
},

ResolvedType::Unknown(s) => s.clone(),

ResolvedType::Dependent(s) => s.clone(),
}
}

Expand Down Expand Up @@ -332,7 +344,10 @@ fn contains_template_type(resolved: &ResolvedType) -> bool {
| ResolvedType::Const(inner)
| ResolvedType::Volatile(inner) => contains_template_type(inner),
ResolvedType::Array { element, .. } => contains_template_type(element),
ResolvedType::Builtin(_) | ResolvedType::UserDefined(_) | ResolvedType::Unknown(_) => false,
ResolvedType::Builtin(_)
| ResolvedType::UserDefined(_)
| ResolvedType::Unknown(_)
| ResolvedType::Dependent(_) => false,
}
}

Expand Down Expand Up @@ -520,7 +535,9 @@ fn resolve_named_type(original: &Type, canonical: &Type) -> ResolvedType {
// Fallback order matters:
// 1) source declaration (preserves local spelling when available)
// 2) canonical declaration (captures normalized identity)
// 3) unknown name heuristic
// 3) dependent-expression heuristic (e.g. `decltype(expr_using<T>)` inside an
// uninstantiated template) — structurally unresolvable before instantiation
// 4) unknown name heuristic
if let Some(resolved) = resolve_decl_based(original) {
return resolved;
}
Expand All @@ -529,9 +546,24 @@ fn resolve_named_type(original: &Type, canonical: &Type) -> ResolvedType {
return resolved;
}

if is_dependent_expression_type(original) {
return ResolvedType::Dependent(resolve_unknown_name(original, canonical));
}

ResolvedType::Unknown(resolve_unknown_name(original, canonical))
}

/// Detects types libclang exposes as `Unexposed` because their meaning depends on
/// an unbound template parameter, e.g. `decltype(is_x_impl(std::declval<T>()))`
/// in a template that is never instantiated in this translation unit. Such types
/// cannot be resolved to a concrete entity id without template instantiation,
/// which is out of scope for AST-only analysis. This is checked only after both
/// declaration-based resolution attempts have already failed, so it never shadows
/// a legitimately resolvable type.
fn is_dependent_expression_type(ty: &Type) -> bool {
ty.get_kind() == TypeKind::Unexposed
}

fn resolve_unknown_name(original: &Type, canonical: &Type) -> String {
let display_name = original.get_display_name();
let canonical_name = canonical.get_display_name();
Expand Down
52 changes: 52 additions & 0 deletions cpp/libclang/src/visitor/src/class_parser_helper_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,3 +195,55 @@ fn collapse_std_internal_namespaces_does_not_drop_non_std_internal_namespaces()
vec!["foo".to_string(), "__detail".to_string(), "Bar".to_string()]
);
}

// `ResolvedType::Dependent` represents types libclang cannot resolve without
// template instantiation, e.g. the base class in:
// template <typename T>
// struct is_maplike_container : decltype(is_maplike_container_impl(std::declval<T>())) {};
// It must behave like `Unknown` for entity/relationship lookups (there is no
// entity id to find), but is a distinct variant so callers can tell "expected,
// permanent limitation" apart from "missing/unanalyzed dependency" (`Unknown`).

#[test]
fn dependent_type_has_no_referenced_entity_id() {
let ty = ResolvedType::Dependent(
"decltype(is_maplike_container_impl(std::declval<T>()))".to_string(),
);

assert_eq!(ty.referenced_entity_id(), None);
}

#[test]
fn dependent_type_has_no_relationship_target() {
let ty = ResolvedType::Dependent(
"decltype(is_maplike_container_impl(std::declval<T>()))".to_string(),
);

assert_eq!(ty.relationship_target_entity_id(), None);
}

#[test]
fn dependent_type_is_not_non_owning() {
let ty = ResolvedType::Dependent("decltype(foo(std::declval<T>()))".to_string());

assert!(!ty.is_non_owning());
}

#[test]
fn dependent_type_renders_its_source_text() {
let ty = ResolvedType::Dependent("decltype(foo(std::declval<T>()))".to_string());

assert_eq!(ty.render_for_display(), "decltype(foo(std::declval<T>()))");
}

#[test]
fn dependent_type_nested_in_wrapper_has_no_referenced_entity_id() {
// A dependent expression wrapped in a qualifier (e.g. as a const base or
// through recursive resolution) must still be unresolvable, not silently
// fall back to `self` and look like something concrete.
let ty = ResolvedType::Const(Box::new(ResolvedType::Dependent(
"decltype(foo(std::declval<T>()))".to_string(),
)));

assert_eq!(ty.referenced_entity_id(), None);
}
Loading
Loading