[libcxx-commits] [libcxx] [libc++] Adds a new feature-test macro generator class. (PR #90889)

Mark de Wever via libcxx-commits libcxx-commits at lists.llvm.org
Thu Jul 4 05:09:32 PDT 2024


https://github.com/mordante updated https://github.com/llvm/llvm-project/pull/90889

>From 63a4fe67fb33acb66793c495722ab680de6220af Mon Sep 17 00:00:00 2001
From: Mark de Wever <koraq at xs4all.nl>
Date: Mon, 8 Apr 2024 21:46:06 +0200
Subject: [PATCH 1/4] [RFC][libc++] Testing feature test macro script.

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 https://github.com/llvm/llvm-project/pull/89499
---
 .../test/libcxx/feature_test_macro_csv.sh.py  |  35 +++++
 .../data/feature_test_macros/test_data.json   | 136 ++++++++++++++++++
 .../generate_feature_test_macro_components.py |  33 +++++
 3 files changed, 204 insertions(+)
 create mode 100644 libcxx/test/libcxx/feature_test_macro_csv.sh.py
 create mode 100644 libcxx/utils/data/feature_test_macros/test_data.json

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 fe5bab05195a38..7be055cdaa188c 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():
@@ -1867,6 +1868,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()

>From 49c0d92a456f76f3aa086fbdb376d83c115df40d Mon Sep 17 00:00:00 2001
From: Mark de Wever <koraq at xs4all.nl>
Date: Mon, 8 Apr 2024 21:46:06 +0200
Subject: [PATCH 2/4] Addresses review comments.

---
 .../get_dialect_versions.sh.py                |  53 +++++
 .../get_std_dialect_versions.sh.py            |  53 +++++
 .../feature_test_macro/get_std_dialects.sh.py |  29 +++
 .../libcxx/feature_test_macro/invalid.sh.py   | 107 ++++++++++
 .../test/libcxx/feature_test_macro_csv.sh.py  |  35 ----
 .../data/feature_test_macros/test_data.json   |  17 +-
 .../generate_feature_test_macro_components.py | 191 +++++++++++++++++-
 7 files changed, 442 insertions(+), 43 deletions(-)
 create mode 100644 libcxx/test/libcxx/feature_test_macro/get_dialect_versions.sh.py
 create mode 100644 libcxx/test/libcxx/feature_test_macro/get_std_dialect_versions.sh.py
 create mode 100644 libcxx/test/libcxx/feature_test_macro/get_std_dialects.sh.py
 create mode 100644 libcxx/test/libcxx/feature_test_macro/invalid.sh.py
 delete mode 100644 libcxx/test/libcxx/feature_test_macro_csv.sh.py

diff --git a/libcxx/test/libcxx/feature_test_macro/get_dialect_versions.sh.py b/libcxx/test/libcxx/feature_test_macro/get_dialect_versions.sh.py
new file mode 100644
index 00000000000000..fffe0ddc6e6a75
--- /dev/null
+++ b/libcxx/test/libcxx/feature_test_macro/get_dialect_versions.sh.py
@@ -0,0 +1,53 @@
+# RUN: %{python} %s %{libcxx-dir}/utils %{libcxx-dir}/utils/data/feature_test_macros/test_data.json
+# ===----------------------------------------------------------------------===##
+#
+# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+# See https://llvm.org/LICENSE.txt for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+#
+# ===----------------------------------------------------------------------===##
+
+import sys
+
+sys.path.append(sys.argv[1])
+from generate_feature_test_macro_components import feature_test_macros
+
+
+def test(output, expected):
+    assert output == expected, f"expected\n{expected}\n\noutput\n{output}"
+
+
+fmt = feature_test_macros(sys.argv[2])
+test(
+    fmt.get_dialect_versions(),
+    {
+        "__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": None,
+            "c++23": None,
+            "c++26": None,
+        },
+        "__cpp_lib_parallel_algorithm": {
+            "c++17": "201603L",
+            "c++20": "201603L",
+            "c++23": "201603L",
+            "c++26": "201603L",
+        },
+        "__cpp_lib_variant": {
+            "c++17": "202102L",
+            "c++20": "202102L",
+            "c++23": "202102L",
+            "c++26": "202102L",
+        },
+    },
+)
diff --git a/libcxx/test/libcxx/feature_test_macro/get_std_dialect_versions.sh.py b/libcxx/test/libcxx/feature_test_macro/get_std_dialect_versions.sh.py
new file mode 100644
index 00000000000000..5c438da6019211
--- /dev/null
+++ b/libcxx/test/libcxx/feature_test_macro/get_std_dialect_versions.sh.py
@@ -0,0 +1,53 @@
+# RUN: %{python} %s %{libcxx-dir}/utils %{libcxx-dir}/utils/data/feature_test_macros/test_data.json
+# ===----------------------------------------------------------------------===##
+#
+# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+# See https://llvm.org/LICENSE.txt for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+#
+# ===----------------------------------------------------------------------===##
+
+import sys
+
+sys.path.append(sys.argv[1])
+from generate_feature_test_macro_components import feature_test_macros
+
+
+def test(output, expected):
+    assert output == expected, f"expected\n{expected}\n\noutput\n{output}"
+
+
+fmt = feature_test_macros(sys.argv[2])
+test(
+    fmt.get_std_dialect_versions(),
+    {
+        "__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": "202110L",
+            "c++23": "202207L",
+            "c++26": "202311L",
+        },
+        "__cpp_lib_parallel_algorithm": {
+            "c++17": "201603L",
+            "c++20": "201603L",
+            "c++23": "201603L",
+            "c++26": "201603L",
+        },
+        "__cpp_lib_variant": {
+            "c++17": "202102L",
+            "c++20": "202106L",
+            "c++23": "202106L",
+            "c++26": "202306L",
+        },
+    },
+)
diff --git a/libcxx/test/libcxx/feature_test_macro/get_std_dialects.sh.py b/libcxx/test/libcxx/feature_test_macro/get_std_dialects.sh.py
new file mode 100644
index 00000000000000..af93f94cbfbe77
--- /dev/null
+++ b/libcxx/test/libcxx/feature_test_macro/get_std_dialects.sh.py
@@ -0,0 +1,29 @@
+# RUN: %{python} %s %{libcxx-dir}/utils %{libcxx-dir}/utils/data/feature_test_macros/test_data.json
+# ===----------------------------------------------------------------------===##
+#
+# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+# See https://llvm.org/LICENSE.txt for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+#
+# ===----------------------------------------------------------------------===##
+
+import sys
+
+sys.path.append(sys.argv[1])
+from generate_feature_test_macro_components import feature_test_macros
+
+
+def test(output, expected):
+    assert output == expected, f"expected\n{expected}\n\noutput\n{output}"
+
+
+fmt = feature_test_macros(sys.argv[2])
+test(
+    fmt.get_std_dialects(),
+    [
+        "c++17",
+        "c++20",
+        "c++23",
+        "c++26",
+    ],
+)
diff --git a/libcxx/test/libcxx/feature_test_macro/invalid.sh.py b/libcxx/test/libcxx/feature_test_macro/invalid.sh.py
new file mode 100644
index 00000000000000..ed65242ccc750c
--- /dev/null
+++ b/libcxx/test/libcxx/feature_test_macro/invalid.sh.py
@@ -0,0 +1,107 @@
+# RUN: %{python} %s %{libcxx-dir}/utils %t
+# ===----------------------------------------------------------------------===##
+#
+# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+# See https://llvm.org/LICENSE.txt for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+#
+# ===----------------------------------------------------------------------===##
+
+import sys
+import json
+
+sys.path.append(sys.argv[1])
+from generate_feature_test_macro_components import feature_test_macros
+
+
+def test(output, expected):
+    assert output == expected, f"expected\n{expected}\n\noutput\n{output}"
+
+
+def test_error(data, type, message):
+    tmp = sys.argv[2]
+    with open(tmp, "w") as file:
+        file.write(json.dumps(data))
+    fmt = feature_test_macros(tmp)
+    try:
+        fmt.get_dialect_versions()
+    except type as error:
+        test(str(error), message)
+    else:
+        assert False, "no exception was thrown"
+
+
+test_error(
+    [
+        {
+            "values": {
+                "c++17": {
+                    "197001": [
+                        {
+                            "implemented": False,
+                        },
+                    ],
+                },
+            },
+            "headers": [],
+        },
+    ],
+    KeyError,
+    "'name'",
+)
+
+test_error(
+    [
+        {
+            "name": "a",
+            "headers": [],
+        },
+    ],
+    KeyError,
+    "'values'",
+)
+
+test_error(
+    [
+        {
+            "name": "a",
+            "values": {},
+            "headers": [],
+        },
+    ],
+    AssertionError,
+    "'values' is empty",
+)
+
+
+test_error(
+    [
+        {
+            "name": "a",
+            "values": {
+                "c++17": {},
+            },
+            "headers": [],
+        },
+    ],
+    AssertionError,
+    "a[c++17] has no entries",
+)
+
+test_error(
+    [
+        {
+            "name": "a",
+            "values": {
+                "c++17": {
+                    "197001": [
+                        {},
+                    ],
+                },
+            },
+            "headers": [],
+        },
+    ],
+    KeyError,
+    "'implemented'",
+)
diff --git a/libcxx/test/libcxx/feature_test_macro_csv.sh.py b/libcxx/test/libcxx/feature_test_macro_csv.sh.py
deleted file mode 100644
index 2d80bfad63c859..00000000000000
--- a/libcxx/test/libcxx/feature_test_macro_csv.sh.py
+++ /dev/null
@@ -1,35 +0,0 @@
-# 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
index 5a98fba6403c09..1f8bbe5d769b5b 100644
--- a/libcxx/utils/data/feature_test_macros/test_data.json
+++ b/libcxx/utils/data/feature_test_macros/test_data.json
@@ -98,13 +98,28 @@
       "format"
     ]
   },
+  {
+    "name": "__cpp_lib_parallel_algorithm",
+    "values": {
+      "c++17": {
+        "201603": [
+          {
+            "implemented": true
+          }
+        ]
+      }
+    },
+    "headers": [
+      "algorithm",
+      "numeric"
+    ]
+  },
   {
     "name": "__cpp_lib_variant",
     "values": {
       "c++17": {
         "202102": [
           {
-            "number": "",
             "title": "``std::visit`` for classes derived from ``std::variant``",
             "implemented": true
           }
diff --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py
index 7be055cdaa188c..916ea70e87bcbf 100755
--- a/libcxx/utils/generate_feature_test_macro_components.py
+++ b/libcxx/utils/generate_feature_test_macro_components.py
@@ -1868,25 +1868,41 @@ def produce_docs():
         f.write(doc_str)
 
 
-def get_table(data):
+def get_std_dialects(data):
+    """Impementation for feature_test_macros.get_std_dialects()."""
+    dialects = set()
+    for feature in data:
+        keys = feature["values"].keys()
+        assert len(keys) > 0, "'values' is empty"
+        dialects |= keys
+
+    return sorted(dialects)
+
+
+def get_dialect_versions(data, std_dialects, use_implemented_status):
+    """Impementation for feature_test_macros.get_(std_|)dialect_versions()."""
     result = dict()
     for feature in data:
         last = None
         entry = dict()
         implemented = True
-        for std in get_std_dialects():
+        for std in 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"]:
+                    values = feature["values"][std]
+                    assert len(values) > 0, f"{feature['name']}[{std}] has no entries"
+                    for value in values:
+                        papers = list(values[value])
+                        assert (
+                            len(papers) > 0
+                        ), f"{feature['name']}[{std}][{value}] has no entries"
+                        for paper in papers:
+                            if use_implemented_status and not paper["implemented"]:
                                 implemented = False
                                 break
                         if implemented:
@@ -1900,6 +1916,167 @@ def get_table(data):
     return result
 
 
+class feature_test_macros:
+    """Provides all feature-test macro (FMT) output components.
+
+    The class has several generators to use the feature-test macros in libc++:
+    - FTM status page
+    - The version header and its tests
+
+    This class is not intended to duplicate
+    https://isocpp.org/std/standing-documents/sd-6-sg10-feature-test-recommendations#library-feature-test-macros
+    SD-FeatureTest: Feature-Test Macros and Policies
+
+    Historically libc++ did not list all papers affecting a FTM, the new data
+    structure is able to do that. However there is no intention to add the
+    historical data. After papers have been implemented this information can be
+    removed. For example, __cpp_lib_format's value 201907 requires 3 papers,
+    once implemented it can be reduced to 1 paper and remove the paper number
+    and title. This would reduce the size of the data.
+
+    The input data is stored in the following JSON format:
+    [ # A list with multiple feature-test macro entries.
+      {
+        # required
+        # The name of the feature test macro. These names should be unique and
+        # sorted in the list.
+        "name": "__cpp_lib_any",
+
+        # required
+        # A map with the value of the FTM based on the language standard. Only
+        # the versions in which the value of the FTM changes are listed. For
+        # example, this macro's value does not change in C++20 so it does not
+        # list C++20. If it changes in C++26, it will have entries for C++17 and
+        # C++26.
+        "values": {
+
+          # required
+          # The language standard, also named dialect in this class.
+          "c++17": {
+
+            # required
+            # The value of the feature test macro. This contains an array with
+            # one or more papers that need to be implemented before this value
+            # is considered implemented.
+            "201606": [
+              {
+                # optional
+                # Contains the paper number that is part of the FTM version.
+                "number": "P0220R1",
+
+                # optional
+                # Contains the title of the paper that is part of the FTM
+                # version.
+                "title": "Adopt Library Fundamentals V1 TS Components for C++17"
+
+                # required
+                # The implementation status of the paper.
+                "implemented": true
+              }
+            ]
+          }
+        },
+
+        # required
+        # A sorted list of headers that should provide the FTM. The header
+        # <version> is automatically added to this list. This list could be
+        # empty. For example, __cpp_lib_modules is only present in version.
+        # Requiring the field makes it easier to detect accidental omission.
+        "headers": [
+          "any"
+        ],
+
+        # optional, required when libcxx_guard is present
+        # This field is used only to generate the unit tests for the
+        # feature-test macros. It can't depend on macros defined in <__config>
+        # because the `test/std/` parts of the test suite are intended to be
+        # portable to any C++ standard library implementation, not just libc++.
+        # It may depend on
+        # * macros defined by the compiler itself, or
+        # * macros generated by CMake.
+        # In some cases we add also depend on macros defined in
+        # <__availability>.
+        "test_suite_guard": "!defined(_LIBCPP_VERSION) || _LIBCPP_AVAILABILITY_HAS_PMR"
+
+        # optional, required when test_suite_guard is present
+        # This field is used only to guard the feature-test macro in
+        # <version>. It may be the same as `test_suite_guard`, or it may
+        # depend on macros defined in <__config>.
+        "libcxx_guard": "_LIBCPP_AVAILABILITY_HAS_PMR"
+      },
+    ]
+    """
+
+    # The JSON data structor.
+    __data = None
+
+    # 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
+
+    def __init__(self, filename):
+        """Initializes the class with the JSON data in the file 'filename'."""
+        self.__data = json.load(open(filename))
+
+    def get_std_dialects(self):
+        """Returns the C++ dialects avaiable.
+
+        The available dialects are based on the 'c++xy' keys found the 'values'
+        entries in '__data'. So when WG21 starts to feature-test macros for a
+        future C++ Standard this dialect will automatically be available.
+
+        The return value is a sorted list with the C++ dialects used. Since FTM
+        were added in C++14 the list will not contain C++98 or C++11.
+        """
+        if not self.__std_dialects:
+            self.__std_dialects = get_std_dialects(self.__data)
+
+        return self.__std_dialects
+
+    def get_std_dialect_versions(self):
+        """Returns the FTM versions per dialect in the Standard.
+
+        This function does not use the 'implemented' flag. The output contains
+        the versions used in the Standard. When a FTM in libc++ is not
+        implemented according to the Standard to output may opt to show the
+        expected value.
+
+        The result is a dict with the following content
+        - key: Name of the feature test macro.
+        - value: A dict with the following content:
+          * key: The version of the C++ dialect.
+          * value: The value of the feature-test macro.
+        """
+        if not self.__std_dialect_versions:
+            self.__std_dialect_versions = get_dialect_versions(
+                self.__data, self.get_std_dialects(), False
+            )
+
+        return self.__std_dialect_versions
+
+    def get_dialect_versions(self):
+        """Returns the FTM versions per dialect implemented in libc++.
+
+        Unlike `get_std_dialect_versions` this function uses the 'implemented'
+        flag. This returns the actual implementation status in libc++.
+
+        The result is a dict with the following content
+        - key: Name of the feature test macro.
+        - value: A dict with the following content:
+          * key: The version of the C++ dialect.
+          * value: The value of the feature-test macro. When a feature-test
+            macro is not implemented its value is None.
+        """
+        if not self.__dialect_versions:
+            self.__dialect_versions = get_dialect_versions(
+                self.__data, self.get_std_dialects(), True
+            )
+
+        return self.__dialect_versions
+
+
 def main():
     produce_version_header()
     produce_tests()

>From 4eaacab3b225a2cf55e74bfdc62d20d1fb81355c Mon Sep 17 00:00:00 2001
From: Mark de Wever <koraq at xs4all.nl>
Date: Wed, 12 Jun 2024 19:52:40 +0200
Subject: [PATCH 3/4] Addresses review comments.

---
 ..._versions.sh.py => implemented_ftms.sh.py} |  9 +--
 .../libcxx/feature_test_macro/invalid.sh.py   |  9 +--
 ...ect_versions.sh.py => standard_ftms.sh.py} |  9 +--
 ..._std_dialects.sh.py => std_dialects.sh.py} |  9 +--
 .../generate_feature_test_macro_components.py | 55 ++++++-------------
 5 files changed, 38 insertions(+), 53 deletions(-)
 rename libcxx/test/libcxx/feature_test_macro/{get_dialect_versions.sh.py => implemented_ftms.sh.py} (90%)
 rename libcxx/test/libcxx/feature_test_macro/{get_std_dialect_versions.sh.py => standard_ftms.sh.py} (90%)
 rename libcxx/test/libcxx/feature_test_macro/{get_std_dialects.sh.py => std_dialects.sh.py} (83%)

diff --git a/libcxx/test/libcxx/feature_test_macro/get_dialect_versions.sh.py b/libcxx/test/libcxx/feature_test_macro/implemented_ftms.sh.py
similarity index 90%
rename from libcxx/test/libcxx/feature_test_macro/get_dialect_versions.sh.py
rename to libcxx/test/libcxx/feature_test_macro/implemented_ftms.sh.py
index fffe0ddc6e6a75..e210507f18e2e7 100644
--- a/libcxx/test/libcxx/feature_test_macro/get_dialect_versions.sh.py
+++ b/libcxx/test/libcxx/feature_test_macro/implemented_ftms.sh.py
@@ -1,4 +1,3 @@
-# RUN: %{python} %s %{libcxx-dir}/utils %{libcxx-dir}/utils/data/feature_test_macros/test_data.json
 # ===----------------------------------------------------------------------===##
 #
 # Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
@@ -7,19 +6,21 @@
 #
 # ===----------------------------------------------------------------------===##
 
+# RUN: %{python} %s %{libcxx-dir}/utils %{libcxx-dir}/utils/data/feature_test_macros/test_data.json
+
 import sys
 
 sys.path.append(sys.argv[1])
-from generate_feature_test_macro_components import feature_test_macros
+from generate_feature_test_macro_components import FeatureTestMacros
 
 
 def test(output, expected):
     assert output == expected, f"expected\n{expected}\n\noutput\n{output}"
 
 
-fmt = feature_test_macros(sys.argv[2])
+ftm = FeatureTestMacros(sys.argv[2])
 test(
-    fmt.get_dialect_versions(),
+    ftm.implemented_ftms,
     {
         "__cpp_lib_any": {
             "c++17": "201606L",
diff --git a/libcxx/test/libcxx/feature_test_macro/invalid.sh.py b/libcxx/test/libcxx/feature_test_macro/invalid.sh.py
index ed65242ccc750c..ae457f6e1a545a 100644
--- a/libcxx/test/libcxx/feature_test_macro/invalid.sh.py
+++ b/libcxx/test/libcxx/feature_test_macro/invalid.sh.py
@@ -1,4 +1,3 @@
-# RUN: %{python} %s %{libcxx-dir}/utils %t
 # ===----------------------------------------------------------------------===##
 #
 # Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
@@ -7,11 +6,13 @@
 #
 # ===----------------------------------------------------------------------===##
 
+# RUN: %{python} %s %{libcxx-dir}/utils %t
+
 import sys
 import json
 
 sys.path.append(sys.argv[1])
-from generate_feature_test_macro_components import feature_test_macros
+from generate_feature_test_macro_components import FeatureTestMacros
 
 
 def test(output, expected):
@@ -22,9 +23,9 @@ def test_error(data, type, message):
     tmp = sys.argv[2]
     with open(tmp, "w") as file:
         file.write(json.dumps(data))
-    fmt = feature_test_macros(tmp)
+    ftm = FeatureTestMacros(tmp)
     try:
-        fmt.get_dialect_versions()
+        ftm.implemented_ftms
     except type as error:
         test(str(error), message)
     else:
diff --git a/libcxx/test/libcxx/feature_test_macro/get_std_dialect_versions.sh.py b/libcxx/test/libcxx/feature_test_macro/standard_ftms.sh.py
similarity index 90%
rename from libcxx/test/libcxx/feature_test_macro/get_std_dialect_versions.sh.py
rename to libcxx/test/libcxx/feature_test_macro/standard_ftms.sh.py
index 5c438da6019211..25cb306998721a 100644
--- a/libcxx/test/libcxx/feature_test_macro/get_std_dialect_versions.sh.py
+++ b/libcxx/test/libcxx/feature_test_macro/standard_ftms.sh.py
@@ -1,4 +1,3 @@
-# RUN: %{python} %s %{libcxx-dir}/utils %{libcxx-dir}/utils/data/feature_test_macros/test_data.json
 # ===----------------------------------------------------------------------===##
 #
 # Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
@@ -7,19 +6,21 @@
 #
 # ===----------------------------------------------------------------------===##
 
+# RUN: %{python} %s %{libcxx-dir}/utils %{libcxx-dir}/utils/data/feature_test_macros/test_data.json
+
 import sys
 
 sys.path.append(sys.argv[1])
-from generate_feature_test_macro_components import feature_test_macros
+from generate_feature_test_macro_components import FeatureTestMacros
 
 
 def test(output, expected):
     assert output == expected, f"expected\n{expected}\n\noutput\n{output}"
 
 
-fmt = feature_test_macros(sys.argv[2])
+ftm = FeatureTestMacros(sys.argv[2])
 test(
-    fmt.get_std_dialect_versions(),
+    ftm.standard_ftms,
     {
         "__cpp_lib_any": {
             "c++17": "201606L",
diff --git a/libcxx/test/libcxx/feature_test_macro/get_std_dialects.sh.py b/libcxx/test/libcxx/feature_test_macro/std_dialects.sh.py
similarity index 83%
rename from libcxx/test/libcxx/feature_test_macro/get_std_dialects.sh.py
rename to libcxx/test/libcxx/feature_test_macro/std_dialects.sh.py
index af93f94cbfbe77..42a6d169f720b7 100644
--- a/libcxx/test/libcxx/feature_test_macro/get_std_dialects.sh.py
+++ b/libcxx/test/libcxx/feature_test_macro/std_dialects.sh.py
@@ -1,4 +1,3 @@
-# RUN: %{python} %s %{libcxx-dir}/utils %{libcxx-dir}/utils/data/feature_test_macros/test_data.json
 # ===----------------------------------------------------------------------===##
 #
 # Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
@@ -7,19 +6,21 @@
 #
 # ===----------------------------------------------------------------------===##
 
+# RUN: %{python} %s %{libcxx-dir}/utils %{libcxx-dir}/utils/data/feature_test_macros/test_data.json
+
 import sys
 
 sys.path.append(sys.argv[1])
-from generate_feature_test_macro_components import feature_test_macros
+from generate_feature_test_macro_components import FeatureTestMacros
 
 
 def test(output, expected):
     assert output == expected, f"expected\n{expected}\n\noutput\n{output}"
 
 
-fmt = feature_test_macros(sys.argv[2])
+ftm = FeatureTestMacros(sys.argv[2])
 test(
-    fmt.get_std_dialects(),
+    ftm.std_dialects,
     [
         "c++17",
         "c++20",
diff --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py
index 916ea70e87bcbf..b03d445e3b4a0d 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 functools
 import json
 
 
@@ -1868,17 +1869,6 @@ def produce_docs():
         f.write(doc_str)
 
 
-def get_std_dialects(data):
-    """Impementation for feature_test_macros.get_std_dialects()."""
-    dialects = set()
-    for feature in data:
-        keys = feature["values"].keys()
-        assert len(keys) > 0, "'values' is empty"
-        dialects |= keys
-
-    return sorted(dialects)
-
-
 def get_dialect_versions(data, std_dialects, use_implemented_status):
     """Impementation for feature_test_macros.get_(std_|)dialect_versions()."""
     result = dict()
@@ -1916,8 +1906,8 @@ def get_dialect_versions(data, std_dialects, use_implemented_status):
     return result
 
 
-class feature_test_macros:
-    """Provides all feature-test macro (FMT) output components.
+class FeatureTestMacros:
+    """Provides all feature-test macro (FTM) output components.
 
     The class has several generators to use the feature-test macros in libc++:
     - FTM status page
@@ -2007,20 +1997,15 @@ class feature_test_macros:
     ]
     """
 
-    # The JSON data structor.
+    # The JSON data structure.
     __data = None
 
-    # 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
-
     def __init__(self, filename):
         """Initializes the class with the JSON data in the file 'filename'."""
         self.__data = json.load(open(filename))
 
-    def get_std_dialects(self):
+    @functools.cached_property
+    def std_dialects(self):
         """Returns the C++ dialects avaiable.
 
         The available dialects are based on the 'c++xy' keys found the 'values'
@@ -2030,12 +2015,16 @@ def get_std_dialects(self):
         The return value is a sorted list with the C++ dialects used. Since FTM
         were added in C++14 the list will not contain C++98 or C++11.
         """
-        if not self.__std_dialects:
-            self.__std_dialects = get_std_dialects(self.__data)
+        dialects = set()
+        for feature in self.__data:
+            keys = feature["values"].keys()
+            assert len(keys) > 0, "'values' is empty"
+            dialects |= keys
 
-        return self.__std_dialects
+        return sorted(list(dialects))
 
-    def get_std_dialect_versions(self):
+    @functools.cached_property
+    def standard_ftms(self):
         """Returns the FTM versions per dialect in the Standard.
 
         This function does not use the 'implemented' flag. The output contains
@@ -2049,14 +2038,10 @@ def get_std_dialect_versions(self):
           * key: The version of the C++ dialect.
           * value: The value of the feature-test macro.
         """
-        if not self.__std_dialect_versions:
-            self.__std_dialect_versions = get_dialect_versions(
-                self.__data, self.get_std_dialects(), False
-            )
+        return get_dialect_versions(self.__data, self.std_dialects, False)
 
-        return self.__std_dialect_versions
-
-    def get_dialect_versions(self):
+    @functools.cached_property
+    def implemented_ftms(self):
         """Returns the FTM versions per dialect implemented in libc++.
 
         Unlike `get_std_dialect_versions` this function uses the 'implemented'
@@ -2069,12 +2054,8 @@ def get_dialect_versions(self):
           * value: The value of the feature-test macro. When a feature-test
             macro is not implemented its value is None.
         """
-        if not self.__dialect_versions:
-            self.__dialect_versions = get_dialect_versions(
-                self.__data, self.get_std_dialects(), True
-            )
 
-        return self.__dialect_versions
+        return get_dialect_versions(self.__data, self.std_dialects, True)
 
 
 def main():

>From c8dd9ad525b59d3630f85d207df7d1b7da80015e Mon Sep 17 00:00:00 2001
From: Mark de Wever <koraq at xs4all.nl>
Date: Thu, 4 Jul 2024 14:08:05 +0200
Subject: [PATCH 4/4] Address review comments.

---
 .../feature_test_macro/implemented_ftms.sh.py |  2 +-
 .../feature_test_macro/standard_ftms.sh.py    |  2 +-
 .../feature_test_macro/std_dialects.sh.py     |  2 +-
 .../libcxx/feature_test_macro}/test_data.json |  0
 .../generate_feature_test_macro_components.py | 20 ++++++++++---------
 5 files changed, 14 insertions(+), 12 deletions(-)
 rename libcxx/{utils/data/feature_test_macros => test/libcxx/feature_test_macro}/test_data.json (100%)

diff --git a/libcxx/test/libcxx/feature_test_macro/implemented_ftms.sh.py b/libcxx/test/libcxx/feature_test_macro/implemented_ftms.sh.py
index e210507f18e2e7..67353fc41e5098 100644
--- a/libcxx/test/libcxx/feature_test_macro/implemented_ftms.sh.py
+++ b/libcxx/test/libcxx/feature_test_macro/implemented_ftms.sh.py
@@ -6,7 +6,7 @@
 #
 # ===----------------------------------------------------------------------===##
 
-# RUN: %{python} %s %{libcxx-dir}/utils %{libcxx-dir}/utils/data/feature_test_macros/test_data.json
+# RUN: %{python} %s %{libcxx-dir}/utils %{libcxx-dir}/test/libcxx/feature_test_macro/test_data.json
 
 import sys
 
diff --git a/libcxx/test/libcxx/feature_test_macro/standard_ftms.sh.py b/libcxx/test/libcxx/feature_test_macro/standard_ftms.sh.py
index 25cb306998721a..43c90b131bff16 100644
--- a/libcxx/test/libcxx/feature_test_macro/standard_ftms.sh.py
+++ b/libcxx/test/libcxx/feature_test_macro/standard_ftms.sh.py
@@ -6,7 +6,7 @@
 #
 # ===----------------------------------------------------------------------===##
 
-# RUN: %{python} %s %{libcxx-dir}/utils %{libcxx-dir}/utils/data/feature_test_macros/test_data.json
+# RUN: %{python} %s %{libcxx-dir}/utils %{libcxx-dir}/test/libcxx/feature_test_macro/test_data.json
 
 import sys
 
diff --git a/libcxx/test/libcxx/feature_test_macro/std_dialects.sh.py b/libcxx/test/libcxx/feature_test_macro/std_dialects.sh.py
index 42a6d169f720b7..368020c91e1d2b 100644
--- a/libcxx/test/libcxx/feature_test_macro/std_dialects.sh.py
+++ b/libcxx/test/libcxx/feature_test_macro/std_dialects.sh.py
@@ -6,7 +6,7 @@
 #
 # ===----------------------------------------------------------------------===##
 
-# RUN: %{python} %s %{libcxx-dir}/utils %{libcxx-dir}/utils/data/feature_test_macros/test_data.json
+# RUN: %{python} %s %{libcxx-dir}/utils %{libcxx-dir}/test/libcxx/feature_test_macro/test_data.json
 
 import sys
 
diff --git a/libcxx/utils/data/feature_test_macros/test_data.json b/libcxx/test/libcxx/feature_test_macro/test_data.json
similarity index 100%
rename from libcxx/utils/data/feature_test_macros/test_data.json
rename to libcxx/test/libcxx/feature_test_macro/test_data.json
diff --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py
index b03d445e3b4a0d..70693a616a4921 100755
--- a/libcxx/utils/generate_feature_test_macro_components.py
+++ b/libcxx/utils/generate_feature_test_macro_components.py
@@ -1869,8 +1869,10 @@ def produce_docs():
         f.write(doc_str)
 
 
-def get_dialect_versions(data, std_dialects, use_implemented_status):
-    """Impementation for feature_test_macros.get_(std_|)dialect_versions()."""
+def get_ftms(
+    data, std_dialects: list[str], use_implemented_status: bool
+) -> dict[str, dict[str, any]]:
+    """Impementation for FeatureTestMacros.(standard|implemented)_ftms()."""
     result = dict()
     for feature in data:
         last = None
@@ -2000,12 +2002,12 @@ class FeatureTestMacros:
     # The JSON data structure.
     __data = None
 
-    def __init__(self, filename):
+    def __init__(self, filename: str):
         """Initializes the class with the JSON data in the file 'filename'."""
         self.__data = json.load(open(filename))
 
     @functools.cached_property
-    def std_dialects(self):
+    def std_dialects(self) -> list[str]:
         """Returns the C++ dialects avaiable.
 
         The available dialects are based on the 'c++xy' keys found the 'values'
@@ -2013,7 +2015,7 @@ def std_dialects(self):
         future C++ Standard this dialect will automatically be available.
 
         The return value is a sorted list with the C++ dialects used. Since FTM
-        were added in C++14 the list will not contain C++98 or C++11.
+        were added in C++14 the list will not contain C++03 or C++11.
         """
         dialects = set()
         for feature in self.__data:
@@ -2024,7 +2026,7 @@ def std_dialects(self):
         return sorted(list(dialects))
 
     @functools.cached_property
-    def standard_ftms(self):
+    def standard_ftms(self) -> dict[str, dict[str, any]]:
         """Returns the FTM versions per dialect in the Standard.
 
         This function does not use the 'implemented' flag. The output contains
@@ -2038,10 +2040,10 @@ def standard_ftms(self):
           * key: The version of the C++ dialect.
           * value: The value of the feature-test macro.
         """
-        return get_dialect_versions(self.__data, self.std_dialects, False)
+        return get_ftms(self.__data, self.std_dialects, False)
 
     @functools.cached_property
-    def implemented_ftms(self):
+    def implemented_ftms(self) -> dict[str, dict[str, any]]:
         """Returns the FTM versions per dialect implemented in libc++.
 
         Unlike `get_std_dialect_versions` this function uses the 'implemented'
@@ -2055,7 +2057,7 @@ def implemented_ftms(self):
             macro is not implemented its value is None.
         """
 
-        return get_dialect_versions(self.__data, self.std_dialects, True)
+        return get_ftms(self.__data, self.std_dialects, True)
 
 
 def main():



More information about the libcxx-commits mailing list