# ===----------------------------------------------------------------------===## # # 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 # # ===----------------------------------------------------------------------===## from libcxx.header_information import module_headers from libcxx.header_information import header_restrictions from dataclasses import dataclass ### SkipDeclarations # Ignore several declarations found in the includes. # # Part of these items are bugs other are not yet implemented features. SkipDeclarations = dict() # See comment in the header. SkipDeclarations["cuchar"] = ["std::mbstate_t", "std::size_t"] # Not in the synopsis. SkipDeclarations["cwchar"] = ["std::FILE"] # The operators are added for private types like __iom_t10. SkipDeclarations["iomanip"] = ["std::operator<<", "std::operator>>"] # This header also provides declarations in the namespace that might be # an error. SkipDeclarations["filesystem"] = [ "std::filesystem::operator==", "std::filesystem::operator!=", ] # This is a specialization for a private type SkipDeclarations["iterator"] = ["std::pointer_traits"] # TODO MODULES # This definition is declared in string and defined in istream # This declaration should be part of string SkipDeclarations["istream"] = ["std::getline"] # P1614 (at many places) and LWG3519 too. SkipDeclarations["random"] = [ "std::operator!=", # LWG3519 makes these hidden friends. # Note the older versions had the requirement of these operations but not in # the synopsis. "std::operator<<", "std::operator>>", "std::operator==", ] # include/__type_traits/is_swappable.h SkipDeclarations["type_traits"] = [ "std::swap", # TODO MODULES gotten through __functional/unwrap_ref.h "std::reference_wrapper", ] ### ExtraDeclarations # Add declarations in headers. # # Some headers have their defines in a different header, which may have # additional declarations. ExtraDeclarations = dict() # This declaration is in the ostream header. ExtraDeclarations["system_error"] = ["std::operator<<"] # TODO MODULES avoid this work-around # This is a work-around for the special math functions. They are declared in # __math/special_functions.h. Adding this as an ExtraHeader works for the std # module. However these functions are special; they are not available in the # global namespace. ExtraDeclarations["cmath"] = ["std::hermite", "std::hermitef", "std::hermitel"] ### ExtraHeader # Adds extra headers file to scan # # Some C++ headers in libc++ are stored in multiple physical files. There is a # pattern to find these files. However there are some exceptions these are # listed here. ExtraHeader = dict() # locale has a file and not a subdirectory ExtraHeader["locale"] = "v1/__locale$" ExtraHeader["ranges"] = "v1/__fwd/subrange.h$" # The extra header is needed since two headers are required to provide the # same definition. ExtraHeader["functional"] = "v1/__compare/compare_three_way.h$" # Some C compatibility headers define std::size_t, which is in <__cstddef/size_t.h> for header in ("cstdio", "cstdlib", "cstring", "ctime", "cuchar", "cwchar"): ExtraHeader[header] = "v1/__cstddef/size_t.h$" # newline needs to be escaped for the module partition output. nl = "\\\\n" @dataclass class module_test_generator: tmp_prefix: str module_path: str clang_tidy: str clang_tidy_plugin: str compiler: str compiler_flags: str module: str def write_lit_configuration(self): print( f"""\ // UNSUPPORTED: c++03, c++11, c++14, c++17 // UNSUPPORTED: clang-modules-build // REQUIRES: has-clang-tidy // The GCC compiler flags are not always compatible with clang-tidy. // UNSUPPORTED: gcc // MODULE_DEPENDENCIES: {self.module} // RUN: echo -n > {self.tmp_prefix}.all_partitions """ ) def process_module_partition(self, header, is_c_header): # Some headers cannot be included when a libc++ feature is disabled. # In that case include the header conditionally. The header __config # ensures the libc++ feature macros are available. if header in header_restrictions: include = ( f"#include <__config>{nl}" f"#if {header_restrictions[header]}{nl}" f"# include <{header}>{nl}" f"#endif{nl}" ) else: include = f"#include <{header}>{nl}" module_files = f'#include \\"{self.module_path}/std/{header}.inc\\"{nl}' if is_c_header: module_files += ( f'#include \\"{self.module_path}/std.compat/{header}.inc\\"{nl}' ) # Generate a module partition for the header module includes. This # makes it possible to verify that all headers export all their # named declarations. print( '// RUN: echo -e "' f"module;{nl}" f"{include}{nl}" f"{nl}" f"// Use __libcpp_module_
to ensure that modules{nl}" f"// are not named as keywords or reserved names.{nl}" f"export module std:__libcpp_module_{header};{nl}" f"{module_files}" f'" > {self.tmp_prefix}.{header}.cppm' ) # Extract the information of the module partition using lang-tidy print( f"// RUN: {self.clang_tidy} {self.tmp_prefix}.{header}.cppm " " --checks='-*,libcpp-header-exportable-declarations' " " -config='{CheckOptions: [ " " {" " key: libcpp-header-exportable-declarations.Filename, " f" value: {header}.inc" " }, {" " key: libcpp-header-exportable-declarations.FileType, " f" value: {'CompatModulePartition' if is_c_header else 'ModulePartition'}" " }, " " ]}' " f"--load={self.clang_tidy_plugin} " f"-- {self.compiler_flags} " f"| sort > {self.tmp_prefix}.{header}.module" ) print( f"// RUN: cat {self.tmp_prefix}.{header}.module >> {self.tmp_prefix}.all_partitions" ) return include def process_header(self, header, include, is_c_header): # Dump the information as found in the module by using the header file(s). skip_declarations = " ".join(SkipDeclarations.get(header, [])) if skip_declarations: skip_declarations = ( "{" " key: libcpp-header-exportable-declarations.SkipDeclarations, " f' value: "{skip_declarations}" ' "}, " ) extra_declarations = " ".join(ExtraDeclarations.get(header, [])) if extra_declarations: extra_declarations = ( "{" " key: libcpp-header-exportable-declarations.ExtraDeclarations, " f' value: "{extra_declarations}" ' "}, " ) extra_header = ExtraHeader.get(header, "") if extra_header: extra_header = ( "{" " key: libcpp-header-exportable-declarations.ExtraHeader, " f' value: "{extra_header}" ' "}, " ) # Clang-tidy needs a file input print(f'// RUN: echo -e "' f"{include}" f'" > {self.tmp_prefix}.{header}.cpp') print( f"// RUN: {self.clang_tidy} {self.tmp_prefix}.{header}.cpp " " --checks='-*,libcpp-header-exportable-declarations' " " -config='{CheckOptions: [ " " {" " key: libcpp-header-exportable-declarations.Filename, " f" value: {header}" " }, {" " key: libcpp-header-exportable-declarations.FileType, " f" value: {'CHeader' if is_c_header else 'Header'}" " }, " f" {skip_declarations} {extra_declarations} {extra_header}, " " ]}' " f"--load={self.clang_tidy_plugin} " f"-- {self.compiler_flags} " f"| sort > {self.tmp_prefix}.{header}.include" ) print( f"// RUN: diff -u {self.tmp_prefix}.{header}.module {self.tmp_prefix}.{header}.include" ) def process_module(self, module): # Merge the data of the parts print( f"// RUN: sort -u -o {self.tmp_prefix}.all_partitions {self.tmp_prefix}.all_partitions" ) # Dump the information as found in top-level module. print( f"// RUN: {self.clang_tidy} {self.module_path}/{module}.cppm " " --checks='-*,libcpp-header-exportable-declarations' " " -config='{CheckOptions: [ " " {" " key: libcpp-header-exportable-declarations.Header, " f" value: {module}.cppm" " }, {" " key: libcpp-header-exportable-declarations.FileType, " " value: Module" " }, " " ]}' " f"--load={self.clang_tidy_plugin} " f"-- {self.compiler_flags} " f"| sort > {self.tmp_prefix}.module" ) # Compare the sum of the parts with the top-level module. print( f"// RUN: diff -u {self.tmp_prefix}.all_partitions {self.tmp_prefix}.module" ) # Basic smoke test. Import a module and try to compile when using all # exported names. This validates the clang-tidy script does not # accidentally add named declarations to the list that are not available. def test_module(self, module): print( f"""\ // RUN: echo 'import {module};' > {self.tmp_prefix}.compile.pass.cpp // RUN: cat {self.tmp_prefix}.all_partitions >> {self.tmp_prefix}.compile.pass.cpp // RUN: {self.compiler} {self.compiler_flags} -fsyntax-only {self.tmp_prefix}.compile.pass.cpp """ ) def write_test(self, module, c_headers=[]): self.write_lit_configuration() # Validate all module parts. for header in module_headers: is_c_header = header in c_headers include = self.process_module_partition(header, is_c_header) self.process_header(header, include, is_c_header) self.process_module(module) self.test_module(module)