Skip to content

[libclang/python] Add equality comparison operators for File #130383

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

Merged
merged 12 commits into from
Apr 24, 2025

Conversation

DeinAlptraum
Copy link
Contributor

@DeinAlptraum DeinAlptraum commented Mar 8, 2025

This covers the File interface changes added by #120590

@DeinAlptraum DeinAlptraum requested a review from Endilll March 8, 2025 03:01
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:as-a-library libclang and C++ API labels Mar 8, 2025
@llvmbot
Copy link
Member

llvmbot commented Mar 8, 2025

@llvm/pr-subscribers-clang

Author: Jannick Kremer (DeinAlptraum)

Changes

This covers the File interface changes added by #120590


Full diff: https://github.com/llvm/llvm-project/pull/130383.diff

3 Files Affected:

  • (modified) clang/bindings/python/clang/cindex.py (+7)
  • (modified) clang/bindings/python/tests/cindex/test_file.py (+15)
  • (modified) clang/docs/ReleaseNotes.rst (+1)
diff --git a/clang/bindings/python/clang/cindex.py b/clang/bindings/python/clang/cindex.py
index 879a0a3c5c58c..bc8fb7c27fdac 100644
--- a/clang/bindings/python/clang/cindex.py
+++ b/clang/bindings/python/clang/cindex.py
@@ -3470,6 +3470,12 @@ def __str__(self):
     def __repr__(self):
         return "<File: %s>" % (self.name)
 
+    def __eq__(self, other) -> bool:
+        return isinstance(other, File) and bool(conf.lib.clang_File_isEqual(self, other))
+
+    def __ne__(self, other) -> bool:
+        return not self.__eq__(other)
+
     @staticmethod
     def from_result(res, arg):
         assert isinstance(res, c_object_p)
@@ -3956,6 +3962,7 @@ def set_property(self, property, value):
     ("clang_getFile", [TranslationUnit, c_interop_string], c_object_p),
     ("clang_getFileName", [File], _CXString),
     ("clang_getFileTime", [File], c_uint),
+    ("clang_File_isEqual", [File, File], bool),
     ("clang_getIBOutletCollectionType", [Cursor], Type),
     ("clang_getIncludedFile", [Cursor], c_object_p),
     (
diff --git a/clang/bindings/python/tests/cindex/test_file.py b/clang/bindings/python/tests/cindex/test_file.py
index 14a3084ee2b47..0e8431054e951 100644
--- a/clang/bindings/python/tests/cindex/test_file.py
+++ b/clang/bindings/python/tests/cindex/test_file.py
@@ -16,3 +16,18 @@ def test_file(self):
         self.assertEqual(str(file), "t.c")
         self.assertEqual(file.name, "t.c")
         self.assertEqual(repr(file), "<File: t.c>")
+
+    def test_file_eq(self):
+        index = Index.create()
+        tu = index.parse(
+            "t.c",
+            unsaved_files=[
+                ("t.c", "int a = 729;"),
+                ("s.c", "int a = 729;"),
+            ],
+        )
+        file1 = File.from_name(tu, "t.c")
+        file2 = File.from_name(tu, "s.c")
+        self.assertEqual(file1, file1)
+        self.assertNotEqual(file1, file2)
+        self.assertNotEqual(file1, "t.c")
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index a25808e36bd51..a2f10f20ce716 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -444,6 +444,7 @@ Python Binding Changes
 ----------------------
 - Added ``Type.get_methods``, a binding for ``clang_visitCXXMethods``, which
   allows visiting the methods of a class.
+- Add equality comparison operators for ``File`` type
 
 OpenMP Support
 --------------

Copy link

github-actions bot commented Mar 8, 2025

✅ With the latest revision this PR passed the Python code formatter.

@DeinAlptraum
Copy link
Contributor Author

@Endilll do you understand where this test failure might come from? It passes for me locally...

@Endilll
Copy link
Contributor

Endilll commented Mar 8, 2025

I think the failure in question is

 ======================================================================
FAIL: test_file_eq (tests.cindex.test_file.TestFile.test_file_eq)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/__w/llvm-project/llvm-project/clang/bindings/python/tests/cindex/test_file.py", line 32, in test_file_eq
    self.assertNotEqual(file1, file2)
AssertionError: <File: t.c> == <File: s.c>

----------------------------------------------------------------------

and I have to idea why this happens.

@DeinAlptraum
Copy link
Contributor Author

@Endilll I managed to align my local errors with those in the CI and fix them by using actual files for the test instead... curious where the issue comes from, but I don't feel like looking into this deeper rn.
Anyway, this is ready for review now. Sorry for the delay.

@DeinAlptraum DeinAlptraum force-pushed the 120590-fileinterface branch from 4cc056c to b8a4f30 Compare March 22, 2025 02:52
Copy link
Contributor

@Endilll Endilll left a comment

Choose a reason for hiding this comment

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

@AaronBallman do you have a clue why equality comparison for in-memory files works differently compared to on-disk files?

Copy link

github-actions bot commented Mar 22, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@DeinAlptraum
Copy link
Contributor Author

DeinAlptraum commented Mar 22, 2025

As for investigation:
I wrote four tests in test_file.py that just assert all files as equal (the ones named test_file_eq_failing...) which is expected to fail, and all four of these tests fail for me locally. But in the CI the first three (using in-memory files) succeed, so it seems that indeed all in-memory files seem to be treated as equal. The fourth one using files read from disk correctly treats them as unique and thus fails.

@AaronBallman
Copy link
Collaborator

@AaronBallman do you have a clue why equality comparison for in-memory files works differently compared to on-disk files?

Are they? I think you're supposed to use FileSystem::equivalent() to test for it which uses an underlying unique ID.

Comment on lines 34 to 92
def test_file_eq_failing(self):
index = Index.create()
tu = index.parse(
"t.c",
unsaved_files=[
("t.c", "int a = 729;"),
("s.c", "int a = 729;"),
],
)
file1 = File.from_name(tu, "t.c")
file2 = File.from_name(tu, "s.c")
# FIXME: These files are not supposed to be equal
self.assertEqual(file1, file2)

def test_file_eq_failing_2(self):
index = Index.create()
tu = index.parse(
"t.c",
unsaved_files=[
("t.c", "int a = 729;"),
("s.c", "int a = 728;"),
],
)
file1 = File.from_name(tu, "t.c")
file2 = File.from_name(tu, "s.c")
# FIXME: These files are not supposed to be equal
self.assertEqual(file1, file2)

def test_file_eq_failing_3(self):
index = Index.create()
tu = index.parse(
"t.c",
unsaved_files=[
("t.c", '#include "a.c"\n#include "b.c";'),
("a.c", "int a = 729;"),
("b.c", "int b = 729;"),
],
)
file1 = File.from_name(tu, "t.c")
file2 = File.from_name(tu, "a.c")
file3 = File.from_name(tu, "b.c")
# FIXME: These files are not supposed to be equal
self.assertEqual(file2, file3)
self.assertEqual(file1, file2)
self.assertEqual(file1, file3)

def test_file_eq_failing_4(self):
path = os.path.join(inputs_dir, "testfile.c")
path_a = os.path.join(inputs_dir, "a.inc")
path_b = os.path.join(inputs_dir, "b.inc")
tu = TranslationUnit.from_source(path)
print(tu.spelling, tu.cursor.spelling)
file1 = File.from_name(tu, path)
file2 = File.from_name(tu, path_a)
file3 = File.from_name(tu, path_b)
# FIXME: These files are not supposed to be equal
self.assertEqual(file2, file3)
self.assertEqual(file1, file2)
self.assertEqual(file1, file3)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@AaronBallman take a look at the tests I wrote here. At least in the CI, only the last one is actually failing. From the first three, it looks like all in-memory files are considered equal, but test_file_eq_failing_4 which reads them from disk doesn't have that problem.

Even weirder, all four of these tests fail when I run them locally.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this might be a bug here:

return FEnt1.getUniqueID() == FEnt2.getUniqueID();

For starters, FileEntryRef has an equality operator:

friend bool operator==(const FileEntryRef &LHS, const FileEntryRef &RHS) {
so it's worth seeing if using that gives the correct behavior.

But also, you can use FileManager::getNoncachedStatValue to get a vfs::Status object on which you can call equivalent() to test for equivalency, so it's worth seeing if that gives the correct behavior.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@AaronBallman sorry for the delay and thanks a lot for the suggestions!
Changing the line you mentioned to use the equality operator instead indeed seems to result in the correct behavior! It seems like file equality doesn't have any tests in LibclangTest.cpp either, so I'll add one for this and open a PR later.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Opened a PR at #135773

@DeinAlptraum DeinAlptraum force-pushed the 120590-fileinterface branch from f0951dd to bb4e33e Compare April 24, 2025 02:47
@DeinAlptraum
Copy link
Contributor Author

@Endilll having merged #135773 and rebased, the issues with file equality for in-memory files are resolved.
I've reduced this to two tests now, that run on the except same C code except one of them uses on-disk files and the other one uses in-memory files. This replicates pretty much exactly how we test clang_File_isEqual on C++ side.

@DeinAlptraum DeinAlptraum merged commit c6c0846 into llvm:main Apr 24, 2025
9 of 13 checks passed
@DeinAlptraum DeinAlptraum deleted the 120590-fileinterface branch April 24, 2025 09:16
IanWood1 pushed a commit to IanWood1/llvm-project that referenced this pull request May 6, 2025
…0383)

This covers the `File` interface changes added by llvm#120590

---------

Co-authored-by: Mathias Stearn <[email protected]>
Co-authored-by: Vlad Serebrennikov <[email protected]>
IanWood1 pushed a commit to IanWood1/llvm-project that referenced this pull request May 6, 2025
…0383)

This covers the `File` interface changes added by llvm#120590

---------

Co-authored-by: Mathias Stearn <[email protected]>
Co-authored-by: Vlad Serebrennikov <[email protected]>
IanWood1 pushed a commit to IanWood1/llvm-project that referenced this pull request May 6, 2025
…0383)

This covers the `File` interface changes added by llvm#120590

---------

Co-authored-by: Mathias Stearn <[email protected]>
Co-authored-by: Vlad Serebrennikov <[email protected]>
Ankur-0429 pushed a commit to Ankur-0429/llvm-project that referenced this pull request May 9, 2025
…0383)

This covers the `File` interface changes added by llvm#120590

---------

Co-authored-by: Mathias Stearn <[email protected]>
Co-authored-by: Vlad Serebrennikov <[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 clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants