Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Possible regression in libclang clang_getTypeSpelling #61810

Closed
gjasny opened this issue Mar 29, 2023 · 7 comments
Closed

Possible regression in libclang clang_getTypeSpelling #61810

gjasny opened this issue Mar 29, 2023 · 7 comments
Labels
clang:as-a-library libclang and C++ API

Comments

@gjasny
Copy link

gjasny commented Mar 29, 2023

Hello,

I inherited a code generator that is based on luaclang and the libclang C interface.
That generator can be used to create pimpl-like proxy interfaces or Google Mock classes for existing interfaces.

While upgrading libclang from v15 to v16 I noticed a change in clang_getTypeSpelling. Before v16 it printed fully qualified namespaces for function argument types, now only the types as written in the declaration itself.

I created a small test case within the libclang Python binding test:

diff --git a/clang/bindings/python/tests/cindex/test_type.py b/clang/bindings/python/tests/cindex/test_type.py
index efe9b0f50be8..4f1ade7ab2f1 100644
--- a/clang/bindings/python/tests/cindex/test_type.py
+++ b/clang/bindings/python/tests/cindex/test_type.py
@@ -466,3 +466,30 @@ class TestType(unittest.TestCase):
         # Variable without a template argument.
         cursor = get_cursor(tu, 'bar')
         self.assertEqual(cursor.get_num_template_arguments(), -1)
+
+    def test_type_spelling_namespace(self):
+        """Ensure Type.spelling prints whole namespace."""
+
+        source ="""
+namespace X {
+    namespace Y {
+        struct Params {
+            int i;
+        };
+        struct Foo {
+            virtual void f(const Params& p) = 0;
+        };
+    }
+}
+    """
+        tu = get_tu(source, lang='cpp')
+        f = get_cursor(tu, 'f')
+        self.assertIsNotNone(f)
+
+        args = f.type.argument_types()
+        self.assertIsNotNone(args)
+        self.assertEqual(len(args), 1)
+
+        t0 = args[0]
+        self.assertIsNotNone(t0)
+        self.assertEqual(t0.spelling, 'const X::Y::Params &')

The test works as expected on the release/15.x branch but fails on the release/16.x branch:

$ ninja check-clang-python
...
======================================================================
FAIL: test_type_spelling_namespace (tests.cindex.test_type.TestType)
Ensure Type.is_function_variadic works.
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/gregorj/src/llvm-project/clang/bindings/python/tests/cindex/test_type.py", line 495, in test_type_spelling_namespace
    self.assertEqual(t0.spelling, 'const X::Y::Params &')
AssertionError: 'const Params &' != 'const X::Y::Params &'
- const Params &
+ const X::Y::Params &
?       ++++++

Would the change in behaviour above qualify as a regression? If not, is there a way to restore the old behaviour somehow?

Thanks,
Gregor

@EugeneZelenko EugeneZelenko added clang:as-a-library libclang and C++ API and removed new issue labels Mar 29, 2023
@gjasny
Copy link
Author

gjasny commented Mar 30, 2023

It bisects down to 15f3cd6 (from @mizvekov).

@diseraluca
Copy link
Contributor

We have the same issue for QDoc, where it is blocking our transition to Clang 16.

We independently found 15f3cd6 to be the culprit too.

I've been trying to restore the same behavior but failed in doing so.

PrintingPolicy does not seem to help independently of how we set it up.
The upcoming SuppressElaboration does seem to do the most of the trick, but it is not in 16 (And I haven't seen a patch adding it to libclang either).

Lowering to the C++ AST and playing from there has not been working much either, as the code that libclang internally uses has been equally touched.

The only thing that comes near that I could find was manually replicating much of the work done in "QualTypeNames.cpp", with some special casing, but I still haven't reached parity with the previous behavior.

@shafik
Copy link
Collaborator

shafik commented Sep 16, 2023

CC @AaronBallman @erichkeane because I don't know who to tag in here

@mizvekov
Copy link
Contributor

I think this is essentially a duplicate of #58040

This relied on an inconsistent behavior that only worked for the simple case the name had no kw / qualifiers, as soon as you would add one, you see the type name as written, instead of fully qualified.
That patch just made that print as-written for all cases.

The recommended approach is indeed to use clang::TypeName::getFullyQualifiedName from QualTypeNames.cpp.

@diseraluca
Copy link
Contributor

Thank you for your answer @mizvekov .

I apologize as I hadn't found #58040, which would have shed some more light on our problem.

For our case, clang::TypeName::getFullyQualifiedName is not going to cut it as it does a bit too much. For example, it fully qualifies all template parameters, which in turn breaks quite a lot of our downstream code that wasn't expecting that.

I think I will need to improve our, quite old, code, as I understand it was really working because of a bug.
Nonetheless, I was wondering if it might makes sense to expose some more of the code in QualTypeNames.cpp.

For example, for our case, it would have been great to have access to createNestedNameSpecifierForScopeOf(ASTContext, const Type*, ...) which would have helped in transitioning while keeping backwards compatibility, so that we could improve our code with a better foundation and without having some important tasks blocked.

Apart from this, at least on our side, the answer you gave is completely satisfying, and we thank you for that, and consider this bug to be closable.

@mizvekov
Copy link
Contributor

Don't worry, no need for apologies :)

I think exposing createNestedNameSpecifierForScopeOf would be fine, this would be an easy first patch if you want to get involved.

@mizvekov
Copy link
Contributor

mizvekov commented Sep 17, 2023

Duplicate of #58040

@mizvekov mizvekov closed this as not planned Won't fix, can't repro, duplicate, stale Sep 17, 2023
@mizvekov mizvekov marked this as a duplicate of #58040 Sep 17, 2023
qtprojectorg pushed a commit to qt/qttools that referenced this issue Nov 11, 2023
QDoc depends on the LLVM project, specifically Clang and its exported
APIs, to perform its operations.

The Clang APIs are used by QDoc to parse the source code of a project,
both to find the user-provided documentation and to perform certain
sanity checks about it.

For many elements, the documentation that is at the end shown to the
user, uses text that is extracted from the source code by the usage of
Clang.

For example, when a user documents a method, the stringified
user-presentable version of the types of the arguments of that method
will be obtained by a usage of Clang's APIs by QDoc.

In-between LLVM 15 and LLVM 16, the upstream project fixed a multi-year
long bug that modified the way in which the common operations to obtain
certain stringified representations of the code actually format that
code.

For example, the stringified representation of a type's name will be
qualified differently on LLVM 15 and LLVM 16+.

Due to the way QDoc acquires the representation for certain source code
components and the changes between LLVM 15 and LLVM 16, the output that
QDoc produces will be different between the two versions.

QDoc provides a regression testing suite that is based on the final
output documentation that QDoc builds and that is enforced in CI.
The changes in the final documentation output caused by the changes in
the LLVM project made it difficult to update the provisioned version in
CI as the regression test suite would break and it is not currently
possible to provide a version that would work on both LLVM 15 and LLVM
16 with the current codebase.

Furthermore, the changes in the output documentation produce generally
less information dense sets, which is a step back from the previous
format that the usage of Clang would produce.

The suggested path of conversion between the two version is the usage of
`clang::TypeName::getFullyQualifiedName` to handle the retrieval of type
representations (llvm/llvm-project#61810).

While that is mostly adept at the purposes that QDoc intends to use
Clang for, a very specific behavior of
`clang::TypeName::getFullyQualifiedName` is just not suitable for the
production of documentation sets.

Changes to this behavior were attempted to push upstream, but due to an
unfortunate collision of interest with other projects depending on the
behavior it was not possible to find a satisfying
compromise (llvm/llvm-project#67566).

Furthermore, even if a compromise was possible, the changes would not be
available until at least LLVM 18, which is the next release, so that any
transition to LLVM 16 would incur into the same issue.

Due to this unsuitable behavior, QDoc does not have a path of migration
that would be consistent on both LLVM 15 and LLVM 16, as
`clang::TypeName::getFullyQualifiedName` would not be usable.

This would leave QDoc with producing a custom solution. This custom
solution would generally look very similar to
`clang::TypeName::getFullyQualifiedName`, as the purposes of QDoc and
that of that function are mostly equivalent.

Hence, instead of producing a completely custom solution,
`QualTypeNames`, the file that provides
`clang::TypeName::getFullyQualifiedName` is imported from the upstream
LLVM project and redistributed as part of QDoc, so as to allow us to
perform the very small modifications we need for it to be suitable for
our project.

A new directory structure "clang/AST" was added under the source
directory for QDoc, to replicate the the actual include path that is
exported from the LLVM project.

Under "clang/AST", "QualTypeNames.h" was added, replicating the upstream
code that defines `clang::TypeName::getFullyQualifiedName`.

A "qt_attribution.json" file was added under "clang/AST" to satisfy the
license requirement from the LLVM project.
Similarly, an "LLVM_LICENSE.txt" was put under the same directory to
redistribute the license file of the LLVM project, as part of license
compliance.

Task-number: QTBUG-111580
Change-Id: I3f51222ea1cfe2f3e526cc2b43df211622b85fb9
Reviewed-by: Qt CI Bot <[email protected]>
Reviewed-by: Topi Reiniö <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:as-a-library libclang and C++ API
Projects
None yet
Development

No branches or pull requests

5 participants