Add clang_CXXMethod_isExplicit to libclang

The new method is a wrapper of `CXXConstructorDecl::isExplicit` and
`CXXConversionDecl::isExplicit`, allowing the user to recognize whether
the declaration pointed to by a cursor was marked with the explicit
specifier.

An export for the function, together with its documentation, was added
to "clang/include/clang-c/Index.h" with an implementation provided in
"clang/tools/libclang/CIndex.cpp".

The implementation is based on similar `clang_CXXMethod`
implementations, returning a falsy unsigned value when the cursor is not
a declaration, is not a declaration for a constructor or conversion
function or is not a relevant declaration that was marked with the
`explicit` specifier.

The new symbol was added to "clang/tools/libclang/libclang.map" to be
exported, under the LLVM16 tag.

"clang/tools/c-index-test/c-index-test.c" was modified to print a
specific tag, "(explicit)", for cursors that are recognized by
`clang_CXXMethod_isExplicit`.

Two new regression files, "explicit-constructor.cpp" and
"explicit-conversion-function.cpp", were added to "clang/test/Index", to
ensure that the behavior of the new function is correct for constructors
and conversion functions, respectively.

The "get-cursor.cpp", "index-file.cpp" and
"recursive-cxx-member-calls.cpp" regression files in "clang/test/Index"
were updated as they were affected by the new "(explicit)" tag.

A binding for the new function was added to libclang's python's
bindings, in "clang/bindings/python/clang/cindex.py", as the
"is_explicit_method" method under `Cursor`.

An accompanying test was added to
"clang/bindings/python/tests/cindex/test_cursor.py", mimicking the
regression tests for the C side.

The current release note for Clang, "clang/docs/ReleaseNotes.rst" was
modified to report the new addition under the "libclang" section.

Reviewed By: aaron.ballman

Differential Revision: https://reviews.llvm.org/D140756
This commit is contained in:
Luca Di Sera 2023-01-23 13:14:17 +01:00
parent 9922c78a67
commit 0a51bc731b
11 changed files with 203 additions and 4 deletions

View File

@ -1529,6 +1529,51 @@ class Cursor(Structure):
"""
return conf.lib.clang_CXXMethod_isMoveAssignmentOperator(self)
def is_explicit_method(self):
"""Determines if a C++ constructor or conversion function is
explicit, returning 1 if such is the case and 0 otherwise.
Constructors or conversion functions are declared explicit through
the use of the explicit specifier.
For example, the following constructor and conversion function are
not explicit as they lack the explicit specifier:
class Foo {
Foo();
operator int();
};
While the following constructor and conversion function are
explicit as they are declared with the explicit specifier.
class Foo {
explicit Foo();
explicit operator int();
};
This method will return 0 when given a cursor pointing to one of
the former declarations and it will return 1 for a cursor pointing
to the latter declarations.
The explicit specifier allows the user to specify a
conditional compile-time expression whose value decides
whether the marked element is explicit or not.
For example:
constexpr bool foo(int i) { return i % 2 == 0; }
class Foo {
explicit(foo(1)) Foo();
explicit(foo(2)) operator int();
}
This method will return 0 for the constructor and 1 for
the conversion function.
"""
return conf.lib.clang_CXXMethod_isExplicit(self)
def is_mutable_field(self):
"""Returns True if the cursor refers to a C++ field that is declared
'mutable'.
@ -3494,6 +3539,10 @@ functionList = [
[Cursor],
bool),
("clang_CXXMethod_isExplicit",
[Cursor],
bool),
("clang_CXXMethod_isPureVirtual",
[Cursor],
bool),

View File

@ -277,6 +277,57 @@ class TestCursor(unittest.TestCase):
for cursor in non_move_assignment_operators_cursors
]))
def test_is_explicit_method(self):
"""Ensure Cursor.is_explicit_method works."""
source_with_explicit_methods = """
struct Foo {
// Those are explicit
explicit Foo(double);
explicit(true) Foo(char);
explicit operator double();
explicit(true) operator char();
};
"""
source_without_explicit_methods = """
struct Foo {
// Those are not explicit
Foo(int);
explicit(false) Foo(float);
operator int();
explicit(false) operator float();
};
"""
tu_with_explicit_methods = get_tu(
source_with_explicit_methods, lang="cpp"
)
tu_without_explicit_methods = get_tu(
source_without_explicit_methods, lang="cpp"
)
explicit_methods_cursors = [
*get_cursors(tu_with_explicit_methods, "Foo")[1:],
get_cursor(tu_with_explicit_methods, "operator double"),
get_cursor(tu_with_explicit_methods, "operator char"),
]
non_explicit_methods_cursors = [
*get_cursors(tu_without_explicit_methods, "Foo")[1:],
get_cursor(tu_without_explicit_methods, "operator int"),
get_cursor(tu_without_explicit_methods, "operator float"),
]
self.assertEqual(len(explicit_methods_cursors), 4)
self.assertTrue(len(non_explicit_methods_cursors), 4)
self.assertTrue(all([
cursor.is_explicit_method()
for cursor in explicit_methods_cursors
]))
self.assertFalse(any([
cursor.is_explicit_method()
for cursor in non_explicit_methods_cursors
]))
def test_is_mutable_field(self):
"""Ensure Cursor.is_mutable_field works."""
source = 'class X { int x_; mutable int y_; };'

View File

@ -4343,6 +4343,51 @@ CINDEX_LINKAGE unsigned clang_CXXMethod_isCopyAssignmentOperator(CXCursor C);
*/
CINDEX_LINKAGE unsigned clang_CXXMethod_isMoveAssignmentOperator(CXCursor C);
/**
* Determines if a C++ constructor or conversion function was declared
* explicit, returning 1 if such is the case and 0 otherwise.
*
* Constructors or conversion functions are declared explicit through
* the use of the explicit specifier.
*
* For example, the following constructor and conversion function are
* not explicit as they lack the explicit specifier:
*
* class Foo {
* Foo();
* operator int();
* };
*
* While the following constructor and conversion function are
* explicit as they are declared with the explicit specifier.
*
* class Foo {
* explicit Foo();
* explicit operator int();
* };
*
* This function will return 0 when given a cursor pointing to one of
* the former declarations and it will return 1 for a cursor pointing
* to the latter declarations.
*
* The explicit specifier allows the user to specify a
* conditional compile-time expression whose value decides
* whether the marked element is explicit or not.
*
* For example:
*
* constexpr bool foo(int i) { return i % 2 == 0; }
*
* class Foo {
* explicit(foo(1)) Foo();
* explicit(foo(2)) operator int();
* }
*
* This function will return 0 for the constructor and 1 for
* the conversion function.
*/
CINDEX_LINKAGE unsigned clang_CXXMethod_isExplicit(CXCursor C);
/**
* Determine if a C++ record is abstract, i.e. whether a class or struct
* has a pure virtual member function.

View File

@ -0,0 +1,16 @@
struct Foo {
// Those are not explicit constructors
Foo(int);
explicit(false) Foo(float);
// Those are explicit constructors
explicit Foo(double);
explicit(true) Foo(unsigned char);
};
// RUN: c-index-test -test-print-type --std=c++20 %s | FileCheck %s
// CHECK: StructDecl=Foo:1:8 (Definition) [type=Foo] [typekind=Record] [isPOD=0]
// CHECK: CXXConstructor=Foo:3:5 (converting constructor) [type=void (int)] [typekind=FunctionProto] [resulttype=void] [resulttypekind=Void] [args= [int] [Int]] [isPOD=0] [isAnonRecDecl=0]
// CHECK: CXXConstructor=Foo:4:21 (converting constructor) [type=void (float)] [typekind=FunctionProto] [resulttype=void] [resulttypekind=Void] [args= [float] [Float]] [isPOD=0] [isAnonRecDecl=0]
// CXXConstructor=Foo:7:20 (explicit) [type=void (double)] [typekind=FunctionProto] [resulttype=void] [resulttypekind=Void] [args= [double] [Double]] [isPOD=0] [isAnonRecDecl=0]
// CXXConstructor=Foo:8:20 (explicit) [type=void (unsigned char)] [typekind=FunctionProto] [resulttype=void] [resulttypekind=Void] [args= [unsigned char] [UChar]] [isPOD=0] [isAnonRecDecl=0]

View File

@ -0,0 +1,16 @@
struct Foo {
// Those are not explicit conversion functions
operator int();
explicit(false) operator float();
// Those are explicit conversion functions
explicit operator double();
explicit(true) operator unsigned char();
};
// RUN: c-index-test -test-print-type --std=c++20 %s | FileCheck %s
// CHECK: StructDecl=Foo:1:8 (Definition) [type=Foo] [typekind=Record]
// CHECK: CXXConversion=operator int:3:5 [type=int ()] [typekind=FunctionProto] [resulttype=int] [resulttypekind=Int] [isPOD=0] [isAnonRecDecl=0]
// CHECK: CXXConversion=operator float:4:21 [type=float ()] [typekind=FunctionProto] [resulttype=float] [resulttypekind=Float] [isPOD=0] [isAnonRecDecl=0]
// CHECK: CXXConversion=operator double:7:14 (explicit) [type=double ()] [typekind=FunctionProto] [resulttype=double] [resulttypekind=Double] [isPOD=0] [isAnonRecDecl=0]
// CHECK: CXXConversion=operator unsigned char:8:20 (explicit) [type=unsigned char ()] [typekind=FunctionProto] [resulttype=unsigned char] [resulttypekind=UChar] [isPOD=0] [isAnonRecDecl=0]

View File

@ -269,7 +269,7 @@ struct Z {
// CHECK-SPELLING: 128:6 CXXMethod=operator[]:128:6 Extent=[128:3 - 128:36] Spelling=operator[] ([128:6 - 128:16])
// CHECK-SPELLING: 129:6 CXXMethod=operator->:129:6 Extent=[129:3 - 129:18] Spelling=operator-> ([129:6 - 129:16])
// CHECK-SPELLING: 130:6 CXXMethod=operator():130:6 (const) Extent=[130:3 - 130:37] Spelling=operator() ([130:6 - 130:16])
// CHECK-SPELLING: 132:12 CXXConversion=operator bool:132:12 (const) Extent=[132:3 - 132:33] Spelling=operator bool ([132:12 - 132:25])
// CHECK-SPELLING: 132:12 CXXConversion=operator bool:132:12 (const) (explicit) Extent=[132:3 - 132:33] Spelling=operator bool ([132:12 - 132:25])
// CHECK-SPELLING: 146:11 FunctionDecl=operator""_toint:146:11 (Definition) Extent=[146:1 - 146:72] Spelling=operator""_toint ([146:11 - 146:27])
// CHECK-SPELLING: 149:6 FunctionDecl=f_noexcept:149:6 (noexcept) Extent=[149:1 - 149:27] Spelling=f_noexcept ([149:6 - 149:16])
// CHECK-SPELLING: 150:25 FunctionTemplate=f_computed_noexcept:150:25 (computed-noexcept) Extent=[150:1 - 150:73] Spelling=f_computed_noexcept ([150:25 - 150:44])

View File

@ -53,4 +53,4 @@ class C {
// CHECK: [indexDeclaration]: kind: constructor | name: B | {{.*}} | loc: 33:12
// CHECK: [indexDeclaration]: kind: constructor | name: B | {{.*}} (copy constructor) (converting constructor) | loc: 34:3
// CHECK: [indexDeclaration]: kind: constructor | name: B | {{.*}} (move constructor) (converting constructor) | loc: 35:3
// CHECK: [indexDeclaration]: kind: constructor | name: C | {{.*}} (copy constructor) | loc: 39:12
// CHECK: [indexDeclaration]: kind: constructor | name: C | {{.*}} (copy constructor) (explicit) | loc: 39:12

View File

@ -824,7 +824,7 @@ AttributeList::Kind AttributeList::getKind(const IdentifierInfo * Name) {
// CHECK-tokens: Keyword: "public" [86:1 - 86:7] CXXAccessSpecifier=:86:1 (Definition)
// CHECK-tokens: Punctuation: ":" [86:7 - 86:8] CXXAccessSpecifier=:86:1 (Definition)
// CHECK-tokens: Keyword: "explicit" [87:3 - 87:11] CXXConstructor=StringSwitch<T, R>:87:12 (Definition)
// CHECK-tokens: Identifier: "StringSwitch" [87:12 - 87:24] CXXConstructor=StringSwitch<T, R>:87:12 (Definition)
// CHECK-tokens: Identifier: "StringSwitch" [87:12 - 87:24] CXXConstructor=StringSwitch<T, R>:87:12 (Definition) (explicit)
// CHECK-tokens: Punctuation: "(" [87:24 - 87:25] CXXConstructor=StringSwitch<T, R>:87:12 (Definition)
// CHECK-tokens: Identifier: "StringRef" [87:25 - 87:34] TypeRef=class llvm::StringRef:38:7
// CHECK-tokens: Identifier: "Str" [87:35 - 87:38] ParmDecl=Str:87:35 (Definition)
@ -1839,7 +1839,7 @@ AttributeList::Kind AttributeList::getKind(const IdentifierInfo * Name) {
// CHECK: 84:3: TypeRef=class llvm::StringRef:38:7 Extent=[84:3 - 84:12]
// CHECK: 85:12: FieldDecl=Result:85:12 (Definition) Extent=[85:3 - 85:18]
// CHECK: 86:1: CXXAccessSpecifier=:86:1 (Definition) Extent=[86:1 - 86:8]
// CHECK: 87:12: CXXConstructor=StringSwitch<T, R>:87:12 (Definition) Extent=[87:3 - 87:64]
// CHECK: 87:12: CXXConstructor=StringSwitch<T, R>:87:12 (Definition) (explicit) Extent=[87:3 - 87:64]
// CHECK: 87:35: ParmDecl=Str:87:35 (Definition) Extent=[87:25 - 87:38]
// CHECK: 87:25: TypeRef=class llvm::StringRef:38:7 Extent=[87:25 - 87:34]
// CHECK: 87:42: MemberRef=Str:84:13 Extent=[87:42 - 87:45]

View File

@ -916,6 +916,8 @@ static void PrintCursor(CXCursor Cursor, const char *CommentSchemaFile) {
printf(" (copy-assignment operator)");
if (clang_CXXMethod_isMoveAssignmentOperator(Cursor))
printf(" (move-assignment operator)");
if (clang_CXXMethod_isExplicit(Cursor))
printf(" (explicit)");
if (clang_CXXRecord_isAbstract(Cursor))
printf(" (abstract)");
if (clang_EnumDecl_isScoped(Cursor))

View File

@ -8947,6 +8947,25 @@ unsigned clang_CXXMethod_isMoveAssignmentOperator(CXCursor C) {
return (Method && Method->isMoveAssignmentOperator()) ? 1 : 0;
}
unsigned clang_CXXMethod_isExplicit(CXCursor C) {
if (!clang_isDeclaration(C.kind))
return 0;
const Decl *D = cxcursor::getCursorDecl(C);
const FunctionDecl *FD = D->getAsFunction();
if (!FD)
return 0;
if (const auto *Ctor = dyn_cast<CXXConstructorDecl>(FD))
return Ctor->isExplicit();
if (const auto *Conv = dyn_cast<CXXConversionDecl>(FD))
return Conv->isExplicit();
return 0;
}
unsigned clang_CXXRecord_isAbstract(CXCursor C) {
if (!clang_isDeclaration(C.kind))
return 0;

View File

@ -416,6 +416,7 @@ LLVM_16 {
clang_disposeAPISet;
clang_getSymbolGraphForCursor;
clang_getSymbolGraphForUSR;
clang_CXXMethod_isExplicit;
};
# Example of how to add a new symbol version entry. If you do add a new symbol