-
Notifications
You must be signed in to change notification settings - Fork 10.7k
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
[libc++] Adds a new feature-test macro generator class. #90889
base: main
Are you sure you want to change the base?
Conversation
This is a proof-of-concept how we can test the script. Instead of storing the data in the script it's stored in a JSON file so a different file can be used for testing. This is related to the RFC #89499
@llvm/pr-subscribers-libcxx Author: Mark de Wever (mordante) ChangesThis is a proof-of-concept how we can test the script. Instead of storing the data in the script it's stored in a JSON file so a different file can be used for testing. This is related to the RFC #89499 Full diff: https://github.com/llvm/llvm-project/pull/90889.diff 3 Files Affected:
diff --git a/libcxx/test/libcxx/feature_test_macro_csv.sh.py b/libcxx/test/libcxx/feature_test_macro_csv.sh.py
new file mode 100644
index 00000000000000..2d80bfad63c859
--- /dev/null
+++ b/libcxx/test/libcxx/feature_test_macro_csv.sh.py
@@ -0,0 +1,35 @@
+# RUN: %{python} %s %{libcxx-dir}/utils %{libcxx-dir}/utils/data/feature_test_macros/test_data.json
+
+import sys
+import json
+
+sys.path.append(sys.argv[1])
+from generate_feature_test_macro_components import get_table
+
+
+data = json.load(open(f"{sys.argv[2]}"))
+table = get_table(data)
+
+expected = {
+ "__cpp_lib_any": {
+ "c++17": "201606L",
+ "c++20": "201606L",
+ "c++23": "201606L",
+ "c++26": "201606L",
+ },
+ "__cpp_lib_barrier": {"c++20": "201907L", "c++23": "201907L", "c++26": "201907L"},
+ "__cpp_lib_format": {
+ "c++20": "",
+ "c++23": "",
+ "c++26": "",
+ },
+ "__cpp_lib_variant": {
+ "c++17": "202102L",
+ "c++20": "202102L",
+ "c++23": "202102L",
+ "c++26": "202102L",
+ },
+}
+
+
+assert table == expected, f"expected\n{expected}\n\nresult\n{table}"
diff --git a/libcxx/utils/data/feature_test_macros/test_data.json b/libcxx/utils/data/feature_test_macros/test_data.json
new file mode 100644
index 00000000000000..5a98fba6403c09
--- /dev/null
+++ b/libcxx/utils/data/feature_test_macros/test_data.json
@@ -0,0 +1,136 @@
+[
+ {
+ "name": "__cpp_lib_any",
+ "values": {
+ "c++17": {
+ "201606": [
+ {
+ "implemented": true
+ }
+ ]
+ }
+ },
+ "headers": [
+ "any"
+ ]
+ },
+ {
+ "name": "__cpp_lib_barrier",
+ "values": {
+ "c++20": {
+ "201907": [
+ {
+ "implemented": true
+ }
+ ]
+ }
+ },
+ "headers": [
+ "barrier"
+ ],
+ "test_suite_guard":
+ "!defined(_LIBCPP_HAS_NO_THREADS) && (!defined(_LIBCPP_VERSION) || _LIBCPP_AVAILABILITY_HAS_SYNC)",
+ "libcxx_guard": "!defined(_LIBCPP_HAS_NO_THREADS) && _LIBCPP_AVAILABILITY_HAS_SYNC"
+ },
+ {
+ "name": "__cpp_lib_format",
+ "values": {
+ "c++20": {
+ "201907": [
+ {
+ "number": "P0645R10",
+ "title": "Text Formatting",
+ "implemented": true
+ },
+ {
+ "number": "P1361R2",
+ "title": "Integration of chrono with text formatting",
+ "implemented": false
+ }
+ ],
+ "202106": [
+ {
+ "number": "P2216R3",
+ "title": "std::format improvements",
+ "implemented": true
+ }
+ ],
+ "202110": [
+ {
+ "number": "P2372R3",
+ "title": "Fixing locale handling in chrono formatters",
+ "implemented": false
+ },
+ {
+ "number": "P2418R2",
+ "title": "FAdd support for std::generator-like types to std::format",
+ "implemented": true
+ }
+ ]
+ },
+ "c++23": {
+ "202207": [
+ {
+ "number": "P2419R2",
+ "title": "Clarify handling of encodings in localized formatting of chrono types",
+ "implemented": false
+ }
+ ]
+ },
+ "c++26": {
+ "202306": [
+ {
+ "number": "P2637R3",
+ "title": "Member Visit",
+ "implemented": true
+ }
+ ],
+ "202311": [
+ {
+ "number": "P2918R2",
+ "title": "Runtime format strings II",
+ "implemented": true
+ }
+ ]
+ }
+ },
+ "headers": [
+ "format"
+ ]
+ },
+ {
+ "name": "__cpp_lib_variant",
+ "values": {
+ "c++17": {
+ "202102": [
+ {
+ "number": "",
+ "title": "``std::visit`` for classes derived from ``std::variant``",
+ "implemented": true
+ }
+ ]
+ },
+ "c++20": {
+ "202106": [
+ {
+ "number": "",
+ "title": "Fully constexpr ``std::variant``",
+ "implemented": false
+ }
+ ]
+ },
+ "c++26": {
+ "202306": [
+ {
+ "number": "",
+ "title": "Member visit",
+ "implemented": true
+ }
+ ]
+ }
+ },
+ "headers": [
+ "variant"
+ ]
+ }
+]
diff --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py
index f2b8d55c0e11b0..04a8a8786b1a35 100755
--- a/libcxx/utils/generate_feature_test_macro_components.py
+++ b/libcxx/utils/generate_feature_test_macro_components.py
@@ -3,6 +3,7 @@
import os
from builtins import range
from functools import reduce
+import json
def get_libcxx_paths():
@@ -1857,6 +1858,38 @@ def produce_docs():
f.write(doc_str)
+def get_table(data):
+ result = dict()
+ for feature in data:
+ last = None
+ entry = dict()
+ implemented = True
+ for std in get_std_dialects():
+ if std not in feature["values"].keys():
+ if last == None:
+ continue
+ else:
+ entry[std] = last
+ else:
+ if last == None:
+ last = ""
+ if implemented:
+ for value in feature["values"][std]:
+ for paper in list(feature["values"][std][value]):
+ if not paper["implemented"]:
+ implemented = False
+ break
+ if implemented:
+ last = f"{value}L"
+ else:
+ break
+
+ entry[std] = last
+ result[feature["name"]] = entry
+
+ return result
+
+
def main():
produce_version_header()
produce_tests()
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The approach LGTM but I'd like to see this again!
@@ -0,0 +1,35 @@ | |||
# RUN: %{python} %s %{libcxx-dir}/utils %{libcxx-dir}/utils/data/feature_test_macros/test_data.json | |||
|
|||
import sys |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe rename to libcxx/test/libcxx/feature_test_macros/get_table.sh.py
? We'll have other tests in that subdirectory later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Based on other review comments there no longer is a get_table
function. I removed this file and added the new tests in the suggested s
@@ -1857,6 +1858,38 @@ def produce_docs(): | |||
f.write(doc_str) | |||
|
|||
|
|||
def get_table(data): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you document the format of the data taken by this function? Also, the script assumes a lot of global state right now (like get_std_dialects()
), it would be nice to improve on this as we're basically rewriting this. Can't we actually infer the standard dialects from the json data we're reading?
We could potentially have a Python class that represents this JSON data and provides a few useful attributes like .dialects
and more, and then we could query the things we implement with something like
for std in table.dialects:
for ftm in table.entries:
if ftm.implemented:
# print information for this FTM in this dialect
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I disagree with the "a lot of global state". It only requires get_std_dialects()
.
I've looked at your Python class suggestion and extracting the C++ dialects from the data. This indeed seems to work and we can indeed extract the dialects from the data.
I've updated the documentation and added more tests; the next version of the patch no longer is considered RFC.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks nice! I have some comments but this should be good to go then.
@@ -0,0 +1,151 @@ | |||
[ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest moving this file under test
since it contains test data.
return sorted(dialects) | ||
|
||
|
||
def get_dialect_versions(data, std_dialects, use_implemented_status): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
def get_dialect_versions(data, std_dialects, use_implemented_status): | |
def get_dialect_versions(data, std_dialects: List[str], use_implemented_status: bool) -> Map[str, Map[str, int]]: |
"""Initializes the class with the JSON data in the file 'filename'.""" | ||
self.__data = json.load(open(filename)) | ||
|
||
def get_std_dialects(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
def get_std_dialects(self): | |
@functools.cached_property | |
def std_dialects(self) -> List[str]: |
This seems a bit more Pythonic to me. And then the implementation can be just return get_std_dialects(self.__data)
.
@@ -1857,6 +1858,215 @@ def produce_docs(): | |||
f.write(doc_str) | |||
|
|||
|
|||
def get_std_dialects(data): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think inlining this one inside the class will help readability.
|
||
return self.__std_dialects | ||
|
||
def get_std_dialect_versions(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could instead provide the following interface:
def get_standard_ftms(self, dialect: str) -> List[Tuple[str, int]]:
# returns the list of ('__cpp_lib_foo', 2017xxx) FTMs in the Standard
def get_implemented_ftms(self, dialect: str) -> List[Tuple[str, int]]:
# returns the list of ('__cpp_lib_foo', 2017xxx) FTMs implemented by libc++
# These values are used internally multiple times. They are lazily loaded | ||
# and cached. Values that are expected to be used once are not cached. | ||
__std_dialects = None | ||
__std_dialect_versions = None | ||
__dialect_versions = None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# These values are used internally multiple times. They are lazily loaded | |
# and cached. Values that are expected to be used once are not cached. | |
__std_dialects = None | |
__std_dialect_versions = None | |
__dialect_versions = None |
assert len(keys) > 0, "'values' is empty" | ||
dialects |= keys | ||
|
||
return sorted(dialects) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return sorted(dialects) | |
return sorted(list(dialects)) |
Because the attribute is going to return a List[str]
.
@@ -0,0 +1,53 @@ | |||
# RUN: %{python} %s %{libcxx-dir}/utils %{libcxx-dir}/utils/data/feature_test_macros/test_data.json |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit, but I think these RUN lines should come after the license.
assert output == expected, f"expected\n{expected}\n\noutput\n{output}" | ||
|
||
|
||
fmt = feature_test_macros(sys.argv[2]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fmt = feature_test_macros(sys.argv[2]) | |
ftm = feature_test_macros(sys.argv[2]) |
from generate_feature_test_macro_components import feature_test_macros | ||
|
||
|
||
def test(output, expected): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's use Python's unittest
module instead.
The new feature-test macro generator uses a JSON file as input. Separating the
code from the data allows for testing the code. The generator has several tests.
This JSON format is based on the new format proposed in #88630
At the moment the FTM script has the existing code and an unused new
generator. Followup patches will complete the generator and convert the existing
Python
dict
to the new JSON format. Since that conversion is a manual job andquite a bit of work the transition path has some unused code for some time.